[pgsql-ogr-fdw] 01/02: New upstream version 1.0.2

Michael Fladischer fladi at moszumanska.debian.org
Thu Mar 2 09:01:07 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository pgsql-ogr-fdw.

commit 82b200897fb09f37e05df741b7e4e0708a8a9a61
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Sun Feb 12 21:30:22 2017 +0100

    New upstream version 1.0.2
---
 .gitignore           |   13 +
 .travis.yml          |   52 +
 FAQ.md               |   22 +
 LICENSE.md           |    7 +
 META.json            |   51 +
 Makefile             |   53 +
 README.md            |  344 +++++++
 data/2launder.dbf    |  Bin 0 -> 296 bytes
 data/2launder.prj    |    1 +
 data/2launder.shp    |  Bin 0 -> 156 bytes
 data/2launder.shx    |  Bin 0 -> 116 bytes
 data/Querying.zip    |  Bin 0 -> 203486 bytes
 data/enc.dbf         |  Bin 0 -> 295 bytes
 data/enc.prj         |    1 +
 data/enc.shp         |  Bin 0 -> 156 bytes
 data/enc.shx         |  Bin 0 -> 116 bytes
 data/natural.dbf     |  Bin 0 -> 145 bytes
 data/no_geom.csv     |    5 +
 data/pt_two.dbf      |  Bin 0 -> 295 bytes
 data/pt_two.prj      |    1 +
 data/pt_two.qpj      |    1 +
 data/pt_two.shp      |  Bin 0 -> 156 bytes
 data/pt_two.shx      |  Bin 0 -> 116 bytes
 expected/.gitignore  |    4 +
 input/file.source    |  106 ++
 input/import.source  |   27 +
 input/pgsql.source   |   88 ++
 ogr_fdw--1.0.sql     |   18 +
 ogr_fdw.c            | 2609 ++++++++++++++++++++++++++++++++++++++++++++++++++
 ogr_fdw.control      |    5 +
 ogr_fdw.h            |  189 ++++
 ogr_fdw_common.c     |  356 +++++++
 ogr_fdw_common.h     |   33 +
 ogr_fdw_deparse.c    |  598 ++++++++++++
 ogr_fdw_gdal.h       |   42 +
 ogr_fdw_info.c       |  244 +++++
 output/file.source   |  127 +++
 output/import.source |   52 +
 output/pgsql.source  |  117 +++
 sql/.gitignore       |    4 +
 stringbuffer.c       |  367 +++++++
 stringbuffer.h       |   48 +
 42 files changed, 5585 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f45101a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.o
+*.a
+*.so
+*.dll
+*.pc
+ogr_fdw_info*
+sql/ogr_fdw.sql
+expected/ogr_fdw.out
+results/
+regression.diffs
+regression.out
+tmp_check/
+log/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..66ed0c0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,52 @@
+sudo: required
+
+compiler: gcc
+
+
+matrix:
+  include:
+    - os: linux
+      language: c
+      env:
+        - GDAL_VERSION=11
+    - os: linux
+      language: cpp
+      env:
+        - GDAL_VERSION=20
+    - os: linux
+      language: cpp
+      env:
+        - GDAL_VERSION=21
+
+before_script:
+  - sudo /etc/init.d/postgresql stop
+  - sudo apt-get -y remove --purge postgresql-9.1
+  - sudo apt-get -y remove --purge postgresql-9.2
+  - sudo apt-get -y remove --purge postgresql-9.3
+  - sudo apt-get -y remove --purge postgresql-9.4
+  - sudo apt-get -y autoremove
+  - sudo apt-key adv --keyserver keys.gnupg.net --recv-keys 7FCC7D46ACCC4CF8
+  - sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main 9.5" >> /etc/apt/sources.list.d/postgresql.list'
+  - sudo apt-get update
+  - sudo apt-get -y install postgresql-9.5 postgresql-server-dev-9.5
+  - sudo sh -c 'echo "local all all trust" > /etc/postgresql/9.5/main/pg_hba.conf'
+  - sudo sh -c 'echo -n "host all all 127.0.0.1/32 trust" >> /etc/postgresql/9.5/main/pg_hba.conf'
+  - sudo /etc/init.d/postgresql stop
+#  - sudo /etc/init.d/postgresql start
+#  - sudo apt-get install valgrind
+  - sudo -u postgres /usr/lib/postgresql/9.5/bin/postgres -D /var/lib/postgresql/9.5/main -c config_file=/etc/postgresql/9.5/main/postgresql.conf 2>/tmp/postgres.log &
+# Run the Postgres instance under Valgrind and collect Valgrind log
+#  - sudo -u postgres valgrind --trace-children=yes --leak-check=full /usr/lib/postgresql/9.5/bin/postgres -D /var/lib/postgresql/9.5/main -c config_file=/etc/postgresql/9.5/main/postgresql.conf 2>/tmp/postgres.log &
+  - psql --version
+  - if test "$GDAL_VERSION" = "11"; then sudo apt-get install libgdal-dev; fi
+  - if test "$GDAL_VERSION" = "20"; then wget http://download.osgeo.org/gdal/2.0.2/gdal-2.0.2.tar.xz; tar xJf gdal-2.0.2.tar.xz; cd gdal-2.0.2; ./configure --prefix=/usr --enable-debug --without-libtool; make -j4 >/dev/null; sudo make install >/dev/null; cd ..; gdalinfo --version; fi
+  - if test "$GDAL_VERSION" = "21"; then wget http://download.osgeo.org/gdal/2.1.0/gdal-2.1.0.tar.xz; tar xJf gdal-2.1.0.tar.xz; cd gdal-2.1.0; ./configure --prefix=/usr --enable-debug --without-libtool; make -j4 >/dev/null; sudo make install >/dev/null; cd ..; gdalinfo --version; fi
+
+script:
+  - make
+  - sudo make install
+  - PGUSER=postgres make installcheck || (cat regression.diffs && /bin/false)
+# Small delay so that things get flushed
+#  - sleep 5
+# Check that ogr_fdw doesn't appear in Valgrind log
+#  - if sudo -u postgres grep ogr_fdw /tmp/postgres.log > /dev/null; then sudo -u postgres cat /tmp/postgres.log && /bin/false; fi
diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 0000000..b3e1771
--- /dev/null
+++ b/FAQ.md
@@ -0,0 +1,22 @@
+# Frequently Asked Questions
+
+### ODBC connections work in `ogr_fdw_info.exe` but not in the database, why?
+
+1. One possibility is that you registered DSN under USER instead of System.  It should be system since otherwise it will only work under the account you are logged in as. Since `ogr_fdw_info.exe` is  a client app, it will give you a false sense of success since ODBC will in server, run under the context of the PostgreSQL service account. Verify that you have a System DSN (and **not** a User DSN).
+2. If you used the Windows installer for PostgreSQL, it starts up PostgreSQL using Network Service account. I always manually switch it to a real user account since I need it to access some network resources. I never tested, but I suspect ODBC keys may not be readable by Network Service account.
+ 
+If change #1 doesn't work, try changing PostgreSQL to run under a regular user account.  Make sure that user has full control of the PostgreSQL data folder.
+
+### Do I need ODBC to read an MS Access database?
+
+You shouldn't need to do ODBC for MS Access, should be able to do:
+ 
+    ogr_fdw_info.exe -s "D:\FDW Data\My Folder\MYFILE.MDB"
+ 
+As an added bonus, using a direct access shouldn't need to read ODBC registry keys since it's a DNSless connection.
+ 
+### Why doesn't my MS Access connection work?
+ 
+If your MS Access database is on a network share, your PostgreSQL service account needs to be able to access it by the path you use. For MS Access databases it also has to have write permissions. The reason is MS Access databases use a locking file to manage access, so all users that have read permission also need to have write permission into the folder to create the lock file and delete the lock file (if they are the last ones in) if it doesn't exist.
+ 
+Also note: even if the PostgreSQL service account can access the folder `\\S\Files`, if you map it to say `S:\` the connection will not work if the mapped drive is not set under the user account that PostgreSQL runs under. (That said – it's safer to use UNCs (`\\S\Files`) instead of mapped drives.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..e1c3de3
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,7 @@
+Copyright (C) 2014 Paul Ramsey <pramsey at cleverelephant.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/META.json b/META.json
new file mode 100644
index 0000000..4fcbb18
--- /dev/null
+++ b/META.json
@@ -0,0 +1,51 @@
+{
+   "name": "ogr_fdw",
+   "abstract": "OGR foreign data wrapper",
+   "description": "OGR FDW allows you to connect to any OGR supported data source.",
+   "version": "1.0",
+   "maintainer": [
+      "Paul Ramsey <pramsey at cleverelephant.ca>"
+   ],
+   "license": {
+      "mit": "http://en.wikipedia.org/wiki/MIT_License"
+   },
+   "prereqs": {
+      "runtime": {
+         "requires": {
+            "PostgreSQL": "9.3.0"
+         },
+         "recommends": {
+            "PostgreSQL": "9.3.4"
+         }
+      }
+   },
+   "provides": {
+     "http": {
+       "file": "ogr_fdw--1.0.sql",
+       "docfile": "README.md",
+       "version": "1.0",
+       "abstract": "OGR FDW wrapper"
+     },
+   },
+   "resources": {
+      "homepage": "https://github.com/pramsey/pgsql-ogr-fdw/",
+      "bugtracker": {
+         "web": "https://github.com/pramsey/pgsql-ogr-fdw/issues"
+      },
+      "repository": {
+        "url":  "https://github.com/pramsey/pgsql-ogr-fdw.git",
+        "web":  "https://github.com/pramsey/pgsql-ogr-fdw/",
+        "type": "git"
+      }
+   },
+   "generated_by": "Paul Ramsey",
+   "meta-spec": {
+      "version": "1.0.0",
+      "url": "http://pgxn.org/meta/spec.txt"
+   },
+   "tags": [
+      "http",
+      "curl",
+      "web"
+   ]
+}
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..86f89e3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+# ogr_fdw/Makefile
+
+MODULE_big = ogr_fdw
+OBJS = ogr_fdw.o ogr_fdw_deparse.o ogr_fdw_common.o stringbuffer_pg.o
+EXTENSION = ogr_fdw
+DATA = ogr_fdw--1.0.sql
+
+REGRESS = ogr_fdw
+
+EXTRA_CLEAN = sql/*.sql expected/*.out
+
+GDAL_CONFIG = gdal-config
+GDAL_CFLAGS = $(shell $(GDAL_CONFIG) --cflags)
+GDAL_LIBS = $(shell $(GDAL_CONFIG) --libs)
+
+PG_CONFIG = pg_config
+REGRESS_OPTS = --encoding=UTF8
+
+PG_CPPFLAGS += $(GDAL_CFLAGS)
+LIBS += $(GDAL_LIBS)
+SHLIB_LINK := $(LIBS)
+
+# order matters, file first, import last
+REGRESS = file pgsql import
+
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+
+
+###############################################################
+# Build the utility program after PGXS to override the
+# PGXS environment
+
+CFLAGS = $(GDAL_CFLAGS)
+LIBS = $(GDAL_LIBS)
+
+ogr_fdw_info$(X): ogr_fdw_info.o ogr_fdw_common.o stringbuffer.o
+	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
+
+stringbuffer_pg.o: stringbuffer.c stringbuffer.h
+	$(CC) $(CFLAGS) -D USE_PG_MEM -c -o $@ $<
+
+clean-exe:
+	rm -f ogr_fdw_info$(X) ogr_fdw_info.o stringbuffer.o
+
+install-exe: all
+	$(INSTALL_PROGRAM) ogr_fdw_info$(X) '$(DESTDIR)$(bindir)'
+
+all: ogr_fdw_info$(X)
+
+clean: clean-exe
+
+install: install-exe
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a40e0fd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,344 @@
+Travis:
+ [![Build Status](https://secure.travis-ci.org/pramsey/pgsql-ogr-fdw.png)]
+ (http://travis-ci.org/pramsey/pgsql-ogr-fdw)
+
+# PostgreSQL OGR Foreign Data Wrapper
+
+## Motivation
+
+OGR is the vector half of the [GDAL](http://www.gdal.org/) spatial data access library. It allows access to a [large number of GIS data formats](http://www.gdal.org/ogr_formats.html) using a [simple C API](http://www.gdal.org/ogr__api_8h.html) for data reading and writing. Since OGR exposes a simple table structure and PostgreSQL [foreign data wrappers](https://wiki.postgresql.org/wiki/Foreign_data_wrappers) allow access to table structures, the fit seems pretty perfect. 
+
+## Limitations
+
+This implementation currently has the following limitations:
+
+* **PostgreSQL 9.3+** This wrapper does not support the FDW implementations in older versions of PostgreSQL.
+* **Only non-spatial query restrictions are pushed down to the OGR driver.** PostgreSQL foreign data wrappers support delegating portions of the SQL query to the underlying data source, in this case OGR. This implementation currently pushes down only non-spatial query restrictions, and only for the small subset of comparison operators (>, <, <=, >=, =) supported by OGR.
+* **Spatial restrictions are not pushed down.** OGR can handle basic bounding box restrictions and even (for some drivers) more explicit intersection restrictions, but those are not passed to the OGR driver yet.
+* **OGR connections every time** Rather than pooling OGR connections, each query makes (and disposes of) two new ones, which seems to be the largest performance drag at the moment for restricted (small) queries.
+* **All columns are retrieved every time.** PostgreSQL foreign data wrappers don't require all columns all the time, and some efficiencies can be gained by only requesting the columns needed to fulfill a query. This would be a minimal efficiency improvement, but can be removed given some development time, since the OGR API supports returning a subset of columns.
+
+## Basic Operation
+
+In order to access geometry data from OGR, the PostGIS extension has to be installed: if it is not installed, geometry will be represented as bytea columns, with well-known binary (WKB) values.
+
+To build the wrapper, make sure you have the GDAL library and development packages (is `gdal-config` on your path?) installed, as well as the PostgreSQL development packages (is `pg_config` on your path?)
+
+Build the wrapper with `make` and `make install`. Now you are ready to create a foreign table.
+
+First install the `postgis` and `ogr_fdw` extensions in your database.
+
+    -- Install the required extensions
+    CREATE EXTENSION postgis;
+    CREATE EXTENSION ogr_fdw;
+
+For a test data set, copy the `pt_two` example shape file from the `data` directory to a location where the PostgreSQL server can read it (like `/tmp/test/` for example). 
+
+Use the `ogr_fdw_info` tool to read an OGR data source and output a server and table definition for a particular layer. (You can write these manually, but the utility makes it a little more foolproof.)
+
+    > ogr_fdw_info -f
+
+    Supported Formats:
+      -> "PCIDSK" (read/write)
+      -> "netCDF" (read/write)
+      ...
+      -> "HTTP" (readonly)
+
+
+    > ogr_fdw_info -s /tmp/test
+    
+    Layers:
+      pt_two
+
+    
+    > ogr_fdw_info -s /tmp/test -l pt_two
+    
+    CREATE SERVER myserver
+      FOREIGN DATA WRAPPER ogr_fdw
+      OPTIONS (
+        datasource '/tmp/test',
+        format 'ESRI Shapefile' );
+
+    CREATE FOREIGN TABLE pt_two (
+      fid integer,
+      geom geometry(Point, 4326),
+      name varchar,
+      age integer,
+      height real,
+      birthdate date )
+      SERVER myserver
+      OPTIONS (layer 'pt_two');
+    
+Copy the `CREATE SERVER` and `CREATE FOREIGN SERVER` SQL commands into the database and you'll have your foreign table definition.
+
+                 Foreign table "public.pt_two"
+      Column  |       Type        | Modifiers | FDW Options 
+    ----------+-------------------+-----------+-------------
+     fid      | integer           |           | 
+     geom     | geometry          |           | 
+     name     | character varying |           | 
+     age      | integer           |           | 
+     height   | real              |           | 
+     birthday | date              |           | 
+    Server: tmp_shape
+    FDW Options: (layer 'pt_two')
+
+And you can query the table directly, even though it's really just a shape file.
+
+    > SELECT * FROM pt_two;
+
+     fid |                    geom                    | name  | age | height |  birthday  
+    -----+--------------------------------------------+-------+-----+--------+------------
+       0 | 0101000000C00497D1162CB93F8CBAEF08A080E63F | Peter |  45 |    5.6 | 1965-04-12
+       1 | 010100000054E943ACD697E2BFC0895EE54A46CF3F | Paul  |  33 |   5.84 | 1971-03-25
+
+
+## Examples
+
+### WFS FDW
+
+Since we can access any OGR data source as a table, how about a public WFS server?
+
+    CREATE EXTENSION postgis;
+    CREATE EXTENSION ogr_fdw;
+
+    CREATE SERVER opengeo
+      FOREIGN DATA WRAPPER ogr_fdw
+      OPTIONS (
+        datasource 'WFS:http://demo.opengeo.org/geoserver/wfs',
+        format 'WFS' );
+
+    CREATE FOREIGN TABLE topp_states (
+      fid integer,
+      geom geometry,
+      gml_id varchar,
+      state_name varchar,
+      state_fips varchar,
+      sub_region varchar,
+      state_abbr varchar,
+      land_km real,
+      water_km real,
+      persons real,
+      families real,
+      houshold real,
+      male real,
+      female real,
+      workers real,
+      drvalone real,
+      carpool real,
+      pubtrans real,
+      employed real,
+      unemploy real,
+      service real,
+      manual real,
+      p_male real,
+      p_female real,
+      samp_pop real )
+      SERVER opengeo
+      OPTIONS (layer 'topp:states');
+
+### FGDB FDW
+
+Unzip the `Querying.zip` file from the `data` directory to get a `Querying.gdb` file, and put it somewhere public (like `/tmp`). Now run the `ogr_fdw_info` tool on it to get a table definition.
+
+    CREATE SERVER fgdbtest
+      FOREIGN DATA WRAPPER ogr_fdw
+      OPTIONS (
+        datasource '/tmp/Querying.gdb',
+        format 'OpenFileGDB' );
+
+    CREATE FOREIGN TABLE cities (
+      fid integer,
+      geom geometry(Point, 4326),
+      city_fips varchar,
+      city_name varchar,
+      state_fips varchar,
+      state_name varchar,
+      state_city varchar,
+      type varchar,
+      capital varchar,
+      elevation integer,
+      pop1990 integer,
+      popcat integer )
+      SERVER fgdbtest
+      OPTIONS (layer 'Cities');
+
+Query away!
+
+### PostgreSQL FDW
+
+Wraparound action! Handy for testing. Connect your database back to your database and watch the fur fly.
+
+    CREATE TABLE typetest ( 
+      fid serial primary key,
+      geom geometry(Point, 4326),
+      num real, 
+      name varchar, 
+      clock time, 
+      calendar date, 
+      tstmp timestamp 
+    );
+
+    INSERT INTO typetest 
+      VALUES (1, 'SRID=4326;POINT(-126 46)', 4.5, 'Paul', '09:34:23', 'June 1, 2013', '12:34:56 December 14, 1823');
+    INSERT INTO typetest 
+      VALUES (2, 'SRID=4326;POINT(-126 46)', 4.8, 'Peter', '14:34:53', 'July 12, 2011', '1:34:12 December 24, 1923');
+
+    CREATE SERVER wraparound
+      FOREIGN DATA WRAPPER ogr_fdw
+      OPTIONS (
+        datasource 'Pg:dbname=fdw user=postgres',
+        format 'PostgreSQL' );
+
+    CREATE FOREIGN TABLE typetest_fdw (
+      fid integer,
+      geom geometry(Point, 4326),
+      num real,
+      name varchar,
+      clock time,
+      calendar date,
+      tstmp timestamp )
+      SERVER wraparound
+      OPTIONS (layer 'typetest');
+
+    SELECT * FROM typetest_fdw;
+    
+## Advanced Features
+
+### Writeable FDW Tables
+
+If the OGR driver you are using supports it, you can insert/update/delete records from your FDW tables. 
+
+For file-backed drivers, the user under which `postgres` runs will need read/write access to the file being altered. For database-backed drivers, your connection needs a user with read/write permissions to the database.
+
+By default, servers and tables are updateable if the OGR driver supports it, but you can turn off updateability at a server or table level using the `updateable` option:
+
+    ALTER SERVER myserver 
+      OPTIONS (ADD updatable 'false');
+
+    ALTER FOREIGN TABLE mytable 
+      OPTIONS (ADD updatable 'false');
+
+Writeable tables only work if you have included a `fid` column in your table definition. By default, tables imported by `IMPORT FOREIGN SCHEMA` or using the example SQL code from `ogr_fdw_info` include a `fid` column.
+
+### Column Name Mapping
+
+You can create an FDW table with any subset of columns from the OGR source you like, just by using the same column names as the source:
+
+    CREATE FOREIGN TABLE typetest_fdw_partial (
+      clock time,
+      name varchar 
+      )
+      SERVER wraparound
+      OPTIONS (layer 'typetest');
+      
+You can also explicitly map remote column names to different local names using the `column_name` option:
+
+    CREATE FOREIGN TABLE typetest_fdw_mapped (
+      fid bigint,
+      supertime time OPTIONS (column_name 'clock'),
+      thebestnamething varchar OPTIONS (column_name 'name') 
+      )
+      SERVER wraparound
+      OPTIONS (layer 'typetest');
+      
+
+### Automatic Foreign Table Creation
+
+**This feature is only available with PostgreSQL 9.5 and higher**
+
+You can use the PostgreSQL `IMPORT FOREIGN SCHEMA` command to [import table definitions from an OGR data source](http://www.postgresql.org/docs/9.5/static/sql-importforeignschema.html).
+
+#### Import All Tables
+
+If you want to import all tables in the OGR data source use the special schema called "ogr_all".
+
+	CREATE SCHEMA fgdball;
+    
+	IMPORT FOREIGN SCHEMA ogr_all 
+		FROM SERVER fgdbtest 
+        INTO fgdball;
+
+#### Import a Subset of Tables
+
+Not all OGR data sources have a concept of schema, so we use the remote schema string as a prefix to match OGR layers. The matching is case sensitive, so make sure casing matches your layer names.
+
+For example, the following will only import tables that start with *CitiesIn*. As long as you quote, you can handle true schemaed databases such as SQL Server or PostgreSQL by using something like *"dbo."*
+
+	CREATE SCHEMA fgdbcityinf;
+    
+	IMPORT FOREIGN SCHEMA "CitiesIn"
+		FROM SERVER fgdbtest 
+        INTO fgdbcityinf;
+
+You can also use PostgreSQL clauses `LIMIT TO` and `EXCEPT` to restrict the tables you are importing.
+
+	CREATE SCHEMA fgdbcitysub;
+
+	-- import only layer called Cities
+	IMPORT FOREIGN SCHEMA ogr_all 
+        LIMIT TO(cities) 
+		FROM server fgdbtest 
+        INTO fgdbcitysub ;
+		
+	-- import only layers not called Cities or Countries
+	IMPORT FOREIGN SCHEMA ogr_all 
+        EXCEPT (cities, countries)
+		FROM server fgdbtest 
+        INTO fgdbcitysub;
+		
+	-- With table laundering turned off, need to use exact layer names
+	DROP SCHEMA IF EXISTS fgdbcitysub CASCADE;
+	
+    -- import with un-laundered table name
+	IMPORT FOREIGN SCHEMA ogr_all 
+    	LIMIT TO("Cities") 
+		FROM server fgdbtest 
+        INTO fgdbcitysub 
+        OPTIONS (launder_table_names 'false') ;
+
+
+#### Mixed Case and Special Characters
+
+In general, PostgreSQL prefers table names with [simple numbers and letters](http://www.postgresql.org/docs/9.5/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS), no punctuation or special characters. 
+
+By default, when `IMPORT FOREIGN SCHEMA` is run on an OGR foreign data server, the table names and column names are "laundered" -- all upper case is converted to lowercase and special characters such as spaces and punctuation are replaced with "_".
+
+Laundering is not desirable in all cases. You can override this behavior with two `IMPORT FOREIGN SCHEMA` options specific to `ogr_fdw` servers: `launder_column_names` and `launder_table_names`.
+
+To preserve casing and other funky characters in both column names and table names, do the following:
+
+	CREATE SCHEMA fgdbcitypreserve;
+    
+	IMPORT FOREIGN SCHEMA ogr_all
+		FROM SERVER fgdbtest 
+        INTO fgdbpreserve 
+		OPTIONS (
+            launder_table_names 'false', 
+            launder_column_names 'false'
+            ) ;
+		
+
+###  GDAL Options
+
+The behavior of your GDAL/OGR connection can be altered by passing GDAL `config_options` to the connection when you set up the server. Most GDAL/OGR drivers have some specific behaviours that are controlled by configuration options. For example, the "[ESRI Shapefile](http://www.gdal.org/drv_shapefile.html)" driver includes a `SHAPE_ENCODING` option that controls the character encoding applied to text data.
+
+Since many Shapefiles are encoded using LATIN1, and most PostgreSQL databases are encoded in UTF-8, it is useful to specify the encoding to get proper handling of special characters like accents.
+
+    CREATE SERVER myserver_latin1
+      FOREIGN DATA WRAPPER ogr_fdw
+      OPTIONS (
+        datasource '/tmp/test',
+        format 'ESRI Shapefile',
+        config_options 'SHAPE_ENCODING=LATIN1' );
+
+If you are using GDAL 2.0 or higher, you can also pass "open options" to your OGR foreign data wrapper, using the `open_options` parameter. In GDAL 2.0, the global `SHAPE_ENCODING` option has been superceded by a driver-specific `ENCODING` option, which can be called like this:
+
+    CREATE SERVER myserver_latin1
+      FOREIGN DATA WRAPPER ogr_fdw
+      OPTIONS (
+        datasource '/tmp/test',
+        format 'ESRI Shapefile',
+        open_options 'ENCODING=LATIN1' );
+
+
diff --git a/data/2launder.dbf b/data/2launder.dbf
new file mode 100644
index 0000000..298287e
Binary files /dev/null and b/data/2launder.dbf differ
diff --git a/data/2launder.prj b/data/2launder.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/data/2launder.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
\ No newline at end of file
diff --git a/data/2launder.shp b/data/2launder.shp
new file mode 100644
index 0000000..170aeba
Binary files /dev/null and b/data/2launder.shp differ
diff --git a/data/2launder.shx b/data/2launder.shx
new file mode 100644
index 0000000..14e141c
Binary files /dev/null and b/data/2launder.shx differ
diff --git a/data/Querying.zip b/data/Querying.zip
new file mode 100644
index 0000000..ad97877
Binary files /dev/null and b/data/Querying.zip differ
diff --git a/data/enc.dbf b/data/enc.dbf
new file mode 100644
index 0000000..acb1831
Binary files /dev/null and b/data/enc.dbf differ
diff --git a/data/enc.prj b/data/enc.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/data/enc.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
\ No newline at end of file
diff --git a/data/enc.shp b/data/enc.shp
new file mode 100644
index 0000000..170aeba
Binary files /dev/null and b/data/enc.shp differ
diff --git a/data/enc.shx b/data/enc.shx
new file mode 100644
index 0000000..14e141c
Binary files /dev/null and b/data/enc.shx differ
diff --git a/data/natural.dbf b/data/natural.dbf
new file mode 100644
index 0000000..2b30125
Binary files /dev/null and b/data/natural.dbf differ
diff --git a/data/no_geom.csv b/data/no_geom.csv
new file mode 100644
index 0000000..9f891b1
--- /dev/null
+++ b/data/no_geom.csv
@@ -0,0 +1,5 @@
+name,age,value
+Peter,34,10.2
+John,77,3.4
+Paul,45,19.2
+Matthew,35,18.2
diff --git a/data/pt_two.dbf b/data/pt_two.dbf
new file mode 100644
index 0000000..72a3685
Binary files /dev/null and b/data/pt_two.dbf differ
diff --git a/data/pt_two.prj b/data/pt_two.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/data/pt_two.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
\ No newline at end of file
diff --git a/data/pt_two.qpj b/data/pt_two.qpj
new file mode 100644
index 0000000..5fbc831
--- /dev/null
+++ b/data/pt_two.qpj
@@ -0,0 +1 @@
+GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
diff --git a/data/pt_two.shp b/data/pt_two.shp
new file mode 100644
index 0000000..170aeba
Binary files /dev/null and b/data/pt_two.shp differ
diff --git a/data/pt_two.shx b/data/pt_two.shx
new file mode 100644
index 0000000..14e141c
Binary files /dev/null and b/data/pt_two.shx differ
diff --git a/expected/.gitignore b/expected/.gitignore
new file mode 100644
index 0000000..5e7d273
--- /dev/null
+++ b/expected/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/input/file.source b/input/file.source
new file mode 100644
index 0000000..a228b8e
--- /dev/null
+++ b/input/file.source
@@ -0,0 +1,106 @@
+
+CREATE EXTENSION ogr_fdw;
+
+CREATE SERVER myserver
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource '@abs_srcdir@/data',
+    format 'ESRI Shapefile' );
+
+------------------------------------------------
+
+CREATE FOREIGN TABLE pt_1 (
+  fid integer,
+  geom bytea,
+  name varchar,
+  age integer,
+  height real,
+  birthdate date )
+  SERVER myserver
+  OPTIONS ( layer 'pt_two' );
+
+SELECT * FROM pt_1 WHERE fid = 1;
+
+------------------------------------------------
+
+CREATE FOREIGN TABLE pt_2 (
+  fid integer,
+  name varchar )
+  SERVER myserver
+  OPTIONS ( layer 'pt_two' );
+
+SELECT * FROM pt_2 ORDER BY name;
+
+------------------------------------------------
+
+CREATE FOREIGN TABLE pt_3 (
+  geom bytea,
+  name varchar )
+  SERVER myserver
+  OPTIONS ( layer 'pt_two' );
+
+SELECT * FROM pt_3 ORDER BY name;
+
+------------------------------------------------
+-- Laundering and explicit column naming test
+
+CREATE FOREIGN TABLE column_name_test (
+ fid integer,
+ name varchar OPTIONS (column_name '2ame'),
+ theage integer OPTIONS (column_name 'age'),
+ height real OPTIONS (column_name 'Height'),
+ birthdate date OPTIONS (column_name 'b-rthdate')
+) SERVER myserver
+OPTIONS (layer '2launder');
+
+SELECT * FROM column_name_test ORDER BY fid;
+
+-- Check that columns are reverse-laundered when generating
+-- OGR SQL filters
+SET client_min_messages = debug1;
+SELECT name FROM column_name_test WHERE name = 'Paul';
+SET client_min_messages = notice;
+
+------------------------------------------------
+-- GDAL options passing tests
+
+CREATE SERVER myserver_latin1
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource '@abs_srcdir@/data',
+    format 'ESRI Shapefile',
+    config_options 'SHAPE_ENCODING=LATIN1' );
+
+CREATE FOREIGN TABLE e_1 (
+  fid integer,
+  name varchar )
+  SERVER myserver_latin1
+  OPTIONS ( layer 'enc' );
+
+SET client_min_messages = debug1;  
+SELECT * FROM e_1 WHERE fid = 1;
+SET client_min_messages = notice;  
+
+------------------------------------------------
+-- Geometryless test
+
+CREATE SERVER csvserver
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource '@abs_srcdir@/data/no_geom.csv',
+    format 'CSV' );
+
+CREATE FOREIGN TABLE no_geom (
+  fid bigint,
+  name varchar,
+  age varchar,
+  value varchar
+) SERVER csvserver
+OPTIONS (layer 'no_geom');
+
+SELECT c.* 
+  FROM generate_series(1,4) g 
+  JOIN no_geom c 
+  ON (c.fid = g.g);
+
+------------------------------------------------
diff --git a/input/import.source b/input/import.source
new file mode 100644
index 0000000..5181bbf
--- /dev/null
+++ b/input/import.source
@@ -0,0 +1,27 @@
+------------------------------------------------
+
+CREATE SCHEMA imp1;
+
+IMPORT FOREIGN SCHEMA ogr_all 
+  LIMIT TO (n2launder) 
+  FROM SERVER myserver 
+  INTO imp1;
+
+\d imp1.n2launder
+  
+SELECT * FROM imp1.n2launder WHERE fid = 0;
+
+------------------------------------------------
+
+CREATE SCHEMA imp2;
+
+IMPORT FOREIGN SCHEMA ogr_all 
+  LIMIT TO ("natural") 
+  FROM SERVER myserver 
+  INTO imp2;
+
+\d imp2."natural"
+
+SELECT "natural" FROM imp2."natural";
+
+------------------------------------------------
diff --git a/input/pgsql.source b/input/pgsql.source
new file mode 100644
index 0000000..b0f8d91
--- /dev/null
+++ b/input/pgsql.source
@@ -0,0 +1,88 @@
+CREATE SERVER pgserver
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource 'PG:dbname=contrib_regression host=localhost',
+    format 'PostgreSQL' );
+	
+CREATE TABLE bytea_local (
+  fid serial primary key,
+  geom bytea,
+  name varchar,
+  age bigint,
+  value float8,
+  num numeric(6,2),
+  dt date,
+  tm time,
+  dttm timestamp,
+  varch char(8),
+  yn char
+);
+
+----------------------------------------------------------------------
+
+INSERT INTO bytea_local (name, geom, age, value, num, dt, tm, dttm, varch, yn) 
+  VALUES ('Jim', '14232'::bytea, 23, 4.3, 5.5, '2010-10-10'::date, '13:23:21'::time, '2010-10-10 13:23:21'::timestamp, 'this', 'y' );
+INSERT INTO bytea_local (name, geom, age, value, num, dt, tm, dttm, varch, yn) 
+  VALUES ('Marvin', '55555'::bytea, 34, 5.4, 10.13, '2011-11-11'::date, '15:21:45'::time, '2011-11-11 15:21:45'::timestamp, 'that', 'n' );
+
+----------------------------------------------------------------------
+
+CREATE FOREIGN TABLE bytea_fdw (
+  fid integer,
+  geom bytea,
+  name varchar,
+  age bigint,
+  value float8,
+  num numeric(6,2),
+  dt date,
+  tm time,
+  dttm timestamp,
+  varch char(8),
+  yn char
+) SERVER pgserver OPTIONS (layer 'bytea_local');
+
+SELECT fid, name, geom, age, value, num, dt, tm, dttm, varch, yn FROM bytea_fdw;
+
+SELECT a.name, b.name 
+  FROM bytea_local a 
+  JOIN bytea_fdw b 
+  USING (fid);
+
+EXPLAIN VERBOSE 
+  SELECT fid, name, geom, age, value, num, dt, tm, dttm, varch, yn 
+  FROM bytea_fdw;
+
+----------------------------------------------------------------------
+
+INSERT INTO bytea_fdw (name, geom, age, value, num, dt, tm, dttm, varch, yn) 
+VALUES ('Margaret', '2222'::bytea, 12, 1.4, 19.13, '2001-11-23'::date, '9:12:34'::time, '2001-02-11 09:23:11'::timestamp, 'them', 'y' ) 
+RETURNING fid, name, geom, age, value, num, dt, tm, dttm, varch, yn;
+
+SELECT fid, name, geom, age, value, num, dt, tm, dttm, varch, yn 
+  FROM bytea_fdw
+  WHERE fid = 3;
+
+UPDATE bytea_fdw 
+  SET name = 'Maggie', num = 45.34, yn = 'n'
+  WHERE age = 12;
+
+SELECT fid, name, num, yn
+  FROM bytea_fdw
+  WHERE fid = 3;
+
+UPDATE bytea_fdw 
+  SET dt = '2089-12-13', tm = '01:23:45'
+  WHERE num = 45.34;
+
+SELECT fid, dt, tm
+  FROM bytea_fdw
+  WHERE fid = 3;
+
+DELETE FROM bytea_fdw 
+  WHERE fid = 3;
+  
+SELECT a.fid, a.name, b.name 
+  FROM bytea_local a 
+  JOIN bytea_fdw b 
+  USING (fid);
+  
\ No newline at end of file
diff --git a/ogr_fdw--1.0.sql b/ogr_fdw--1.0.sql
new file mode 100644
index 0000000..c010946
--- /dev/null
+++ b/ogr_fdw--1.0.sql
@@ -0,0 +1,18 @@
+/* ogr_fdw/ogr_fdw--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION ogr_fdw" to load this file. \quit
+
+CREATE FUNCTION ogr_fdw_handler()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE 'c' STRICT;
+
+CREATE FUNCTION ogr_fdw_validator(text[], oid)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE 'c' STRICT;
+
+CREATE FOREIGN DATA WRAPPER ogr_fdw
+  HANDLER ogr_fdw_handler
+  VALIDATOR ogr_fdw_validator;
diff --git a/ogr_fdw.c b/ogr_fdw.c
new file mode 100644
index 0000000..f76b2d4
--- /dev/null
+++ b/ogr_fdw.c
@@ -0,0 +1,2609 @@
+/*-------------------------------------------------------------------------
+ *
+ * ogr_fdw.c
+ *		  foreign-data wrapper for GIS data access.
+ *
+ * Copyright (c) 2014-2015, Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * System
+ */
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "postgres.h"
+
+/*
+ * Require PostgreSQL >= 9.3
+ */
+#if PG_VERSION_NUM < 90300
+#error "OGR FDW requires PostgreSQL version 9.3 or higher"
+#else
+
+/*
+ * Local structures
+ */
+#include "ogr_fdw.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * Describes the valid options for objects that use this wrapper.
+ */
+struct OgrFdwOption
+{
+	const char *optname;
+	Oid optcontext;     /* Oid of catalog in which option may appear */
+	bool optrequired;   /* Flag mandatory options */
+	bool optfound;      /* Flag whether options was specified by user */
+};
+
+#define OPT_DRIVER "format"
+#define OPT_SOURCE "datasource"
+#define OPT_LAYER "layer"
+#define OPT_COLUMN "column_name"
+#define OPT_CONFIG_OPTIONS "config_options"
+#define OPT_OPEN_OPTIONS "open_options"
+#define OPT_UPDATEABLE "updateable"
+
+#define OGR_FDW_FRMT_INT64	 "%lld"
+#define OGR_FDW_CAST_INT64(x)	 (long long)(x)
+
+/*
+ * Valid options for ogr_fdw.
+ * ForeignDataWrapperRelationId (no options)
+ * ForeignServerRelationId (CREATE SERVER options)
+ * UserMappingRelationId (CREATE USER MAPPING options)
+ * ForeignTableRelationId (CREATE FOREIGN TABLE options)
+ */
+static struct OgrFdwOption valid_options[] = {
+
+	/* OGR column mapping */
+	{OPT_COLUMN, AttributeRelationId, false, false},
+
+	/* OGR datasource options */
+	{OPT_SOURCE, ForeignServerRelationId, true, false},
+	{OPT_DRIVER, ForeignServerRelationId, false, false},
+	{OPT_UPDATEABLE, ForeignServerRelationId, false, false},
+	{OPT_CONFIG_OPTIONS, ForeignServerRelationId, false, false},
+#if GDAL_VERSION_MAJOR >= 2
+	{OPT_OPEN_OPTIONS, ForeignServerRelationId, false, false},
+#endif
+	/* OGR layer options */
+	{OPT_LAYER, ForeignTableRelationId, true, false},
+	{OPT_UPDATEABLE, ForeignTableRelationId, false, false},
+
+	/* EOList marker */
+	{NULL, InvalidOid, false, false}
+};
+
+/*
+ * SQL functions
+ */
+extern Datum ogr_fdw_handler(PG_FUNCTION_ARGS);
+extern Datum ogr_fdw_validator(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(ogr_fdw_handler);
+PG_FUNCTION_INFO_V1(ogr_fdw_validator);
+void _PG_init(void);
+
+/*
+ * FDW query callback routines
+ */
+static void ogrGetForeignRelSize(PlannerInfo *root,
+					RelOptInfo *baserel,
+					Oid foreigntableid);
+static void ogrGetForeignPaths(PlannerInfo *root,
+					RelOptInfo *baserel,
+					Oid foreigntableid);
+static ForeignScan *ogrGetForeignPlan(PlannerInfo *root,
+				 	RelOptInfo *baserel,
+				 	Oid foreigntableid,
+				 	ForeignPath *best_path,
+				 	List *tlist,
+				 	List *scan_clauses
+#if PG_VERSION_NUM >= 90500
+					,Plan *outer_plan
+#endif
+);
+static void ogrBeginForeignScan(ForeignScanState *node, int eflags);
+static TupleTableSlot *ogrIterateForeignScan(ForeignScanState *node);
+static void ogrReScanForeignScan(ForeignScanState *node);
+static void ogrEndForeignScan(ForeignScanState *node);
+
+/*
+ * FDW modify callback routines
+ */
+static void ogrAddForeignUpdateTargets (Query *parsetree,
+					RangeTblEntry *target_rte,
+					Relation target_relation);
+static void ogrBeginForeignModify (ModifyTableState *mtstate,
+					ResultRelInfo *rinfo,
+					List *fdw_private,
+					int subplan_index,
+					int eflags);
+static TupleTableSlot *ogrExecForeignInsert (EState *estate,
+					ResultRelInfo *rinfo,
+					TupleTableSlot *slot,
+					TupleTableSlot *planSlot);
+static TupleTableSlot *ogrExecForeignUpdate (EState *estate,
+					ResultRelInfo *rinfo,
+					TupleTableSlot *slot,
+					TupleTableSlot *planSlot);
+static TupleTableSlot *ogrExecForeignDelete (EState *estate,
+					ResultRelInfo *rinfo,
+					TupleTableSlot *slot,
+					TupleTableSlot *planSlot);
+static void ogrEndForeignModify (EState *estate,
+					ResultRelInfo *rinfo);
+static int ogrIsForeignRelUpdatable (Relation rel);
+
+
+#if PG_VERSION_NUM >= 90500
+static List *ogrImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);
+#endif
+
+/*
+ * Helper functions
+ */
+static OgrConnection ogrGetConnectionFromTable(Oid foreigntableid, bool updateable);
+static void ogr_fdw_exit(int code, Datum arg);
+static void ogrReadColumnData(OgrFdwState *state);
+
+/* Global to hold GEOMETRYOID */
+Oid GEOMETRYOID = InvalidOid;
+
+
+
+void
+_PG_init(void)
+{
+	// DefineCustomIntVariable("mysql_fdw.wait_timeout",
+	// 						"Server-side wait_timeout",
+	// 						"Set the maximum wait_timeout"
+	// 						"use to set the MySQL session timeout",
+	// 						&wait_timeout,
+	// 						WAIT_TIMEOUT,
+	// 						0,
+	// 						INT_MAX,
+	// 						PGC_USERSET,
+	// 						0,
+	// 						NULL,
+	// 						NULL,
+	// 						NULL);
+
+	/*
+	 * We assume PostGIS is installed in 'public' and if we cannot
+	 * find it, we'll treat all geometry from OGR as bytea.
+	 */
+	// const char *typname = "geometry";
+	// Oid namesp = LookupExplicitNamespace("public", false);
+	// Oid typoid = GetSysCacheOid2(TYPENAMENSP, CStringGetDatum(typname), ObjectIdGetDatum(namesp));
+	Oid typoid = TypenameGetTypid("geometry");
+
+	if (OidIsValid(typoid) && get_typisdefined(typoid))
+	{
+		GEOMETRYOID = typoid;
+	}
+	else
+	{
+		GEOMETRYOID = BYTEAOID;
+	}
+
+	on_proc_exit(&ogr_fdw_exit, PointerGetDatum(NULL));
+}
+
+/*
+ * ogr_fdw_exit: Exit callback function.
+ */
+static void
+ogr_fdw_exit(int code, Datum arg)
+{
+	OGRCleanupAll();
+}
+
+
+/*
+ * Foreign-data wrapper handler function: return a struct with pointers
+ * to my callback routines.
+ */
+Datum
+ogr_fdw_handler(PG_FUNCTION_ARGS)
+{
+	FdwRoutine *fdwroutine = makeNode(FdwRoutine);
+
+	/* Read support */
+	fdwroutine->GetForeignRelSize = ogrGetForeignRelSize;
+	fdwroutine->GetForeignPaths = ogrGetForeignPaths;
+	fdwroutine->GetForeignPlan = ogrGetForeignPlan;
+	fdwroutine->BeginForeignScan = ogrBeginForeignScan;
+	fdwroutine->IterateForeignScan = ogrIterateForeignScan;
+	fdwroutine->ReScanForeignScan = ogrReScanForeignScan;
+	fdwroutine->EndForeignScan = ogrEndForeignScan;
+	
+	/* Write support */
+	fdwroutine->AddForeignUpdateTargets = ogrAddForeignUpdateTargets;
+	fdwroutine->BeginForeignModify = ogrBeginForeignModify;
+	fdwroutine->ExecForeignInsert = ogrExecForeignInsert;
+	fdwroutine->ExecForeignUpdate = ogrExecForeignUpdate;
+	fdwroutine->ExecForeignDelete = ogrExecForeignDelete;
+	fdwroutine->EndForeignModify = ogrEndForeignModify;
+	fdwroutine->IsForeignRelUpdatable = ogrIsForeignRelUpdatable;
+
+#if PG_VERSION_NUM >= 90500
+	/*  Support functions for IMPORT FOREIGN SCHEMA */
+	fdwroutine->ImportForeignSchema = ogrImportForeignSchema;
+#endif
+
+	PG_RETURN_POINTER(fdwroutine);
+}
+
+/*
+ * Given a connection string and (optional) driver string, try to connect
+ * with appropriate error handling and reporting. Used in query startup,
+ * and in FDW options validation.
+ */
+static GDALDatasetH
+ogrGetDataSource(const char *source, const char *driver, bool updateable, 
+                 const char *config_options, const char *open_options)
+{
+	GDALDatasetH ogr_ds = NULL;
+	GDALDriverH ogr_dr = NULL;
+	char **open_option_list = NULL;
+#if GDAL_VERSION_MAJOR >= 2
+	unsigned int open_flags = GDAL_OF_VECTOR;
+	
+	if ( updateable )
+		open_flags |= GDAL_OF_UPDATE;
+	else
+		open_flags |= GDAL_OF_READONLY;
+#endif
+
+	if ( config_options )
+	{
+		char **option_iter;
+		char **option_list = CSLTokenizeString(config_options);
+
+		for ( option_iter = option_list; option_iter && *option_iter; option_iter++ )
+		{
+			char *key;
+			const char *value;
+			value = CPLParseNameValue(*option_iter, &key);
+			if ( ! (key && value) )
+				elog(ERROR, "bad config option string '%s'", config_options);
+			
+			elog(DEBUG1, "GDAL config option '%s' set to '%s'", key, value);
+			CPLSetConfigOption(key, value);
+			CPLFree(key);
+		}
+		CSLDestroy( option_list );
+	}
+
+	if ( open_options )
+		open_option_list = CSLTokenizeString(open_options);
+
+	/* Cannot search for drivers if they aren't registered */
+	/* But don't call for registration if we already have drivers */
+	if ( GDALGetDriverCount() <= 0 )
+		GDALAllRegister();
+
+	if ( driver )
+	{
+		ogr_dr = GDALGetDriverByName(driver);
+		if ( ! ogr_dr )
+		{
+			ereport(ERROR,
+				(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+					errmsg("unable to find format \"%s\"", driver),
+					errhint("See the formats list at http://www.gdal.org/ogr_formats.html")));
+		}
+#if GDAL_VERSION_MAJOR < 2
+		ogr_ds = OGR_Dr_Open(ogr_dr, source, updateable);
+#else
+		{
+			char** driver_list = CSLAddString(NULL, driver);
+			ogr_ds = GDALOpenEx(source,                                         /* file/data source */
+					    open_flags,                /* open flags */
+					    (const char* const*)driver_list, /* driver */
+					    (const char *const *)open_option_list,          /* open options */
+					    NULL);                                          /* sibling files */
+			CSLDestroy( driver_list );
+		}
+#endif
+	}
+	/* No driver, try a blind open... */
+	else
+	{
+#if GDAL_VERSION_MAJOR < 2
+		ogr_ds = OGROpen(source, updateable, &ogr_dr);
+#else
+		ogr_ds = GDALOpenEx(source, 
+		                    open_flags, 
+		                    NULL, 
+		                    (const char *const *)open_option_list,
+		                    NULL);
+#endif
+	}
+
+	/* Open failed, provide error hint if OGR gives us one. */
+	if ( ! ogr_ds )
+	{
+		const char *ogrerr = CPLGetLastErrorMsg();
+		if ( ogrerr && ! streq(ogrerr, "") )
+		{
+			ereport(ERROR,
+				(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+				 errmsg("unable to connect to data source \"%s\"", source),
+				 errhint("%s", ogrerr)));
+		}
+		else
+		{
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+ 				 errmsg("unable to connect to data source \"%s\"", source)));
+		}
+	}
+	
+	CSLDestroy( open_option_list );
+
+	return ogr_ds;
+}
+
+static bool
+ogrCanReallyCountFast(const OgrConnection *con)
+{
+	GDALDriverH dr = GDALGetDatasetDriver(con->ds);
+	const char *dr_str = GDALGetDriverShortName(dr);
+
+	if ( streq(dr_str, "ESRI Shapefile" ) ||
+   	     streq(dr_str, "FileGDB" ) ||
+	     streq(dr_str, "OpenFileGDB" ) )
+	{
+		return true;
+	}
+	return false;
+}
+
+static void 
+ogrEreportError(const char *errstr)
+{
+	const char *ogrerr = CPLGetLastErrorMsg();
+	if ( ogrerr && ! streq(ogrerr,"") )
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_FDW_ERROR),
+			 errmsg("%s", errstr),
+			 errhint("%s", ogrerr)));
+	}
+	else
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_FDW_ERROR),
+			 errmsg("%s", errstr)));
+	}
+}
+
+/*
+ * Make sure the datasource is cleaned up when we're done
+ * with a connection.
+ */
+static void
+ogrFinishConnection(OgrConnection *ogr)
+{
+	if ( ogr->lyr && OGR_L_SyncToDisk(ogr->lyr) != OGRERR_NONE )
+		elog(NOTICE, "failed to flush writes to OGR data source");
+
+	if ( ogr->ds )
+		GDALClose(ogr->ds);
+
+	ogr->ds = NULL;
+}
+
+static OgrConnection
+ogrGetConnectionFromServer(Oid foreignserverid, bool updateable)
+{
+	ForeignServer *server;
+	OgrConnection ogr;
+	ListCell *cell;
+
+	/* Null all values */
+	memset(&ogr, 0, sizeof(OgrConnection));
+	ogr.ds_updateable = OGR_UPDATEABLE_UNSET;
+	ogr.lyr_updateable = OGR_UPDATEABLE_UNSET;
+	
+	server = GetForeignServer(foreignserverid);
+
+	foreach(cell, server->options)
+	{
+		DefElem *def = (DefElem *) lfirst(cell);
+		if (streq(def->defname, OPT_SOURCE))
+			ogr.ds_str = defGetString(def);
+		if (streq(def->defname, OPT_DRIVER))
+			ogr.dr_str = defGetString(def);
+		if (streq(def->defname, OPT_CONFIG_OPTIONS))
+			ogr.config_options = defGetString(def);
+		if (streq(def->defname, OPT_OPEN_OPTIONS))
+			ogr.open_options = defGetString(def);
+		if (streq(def->defname, OPT_UPDATEABLE))
+		{
+			if ( defGetBoolean(def) )
+				ogr.ds_updateable = OGR_UPDATEABLE_TRUE;
+			else
+				ogr.ds_updateable = OGR_UPDATEABLE_FALSE;				
+		}
+	}
+
+	if ( ! ogr.ds_str )
+		elog(ERROR, "FDW table '%s' option is missing", OPT_SOURCE);
+
+	if ( updateable && ogr.ds_updateable == OGR_UPDATEABLE_FALSE )
+		ereport(ERROR,
+			(errcode(ERRCODE_FDW_ERROR),
+			 errmsg("updates are not allowed on foreign server '%s'", server->servername),
+			 errhint("ALTER FOREIGN SERVER %s OPTIONS (SET updatable 'true')", server->servername)));
+		
+	/*
+	 * TODO: Connections happen twice for each query, having a
+	 * connection pool will certainly make things faster.
+	 */
+
+	/*  Connect! */
+	ogr.ds = ogrGetDataSource(ogr.ds_str, ogr.dr_str, updateable, ogr.config_options, ogr.open_options);
+
+	return ogr;
+}
+
+
+/*
+ * Read the options (data source connection from server and
+ * layer name from table) from a foreign table and use them
+ * to connect to an OGR layer. Return a connection object that
+ * has handles for both the datasource and layer.
+ */
+static OgrConnection
+ogrGetConnectionFromTable(Oid foreigntableid, bool updateable)
+{
+	ForeignTable *table;
+	/* UserMapping *mapping; */
+	/* ForeignDataWrapper *wrapper; */
+	ListCell *cell;
+	OgrConnection ogr;
+
+	/* Gather all data for the foreign table. */
+	table = GetForeignTable(foreigntableid);
+	/* mapping = GetUserMapping(GetUserId(), table->serverid); */
+
+	ogr = ogrGetConnectionFromServer(table->serverid, updateable);
+
+	foreach(cell, table->options)
+	{
+		DefElem *def = (DefElem *) lfirst(cell);
+		if (streq(def->defname, OPT_LAYER))
+			ogr.lyr_str = defGetString(def);
+		if (streq(def->defname, OPT_UPDATEABLE))
+		{
+			if ( defGetBoolean(def) )
+				ogr.lyr_updateable = OGR_UPDATEABLE_TRUE;
+			else
+				ogr.lyr_updateable = OGR_UPDATEABLE_FALSE;				
+		}
+	}
+
+	if ( ! ogr.lyr_str )
+		elog(ERROR, "FDW table '%s' option is missing", OPT_LAYER);
+
+	if ( updateable && ogr.lyr_updateable == OGR_UPDATEABLE_FALSE )
+		ereport(ERROR,
+			(errcode(ERRCODE_FDW_ERROR),
+			 errmsg("updates are not allowed on foreign table '%s'", get_rel_name(table->relid)),
+			 errhint("ALTER FOREIGN TABLE %s OPTIONS (SET updatable 'true')", get_rel_name(table->relid))));
+
+	/* Does the layer exist in the data source? */
+	ogr.lyr = GDALDatasetGetLayerByName(ogr.ds, ogr.lyr_str);
+	if ( ! ogr.lyr )
+	{
+		const char *ogrerr = CPLGetLastErrorMsg();
+		ereport(ERROR, (
+				errcode(ERRCODE_FDW_OPTION_NAME_NOT_FOUND),
+				errmsg("unable to connect to %s to \"%s\"", OPT_LAYER, ogr.lyr_str),
+				(ogrerr && ! streq(ogrerr, ""))
+				? errhint("%s", ogrerr)
+				: errhint("Does the layer exist?")
+				));
+	}
+
+	return ogr;
+}
+
+/*
+ * Validate the options given to a FOREIGN DATA WRAPPER, SERVER,
+ * USER MAPPING or FOREIGN TABLE that uses ogr_fdw.
+ *
+ * Raise an ERROR if the option or its value is considered invalid.
+ */
+Datum
+ogr_fdw_validator(PG_FUNCTION_ARGS)
+{
+	List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
+	Oid catalog = PG_GETARG_OID(1);
+	ListCell *cell;
+	struct OgrFdwOption *opt;
+	const char *source = NULL, *driver = NULL;
+	const char *config_options = NULL, *open_options = NULL;
+	bool updateable = false;
+
+	/* Check that the database encoding is UTF8, to match OGR internals */
+	if ( GetDatabaseEncoding() != PG_UTF8 )
+	{
+		elog(ERROR, "OGR FDW only works with UTF-8 databases");
+		PG_RETURN_VOID();
+	}
+
+	/* Initialize found state to not found */
+	for ( opt = valid_options; opt->optname; opt++ )
+	{
+		opt->optfound = false;
+	}
+
+	/*
+	 * Check that only options supported by ogr_fdw, and allowed for the
+	 * current object type, are given.
+	 */
+	foreach(cell, options_list)
+	{
+		DefElem *def = (DefElem *) lfirst(cell);
+		bool optfound = false;
+
+		for ( opt = valid_options; opt->optname; opt++ )
+		{
+			if ( catalog == opt->optcontext && streq(opt->optname, def->defname) )
+			{
+				/* Mark that this user option was found */
+				opt->optfound = optfound = true;
+
+				/* Store some options for testing later */
+				if ( streq(opt->optname, OPT_SOURCE) )
+					source = defGetString(def);
+				if ( streq(opt->optname, OPT_DRIVER) )
+					driver = defGetString(def);
+				if ( streq(opt->optname, OPT_CONFIG_OPTIONS) )
+					config_options = defGetString(def);
+				if ( streq(opt->optname, OPT_OPEN_OPTIONS) )
+					open_options = defGetString(def);
+				if ( streq(opt->optname, OPT_UPDATEABLE) )
+					updateable = defGetBoolean(def);
+
+				break;
+			}
+		}
+
+		if ( ! optfound )
+		{
+			/*
+			 * Unknown option specified, complain about it. Provide a hint
+			 * with list of valid options for the object.
+			 */
+			const struct OgrFdwOption *opt;
+			StringInfoData buf;
+
+			initStringInfo(&buf);
+			for (opt = valid_options; opt->optname; opt++)
+			{
+				if (catalog == opt->optcontext)
+					appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
+									 opt->optname);
+			}
+
+			ereport(ERROR, (
+				errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+				errmsg("invalid option \"%s\"", def->defname),
+				buf.len > 0
+				? errhint("Valid options in this context are: %s", buf.data)
+				: errhint("There are no valid options in this context.")));
+		}
+	}
+
+	/* Check that all the mandatory options were found */
+	for ( opt = valid_options; opt->optname; opt++ )
+	{
+		/* Required option for this catalog type is missing? */
+		if ( catalog == opt->optcontext && opt->optrequired && ! opt->optfound )
+		{
+			ereport(ERROR, (
+					errcode(ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED),
+					errmsg("required option \"%s\" is missing", opt->optname)));
+		}
+	}
+
+	/* Make sure server connection can actually be established */
+	if ( catalog == ForeignServerRelationId && source )
+	{
+		OGRDataSourceH ogr_ds;
+		ogr_ds = ogrGetDataSource(source, driver, updateable, config_options, open_options);
+		if ( ogr_ds )
+		{
+			GDALClose(ogr_ds);
+		}
+	}
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Initialize an OgrFdwPlanState on the heap.
+ */
+static OgrFdwState*
+getOgrFdwState(Oid foreigntableid, OgrFdwStateType state_type)
+{
+	OgrFdwState *state;
+	size_t size;
+	bool updateable = false;
+	
+	switch (state_type) 
+	{
+		case OGR_PLAN_STATE:
+			size = sizeof(OgrFdwPlanState);
+			updateable = false;
+			break;
+		case OGR_EXEC_STATE:
+			size = sizeof(OgrFdwExecState);
+			updateable = false;
+			break;
+		case OGR_MODIFY_STATE:
+			updateable = true;
+			size = sizeof(OgrFdwModifyState);
+			break;
+		default:
+			elog(ERROR, "invalid state type");
+	}
+	
+	state = palloc0(size);
+	state->type = state_type;
+
+	/*  Connect! */
+	state->ogr = ogrGetConnectionFromTable(foreigntableid, updateable);
+	state->foreigntableid = foreigntableid;
+
+	return state;
+}
+
+
+
+/*
+ * ogrGetForeignRelSize
+ *		Obtain relation size estimates for a foreign table
+ */
+static void
+ogrGetForeignRelSize(PlannerInfo *root,
+                     RelOptInfo *baserel,
+                     Oid foreigntableid)
+{
+	/* Initialize the OGR connection */
+	OgrFdwState *state = (OgrFdwState *)getOgrFdwState(foreigntableid, OGR_PLAN_STATE);
+	OgrFdwPlanState *planstate = (OgrFdwPlanState *)state;
+	List *scan_clauses = baserel->baserestrictinfo;
+
+	/* Set to NULL to clear the restriction clauses in OGR */
+	OGR_L_SetIgnoredFields(planstate->ogr.lyr, NULL);
+	OGR_L_SetSpatialFilter(planstate->ogr.lyr, NULL);
+	OGR_L_SetAttributeFilter(planstate->ogr.lyr, NULL);
+
+	/*
+	* The estimate number of rows returned must actually use restrictions.
+	* Since OGR can't really give us a fast count with restrictions on
+	* (usually involves a scan) and restrictions in the baserel mean we
+	* must punt row count estimates.
+	*/
+
+	/* TODO: calculate the row width based on the attribute types of the OGR table */
+
+	/*
+	* OGR asks drivers to honestly state if they can provide a fast
+	* row count, but too many drivers lie. We are only listing drivers
+	* we trust in ogrCanReallyCountFast()
+	*/
+
+	/* If we can quickly figure how many rows this layer has, then do so */
+	if ( scan_clauses == NIL &&
+	     OGR_L_TestCapability(planstate->ogr.lyr, OLCFastFeatureCount) == TRUE &&
+	     ogrCanReallyCountFast(&(planstate->ogr)) )
+	{
+		/* Count rows, but don't force a slow count */
+		int rows = OGR_L_GetFeatureCount(planstate->ogr.lyr, false);
+		/* Only use row count if return is valid (>0) */
+		if ( rows >= 0 )
+		{
+			planstate->nrows = rows;
+			baserel->rows = rows;
+		}
+	}
+
+	/* Save connection state for next calls */
+	baserel->fdw_private = (void *) planstate;
+
+	return;
+}
+
+
+
+/*
+ * ogrGetForeignPaths
+ *		Create possible access paths for a scan on the foreign table
+ *
+ *		Currently there is only one
+ *		possible access path, which simply returns all records in the order in
+ *		the data file.
+ */
+static void
+ogrGetForeignPaths(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Oid foreigntableid)
+{
+	OgrFdwPlanState *planstate = (OgrFdwPlanState *)(baserel->fdw_private);
+
+	/* TODO: replace this with something that looks at the OGRDriver and */
+	/* makes a determination based on that? Better: add connection caching */
+	/* so that slow startup doesn't matter so much */
+	planstate->startup_cost = 25;
+
+	/* TODO: more research on what the total cost is supposed to mean, */
+	/* relative to the startup cost? */
+	planstate->total_cost = planstate->startup_cost + baserel->rows;
+
+	/* Built the (one) path we are providing. Providing fancy paths is */
+	/* really only possible with back-ends that can properly provide */
+	/* explain info on how they complete the query, not for something as */
+	/* obtuse as OGR. (So far, have only seen it w/ the postgres_fdw */
+	add_path(baserel,
+		(Path *) create_foreignscan_path(root, baserel,
+#if PG_VERSION_NUM >= 90600
+					NULL, /* PathTarget */
+#endif
+					baserel->rows,
+					planstate->startup_cost,
+					planstate->total_cost,
+					NIL,     /* no pathkeys */
+					NULL,    /* no outer rel either */
+					NULL  /* no extra plan */
+#if PG_VERSION_NUM >= 90500
+					,NIL /* no fdw_private list */
+#endif
+					)
+		);   /* no fdw_private data */
+}
+
+
+
+
+/*
+ * fileGetForeignPlan
+ *		Create a ForeignScan plan node for scanning the foreign table
+ */
+static ForeignScan *
+ogrGetForeignPlan(PlannerInfo *root,
+                  RelOptInfo *baserel,
+                  Oid foreigntableid,
+                  ForeignPath *best_path,
+                  List *tlist,
+                  List *scan_clauses
+#if PG_VERSION_NUM >= 90500
+                  ,Plan *outer_plan
+#endif
+                  )
+{
+	Index scan_relid = baserel->relid;
+	bool sql_generated;
+	StringInfoData sql;
+	List *params_list = NULL;
+	List *fdw_private;
+	OgrFdwPlanState *planstate = (OgrFdwPlanState *)(baserel->fdw_private);
+	OgrFdwState *state = (OgrFdwState *)(baserel->fdw_private);
+
+	/* Add in column mapping data to build SQL with the right OGR column names */
+	ogrReadColumnData(state);
+
+	/*
+	 * TODO: Review the columns requested (via params_list) and only pull those back, using
+	 * OGR_L_SetIgnoredFields. This is less important than pushing restrictions
+	 * down to OGR via OGR_L_SetAttributeFilter (done) and (TODO) OGR_L_SetSpatialFilter.
+	 */
+	initStringInfo(&sql);
+	sql_generated = ogrDeparse(&sql, root, baserel, scan_clauses, state, &params_list);
+	elog(DEBUG1,"OGR SQL: %s", sql.data);
+
+	/*
+	 * Here we strip RestrictInfo
+	 * nodes from the clauses and ignore pseudoconstants (which will be
+	 * handled elsewhere).
+	 * Some FDW implementations (mysql_fdw) just pass this full list on to the
+	 * make_foreignscan function. postgres_fdw carefully separates local and remote
+	 * clauses and only passes the local ones to make_foreignscan, so this
+	 * is probably best practice, though re-applying the clauses is probably
+	 * the least of our performance worries with this fdw. For now, we just
+	 * pass them all to make_foreignscan, see no evil, etc.
+	 */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+	/*
+	 * Serialize the data we want to pass to the execution stage.
+	 * This is ugly but seems to be the only way to pass our constructed
+	 * OGR SQL command to execution.
+	 *
+	 * TODO: Pass a spatial filter down also.
+	 */
+	if ( sql_generated )
+		fdw_private = list_make2(makeString(sql.data), params_list);
+	else
+		fdw_private = list_make2(NULL, params_list);
+
+	/*
+	 * Clean up our connection
+	 */
+	ogrFinishConnection(&(planstate->ogr));
+
+	/* Create the ForeignScan node */
+	return make_foreignscan(tlist,
+							scan_clauses,
+							scan_relid,
+							NIL,	/* no expressions to evaluate */
+							fdw_private
+#if PG_VERSION_NUM >= 90500
+							,NIL  /* no scan_tlist */
+							,NIL   /* no remote quals */
+							,outer_plan
+#endif
+);
+
+
+}
+
+static void
+pgCanConvertToOgr(Oid pg_type, OGRFieldType ogr_type, const char *colname, const char *tblname)
+{	
+	if ( pg_type == BOOLOID && ogr_type == OFTInteger )
+		return;
+	else if ( pg_type == INT2OID && ogr_type == OFTInteger )
+		return;
+	else if ( pg_type == INT4OID && ogr_type == OFTInteger )
+		return;
+	else if ( pg_type == INT8OID )
+	{
+#if GDAL_VERSION_MAJOR >= 2
+		if ( ogr_type == OFTInteger64 ) 
+			return;
+#else
+		if ( ogr_type == OFTInteger )
+			return;
+#endif
+	} 
+	else if ( pg_type == NUMERICOID && ogr_type == OFTReal )
+		return;
+	else if ( pg_type == FLOAT4OID && ogr_type == OFTReal )
+		return;
+	else if ( pg_type == FLOAT8OID && ogr_type == OFTReal )
+		return;
+	else if ( pg_type == TEXTOID && ogr_type == OFTString )
+		return;
+	else if ( pg_type == VARCHAROID && ogr_type == OFTString )
+		return;
+	else if ( pg_type == CHAROID && ogr_type == OFTString )
+		return;
+	else if ( pg_type == BPCHAROID && ogr_type == OFTString )
+		return;
+	else if ( pg_type == NAMEOID && ogr_type == OFTString )
+		return;
+	else if ( pg_type == BYTEAOID && ogr_type == OFTBinary )
+		return;
+	else if ( pg_type == DATEOID && ogr_type == OFTDate )
+		return;
+	else if ( pg_type == TIMEOID && ogr_type == OFTTime )
+		return;
+	else if ( pg_type == TIMESTAMPOID && ogr_type == OFTDateTime )
+		return;
+	else if ( pg_type == TIMESTAMPOID && ogr_type == OFTDateTime )
+		return;
+	
+	ereport(ERROR, (
+			errcode(ERRCODE_FDW_INVALID_DATA_TYPE),
+			errmsg("column \"%s\" of foreign table \"%s\" converts \"%s\" to OGR \"%s\"",
+				colname, tblname,
+				format_type_be(pg_type), OGR_GetFieldTypeName(ogr_type))
+			));	
+}
+
+static void
+ogrCanConvertToPg(OGRFieldType ogr_type, Oid pg_type, const char *colname, const char *tblname)
+{
+	switch (ogr_type)
+	{
+		case OFTInteger:
+			if ( pg_type == BOOLOID ||  pg_type == INT4OID || pg_type == INT8OID || pg_type == NUMERICOID || pg_type == FLOAT4OID || pg_type == FLOAT8OID || pg_type == TEXTOID || pg_type == VARCHAROID )
+				return;
+			break;
+
+		case OFTReal:
+			if ( pg_type == NUMERICOID || pg_type == FLOAT4OID || pg_type == FLOAT8OID || pg_type == TEXTOID || pg_type == VARCHAROID )
+				return;
+			break;
+
+		case OFTBinary:
+			if ( pg_type == BYTEAOID )
+				return;
+			break;
+
+		case OFTString:
+			if ( pg_type == TEXTOID || pg_type == VARCHAROID || pg_type == CHAROID || pg_type == BPCHAROID )
+				return;
+			break;
+
+		case OFTDate:
+			if ( pg_type == DATEOID || pg_type == TIMESTAMPOID || pg_type == TEXTOID || pg_type == VARCHAROID )
+				return;
+			break;
+
+		case OFTTime:
+			if ( pg_type == TIMEOID || pg_type == TEXTOID || pg_type == VARCHAROID )
+				return;
+			break;
+
+		case OFTDateTime:
+			if ( pg_type == TIMESTAMPOID || pg_type == TEXTOID || pg_type == VARCHAROID )
+				return;
+			break;
+
+#if GDAL_VERSION_MAJOR >= 2
+		case OFTInteger64:
+			if ( pg_type == INT8OID || pg_type == NUMERICOID || pg_type == FLOAT8OID || pg_type == TEXTOID || pg_type == VARCHAROID )
+				return;
+			break;
+#endif
+
+		case OFTWideString:
+		case OFTIntegerList:
+#if GDAL_VERSION_MAJOR >= 2
+		case OFTInteger64List:
+#endif
+		case OFTRealList:
+		case OFTStringList:
+		case OFTWideStringList:
+		{
+			ereport(ERROR, (
+					errcode(ERRCODE_FDW_INVALID_DATA_TYPE),
+					errmsg("column \"%s\" of foreign table \"%s\" uses an OGR array, currently unsupported", colname, tblname)
+					));
+			break;
+		}
+	}
+	ereport(ERROR, (
+			errcode(ERRCODE_FDW_INVALID_DATA_TYPE),
+			errmsg("column \"%s\" of foreign table \"%s\" converts OGR \"%s\" to \"%s\"",
+				colname, tblname,
+				OGR_GetFieldTypeName(ogr_type), format_type_be(pg_type))
+			));
+}
+
+#ifdef OGR_FDW_HEXWKB
+
+static char *hexchr = "0123456789ABCDEF";
+
+static char * 
+ogrBytesToHex(unsigned char *bytes, size_t size)
+{
+	char *hex;
+	int i;
+	if ( ! bytes || ! size )
+	{
+		elog(ERROR, "hexbytes_from_bytes: invalid input");
+		return NULL;
+	}
+	hex = palloc(size * 2 + 1);
+	hex[2*size] = '\0';
+	for( i = 0; i < size; i++ )
+	{
+		/* Top four bits to 0-F */
+		hex[2*i] = hexchr[bytes[i] >> 4];
+		/* Bottom four bits to 0-F */
+		hex[2*i+1] = hexchr[bytes[i] & 0x0F];
+	}
+	return hex;
+}
+
+#endif
+
+static void
+freeOgrFdwTable(OgrFdwTable *table)
+{
+	if ( table )
+	{
+		if ( table->tblname ) pfree(table->tblname);
+		if ( table->cols ) pfree(table->cols);
+		pfree(table);
+	}
+}
+
+typedef struct
+{
+	char *fldname;
+	int fldnum;
+} OgrFieldEntry;
+
+static int ogrFieldEntryCmpFunc(const void * a, const void * b)
+{
+	const char *a_name = ((OgrFieldEntry*)a)->fldname;
+	const char *b_name = ((OgrFieldEntry*)b)->fldname;
+
+	return strcasecmp(a_name, b_name);
+}
+
+
+/*
+ * The execstate holds a foreign table relation id and an OGR connection,
+ * this function finds all the OGR fields that match up to columns in the
+ * foreign table definition, using columns name match and data type consistency
+ * as the criteria for making a match.
+ * The results of the matching are stored in the execstate before the function
+ * returns.
+ */
+static void
+ogrReadColumnData(OgrFdwState *state)
+{
+	Relation rel;
+	TupleDesc tupdesc;
+	int i;
+	OgrFdwTable *tbl;
+	OGRFeatureDefnH dfn;
+	int ogr_ncols;
+	int fid_count = 0;
+	int geom_count = 0;
+	int ogr_geom_count = 0;
+	int field_count = 0;
+	OgrFieldEntry *ogr_fields;
+	int ogr_fields_count = 0;
+	char *tblname = get_rel_name(state->foreigntableid);
+
+	/* Blow away any existing table in the state */
+	if ( state->table )
+	{
+		freeOgrFdwTable(state->table);
+		state->table = NULL;
+	}
+
+	/* Fresh table */
+	tbl = palloc0(sizeof(OgrFdwTable));
+
+	/* One column for each PgSQL foreign table column */
+	rel = heap_open(state->foreigntableid, NoLock);
+	tupdesc = rel->rd_att;
+	state->tupdesc = tupdesc;
+	tbl->ncols = tupdesc->natts;
+	tbl->cols = palloc0(tbl->ncols * sizeof(OgrFdwColumn));
+	tbl->tblname = pstrdup(tblname);
+
+	/* Get OGR metadata ready */
+	dfn = OGR_L_GetLayerDefn(state->ogr.lyr);
+	ogr_ncols = OGR_FD_GetFieldCount(dfn);
+#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
+	ogr_geom_count = OGR_FD_GetGeomFieldCount(dfn);
+#else
+	ogr_geom_count = ( OGR_FD_GetGeomType(dfn) != wkbNone ) ? 1 : 0;
+#endif
+
+
+	/* Prepare sorted list of OGR column names */
+	/* TODO: change this to a hash table, to avoid repeated strcmp */
+	/* We will search both the original and laundered OGR field names for matches */
+	ogr_fields_count = 2 * ogr_ncols;
+	ogr_fields = palloc0(ogr_fields_count * sizeof(OgrFieldEntry));
+	for ( i = 0; i < ogr_ncols; i++ )
+	{
+		char *fldname = pstrdup(OGR_Fld_GetNameRef(OGR_FD_GetFieldDefn(dfn, i)));
+		char *fldname_laundered = palloc(STR_MAX_LEN);
+		strncpy(fldname_laundered, fldname, STR_MAX_LEN);
+		ogrStringLaunder(fldname_laundered);
+		ogr_fields[2*i].fldname = fldname;
+		ogr_fields[2*i].fldnum = i;
+		ogr_fields[2*i+1].fldname = fldname_laundered;
+		ogr_fields[2*i+1].fldnum = i;
+	}
+	qsort(ogr_fields, ogr_fields_count, sizeof(OgrFieldEntry), ogrFieldEntryCmpFunc);
+
+
+	/* loop through foreign table columns */
+	for ( i = 0; i < tbl->ncols; i++ )
+	{
+		List *options;
+		ListCell *lc;
+		OgrFieldEntry *found_entry;
+		OgrFieldEntry entry;
+		
+		Form_pg_attribute att_tuple = tupdesc->attrs[i];
+		OgrFdwColumn col = tbl->cols[i];
+		col.pgattnum = att_tuple->attnum;
+		col.pgtype = att_tuple->atttypid;
+		col.pgtypmod = att_tuple->atttypmod;
+		col.pgattisdropped = att_tuple->attisdropped;
+
+		/* Skip filling in any further metadata about dropped columns */
+		if ( col.pgattisdropped )
+			continue;
+
+		/* Find the appropriate conversion functions */
+		getTypeInputInfo(col.pgtype, &col.pginputfunc, &col.pginputioparam);
+		getTypeBinaryInputInfo(col.pgtype, &col.pgrecvfunc, &col.pgrecvioparam);
+		getTypeOutputInfo(col.pgtype, &col.pgoutputfunc, &col.pgoutputvarlena);
+		getTypeBinaryOutputInfo(col.pgtype, &col.pgsendfunc, &col.pgsendvarlena);
+			
+		/* Get the PgSQL column name */
+		col.pgname = get_relid_attribute_name(rel->rd_id, att_tuple->attnum);
+
+		/* Handle FID first */
+		if ( strcaseeq(col.pgname, "fid") && (col.pgtype == INT4OID || col.pgtype == INT8OID) )
+		{
+			if ( fid_count >= 1 )
+				elog(ERROR, "FDW table '%s' includes more than one FID column", tblname);
+
+			col.ogrvariant = OGR_FID;
+			col.ogrfldnum = fid_count++;
+			tbl->cols[i] = col;
+			continue;
+		}
+		
+		/* If the OGR source has geometries, can we match them to Pg columns? */
+		/* We'll match to the first ones we find, irrespective of name */
+		if ( geom_count < ogr_geom_count && col.pgtype == GEOMETRYOID )
+		{
+			col.ogrvariant = OGR_GEOMETRY;
+			col.ogrfldtype = OFTBinary;
+			col.ogrfldnum = geom_count++;
+			tbl->cols[i] = col;
+			continue;
+		}
+
+		/* Now we search for matches in the OGR fields */
+
+		/* By default, search for the PgSQL column name */
+		entry.fldname = col.pgname;
+		entry.fldnum = 0;
+
+		/*
+		 * But, if there is a 'column_name' option for this column, we
+		 * want to search for *that* in the OGR layer.
+		 */
+		options = GetForeignColumnOptions(state->foreigntableid, i + 1);
+		foreach(lc, options)
+		{
+			DefElem    *def = (DefElem *) lfirst(lc);
+
+			if ( streq(def->defname, OPT_COLUMN) )
+			{
+				entry.fldname = defGetString(def);
+				break;
+			}
+		}
+
+		/* Search PgSQL column name in the OGR column name list */
+		found_entry = bsearch(&entry, ogr_fields, ogr_fields_count, sizeof(OgrFieldEntry), ogrFieldEntryCmpFunc);
+
+		/* Column name matched, so save this entry, if the types are consistent */
+		if ( found_entry )
+		{
+			OGRFieldDefnH fld = OGR_FD_GetFieldDefn(dfn, found_entry->fldnum);
+			OGRFieldType fldtype = OGR_Fld_GetType(fld);
+
+			/* Error if types mismatched when column names match */
+			ogrCanConvertToPg(fldtype, col.pgtype, col.pgname, tblname);
+
+			col.ogrvariant = OGR_FIELD;
+			col.ogrfldnum = found_entry->fldnum;
+			col.ogrfldtype = fldtype;
+			field_count++;
+		}
+		else
+		{
+			col.ogrvariant = OGR_UNMATCHED;
+		}
+		tbl->cols[i] = col;
+	}
+
+	elog(DEBUG2, "ogrReadColumnData matched %d FID, %d GEOM, %d FIELDS out of %d PGSQL COLUMNS", fid_count, geom_count, field_count, tbl->ncols);
+
+	/* Clean up */
+	
+	state->table = tbl;
+	for( i = 0; i < 2*ogr_ncols; i++ )
+		if ( ogr_fields[i].fldname ) pfree(ogr_fields[i].fldname);
+	pfree(ogr_fields);
+	heap_close(rel, NoLock);
+
+	return;
+}
+
+/* 
+ * ogrLookupGeometryFunctionOid
+ * 
+ * Find the procedure Oids of useful functions so we can call 
+ * them later.
+ */
+static Oid 
+ogrLookupGeometryFunctionOid(const char *proname)
+{
+	List *names;
+	FuncCandidateList clist;
+	
+	/* This only works if PostGIS is installed */
+	if ( GEOMETRYOID == InvalidOid || GEOMETRYOID == BYTEAOID )
+		return InvalidOid;
+	
+	names = stringToQualifiedNameList(proname);
+#if PG_VERSION_NUM < 90400
+	clist = FuncnameGetCandidates(names, -1, NIL, false, false);
+#else
+	clist = FuncnameGetCandidates(names, -1, NIL, false, false, false);
+#endif
+	if ( streq(proname, "st_setsrid") )
+	{
+		do
+		{
+			int i;
+			for ( i = 0; i < clist->nargs; i++ )
+			{
+				if ( clist->args[i] == GEOMETRYOID )
+					return clist->oid;
+			}
+		}
+		while( (clist = clist->next) );
+	}
+	else if ( streq(proname, "postgis_typmod_srid") )
+	{
+		return clist->oid;
+	}
+	
+	return InvalidOid;
+}
+
+/*
+ * ogrBeginForeignScan
+ */
+static void
+ogrBeginForeignScan(ForeignScanState *node, int eflags)
+{
+	Oid foreigntableid = RelationGetRelid(node->ss.ss_currentRelation);
+	ForeignScan *fsplan = (ForeignScan *)node->ss.ps.plan;
+
+	/* Initialize OGR connection */
+	OgrFdwState *state = getOgrFdwState(foreigntableid, OGR_EXEC_STATE);
+	OgrFdwExecState *execstate = (OgrFdwExecState *)state;
+
+	/* Read the OGR layer definition and PgSQL foreign table definitions */
+	ogrReadColumnData(state);
+	
+	/* Collect the procedure Oids for PostGIS functions we might need */
+	execstate->setsridfunc = ogrLookupGeometryFunctionOid("st_setsrid");
+	execstate->typmodsridfunc = ogrLookupGeometryFunctionOid("postgis_typmod_srid");
+
+	/* Get private info created by planner functions. */
+	execstate->sql = strVal(list_nth(fsplan->fdw_private, 0));
+	// execstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private, 1);
+
+	if ( execstate->sql && strlen(execstate->sql) > 0 )
+	{
+		OGRErr err = OGR_L_SetAttributeFilter(execstate->ogr.lyr, execstate->sql);
+		if ( err != OGRERR_NONE )
+		{
+			const char *ogrerr = CPLGetLastErrorMsg();
+			
+			if ( ogrerr && ! streq(ogrerr,"") )
+			{
+				ereport(NOTICE,
+					(errcode(ERRCODE_FDW_ERROR),
+					 errmsg("unable to set OGR SQL '%s' on layer", execstate->sql),
+					 errhint("%s", ogrerr)));
+			}
+			else
+			{
+				ereport(NOTICE,
+					(errcode(ERRCODE_FDW_ERROR),
+					 errmsg("unable to set OGR SQL '%s' on layer", execstate->sql)));
+			}
+		}
+	}
+	else
+	{
+		OGR_L_SetAttributeFilter(execstate->ogr.lyr, NULL);
+	}
+
+	/* Save the state for the next call */
+	node->fdw_state = (void *) execstate;
+
+	return;
+}
+
+/*
+ * Rather than explicitly try and form PgSQL datums, use the type
+ * input functions, that accept cstring representations, and convert
+ * to the input format. We have to lookup the right input function for
+ * each column in the foreign table. 
+ */
+static Datum
+pgDatumFromCString(const char *cstr, Oid pgtype, int pgtypmod, Oid pginputfunc)
+{
+	Datum value;
+	Datum cdata = CStringGetDatum(cstr);
+
+	/* pgtypmod will be -1 for types w/o typmod  */
+	// if ( pgtypmod >= 0 )
+	// {
+		/* These functions require a type modifier */
+		value = OidFunctionCall3(pginputfunc, cdata,
+			ObjectIdGetDatum(InvalidOid),
+			Int32GetDatum(pgtypmod));
+	// }
+	// else
+	// {
+	// 	/* These functions don't */
+	// 	value = OidFunctionCall1(pginputfunc, cdata);
+	// }
+
+	return value;
+}
+
+static inline void
+ogrNullSlot(Datum *values, bool *nulls, int i)
+{
+	values[i] = PointerGetDatum(NULL);
+	nulls[i] = true;
+}
+
+/*
+* The ogrIterateForeignScan is getting a new TupleTableSlot to handle
+* for each iteration. Each slot contains an entry for every column in
+* in the foreign table, that has to be filled out, either with a value
+* or a NULL for columns that either have been deleted or were not requested
+* in the query.
+*
+* The tupledescriptor tells us about the types of each slot.
+* For now we assume our slot has exactly the same number of
+* records and equivalent types to our OGR layer, and that our
+* foreign table's first two columns are an integer primary key
+* using int8 as the type, and then a geometry using bytea as
+* the type, then everything else.
+*/
+static OGRErr
+ogrFeatureToSlot(const OGRFeatureH feat, TupleTableSlot *slot, const OgrFdwExecState *execstate)
+{
+	const OgrFdwTable *tbl = execstate->table;
+	int i;
+	Datum *values = slot->tts_values;
+	bool *nulls = slot->tts_isnull;
+	TupleDesc tupdesc = slot->tts_tupleDescriptor;
+	int have_typmod_funcs = (execstate->setsridfunc && execstate->typmodsridfunc);
+
+	/* Check our assumption that slot and setup data match */
+	if ( tbl->ncols != tupdesc->natts )
+	{
+		elog(ERROR, "FDW metadata table and exec table have mismatching number of columns");
+		return OGRERR_FAILURE;
+	}
+
+	/* For each pgtable column, get a value from OGR */
+	for ( i = 0; i < tbl->ncols; i++ )
+	{
+		OgrFdwColumn col = tbl->cols[i];
+		const char *pgname = col.pgname;
+		Oid pgtype = col.pgtype;
+		int pgtypmod = col.pgtypmod;
+		Oid pginputfunc = col.pginputfunc;
+		int ogrfldnum = col.ogrfldnum;
+		OGRFieldType ogrfldtype = col.ogrfldtype;
+		OgrColumnVariant ogrvariant = col.ogrvariant;
+
+		/*
+		 * Fill in dropped attributes with NULL
+		 */
+		if ( col.pgattisdropped )
+		{
+			ogrNullSlot(values, nulls, i);
+			continue;
+		}
+
+		if ( ogrvariant == OGR_FID )
+		{
+			GIntBig fid = OGR_F_GetFID(feat);
+
+			if ( fid == OGRNullFID )
+			{
+				ogrNullSlot(values, nulls, i);
+			}
+			else
+			{
+				char fidstr[256];
+				snprintf(fidstr, 256, OGR_FDW_FRMT_INT64, OGR_FDW_CAST_INT64(fid));
+
+				nulls[i] = false;
+				values[i] = pgDatumFromCString(fidstr, pgtype, pgtypmod, pginputfunc);
+			}
+		}
+		else if ( ogrvariant == OGR_GEOMETRY )
+		{
+			int wkbsize;
+			int varsize;
+			bytea *varlena;
+			unsigned char *wkb;
+			OGRErr err;
+
+#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
+			OGRGeometryH geom = OGR_F_GetGeomFieldRef(feat, ogrfldnum);
+#else
+			OGRGeometryH geom = OGR_F_GetGeometryRef(feat);
+#endif
+
+			/* No geometry ? NULL */
+			if ( ! geom )
+			{
+				/* No geometry column, so make the output null */
+				ogrNullSlot(values, nulls, i);
+				continue;
+			}
+
+			/*
+			 * Start by generating standard PgSQL variable length byte
+			 * buffer, with WKB filled into the data area.
+			 */
+			wkbsize = OGR_G_WkbSize(geom);
+			varsize = wkbsize + VARHDRSZ;
+			varlena = palloc(varsize);
+			wkb = (unsigned char *)VARDATA(varlena);
+			err = OGR_G_ExportToWkb(geom, wkbNDR, wkb);
+			SET_VARSIZE(varlena, varsize);
+
+			/* Couldn't create WKB from OGR geometry? error */
+			if ( err != OGRERR_NONE )
+			{
+				return err;
+			}
+
+			if ( pgtype == BYTEAOID )
+			{
+				/*
+				 * Nothing special to do for bytea, just send the varlena data through!
+				 */
+				nulls[i] = false;
+				values[i] = PointerGetDatum(varlena);
+			}
+			else if ( pgtype == GEOMETRYOID )
+			{
+				/*
+				 * For geometry we need to convert the varlena WKB data into a serialized
+				 * geometry (aka "gserialized"). For that, we can use the type's "recv" function
+				 * which takes in WKB and spits out serialized form, or the "input" function
+				 * that takes in HEXWKB. The "input" function is more lax about geometry
+				 * structure errors (unclosed polys, etc).
+				 */
+#ifdef OGR_FDW_HEXWKB
+				char *hexwkb = ogrBytesToHex(wkb, wkbsize);
+				/*
+				 * Use the input function to convert the WKB from OGR into
+				 * a PostGIS internal format.
+				 */
+				nulls[i] = false;
+				values[i] = OidFunctionCall1(col.pginputfunc, PointerGetDatum(hexwkb));
+				pfree(hexwkb);
+#else
+				/*
+				 * The "recv" function expects to receive a StringInfo pointer
+				 * on the first argument, so we form one of those ourselves by
+				 * hand. Rather than copy into a fresh buffer, we'll just use the
+				 * existing varlena buffer and point to the data area.
+				 *
+				 * The "recv" function tests for basic geometry validity,
+				 * things like polygon closure, etc. So don't feed it junk.
+				 */
+				StringInfoData strinfo;
+				strinfo.data = (char *)wkb;
+				strinfo.len = wkbsize;
+				strinfo.maxlen = strinfo.len;
+				strinfo.cursor = 0;
+
+				/*
+				 * Use the recv function to convert the WKB from OGR into
+				 * a PostGIS internal format.
+				 */
+				nulls[i] = false;
+				values[i] = OidFunctionCall1(col.pgrecvfunc, PointerGetDatum(&strinfo));
+#endif
+				
+				/* 
+				 * Apply the typmod restriction to the incoming geometry, so it's
+				 * not really a restriction anymore, it's more like a requirement.
+				 * 
+				 * TODO: In the case where the OGR input actually *knows* what SRID
+				 * it is, we should actually apply *that* and let the restriction run
+				 * its usual course.
+				 */
+				if ( have_typmod_funcs && col.pgtypmod >= 0 ) 
+				{
+					Datum srid = OidFunctionCall1(execstate->typmodsridfunc, Int32GetDatum(col.pgtypmod));
+					values[i] = OidFunctionCall2(execstate->setsridfunc, values[i], srid);
+				}			
+			}
+			else
+			{
+				elog(NOTICE, "conversion to geometry called with column type not equal to bytea or geometry");
+				ogrNullSlot(values, nulls, i);
+			}
+			
+		}
+		else if ( ogrvariant == OGR_FIELD )
+		{
+			/* Ensure that the OGR data type fits the destination Pg column */
+			ogrCanConvertToPg(ogrfldtype, pgtype, pgname, tbl->tblname);
+
+			/* Only convert non-null fields */
+			if ( OGR_F_IsFieldSet(feat, ogrfldnum) )
+			{
+				switch(ogrfldtype)
+				{
+					case OFTBinary:
+					{
+						/*
+						 * Convert binary fields to bytea directly
+						 */
+						int bufsize;
+						GByte *buf = OGR_F_GetFieldAsBinary(feat, ogrfldnum, &bufsize);
+						int varsize = bufsize + VARHDRSZ;
+						bytea *varlena = palloc(varsize);
+						memcpy(VARDATA(varlena), buf, bufsize);
+						SET_VARSIZE(varlena, varsize);
+						nulls[i] = false;
+						values[i] = PointerGetDatum(varlena);
+						break;
+					}
+					case OFTInteger:
+					case OFTReal:
+					case OFTString:
+#if GDAL_VERSION_MAJOR >= 2
+					case OFTInteger64:
+#endif
+					{
+						/*
+						 * Convert numbers and strings via a string representation.
+						 * Handling numbers directly would be faster, but require a lot of extra code.
+						 * For now, we go via text.
+						 */
+						const char *cstr = OGR_F_GetFieldAsString(feat, ogrfldnum);
+						if ( cstr )
+						{
+							nulls[i] = false;
+							values[i] = pgDatumFromCString(cstr, pgtype, pgtypmod, pginputfunc);
+						}
+						else
+						{
+							ogrNullSlot(values, nulls, i);
+						}
+						break;
+					}
+					case OFTDate:
+					case OFTTime:
+					case OFTDateTime:
+					{
+						/*
+						 * OGR date/times have a weird access method, so we use that to pull
+						 * out the raw data and turn it into a string for PgSQL's (very
+						 * sophisticated) date/time parsing routines to handle.
+						 */
+						int year, month, day, hour, minute, second, tz;
+						char cstr[256];
+
+						OGR_F_GetFieldAsDateTime(feat, ogrfldnum,
+						                         &year, &month, &day,
+						                         &hour, &minute, &second, &tz);
+
+						if ( ogrfldtype == OFTDate )
+						{
+							snprintf(cstr, 256, "%d-%02d-%02d", year, month, day);
+						}
+						else if ( ogrfldtype == OFTTime )
+						{
+							snprintf(cstr, 256, "%02d:%02d:%02d", hour, minute, second);
+						}
+						else
+						{
+							snprintf(cstr, 256, "%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second);
+						}
+
+						nulls[i] = false;
+						values[i] = pgDatumFromCString(cstr, pgtype, pgtypmod, pginputfunc);
+						break;
+
+					}
+					case OFTIntegerList:
+					case OFTRealList:
+					case OFTStringList:
+					{
+						/* TODO, map these OGR array types into PgSQL arrays (fun!) */
+						elog(ERROR, "unsupported OGR array type \"%s\"", OGR_GetFieldTypeName(ogrfldtype));
+						break;
+					}
+					default:
+					{
+						elog(ERROR, "unsupported OGR type \"%s\"", OGR_GetFieldTypeName(ogrfldtype));
+						break;
+					}
+
+				}
+			}
+			else
+			{
+				ogrNullSlot(values, nulls, i);
+			}
+		}
+		/* Fill in unmatched columns with NULL */
+		else if ( ogrvariant == OGR_UNMATCHED )
+		{
+			ogrNullSlot(values, nulls, i);
+		}
+		else
+		{
+			elog(ERROR, "OGR FDW unsupported column variant in \"%s\", %d", pgname, ogrvariant);
+			return OGRERR_FAILURE;
+		}
+
+	}
+
+	/* done! */
+	return OGRERR_NONE;
+}
+
+static void ogrStaticText(char *text, const char *str)
+{
+	size_t len = strlen(str);
+	memcpy(VARDATA(text), str, len);
+	SET_VARSIZE(text, len + VARHDRSZ);
+	return;
+}
+
+/*
+ * EWKB includes a flag that indicates an SRID embedded in the
+ * binary. The EWKB has an endian byte, four bytes of type information
+ * and then 4 bytes of optional SRID information. If that info is
+ * there, we want to over-write it, and remove the SRID flag, to
+ * generate more "standard" WKB for OGR to consume.
+ */
+static size_t
+ogrEwkbStripSrid(unsigned char *wkb, size_t wkbsize)
+{
+	unsigned int type = 0;
+	int has_srid = 0;
+	size_t newwkbsize = wkbsize;
+	memcpy(&type, wkb+1, 4);
+	/* has_z = type & 0x80000000; */
+	/* has_m = type & 0x40000000; */
+	has_srid = type & 0x20000000;
+	
+	/* Flatten SRID flag away */
+	type &= 0xDFFFFFFF;
+	memcpy(wkb+1, &type, 4);
+	
+	/* If there was an SRID number embedded, overwrite it */
+	if ( has_srid )
+	{
+		newwkbsize -= 4; /* no space for SRID number needed */
+		memmove(wkb+5, wkb+9, newwkbsize - 5);
+	}
+	
+	return newwkbsize;
+}
+
+static OGRErr
+ogrSlotToFeature(const TupleTableSlot *slot, OGRFeatureH feat, const OgrFdwTable *tbl)
+{
+	int i;
+	Datum *values = slot->tts_values;
+	bool *nulls = slot->tts_isnull;
+	TupleDesc tupdesc = slot->tts_tupleDescriptor;
+
+	int year, month, day, hour, minute, second;
+	
+	/* Prepare date-time part tokens for use later */
+	char txtyear[STR_MAX_LEN];
+	char txtmonth[STR_MAX_LEN];
+	char txtday[STR_MAX_LEN];
+	char txthour[STR_MAX_LEN];
+	char txtminute[STR_MAX_LEN];
+	char txtsecond[STR_MAX_LEN];
+
+	ogrStaticText(txtyear, "year");
+	ogrStaticText(txtmonth, "month");
+	ogrStaticText(txtday, "day");
+	ogrStaticText(txthour, "hour");
+	ogrStaticText(txtminute, "minute");
+	ogrStaticText(txtsecond, "second");
+
+	/* Check our assumption that slot and setup data match */
+	if ( tbl->ncols != tupdesc->natts )
+	{
+		elog(ERROR, "FDW metadata table and slot table have mismatching number of columns");
+		return OGRERR_FAILURE;
+	}
+
+	/* For each pgtable column, set a value on the feature OGR */
+	for ( i = 0; i < tbl->ncols; i++ )
+	{
+		OgrFdwColumn col = tbl->cols[i];
+		const char *pgname = col.pgname;
+		Oid pgtype = col.pgtype;
+		Oid pgoutputfunc = col.pgoutputfunc;
+		int ogrfldnum = col.ogrfldnum;
+		OGRFieldType ogrfldtype = col.ogrfldtype;
+		OgrColumnVariant ogrvariant = col.ogrvariant;
+
+		/* Skip dropped attributes */
+		if ( col.pgattisdropped )
+			continue;
+
+		/* Skip the FID, we have to treat it as immutable anyways */
+		if ( ogrvariant == OGR_FID )
+		{
+			if ( nulls[i] )
+			{
+				OGR_F_SetFID(feat, OGRNullFID);
+			}
+			else
+			{
+				if ( pgtype == INT4OID )
+				{
+					int32 val = DatumGetInt32(values[i]);
+					OGR_F_SetFID(feat, val);
+				}
+				else if ( pgtype == INT8OID )
+				{
+					int64 val = DatumGetInt64(values[i]);
+					OGR_F_SetFID(feat, val);
+				}
+				else
+				{
+					elog(ERROR, "unable to handle non-integer fid");
+				}
+			}
+			continue;				
+		}
+		
+		/* TODO: For updates, we should only set the fields that are */
+		/*       in the target list, and flag the others as unchanged */
+		if ( ogrvariant == OGR_GEOMETRY )
+		{			
+			OGRErr err;
+			if ( nulls[i] )
+			{
+#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
+				err = OGR_F_SetGeomFieldDirectly(feat, ogrfldnum, NULL);
+#else
+				err = OGR_F_SetGeometryDirectly(feat, NULL);
+#endif
+				continue;
+			}
+			else
+			{
+				OGRGeometryH geom;
+				bytea *wkb_bytea = DatumGetByteaP(OidFunctionCall1(col.pgsendfunc, values[i]));
+				unsigned char *wkb = (unsigned char *)VARDATA(wkb_bytea);
+				int wkbsize = VARSIZE(wkb_bytea) - VARHDRSZ;
+				wkbsize = ogrEwkbStripSrid(wkb, wkbsize);
+				
+				/* TODO, create geometry with SRS of table? */
+				err = OGR_G_CreateFromWkb(wkb, NULL, &geom, wkbsize);
+				if ( wkb_bytea ) pfree(wkb_bytea);
+				if ( err != OGRERR_NONE ) 
+					return err;
+				
+#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
+				err = OGR_F_SetGeomFieldDirectly(feat, ogrfldnum, geom);
+#else
+				err = OGR_F_SetGeometryDirectly(feat, geom);
+#endif
+			}
+		}
+		else if ( ogrvariant == OGR_FIELD )
+		{
+			/* Ensure that the OGR data type fits the destination Pg column */
+			pgCanConvertToOgr(pgtype, ogrfldtype, pgname, tbl->tblname);
+
+			/* Skip NULL case */
+			if ( nulls[i] )
+			{
+				OGR_F_UnsetField (feat, ogrfldnum);
+				continue;
+			}
+
+			switch(pgtype)
+			{
+				case BOOLOID:
+				{
+					int8 val = DatumGetBool(values[i]);
+					OGR_F_SetFieldInteger(feat, ogrfldnum, val);
+					break;
+				}
+				case INT2OID:
+				{
+					int16 val = DatumGetInt16(values[i]);
+					OGR_F_SetFieldInteger(feat, ogrfldnum, val);
+					break;
+				}
+				case INT4OID:
+				{
+					int32 val = DatumGetInt32(values[i]);
+					OGR_F_SetFieldInteger(feat, ogrfldnum, val);
+					break;
+				}
+				case INT8OID:
+				{
+					int64 val = DatumGetInt64(values[i]);
+#if GDAL_VERSION_MAJOR >= 2					
+					OGR_F_SetFieldInteger64(feat, ogrfldnum, val);
+#else
+					if ( val < INT_MAX )
+						OGR_F_SetFieldInteger(feat, ogrfldnum, (int32)val);
+					else
+						elog(ERROR, "unable to coerce int64 into int32 OGR field");
+#endif
+					break;
+					
+				}
+				
+				case NUMERICOID:
+				{
+					Datum d;
+					float8 f;
+					
+					/* Convert to string */
+					d = OidFunctionCall1(pgoutputfunc, values[i]);
+					/* Convert back to float8 */
+					f = DatumGetFloat8(DirectFunctionCall1(float8in, d));
+
+					OGR_F_SetFieldDouble(feat, ogrfldnum, f);
+					break;					
+				}
+				case FLOAT4OID:	
+				{
+					OGR_F_SetFieldDouble(feat, ogrfldnum, DatumGetFloat4(values[i]));
+					break;
+				}
+				case FLOAT8OID:	
+				{
+					OGR_F_SetFieldDouble(feat, ogrfldnum, DatumGetFloat8(values[i]));
+					break;
+				}
+
+				case TEXTOID:
+				case VARCHAROID:
+				case NAMEOID:
+				case BPCHAROID: /* char(n) */
+				{
+					char *varlena = (char *)DatumGetPointer(values[i]);
+					size_t varsize = VARSIZE(varlena)-VARHDRSZ;
+					char *str = palloc0(varsize+1);
+					memcpy(str, VARDATA(varlena), varsize);
+					OGR_F_SetFieldString(feat, ogrfldnum, str);
+					pfree(str);
+					break;
+				}
+				
+				case CHAROID: /* char */
+				{
+					char str[2];
+					str[0] = DatumGetChar(values[i]);
+					str[1] = '\0';
+					OGR_F_SetFieldString(feat, ogrfldnum, str);
+					break;
+				}
+				
+				case BYTEAOID:
+				{
+					bytea *varlena = PG_DETOAST_DATUM(values[i]);
+					size_t varsize = VARSIZE(varlena) - VARHDRSZ;
+					OGR_F_SetFieldBinary(feat, ogrfldnum, varsize, (GByte *)VARDATA(varlena));
+					break;				
+				}
+
+				case DATEOID:
+				{
+					/* Convert date to timestamp */
+					Datum d = DirectFunctionCall1(date_timestamp, values[i]);
+					
+					/* Read out the parts */
+					year = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtyear), d)));
+					month = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtmonth), d)));
+					day = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtday), d)));
+					OGR_F_SetFieldDateTime(feat, ogrfldnum, year, month, day, 0, 0, 0, 0);
+					break;
+				}
+				
+				/* TODO: handle time zones explicitly */
+				case TIMEOID:
+				case TIMETZOID:
+				{
+					/* Read the parts of the time */
+					hour = lround(DatumGetFloat8(DirectFunctionCall2(time_part, PointerGetDatum(txthour), values[i])));
+					minute = lround(DatumGetFloat8(DirectFunctionCall2(time_part, PointerGetDatum(txtminute), values[i])));
+					second = lround(DatumGetFloat8(DirectFunctionCall2(time_part, PointerGetDatum(txtsecond), values[i])));
+					OGR_F_SetFieldDateTime(feat, ogrfldnum, 0, 0, 0, hour, minute, second, 0);
+					break;
+				}
+
+
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+				{
+					Datum d = values[i];
+					year = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtyear), d)));
+					month = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtmonth), d)));
+					day = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtday), d)));
+					hour = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txthour), d)));
+					minute = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtminute), d)));
+					second = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtsecond), d)));
+					OGR_F_SetFieldDateTime(feat, ogrfldnum, year, month, day, hour, minute, second, 0);
+					break;
+				}
+
+				/* TODO: array types for string, integer, float */
+				default:
+				{
+					elog(ERROR, "OGR FDW unsupported PgSQL column type in \"%s\", %d", pgname, pgtype);
+					return OGRERR_FAILURE;
+				}				
+			}
+		}
+		/* Fill in unmatched columns with NULL */
+		else if ( ogrvariant == OGR_UNMATCHED )
+		{
+			OGR_F_UnsetField (feat, ogrfldnum);
+		}
+		else
+		{
+			elog(ERROR, "OGR FDW unsupported column variant in \"%s\", %d", pgname, ogrvariant);
+			return OGRERR_FAILURE;
+		}
+
+	}
+
+	/* done! */
+	return OGRERR_NONE;
+}
+
+/*
+ * ogrIterateForeignScan
+ *		Read next record from OGR and store it into the
+ *		ScanTupleSlot as a virtual tuple
+ */
+static TupleTableSlot *
+ogrIterateForeignScan(ForeignScanState *node)
+{
+	OgrFdwExecState *execstate = (OgrFdwExecState *) node->fdw_state;
+	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+	OGRFeatureH feat;
+
+	/*
+	 * Clear the slot. If it gets through w/o being filled up, that means
+	 * we're all done.
+	 */
+	ExecClearTuple(slot);
+
+	/*
+	 * First time through, reset reading. Then keep reading until
+	 * we run out of records, then return a cleared (NULL) slot, to
+	 * notify the core we're done.
+	 */
+	if ( execstate->rownum == 0 )
+	{
+		OGR_L_ResetReading(execstate->ogr.lyr);
+	}
+
+	/* If we rectreive a feature from OGR, copy it over into the slot */
+	if ( (feat = OGR_L_GetNextFeature(execstate->ogr.lyr)) )
+	{
+		/* convert result to arrays of values and null indicators */
+		if ( OGRERR_NONE != ogrFeatureToSlot(feat, slot, execstate) )
+			ogrEreportError("failure reading OGR data source");
+
+		/* store the virtual tuple */
+		ExecStoreVirtualTuple(slot);
+
+		/* increment row count */
+		execstate->rownum++;
+
+		/* Release OGR feature object */
+		OGR_F_Destroy(feat);
+	}
+
+	return slot;
+}
+
+/*
+ * ogrReScanForeignScan
+ *		Rescan table, possibly with new parameters
+ */
+static void
+ogrReScanForeignScan(ForeignScanState *node)
+{
+	OgrFdwExecState *execstate = (OgrFdwExecState *) node->fdw_state;
+
+	OGR_L_ResetReading(execstate->ogr.lyr);
+	execstate->rownum = 0;
+
+	return;
+}
+
+/*
+ * ogrEndForeignScan
+ *		Finish scanning foreign table and dispose objects used for this scan
+ */
+static void
+ogrEndForeignScan(ForeignScanState *node)
+{
+	OgrFdwExecState *execstate = (OgrFdwExecState *) node->fdw_state;
+
+	elog(DEBUG2, "processed %d rows from OGR", execstate->rownum);
+
+	ogrFinishConnection( &(execstate->ogr) );
+
+	return;
+}
+
+/* ======================================================== */
+/* WRITE SUPPORT */
+/* ======================================================== */
+
+// OgrFdwTable *tbl;
+
+/* if the scanning functions above respected the targetlist, 
+we would only be getting back the SET target=foo columns in the slots below,
+so we would need to add the "fid" to all targetlists (and also disallow fid changing
+perhaps).
+
+since we always pull complete tables in the scan functions, the
+slots below are basically full tables, in fact they include (?) one entry
+for each OGR column, even when the table does not include the column, 
+just nulling out the entries that are not in the table definition
+
+it might be better to update the scan code to properly manage target lists
+first, and then come back here and do things properly
+
+we will need a ogrSlotToFeature to feed into the OGR_L_SetFeature and
+OGR_L_CreateFeature functions. Also will use OGR_L_DeleteFeature and
+fid value
+
+in ogrGetForeignPlan we get a tlist that includes just the attributes we
+are interested in, can use that to pare down the request perhaps
+*/
+
+static int ogrGetFidColumn(const TupleDesc td)
+{
+	int i;
+	for ( i = 0; i < td->natts; i++ )
+	{
+		NameData attname = td->attrs[i]->attname;
+		Oid atttypeid = td->attrs[i]->atttypid;
+		if ( (atttypeid == INT4OID || atttypeid == INT8OID) && 
+		     strcaseeq("fid", attname.data) )
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+/*
+ * ogrAddForeignUpdateTargets
+ * 
+ * For now we no-op this callback, as we are making the presence of
+ * "fid" in the FDW table definition a requirement for any update.
+ * It might be possible to add nonexisting "junk" columns? In which case
+ * there could always be a virtual fid travelling with the queries,
+ * and the FDW table itself wouldn't need such a column?
+ */
+static void ogrAddForeignUpdateTargets (Query *parsetree,
+					RangeTblEntry *target_rte,
+					Relation target_relation)
+{
+	ListCell *cell;
+	Form_pg_attribute att;
+	Var *var;
+	TargetEntry *tle;
+	TupleDesc tupdesc = target_relation->rd_att;
+	int fid_column = ogrGetFidColumn(tupdesc);
+	
+	elog(DEBUG2, "ogrAddForeignUpdateTargets");
+	
+	if ( fid_column < 0 )
+		elog(ERROR,"table '%s' does not have a 'fid' column", RelationGetRelationName(target_relation));
+	
+	att = tupdesc->attrs[fid_column];
+	
+	/* Make a Var representing the desired value */
+	var = makeVar(parsetree->resultRelation,
+			att->attnum,
+			att->atttypid,
+			att->atttypmod,
+			att->attcollation,
+			0);
+
+	/* Wrap it in a resjunk TLE with the right name ... */
+	tle = makeTargetEntry((Expr *)var,
+			list_length(parsetree->targetList) + 1,
+			pstrdup(NameStr(att->attname)),
+			true);
+
+	parsetree->targetList = lappend(parsetree->targetList, tle);
+	
+	foreach(cell, parsetree->targetList)
+	{
+		TargetEntry *target = (TargetEntry *) lfirst(cell);
+		elog(DEBUG4, "parsetree->targetList %s:%d", target->resname, target->resno);
+	}
+	
+	return;
+
+}
+
+/* 
+ * ogrBeginForeignModify
+ * For now the only thing we'll do here is set up the connection
+ * and pass that on to the next functions.
+ */
+static void ogrBeginForeignModify (ModifyTableState *mtstate,
+					ResultRelInfo *rinfo,
+					List *fdw_private,
+					int subplan_index,
+					int eflags)
+{
+	Oid foreigntableid;
+	OgrFdwState *state;
+	
+	elog(DEBUG2, "ogrBeginForeignModify");
+
+	foreigntableid = RelationGetRelid(rinfo->ri_RelationDesc);
+	state = getOgrFdwState(foreigntableid, OGR_MODIFY_STATE);
+
+	/* Read the OGR layer definition and PgSQL foreign table definitions */
+	ogrReadColumnData(state);
+	
+	/* Save OGR connection, etc, for later */
+	rinfo->ri_FdwState = state;
+	return;
+}
+
+/*
+ * ogrExecForeignUpdate
+ * Find out what the fid is, get the OGR feature for that FID, 
+ * and then update the values on that feature.
+ */
+static TupleTableSlot *ogrExecForeignUpdate (EState *estate,
+					ResultRelInfo *rinfo,
+					TupleTableSlot *slot,
+					TupleTableSlot *planSlot)
+{
+	OgrFdwModifyState *modstate = rinfo->ri_FdwState;
+	TupleDesc td = slot->tts_tupleDescriptor;
+	Relation rel = rinfo->ri_RelationDesc;
+	Oid foreigntableid = RelationGetRelid(rel);
+	int fid_column;
+	Oid fid_type;
+	Datum fid_datum;
+	int64 fid;
+	OGRFeatureH feat;
+	OGRErr err;
+	
+	/* Is there a fid column? */
+	fid_column = ogrGetFidColumn(td);
+	if ( fid_column < 0 )
+		elog(ERROR, "cannot find 'fid' column in table '%s'", get_rel_name(foreigntableid));
+	
+	/* What is the value of the FID for this record? */
+	fid_datum = slot->tts_values[fid_column];
+	fid_type = td->attrs[fid_column]->atttypid;
+
+	if ( fid_type == INT8OID )
+		fid = DatumGetInt64(fid_datum);
+	else
+		fid = DatumGetInt32(fid_datum);
+
+	elog(DEBUG2, "ogrExecForeignUpdate fid=" OGR_FDW_FRMT_INT64, OGR_FDW_CAST_INT64(fid));
+	
+	/* Get the OGR feature for this fid */
+	feat = OGR_L_GetFeature (modstate->ogr.lyr, fid);
+
+	/* If we found a feature, then copy data from the slot onto the feature */
+	/* and then back into the layer */
+	if ( ! feat )
+		ogrEreportError("failure reading OGR feature");
+
+	err = ogrSlotToFeature(slot, feat, modstate->table);
+	if ( err != OGRERR_NONE )
+		ogrEreportError("failure populating OGR feature");
+	
+	err = OGR_L_SetFeature(modstate->ogr.lyr, feat);
+	if ( err != OGRERR_NONE )
+		ogrEreportError("failure writing back OGR feature");
+	
+	OGR_F_Destroy(feat);
+	
+	/* TODO: slot handling? what happens with RETURNING clauses? */
+	
+	return slot;
+}
+
+// typedef struct TupleTableSlot
+// {
+//     NodeTag     type;
+//     bool        tts_isempty;    /* true = slot is empty */
+//     bool        tts_shouldFree; /* should pfree tts_tuple? */
+//     bool        tts_shouldFreeMin;      /* should pfree tts_mintuple? */
+//     bool        tts_slow;       /* saved state for slot_deform_tuple */
+//     HeapTuple   tts_tuple;      /* physical tuple, or NULL if virtual */
+//     TupleDesc   tts_tupleDescriptor;    /* slot's tuple descriptor */
+//     MemoryContext tts_mcxt;     /* slot itself is in this context */
+//     Buffer      tts_buffer;     /* tuple's buffer, or InvalidBuffer */
+//     int         tts_nvalid;     /* # of valid values in tts_values */
+//     Datum      *tts_values;     /* current per-attribute values */
+//     bool       *tts_isnull;     /* current per-attribute isnull flags */
+//     MinimalTuple tts_mintuple;  /* minimal tuple, or NULL if none */
+//     HeapTupleData tts_minhdr;   /* workspace for minimal-tuple-only case */
+//     long        tts_off;        /* saved state for slot_deform_tuple */
+// } TupleTableSlot;
+
+// typedef struct tupleDesc
+// {
+//     int         natts;          /* number of attributes in the tuple */
+//     Form_pg_attribute *attrs;
+//     /* attrs[N] is a pointer to the description of Attribute Number N+1 */
+//     TupleConstr *constr;        /* constraints, or NULL if none */
+//     Oid         tdtypeid;       /* composite type ID for tuple type */
+//     int32       tdtypmod;       /* typmod for tuple type */
+//     bool        tdhasoid;       /* tuple has oid attribute in its header */
+//     int         tdrefcount;     /* reference count, or -1 if not counting */
+// }   *TupleDesc;
+// 
+
+// typedef struct ResultRelInfo
+// {
+//     NodeTag     type;
+//     Index       ri_RangeTableIndex;
+//     Relation    ri_RelationDesc;
+//     int         ri_NumIndices;
+//     RelationPtr ri_IndexRelationDescs;
+//     IndexInfo **ri_IndexRelationInfo;
+//     TriggerDesc *ri_TrigDesc;
+//     FmgrInfo   *ri_TrigFunctions;
+//     List      **ri_TrigWhenExprs;
+//     Instrumentation *ri_TrigInstrument;
+//     struct FdwRoutine *ri_FdwRoutine;
+//     void       *ri_FdwState;
+//     List       *ri_WithCheckOptions;
+//     List       *ri_WithCheckOptionExprs;
+//     List      **ri_ConstraintExprs;
+//     JunkFilter *ri_junkFilter;
+//     ProjectionInfo *ri_projectReturning;
+//     ProjectionInfo *ri_onConflictSetProj;
+//     List       *ri_onConflictSetWhere;
+// } ResultRelInfo;
+
+// typedef struct TargetEntry
+// 	{
+// 	    Expr        xpr;
+// 	    Expr       *expr;           /* expression to evaluate */
+// 	    AttrNumber  resno;          /* attribute number (see notes above) */
+// 	    char       *resname;        /* name of the column (could be NULL) */
+// 	    Index       ressortgroupref;/* nonzero if referenced by a sort/group
+// 	                                 * clause */
+// 	    Oid         resorigtbl;     /* OID of column's source table */
+// 	    AttrNumber  resorigcol;     /* column's number in source table */
+// 	    bool        resjunk;        /* set to true to eliminate the attribute from
+// 	                                 * final target list */
+// 	} TargetEntry;
+
+// TargetEntry *
+// makeTargetEntry(Expr *expr,
+//                 AttrNumber resno,
+//                 char *resname,
+//                 bool resjunk)
+
+// Var *
+// makeVar(Index varno,
+//         AttrNumber varattno,
+//         Oid vartype,
+//         int32 vartypmod,
+//         Oid varcollid,
+//         Index varlevelsup)
+
+// typedef struct Var
+// {
+//     Expr        xpr;
+//     Index       varno;          /* index of this var's relation in the range
+//                                  * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
+//     AttrNumber  varattno;       /* attribute number of this var, or zero for
+//                                  * all */
+//     Oid         vartype;        /* pg_type OID for the type of this var */
+//     int32       vartypmod;      /* pg_attribute typmod value */
+//     Oid         varcollid;      /* OID of collation, or InvalidOid if none */
+//     Index       varlevelsup;    /* for subquery variables referencing outer
+//                                  * relations; 0 in a normal var, >0 means N
+//                                  * levels up */
+//     Index       varnoold;       /* original value of varno, for debugging */
+//     AttrNumber  varoattno;      /* original value of varattno */
+//     int         location;       /* token location, or -1 if unknown */
+// } Var;
+
+static TupleTableSlot *ogrExecForeignInsert (EState *estate,
+					ResultRelInfo *rinfo,
+					TupleTableSlot *slot,
+					TupleTableSlot *planSlot)
+{
+	OgrFdwModifyState *modstate = rinfo->ri_FdwState;
+	OGRFeatureDefnH ogr_fd = OGR_L_GetLayerDefn(modstate->ogr.lyr);
+	OGRFeatureH feat = OGR_F_Create(ogr_fd);
+	TupleDesc td = slot->tts_tupleDescriptor;
+	int fid_column;
+	OGRErr err;
+	GIntBig fid;
+
+	elog(DEBUG2, "ogrExecForeignInsert");
+	
+	/* Copy the data from the slot onto the feature */
+	if ( ! feat )
+		ogrEreportError("failure creating OGR feature");
+
+	err = ogrSlotToFeature(slot, feat, modstate->table);
+	if ( err != OGRERR_NONE )
+		ogrEreportError("failure populating OGR feature");
+				
+	err = OGR_L_CreateFeature(modstate->ogr.lyr, feat);
+	if ( err != OGRERR_NONE )
+		ogrEreportError("failure writing OGR feature");
+
+	fid = OGR_F_GetFID(feat);	
+	OGR_F_Destroy(feat);
+
+	/* Update the FID for RETURNING slot */
+	fid_column = ogrGetFidColumn(td);
+	if ( fid_column >= 0 )
+	{
+		slot->tts_values[fid_column] = Int64GetDatum(fid);
+		slot->tts_isnull[fid_column] = false;
+		slot->tts_nvalid++;
+	}
+	
+	return slot;
+}
+					
+
+					
+static TupleTableSlot *ogrExecForeignDelete (EState *estate,
+					ResultRelInfo *rinfo,
+					TupleTableSlot *slot,
+					TupleTableSlot *planSlot)
+{
+	OgrFdwModifyState *modstate = rinfo->ri_FdwState;
+	TupleDesc td = planSlot->tts_tupleDescriptor;
+	Relation rel = rinfo->ri_RelationDesc;
+	Oid foreigntableid = RelationGetRelid(rel);
+	int fid_column;
+	Oid fid_type;
+	Datum fid_datum;
+	int64 fid;
+	OGRErr err;
+
+	/* Is there a fid column? */
+	fid_column = ogrGetFidColumn(td);
+	if ( fid_column < 0 )
+		elog(ERROR, "cannot find 'fid' column in table '%s'", get_rel_name(foreigntableid));
+	
+	/* What is the value of the FID for this record? */
+	fid_datum = planSlot->tts_values[fid_column];
+	fid_type = td->attrs[fid_column]->atttypid;
+
+	if ( fid_type == INT8OID )
+		fid = DatumGetInt64(fid_datum);
+	else
+		fid = DatumGetInt32(fid_datum);
+	
+	elog(DEBUG2, "ogrExecForeignDelete fid=" OGR_FDW_FRMT_INT64, OGR_FDW_CAST_INT64(fid));
+	
+	/* Delete the OGR feature for this fid */
+	err = OGR_L_DeleteFeature(modstate->ogr.lyr, fid);
+	
+	if ( err != OGRERR_NONE ) 
+		return NULL;
+	else
+		return slot;
+}
+					
+static void ogrEndForeignModify (EState *estate, ResultRelInfo *rinfo)
+{
+	OgrFdwModifyState *modstate = rinfo->ri_FdwState;
+	
+	elog(DEBUG2, "ogrEndForeignModify");
+	
+	ogrFinishConnection( &(modstate->ogr) );
+	
+	return;
+}
+				
+static int ogrIsForeignRelUpdatable (Relation rel)
+{
+	static int readonly = 0;
+	static int updateable = 0;
+	TupleDesc td = RelationGetDescr(rel);
+	OgrConnection ogr;
+	Oid foreigntableid = RelationGetRelid(rel);
+
+	elog(DEBUG2, "ogrIsForeignRelUpdatable");
+
+	/* Before we say "yes"... */
+	/*  Does the foreign relation have a "fid" column? */
+	/* Is that column an integer? */
+	if ( ogrGetFidColumn(td) < 0 )
+	{
+		elog(NOTICE, "no \"fid\" column in foreign table '%s'", get_rel_name(foreigntableid));
+		return readonly;
+	}
+	
+	/*   Is it backed by a writable OGR driver? */
+	/*   Can we open the relation in read/write mode? */
+	ogr = ogrGetConnectionFromTable(foreigntableid, true);
+	if ( ! (ogr.ds && ogr.lyr) )
+		return readonly;
+
+	if ( OGR_L_TestCapability(ogr.lyr, OLCRandomWrite) )
+		updateable |= (1 << CMD_UPDATE);
+
+	if ( OGR_L_TestCapability(ogr.lyr, OLCSequentialWrite) )
+		updateable |= (1 << CMD_INSERT);
+	
+	if ( OGR_L_TestCapability(ogr.lyr, OLCDeleteFeature) )
+		updateable |= (1 << CMD_DELETE);
+	
+	ogrFinishConnection(&ogr);
+		
+	return updateable;
+	
+}
+
+#if PG_VERSION_NUM >= 90500
+
+/*
+ * PostgreSQL 9.5 or above.  Import a foreign schema
+ */
+static List *
+ogrImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
+{
+	List *commands = NIL;
+	ForeignServer *server;
+	ListCell *lc;
+	bool import_all = false;
+	bool launder_column_names, launder_table_names;
+	OgrConnection ogr;
+	int i;
+	char layer_name[STR_MAX_LEN];
+	char table_name[STR_MAX_LEN];
+
+	/* Are we importing all layers in the OGR datasource? */
+	import_all = streq(stmt->remote_schema, "ogr_all");
+
+	/* Make connection to server */
+	server = GetForeignServer(serverOid);
+	ogr = ogrGetConnectionFromServer(serverOid, false);
+
+	/* Launder by default */
+	launder_column_names = launder_table_names = true;
+
+	/* Read user-provided statement laundering options */
+	foreach(lc, stmt->options)
+	{
+		DefElem *def = (DefElem *) lfirst(lc);
+
+		if (streq(def->defname, "launder_column_names"))
+			launder_column_names = defGetBoolean(def);
+		else if (streq(def->defname, "launder_table_names"))
+			launder_table_names = defGetBoolean(def);
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+					 errmsg("invalid option \"%s\"", def->defname)));
+	}
+
+	for ( i = 0; i < GDALDatasetGetLayerCount(ogr.ds); i++ )
+	{
+		bool import_layer = false;
+		OGRLayerH ogr_lyr = GDALDatasetGetLayer(ogr.ds, i);
+
+		if ( ! ogr_lyr )
+		{
+			elog(DEBUG1, "Skipping OGR layer %d, unable to read layer", i);
+			continue;
+		}
+
+		/* Layer name is never laundered, since it's the link back to OGR */
+		strncpy(layer_name, OGR_L_GetName(ogr_lyr), STR_MAX_LEN);
+
+		/*
+		* We need to compare against created table names
+		* because PgSQL does an extra check on CREATE FOREIGN TABLE
+		*/
+		strncpy(table_name, layer_name, STR_MAX_LEN);
+		if (launder_table_names)
+			ogrStringLaunder(table_name);
+
+		/*
+		* Only include if we are importing "ogr_all" or
+		* the layer prefix starts with the remote schema
+		*/
+		import_layer = import_all ||
+				( strncmp(layer_name, stmt->remote_schema, strlen(stmt->remote_schema) ) == 0 );
+
+		/* Apply restrictions for LIMIT TO and EXCEPT */
+		if (import_layer && (
+		    stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
+		    stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT ) )
+		{
+			/* Limited list? Assume we are taking no items */
+			if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO)
+				import_layer = false;
+
+			/* Check the list for our items */
+			foreach(lc, stmt->table_list)
+			{
+				RangeVar *rv = (RangeVar *) lfirst(lc);
+				/* Found one! */
+				if ( streq(rv->relname, table_name) )
+				{
+					if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO)
+						import_layer = true;
+					else
+						import_layer = false;
+
+					break;
+				}
+
+			}
+		}
+
+		if (import_layer)
+		{
+			OGRErr err;
+			stringbuffer_t buf;
+			stringbuffer_init(&buf);
+			
+			err = ogrLayerToSQL(ogr_lyr,
+			         quote_identifier(server->servername), 
+			         launder_table_names, 
+			         launder_column_names,
+			         GEOMETRYOID != BYTEAOID, 
+			         &buf
+			      );
+					 
+			if (err != OGRERR_NONE)
+			{
+				elog(ERROR, "unable to generate IMPORT SQL for '%s'", table_name);
+			}
+
+			commands = lappend(commands, pstrdup(stringbuffer_getstring(&buf)));
+			stringbuffer_release(&buf);
+		}
+	}
+
+	elog(NOTICE, "Number of tables to be created %d", list_length(commands) );
+	
+	ogrFinishConnection(&ogr);
+
+	return commands;
+}
+
+#endif /* PostgreSQL 9.5+ */
+
+
+
+#endif /* PostgreSQL 9.3+ version check */
+
diff --git a/ogr_fdw.control b/ogr_fdw.control
new file mode 100644
index 0000000..a78acdc
--- /dev/null
+++ b/ogr_fdw.control
@@ -0,0 +1,5 @@
+# ogr_fdw extension
+comment = 'foreign-data wrapper for GIS data access'
+default_version = '1.0'
+module_pathname = '$libdir/ogr_fdw'
+relocatable = true
diff --git a/ogr_fdw.h b/ogr_fdw.h
new file mode 100644
index 0000000..b24a676
--- /dev/null
+++ b/ogr_fdw.h
@@ -0,0 +1,189 @@
+/*-------------------------------------------------------------------------
+ *
+ * ogr_fdw.h
+ *		  foreign-data wrapper for GIS data access.
+ *
+ * Copyright (c) 2014-2015, Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _OGR_FDW_H
+#define _OGR_FDW_H 1
+
+/*
+ * PostgreSQL
+ */
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/reloptions.h"
+#include "access/sysattr.h"
+#include "access/transam.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_foreign_table.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/copy.h"
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "commands/vacuum.h"
+#include "foreign/fdwapi.h"
+#include "foreign/foreign.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
+#include "optimizer/var.h"
+#include "parser/parsetree.h"
+#include "storage/ipc.h"
+#include "utils/builtins.h"
+#include "utils/date.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/numeric.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+/* GDAL/OGR includes and compat */
+#include "ogr_fdw_gdal.h"
+#include "ogr_fdw_common.h"
+
+/* Local configuration defines */
+
+/* Use hexwkb input by default, but have option to use */
+/* the binary recv input instead. Binary input is strict */
+/* on geometry structure (no unclosed polys, etc) and */
+/* hexwkb is not. */
+#define OGR_FDW_HEXWKB TRUE
+
+typedef enum 
+{
+	OGR_UNMATCHED,
+	OGR_GEOMETRY,
+	OGR_FID,
+	OGR_FIELD
+} OgrColumnVariant;
+
+typedef enum {
+	OGR_UPDATEABLE_FALSE,
+	OGR_UPDATEABLE_TRUE,
+	OGR_UPDATEABLE_UNSET
+} OgrUpdateable;
+
+typedef struct OgrFdwColumn
+{
+	/* PgSQL metadata */
+	int pgattnum;            /* PostgreSQL attribute number */
+	int pgattisdropped;      /* PostgreSQL attribute dropped? */
+	char *pgname;            /* PostgreSQL column name */
+	Oid pgtype;              /* PostgreSQL data type */
+	int pgtypmod;            /* PostgreSQL type modifier */
+	
+	/* For reading */
+	Oid pginputfunc;         /* PostgreSQL function to convert cstring to type */
+	Oid pginputioparam;
+	Oid pgrecvfunc;          /* PostgreSQL function to convert binary to type */
+	Oid pgrecvioparam;
+	
+	/* For writing */
+	Oid pgoutputfunc;        /* PostgreSQL function to convert type to cstring */
+	bool pgoutputvarlena;
+	Oid pgsendfunc;        /* PostgreSQL function to convert type to binary */
+	bool pgsendvarlena;
+	
+	/* OGR metadata */
+	OgrColumnVariant ogrvariant;
+	int ogrfldnum;
+	OGRFieldType ogrfldtype;
+} OgrFdwColumn;
+
+typedef struct OgrFdwTable
+{
+	int ncols;
+	char *tblname;
+	OgrFdwColumn *cols;
+} OgrFdwTable;
+
+typedef struct OgrConnection
+{
+	char *ds_str;         /* datasource connection string */
+	char *dr_str;         /* driver (format) name */
+	char *lyr_str;        /* layer name */
+	char *config_options; /* GDAL config options */
+	char *open_options;   /* GDAL open options */
+	bool ds_updateable;
+	bool lyr_updateable;
+	GDALDatasetH ds;      /* GDAL datasource handle */
+	OGRLayerH lyr;        /* OGR layer handle */
+} OgrConnection;
+
+typedef enum 
+{
+	OGR_PLAN_STATE,
+	OGR_EXEC_STATE,
+	OGR_MODIFY_STATE
+} OgrFdwStateType;
+
+typedef struct OgrFdwState
+{
+	OgrFdwStateType type;
+	Oid foreigntableid; 
+	OgrConnection ogr;  /* connection object */
+	OgrFdwTable *table;
+	TupleDesc tupdesc;
+} OgrFdwState;
+
+typedef struct OgrFdwPlanState
+{
+	OgrFdwStateType type;
+	Oid foreigntableid; 
+	OgrConnection ogr; 
+	OgrFdwTable *table;
+	TupleDesc tupdesc;
+	int nrows;           /* estimate of number of rows in file */
+	Cost startup_cost; 
+	Cost total_cost;
+	bool *pushdown_clauses;
+} OgrFdwPlanState;
+
+typedef struct OgrFdwExecState
+{
+	OgrFdwStateType type;
+	Oid foreigntableid; 
+	OgrConnection ogr;   
+	OgrFdwTable *table;
+	TupleDesc tupdesc;
+	char *sql;             /* OGR SQL for attribute filter */
+	int rownum;            /* how many rows have we read thus far? */
+	Oid setsridfunc;       /* ST_SetSRID() */
+	Oid typmodsridfunc;    /* postgis_typmod_srid() */
+} OgrFdwExecState;
+
+typedef struct OgrFdwModifyState
+{
+	OgrFdwStateType type;
+	Oid foreigntableid; 
+	OgrConnection ogr;     /* connection object */
+	OgrFdwTable *table;
+	TupleDesc tupdesc;
+} OgrFdwModifyState;
+
+/* Shared function signatures */
+bool ogrDeparse(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, List *exprs, OgrFdwState *state, List **param);
+
+
+/* Shared global value of the Geometry OId */
+extern Oid GEOMETRYOID;
+
+#endif /* _OGR_FDW_H */
\ No newline at end of file
diff --git a/ogr_fdw_common.c b/ogr_fdw_common.c
new file mode 100644
index 0000000..8ff6dd4
--- /dev/null
+++ b/ogr_fdw_common.c
@@ -0,0 +1,356 @@
+/*-------------------------------------------------------------------------
+ *
+ * ogr_fdw_common.c
+ *		  foreign-data wrapper for GIS data access.
+ *
+ * Copyright (c) 2014-2016, Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "ogr_fdw_gdal.h"
+#include "ogr_fdw_common.h"
+#include "stringbuffer.h"
+
+/* Prototype for function that must be defined in PostgreSQL (it is) */
+/* and in ogr_fdw_info (it is) */
+const char * quote_identifier(const char *ident);
+
+
+/*
+ * Append a SQL string literal representing "val" to buf.
+ */
+static void
+ogrDeparseStringLiteral(stringbuffer_t *buf, const char *val)
+{
+	const char *valptr;
+
+	/*
+	 * Rather than making assumptions about the remote server's value of
+	 * standard_conforming_strings, always use E'foo' syntax if there are any
+	 * backslashes.  This will fail on remote servers before 8.1, but those
+	 * are long out of support.
+	 */
+	if ( strchr(val, '\\') != NULL )
+	{
+		stringbuffer_append_char(buf, 'E');
+	}
+	stringbuffer_append_char(buf, '\'');
+	for ( valptr = val; *valptr; valptr++ )
+	{
+		char ch = *valptr;
+		if ( ch == '\'' || ch == '\\' )
+		{
+			stringbuffer_append_char(buf, ch);
+		}
+		stringbuffer_append_char(buf, ch);
+	}
+	stringbuffer_append_char(buf, '\'');
+}
+
+void
+ogrStringLaunder(char *str)
+{
+	int i, j = 0;
+	char tmp[STR_MAX_LEN];
+	memset(tmp, 0, STR_MAX_LEN);
+	
+	for(i = 0; str[i]; i++)
+	{
+		char c = tolower(str[i]);
+
+		/* First character is a numeral, prefix with 'n' */
+		if ( i == 0 && (c >= 48 && c <= 57) )
+		{
+			tmp[j++] = 'n';
+		}
+
+		/* Replace non-safe characters w/ _ */
+		if ( (c >= 48 && c <= 57) || /* 0-9 */
+			 (c >= 65 && c <= 90) || /* A-Z */
+			 (c >= 97 && c <= 122)   /* a-z */ )
+		{
+			/* Good character, do nothing */
+		}
+		else
+		{
+			c = '_';
+		}
+		tmp[j++] = c;
+
+		/* Avoid mucking with data beyond the end of our stack-allocated strings */
+		if ( j >= STR_MAX_LEN )
+			j = STR_MAX_LEN - 1;
+	}
+	strncpy(str, tmp, STR_MAX_LEN);
+	
+}
+
+static char *
+ogrTypeToPgType(OGRFieldDefnH ogr_fld)
+{
+	OGRFieldType ogr_type = OGR_Fld_GetType(ogr_fld);
+	switch(ogr_type)
+	{
+		case OFTInteger:
+#if GDAL_VERSION_MAJOR >= 2
+			if( OGR_Fld_GetSubType(ogr_fld) == OFSTBoolean )
+				return "boolean";
+			else
+#endif
+				return "integer";
+		case OFTReal:
+			return "real";
+		case OFTString:
+			return "varchar";
+		case OFTBinary:
+			return "bytea";
+		case OFTDate:
+			return "date";
+		case OFTTime:
+			return "time";
+		case OFTDateTime:
+			return "timestamp";
+		case OFTIntegerList:
+			return "integer[]";
+		case OFTRealList:
+			return "real[]";
+		case OFTStringList:
+			return "varchar[]";
+#if GDAL_VERSION_MAJOR >= 2
+		case OFTInteger64:
+			return "bigint";
+#endif
+		default:
+			CPLError(CE_Failure, CPLE_AssertionFailed, 
+			         "unsupported GDAL type '%s'", 
+			         OGR_GetFieldTypeName(ogr_type));
+			return NULL;
+	}
+	return NULL;
+}
+
+static void
+ogrGeomTypeToPgGeomType(stringbuffer_t *buf, OGRwkbGeometryType gtype)
+{
+	switch(wkbFlatten(gtype))
+	{
+	    case wkbUnknown:
+			stringbuffer_append(buf, "Geometry");
+			break;
+	    case wkbPoint:
+			stringbuffer_append(buf, "Point");
+			break;
+		case wkbLineString:
+			stringbuffer_append(buf, "LineString");
+			break;
+		case wkbPolygon:
+			stringbuffer_append(buf, "Polygon");
+			break;
+		case wkbMultiPoint:
+			stringbuffer_append(buf, "MultiPoint");
+			break;
+		case wkbMultiLineString:
+			stringbuffer_append(buf, "MultiLineString");
+			break;
+		case wkbMultiPolygon:
+			stringbuffer_append(buf, "MultiPolygon");
+			break;
+		case wkbGeometryCollection:
+			stringbuffer_append(buf, "GeometryCollection");
+			break;
+#if GDAL_VERSION_MAJOR >= 2
+		case wkbCircularString:
+			stringbuffer_append(buf, "CircularString");
+			break;
+		case wkbCompoundCurve:
+			stringbuffer_append(buf, "CompoundCurve");
+			break;
+		case wkbCurvePolygon:
+			stringbuffer_append(buf, "CurvePolygon");
+			break;
+		case wkbMultiCurve:
+			stringbuffer_append(buf, "MultiCurve");
+			break;
+		case wkbMultiSurface:
+			stringbuffer_append(buf, "MultiSurface");
+			break;
+#endif
+		case wkbNone:
+			CPLError(CE_Failure, CPLE_AssertionFailed, "Cannot handle OGR geometry type wkbNone");
+		default:
+			CPLError(CE_Failure, CPLE_AssertionFailed, "Cannot handle OGR geometry type '%d'", gtype);
+	}
+
+#if GDAL_VERSION_MAJOR >= 2 
+	if ( wkbHasZ(gtype) )
+#else
+	if ( gtype & wkb25DBit )
+#endif
+		stringbuffer_append(buf, "Z");
+	
+#if GDAL_VERSION_MAJOR >= 2 && GDAL_VERSION_MINOR >= 1
+	if ( wkbHasM(gtype) )
+		stringbuffer_append(buf, "M");
+#endif
+
+	return;
+}
+
+static OGRErr
+ogrColumnNameToSQL (const char *ogrcolname, const char *pgtype, int launder_column_names, stringbuffer_t *buf)
+{
+	char pgcolname[STR_MAX_LEN];
+	strncpy(pgcolname, ogrcolname, STR_MAX_LEN);
+	ogrStringLaunder(pgcolname);
+
+	if ( launder_column_names )
+	{
+		stringbuffer_aprintf(buf, ",\n  %s %s", quote_identifier(pgcolname), pgtype);
+		if ( ! strcaseeq(pgcolname, ogrcolname) )
+			stringbuffer_aprintf(buf, " OPTIONS (column_name '%s')", ogrcolname);
+	}
+	else
+	{
+		/* OGR column is PgSQL compliant, we're all good */
+		if ( streq(pgcolname, ogrcolname) )
+			stringbuffer_aprintf(buf, ",\n  %s %s", quote_identifier(ogrcolname), pgtype);
+		/* OGR is mixed case or non-compliant, we need to quote it */
+		else
+			stringbuffer_aprintf(buf, ",\n  \"%s\" %s", ogrcolname, pgtype);
+	}
+	return OGRERR_NONE;
+}
+
+OGRErr
+ogrLayerToSQL (const OGRLayerH ogr_lyr, const char *fwd_server, 
+			   int launder_table_names, int launder_column_names,
+			   int use_postgis_geometry, stringbuffer_t *buf)
+{
+	int geom_field_count, i;
+	char table_name[STR_MAX_LEN];
+	OGRFeatureDefnH ogr_fd = OGR_L_GetLayerDefn(ogr_lyr);
+	stringbuffer_t gbuf;
+	
+	stringbuffer_init(&gbuf);
+
+	if ( ! ogr_fd )
+	{
+		CPLError(CE_Failure, CPLE_AssertionFailed, "unable to get OGRFeatureDefnH from OGRLayerH");
+		return OGRERR_FAILURE;
+	}
+
+#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
+	geom_field_count = OGR_FD_GetGeomFieldCount(ogr_fd);
+#else
+	geom_field_count = (OGR_L_GetGeomType(ogr_lyr) != wkbNone);
+#endif
+	
+	/* Process table name */
+	strncpy(table_name, OGR_L_GetName(ogr_lyr), STR_MAX_LEN);
+	if (launder_table_names)
+		ogrStringLaunder(table_name);
+	
+	/* Create table */
+	stringbuffer_aprintf(buf, "CREATE FOREIGN TABLE %s (\n", quote_identifier(table_name));
+	
+	/* For now, every table we auto-create will have a FID */
+	stringbuffer_append(buf, "  fid bigint");
+	
+	/* Handle all geometry columns in the OGR source */
+	for ( i = 0; i < geom_field_count; i++ )
+	{
+		int srid = 0;
+#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
+		OGRGeomFieldDefnH gfld = OGR_FD_GetGeomFieldDefn(ogr_fd, i);
+		OGRwkbGeometryType gtype = OGR_GFld_GetType(gfld);
+		const char *geomfldname = OGR_GFld_GetNameRef(gfld);
+		OGRSpatialReferenceH gsrs = OGR_GFld_GetSpatialRef(gfld);
+#else
+		OGRwkbGeometryType gtype = OGR_FD_GetGeomType(ogr_fd);
+		const char *geomfldname = "geom";
+		OGRSpatialReferenceH gsrs = OGR_L_GetSpatialRef(ogr_lyr);
+#endif
+		/* Skip geometry type we cannot handle */
+		if ( gtype == wkbNone )
+			continue;
+
+		/* Clear out our geometry type buffer */
+		stringbuffer_clear(&gbuf);
+
+		/* PostGIS geometry type has lots of complex stuff */
+		if ( use_postgis_geometry )
+		{	
+			/* Add geometry type info */
+			stringbuffer_append(&gbuf, "Geometry(");
+			ogrGeomTypeToPgGeomType(&gbuf, gtype);
+			
+			/* See if we have an EPSG code to work with */
+			if ( gsrs )
+			{
+				const char *charAuthType;
+				const char *charSrsCode;
+				OSRAutoIdentifyEPSG(gsrs);
+				charAuthType = OSRGetAttrValue(gsrs, "AUTHORITY", 0);
+				charSrsCode = OSRGetAttrValue(gsrs, "AUTHORITY", 1);
+				if ( charAuthType && strcaseeq(charAuthType, "EPSG") &&
+				     charSrsCode && atoi(charSrsCode) > 0 )
+				{
+					srid = atoi(charSrsCode);
+				}
+			}
+		
+			/* Add EPSG number, if figured it out */
+			if ( srid )
+			{
+				stringbuffer_aprintf(&gbuf, ",%d)", srid);
+			}
+			else
+			{
+				stringbuffer_append(&gbuf, ")");
+			}
+		}
+		/* Bytea is simple */
+		else
+		{
+			stringbuffer_append(&gbuf, "bytea");
+		}
+	
+		/* Use geom field name if we have it */
+		if ( geomfldname && strlen(geomfldname) > 0 )
+		{
+			ogrColumnNameToSQL(geomfldname, stringbuffer_getstring(&gbuf), launder_column_names, buf);
+		}
+		/* Or a numbered generic name if we don't */
+		else if ( geom_field_count > 1 )
+		{
+			stringbuffer_aprintf(buf, ",\n  geom%d %s", i, stringbuffer_getstring(&gbuf));
+		}
+		/* Or just a generic name */
+		else
+		{
+			stringbuffer_aprintf(buf, ",\n  geom %s", stringbuffer_getstring(&gbuf));
+		}
+	}
+
+	/* Write out attribute fields */
+	for ( i = 0; i < OGR_FD_GetFieldCount(ogr_fd); i++ )
+	{
+		OGRFieldDefnH ogr_fld = OGR_FD_GetFieldDefn(ogr_fd, i);
+		ogrColumnNameToSQL(OGR_Fld_GetNameRef(ogr_fld), 
+		                   ogrTypeToPgType(ogr_fld), 
+		                   launder_column_names, buf);
+	}
+
+	/*
+	 * Add server name and layer-level options.  We specify remote
+	 * layer name as option
+	 */
+	stringbuffer_aprintf(buf, "\n) SERVER %s\nOPTIONS (", quote_identifier(fwd_server));
+	stringbuffer_append(buf, "layer ");
+	ogrDeparseStringLiteral(buf, OGR_L_GetName(ogr_lyr));
+	stringbuffer_append(buf, ");\n");
+	
+	return OGRERR_NONE;
+}
+
diff --git a/ogr_fdw_common.h b/ogr_fdw_common.h
new file mode 100644
index 0000000..cd761dd
--- /dev/null
+++ b/ogr_fdw_common.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * ogr_fdw_common.h
+ *		  foreign-data wrapper for GIS data access.
+ *
+ * Copyright (c) 2014-2015, Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _OGR_FDW_COMMON_H
+#define _OGR_FDW_COMMON_H 1
+
+#include <string.h>
+#include <ctype.h>
+#include "stringbuffer.h"
+
+#define STR_MAX_LEN 256
+
+/* Utility macros for string equality */
+#define streq(s1,s2) (strcmp((s1),(s2)) == 0)
+#define strcaseeq(s1,s2) (strcasecmp((s1),(s2)) == 0)
+
+
+/* Re-write a string in place with laundering rules */
+void ogrStringLaunder(char *str);
+
+OGRErr ogrLayerToSQL (const OGRLayerH ogr_lyr,
+               const char *fwd_server, int launder_table_names, int launder_column_names,
+			   int use_postgis_geometry, stringbuffer_t *buf);
+
+
+#endif /* _OGR_FDW_COMMON_H */
diff --git a/ogr_fdw_deparse.c b/ogr_fdw_deparse.c
new file mode 100644
index 0000000..85fd98a
--- /dev/null
+++ b/ogr_fdw_deparse.c
@@ -0,0 +1,598 @@
+/*-------------------------------------------------------------------------
+ *
+ * ogr_fdw_deparse.c
+ *		  foreign-data wrapper for GIS data access.
+ *
+ * Copyright (c) 2014-2015, Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * Convert parse tree to a QueryExpression as described at 
+ * http://gdal.org/ogr_sql.html
+ *-------------------------------------------------------------------------
+ */
+
+
+#include "postgres.h"
+
+/*
+ * Local structures
+ */
+#include "ogr_fdw.h"
+
+
+typedef struct OgrDeparseCtx
+{
+	PlannerInfo *root;        /* global planner state */
+	RelOptInfo *foreignrel;   /* the foreign relation we are planning for */
+	StringInfo buf;           /* output buffer to append to */
+	List **params_list;       /* exprs that will become remote Params */
+	OGRGeometryH geom;        /* if filter contains a geometry constant, it resides here */
+	OgrFdwState *state;       /* to convert local column names to OGR names */
+} OgrDeparseCtx;
+
+/* Local function signatures */
+static bool ogrDeparseExpr(Expr *node, OgrDeparseCtx *context);
+// static void ogrDeparseOpExpr(OpExpr* node, OgrDeparseCtx *context);
+
+static void setStringInfoLength(StringInfo str, int len)
+{
+	str->len = len;
+	str->data[len] = '\0';
+}
+
+static char *
+ogrStringFromDatum(Datum datum, Oid type)
+{
+	StringInfoData result;
+	regproc typoutput;
+	HeapTuple tuple;
+	char *str, *p;
+
+	/* Special handling for boolean */
+	if ( type == BOOLOID )
+	{
+		if ( datum )
+			return "1=1";
+		else
+			return "1=0";
+	}
+
+	/* get the type's output function */
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
+	if (!HeapTupleIsValid(tuple))
+	{
+		elog(ERROR, "cache lookup failed for type %u", type);
+	}
+	typoutput = ((Form_pg_type)GETSTRUCT(tuple))->typoutput;
+	ReleaseSysCache(tuple);
+
+	initStringInfo(&result);
+
+	/* Special handling to convert a geometry to a bbox needed here */
+	if ( type == GEOMETRYOID )
+	{
+		elog(ERROR, "got a GEOMETRY!");
+		return NULL;
+	}
+
+	/* render the constant in OGR SQL */
+	switch (type)
+	{
+		case TEXTOID:
+		case DATEOID:
+		case TIMESTAMPOID:
+		case TIMESTAMPTZOID:
+		case CHAROID:
+		case BPCHAROID:
+		case VARCHAROID:
+		case NAMEOID:
+			str = DatumGetCString(OidFunctionCall1(typoutput, datum));
+
+			/* Don't return a zero length string, return an empty string */
+			if (str[0] == '\0')
+				return "''";
+
+			/* wrap string with ' */
+			appendStringInfoChar(&result, '\'');
+			for (p=str; *p; ++p)
+			{
+				/* Escape single quotes as doubled '' */
+				if (*p == '\'')
+					appendStringInfoChar(&result, '\'');
+				appendStringInfoChar(&result, *p);
+			}
+			appendStringInfoChar(&result, '\'');
+			break;
+		case INT8OID:
+		case INT2OID:
+		case INT4OID:
+		case OIDOID:
+		case FLOAT4OID:
+		case FLOAT8OID:
+		case NUMERICOID:
+			appendStringInfoString(&result, DatumGetCString(OidFunctionCall1(typoutput, datum)));
+			break;
+		default:
+			elog(DEBUG1, "could not convert type (%d) to OGR query form", type);
+			return NULL;
+	}
+
+	return result.data;
+}
+
+static bool
+ogrDeparseConst(Const* constant, OgrDeparseCtx *context)
+{
+	/* TODO: Can OGR do anythign w/ NULL? */
+	if (constant->constisnull)
+	{
+		appendStringInfoString(context->buf, "NULL");
+	}
+	/* Use geometry as a spatial filter? */
+	else if ( constant->consttype == GEOMETRYOID )
+	{
+		/*
+		 * For geometry we need to convert the gserialized constant into 
+		 * an OGRGeometry for the OGR spatial filter.
+		 * For that, we can use the type's "send" function
+		 * which takes in gserialized and spits out EWKB.
+		 */
+		Oid sendfunction;
+		bool typeIsVarlena;
+		Datum wkbdatum;
+		char *gser;
+		char *wkb;
+		int wkb_size;
+		OGRGeometryH ogrgeom;
+		OGRErr err;
+
+		/*
+		 * Given a type oid (geometry in this case),
+		 * look up the "send" function that takes in
+		 * serialized input and outputs the binary (WKB) form.
+		 */
+		getTypeBinaryOutputInfo(constant->consttype, &sendfunction, &typeIsVarlena);
+		wkbdatum = OidFunctionCall1(sendfunction, constant->constvalue);
+		
+		/* 
+		 * Convert the WKB into an OGR geometry 
+		 */
+		gser = DatumGetPointer(wkbdatum);
+		wkb = VARDATA(gser);
+		wkb_size = VARSIZE(gser) - VARHDRSZ;
+		err = OGR_G_CreateFromWkb((unsigned char *)wkb, NULL, &ogrgeom, wkb_size);
+		
+		/* 
+		 * Save the result
+		 */
+		if ( err != OGRERR_NONE )
+		{
+			if ( ! context->geom )
+				context->geom = ogrgeom;
+			else
+				elog(WARNING, "got two geometries in OGR FDW query, only using the first");
+		}
+		/* 
+		 * geometry doesn't play a role in the deparsed SQL 
+		 */
+		return false;
+	}
+	else
+	{
+		/* get a string representation of the value */
+		char *c = ogrStringFromDatum(constant->constvalue, constant->consttype);
+		if ( c == NULL )
+		{
+			return false;
+		}
+		else
+		{
+			appendStringInfoString(context->buf, c);
+		}
+	}
+	return true;
+}
+
+
+static bool
+ogrDeparseParam(Param *node, OgrDeparseCtx *context)
+{
+	elog(DEBUG3, "got into ogrDeparseParam code");
+	return false;
+}
+
+static bool
+ogrIsLegalVarName(const char *varname)
+{
+	size_t len = strlen(varname);
+	int i;
+		
+	for ( i = 0; i < len; i++ )
+	{
+		char c = varname[i];
+			
+		/* First char must be a-zA-Z */
+		if ( i == 0 && ! ((c>=97&&c<=122)||(c>=65&&c<=90)) )
+			return false;
+		
+		/* All other chars must be 0-9a-zA-Z_ */
+		if ( ! ((c>=97&&c<=122)||(c>=65&&c<=90)||(c>=48&&c<=59)||(c==96)) )
+			return false;
+
+	}
+	return true;
+}
+
+static bool
+ogrDeparseVar(Var *node, OgrDeparseCtx *context)
+{
+	StringInfoData *buf = context->buf;
+	
+	if (node->varno == context->foreignrel->relid && node->varlevelsup == 0)
+	{
+		/* Var belongs to foreign table */
+		int i;
+		OgrFdwTable *table = context->state->table;
+		OGRLayerH lyr = context->state->ogr.lyr;
+		bool done = false;
+
+		/* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */
+		Assert(!IS_SPECIAL_VARNO(node->varno));
+
+		/* TODO: Handle case of mapping columns to OGR columns that don't share their name */
+		/* TODO: Lookup OGR column name by going from varattno -> OGR via a table/OGR map */
+
+		for ( i = 0; i < table->ncols; i++ )
+		{
+			if ( table->cols[i].pgattnum == node->varattno )
+			{
+				const char *fldname = NULL;
+				
+				if ( table->cols[i].ogrvariant == OGR_FID )
+				{
+					fldname = OGR_L_GetFIDColumn(lyr);
+					if ( ! fldname || strlen(fldname) == 0 )
+						fldname = "fid";
+				}
+				else if ( table->cols[i].ogrvariant == OGR_FIELD )
+				{
+					OGRFeatureDefnH fd = OGR_L_GetLayerDefn(lyr);
+					OGRFieldDefnH fld = OGR_FD_GetFieldDefn(fd, table->cols[i].ogrfldnum);
+					fldname = OGR_Fld_GetNameRef(fld);
+				}
+				
+				if ( fldname )
+				{
+					if ( ogrIsLegalVarName(fldname) )
+						appendStringInfoString(buf, fldname);
+					else
+						appendStringInfo(buf, "\"%s\"", fldname);
+					
+					done = true;
+				}
+			}
+		}
+		
+		return done;
+	}
+	else
+	{
+		elog(ERROR, "got to param handling section of ogrDeparseVar");
+		return false;
+	}
+	
+	return true;
+}
+
+static int ogrOperatorCmpFunc(const void * a, const void * b)
+{
+	return strcasecmp(*(const char**)a, *(const char**)b);
+}
+
+static bool 
+ogrOperatorIsSupported(const char *opname)
+{
+	/* IMPORTANT */
+	/* This array MUST be in sorted order or the bsearch will fail */
+	static const char * ogrOperators[10] = { "!=", "&&", "<", "<=", "<>", "=", ">", ">=", "~~", "~~*" };
+
+	elog(DEBUG3, "ogrOperatorIsSupported got operator '%s'", opname);
+	
+	if ( bsearch(&opname, ogrOperators, 10, sizeof(char*), ogrOperatorCmpFunc) )
+		return true;
+	else
+		return false;
+}
+
+
+static bool
+ogrDeparseOpExpr(OpExpr* node, OgrDeparseCtx *context)
+{
+	StringInfo buf = context->buf;
+	HeapTuple tuple;
+	Form_pg_operator form;
+	char oprkind;
+	char *opname;
+	ListCell *arg;
+	bool result = true;
+
+	/* Retrieve information about the operator from system catalog. */
+	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for operator %u", node->opno);
+	form = (Form_pg_operator) GETSTRUCT(tuple);
+	oprkind = form->oprkind;
+	opname = NameStr(form->oprname);
+
+	/* Don't deparse expressions we cannot support */
+	if ( ! ogrOperatorIsSupported(opname) )
+	{
+		ReleaseSysCache(tuple);
+		return false;
+	}
+
+	/* TODO: When a && operator is found, we need to do special */
+	/*       handling to send the box back up to OGR for SetSpatialFilter */
+
+	/* Overlaps operator is special case: if one side is a constant, */
+	/* then we can pass it as a spatial filter to OGR */
+	if ( strcmp("&&", opname) == 0 )
+	{
+		// Expr *r_arg = lfirst(list_head(node->args));
+		// Expr *l_arg = lfirst(list_tail(node->args));
+		// Const *constant;
+
+		elog(DEBUG1, "whoa, dude, found a && operator");
+
+		/* Specifically, we need a Geometry Const on one side and a Var */
+		/* column on the other side that is from the FDW relation */
+		/* Both of those implies and OGR spatial filter can be reasonably */
+		/* set. */
+
+		// if ( nodeTag(r_arg) == T_Const )
+		// 	constant = (Const*)r_arg;
+		// else if ( nodeTag(l_arg) == T_Const)
+		// 	constant = (Const*)l_arg;
+		// else
+		// 	return false;
+		
+		// if ( constant->consttype != GEOMETRYOID )
+
+		ReleaseSysCache(tuple);
+
+		return false;
+	}
+
+	/* Sanity check. */
+	Assert((oprkind == 'r' && list_length(node->args) == 1) ||
+	       (oprkind == 'l' && list_length(node->args) == 1) ||
+	       (oprkind == 'b' && list_length(node->args) == 2));
+
+	/* Always parenthesize the operator expression. */
+	appendStringInfoChar(buf, '(');
+
+	/* Deparse left operand. */
+	if ( oprkind == 'r' || oprkind == 'b' )
+	{
+		arg = list_head(node->args);
+		/* recurse for nested operations */
+		result &= ogrDeparseExpr(lfirst(arg), context);
+		appendStringInfoChar(buf, ' ');
+	}
+
+	/* Special case, the 'LIKE' operator is converted to ~~ */
+	/* by PgSQL, so we have to convert it back here */
+	/* All OGR string comparisons are case insensitive, so we just */
+	/* use 'ILIKE' all the time. */
+	if ( streq(opname, "~~") || streq(opname, "~~*") )
+		opname = "ILIKE";
+	
+	/* Operator symbol */
+	appendStringInfoString(buf, opname);
+	
+	/* Deparse right operand. */
+	if (oprkind == 'l' || oprkind == 'b')
+	{
+		arg = list_tail(node->args);
+		appendStringInfoChar(buf, ' ');
+		/* recurse for nested operations */
+		result &= ogrDeparseExpr(lfirst(arg), context);
+	}
+
+	appendStringInfoChar(buf, ')');
+
+	ReleaseSysCache(tuple);
+	return result;
+	
+}
+
+static bool
+ogrDeparseBoolExpr(BoolExpr *node, OgrDeparseCtx *context)
+{
+	const char *op = NULL;		/* keep compiler quiet */
+	ListCell *lc;
+	bool first = true;
+	bool result = true;
+	int len_save_all, len_save_part;
+	int boolop = node->boolop;
+	int result_total = 0;
+	StringInfo buf = context->buf;
+
+	switch (boolop)
+	{
+		case AND_EXPR:
+			op = "AND";
+			break;
+		case OR_EXPR:
+			op = "OR";
+			break;
+
+		/* OGR SQL cannot handle "NOT" */
+		case NOT_EXPR:
+			return false;
+	}
+
+	len_save_all = buf->len;
+
+	appendStringInfoChar(buf, '(');
+	foreach(lc, node->args)
+	{
+		
+		len_save_part = buf->len;
+		
+		/* Connect expressions and parenthesize each condition */
+		if ( ! first )
+			appendStringInfo(buf, " %s ", op);
+
+		/* Unparse the expression, if possible */
+		result = ogrDeparseExpr((Expr *) lfirst(lc), context);
+		result_total += result;
+		
+		/* We can backtrack just this term for AND expressions */
+		if ( boolop == AND_EXPR && ! result )
+			setStringInfoLength(buf, len_save_part);
+
+		/* We have to drop the whole thing if we can't get every part of an OR expression */
+		if ( boolop == OR_EXPR && ! result )
+			break;
+		
+		/* Don't flip the "first" bit until we get a good expression */
+		if ( first && result )
+			first = false;
+	}
+	appendStringInfoChar(buf, ')');
+	
+	/* We have to drop the whole thing if we can't get every part of an OR expression */
+	if ( boolop == OR_EXPR && ! result )
+		setStringInfoLength(buf, len_save_all);
+	
+	return result_total > 0;
+}
+
+
+static bool
+ogrDeparseRelabelType(RelabelType *node, OgrDeparseCtx *context)
+{
+	if (node->relabelformat != COERCE_IMPLICIT_CAST)
+		elog(WARNING, "Received a non-implicit relabel expression but did not handle it");
+
+	return ogrDeparseExpr(node->arg, context);
+}
+
+static bool
+ogrDeparseNullTest(NullTest *node, OgrDeparseCtx *context)
+{
+	StringInfo buf = context->buf;
+
+    appendStringInfoChar(buf, '(');
+    ogrDeparseExpr(node->arg, context);
+    if (node->nulltesttype == IS_NULL)
+        appendStringInfoString(buf, " IS NULL)");
+    else
+        appendStringInfoString(buf, " IS NOT NULL)");
+
+	return true;
+}
+
+static bool
+ogrDeparseExpr(Expr *node, OgrDeparseCtx *context)
+{
+	if ( node == NULL )
+		return false;
+
+	switch ( nodeTag(node) )
+	{
+		case T_OpExpr:
+			return ogrDeparseOpExpr((OpExpr *) node, context);
+		case T_Const:
+			return ogrDeparseConst((Const *) node, context);
+		case T_Var:
+			return ogrDeparseVar((Var *) node, context);
+		case T_Param:
+			return ogrDeparseParam((Param *) node, context);
+		case T_BoolExpr:
+			/* Handle "OR" and "NOT" queries */
+			return ogrDeparseBoolExpr((BoolExpr *) node, context);
+		case T_NullTest:
+			/* Handle "IS NULL" queries */
+			return ogrDeparseNullTest((NullTest *) node, context);
+		case T_RelabelType:
+			return ogrDeparseRelabelType((RelabelType *) node, context);
+		case T_ScalarArrayOpExpr:
+			/* TODO: Handle this to support the "IN" operator */
+			elog(NOTICE, "unsupported OGR FDW expression type, T_ScalarArrayOpExpr");
+			return false;
+		case T_ArrayRef:
+			elog(NOTICE, "unsupported OGR FDW expression type, T_ArrayRef");
+			return false;
+		case T_ArrayExpr:
+			elog(NOTICE, "unsupported OGR FDW expression type, T_ArrayExpr");
+			return false;
+		case T_FuncExpr:
+			elog(NOTICE, "unsupported OGR FDW expression type, T_FuncExpr");
+			return false;
+		case T_DistinctExpr:
+			elog(NOTICE, "unsupported OGR FDW expression type, T_DistinctExpr");
+			return false;
+		default:
+			elog(NOTICE, "unsupported OGR FDW expression type for deparse: %d", (int) nodeTag(node));
+			return false;
+	}
+	
+}
+
+
+bool
+ogrDeparse(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, List *exprs, OgrFdwState *state, List **params)
+{
+	OgrDeparseCtx context;
+	ListCell *lc;
+	bool first = true;
+
+	/* initialize result list to empty */
+	if (params)
+		*params = NIL;
+
+	/* Set up context struct for recursion */
+	context.buf = buf;
+	context.root = root;
+	context.foreignrel = foreignrel;
+	context.params_list = params;
+	context.geom = NULL;
+	context.state = state;
+	// context.geom_op = NULL;
+	// context.geom_func = NULL;
+
+	foreach(lc, exprs)
+	{
+		RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+		int len_save = buf->len;
+		bool result;
+
+		/* Connect expressions with "AND" and parenthesize each condition */
+		if ( ! first )
+		{
+			appendStringInfoString(buf, " AND ");
+		}
+
+		/* Unparse the expression, if possible */
+		// appendStringInfoChar(buf, '(');
+		result = ogrDeparseExpr(ri->clause, &context);
+		// appendStringInfoChar(buf, ')');
+
+		if ( ! result )
+		{
+			/* Couldn't unparse some portion of the expression, so rewind the stringinfo */
+			setStringInfoLength(buf, len_save);
+		}
+		
+		/* Don't flip the "first" bit until we get a good expression */
+		if ( first && result )
+			first = false;
+	}	
+	
+	return true;	
+}
+
+
+
+
diff --git a/ogr_fdw_gdal.h b/ogr_fdw_gdal.h
new file mode 100644
index 0000000..57a29d1
--- /dev/null
+++ b/ogr_fdw_gdal.h
@@ -0,0 +1,42 @@
+
+#ifndef _OGR_FDW_GDAL_H
+#define _OGR_FDW_GDAL_H 1
+
+/*
+ * OGR library API
+ */
+#include "gdal.h"
+#include "ogr_api.h"
+#include "ogr_srs_api.h"
+#include "cpl_error.h"
+#include "cpl_string.h"
+
+/* 
+ * As far as possible code is GDAL2 compliant, and these
+ * mappings are used to convert to GDAL1-style function
+ * names. For GDALDatasetH opening, there are specific
+ * code blocks to handle version differences between 
+ * GDALOpenEx() and OGROpen()
+ */
+#if GDAL_VERSION_MAJOR < 2
+
+/* Redefine variable types */
+#define GDALDatasetH OGRDataSourceH
+#define GDALDriverH OGRSFDriverH
+
+/* Rename GDAL2 functions to OGR equivs */
+#define GDALGetDriverCount() OGRGetDriverCount()
+#define GDALGetDriver(i) OGRGetDriver(i)
+#define GDALAllRegister() OGRRegisterAll()
+#define GDALGetDriverByName(name) OGRGetDriverByName(name)
+#define GDALClose(ds) OGR_DS_Destroy(ds)
+#define GDALDatasetGetLayerByName(ds,name) OGR_DS_GetLayerByName(ds,name)
+#define GDALDatasetGetLayerCount(ds) OGR_DS_GetLayerCount(ds)
+#define GDALDatasetGetLayer(ds,i) OGR_DS_GetLayer(ds,i)
+#define GDALGetDriverShortName(dr) OGR_Dr_GetName(dr)
+#define GDALGetDatasetDriver(ds) OGR_DS_GetDriver(ds)
+#define GDALDatasetTestCapability(ds,cap) OGR_Dr_TestCapability(ds,cap)
+
+#endif /* GDAL 1 support */
+
+#endif /* _OGR_FDW_GDAL_H */
\ No newline at end of file
diff --git a/ogr_fdw_info.c b/ogr_fdw_info.c
new file mode 100644
index 0000000..4a59041
--- /dev/null
+++ b/ogr_fdw_info.c
@@ -0,0 +1,244 @@
+/*-------------------------------------------------------------------------
+ *
+ * ogr_fdw_info.c
+ *        Commandline utility to read an OGR layer and output a 
+ *        SQL "create table" statement.
+ *
+ * Copyright (c) 2014-2015, Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* getopt */
+#include <unistd.h>
+
+/*
+ * OGR library API
+ */
+#include "ogr_fdw_gdal.h"
+#include "ogr_fdw_common.h"
+
+static void usage();
+static OGRErr ogrListLayers(const char *source);
+static OGRErr ogrGenerateSQL(const char *source, const char *layer);
+
+#define STR_MAX_LEN 256 
+
+
+/* Define this no-op here, so that code */
+/* in the ogr_fdw_common module works */
+const char * quote_identifier(const char *ident);
+
+const char *
+quote_identifier(const char *ident)
+{
+	return ident;
+}
+
+	
+static void 
+formats()
+{
+	int i;
+	
+	GDALAllRegister();
+	
+    printf( "Supported Formats:\n" );
+    for ( i = 0; i < GDALGetDriverCount(); i++ )
+    {
+		GDALDriverH ogr_dr = GDALGetDriver(i);
+		int vector = FALSE;
+		int createable = TRUE;
+		const char *tmpl;
+
+#if GDAL_VERSION_MAJOR >= 2
+		char** papszMD = GDALGetMetadata(ogr_dr, NULL);
+		vector = CSLFetchBoolean(papszMD, GDAL_DCAP_VECTOR, FALSE);
+		createable = CSLFetchBoolean(papszMD, GDAL_DCAP_CREATE, FALSE);
+#else
+		createable = GDALDatasetTestCapability(ogr_dr, ODrCCreateDataSource);
+#endif
+		/* Skip raster data sources */
+		if ( ! vector ) continue;
+
+		/* Report sources w/ create capability as r/w */
+		if( createable )
+			tmpl = "  -> \"%s\" (read/write)\n";
+		else
+			tmpl = "  -> \"%s\" (readonly)\n";
+		
+		printf(tmpl, GDALGetDriverShortName(ogr_dr));
+	}
+
+	exit(0);
+}
+	
+static void
+usage()
+{
+	printf(
+		"usage: ogr_fdw_info -s <ogr datasource> -l <ogr layer>\n"
+		"       ogr_fdw_info -s <ogr datasource>\n"
+		"       ogr_fdw_info -f\n"
+		"\n");	
+	exit(0);
+}
+
+int
+main (int argc, char **argv)
+{
+    int ch;
+	char *source = NULL, *layer = NULL;
+	OGRErr err = OGRERR_NONE;
+
+	/* If no options are specified, display usage */
+	if (argc == 1)
+		usage();
+
+	while ((ch = getopt(argc, argv, "h?s:l:f")) != -1) {
+		switch (ch) {
+			case 's':
+				source = optarg;
+				break;
+			case 'l':
+				layer = optarg;
+				break;
+			case 'f':
+				formats();
+				break;
+			case '?':
+			case 'h':
+			default:
+				usage();
+				break;
+		}
+	}
+
+	if ( source && ! layer )
+	{ 
+		err = ogrListLayers(source);
+	}
+	else if ( source && layer )
+	{
+		err = ogrGenerateSQL(source, layer);
+	}
+	else if ( ! source && ! layer )
+	{
+		usage();
+	}
+	
+	if ( err != OGRERR_NONE )
+	{
+		// printf("OGR Error: %s\n\n", CPLGetLastErrorMsg());
+	}
+
+	OGRCleanupAll();
+	exit(0);
+}
+
+static OGRErr
+ogrListLayers(const char *source)
+{
+	GDALDatasetH ogr_ds = NULL;
+	int i;
+	
+	GDALAllRegister();
+	
+#if GDAL_VERSION_MAJOR < 2
+	ogr_ds = OGROpen(source, FALSE, NULL);
+#else
+	ogr_ds = GDALOpenEx(source, 
+	                    GDAL_OF_VECTOR|GDAL_OF_READONLY, 
+	                    NULL, NULL, NULL);
+#endif
+	
+	if ( ! ogr_ds )
+	{
+		CPLError(CE_Failure, CPLE_AppDefined, "Could not conect to source '%s'", source);
+		return OGRERR_FAILURE; 
+	}
+
+	printf("Layers:\n");
+	for ( i = 0; i < GDALDatasetGetLayerCount(ogr_ds); i++ )
+	{
+		OGRLayerH ogr_lyr = GDALDatasetGetLayer(ogr_ds, i);
+		if ( ! ogr_lyr ) 
+		{
+			return OGRERR_FAILURE;
+		}
+		printf("  %s\n", OGR_L_GetName(ogr_lyr));
+	}
+	printf("\n");
+	
+	GDALClose(ogr_ds);
+	
+	return OGRERR_NONE;
+}
+
+static OGRErr
+ogrGenerateSQL(const char *source, const char *layer)
+{	
+	OGRErr err;
+	GDALDatasetH ogr_ds = NULL;
+	GDALDriverH ogr_dr = NULL;
+	OGRLayerH ogr_lyr = NULL;
+	char server_name[STR_MAX_LEN];
+	stringbuffer_t buf;
+
+	GDALAllRegister();
+	
+#if GDAL_VERSION_MAJOR < 2
+	ogr_ds = OGROpen(source, FALSE, &ogr_dr);			
+#else
+	ogr_ds = GDALOpenEx(source, 
+	                    GDAL_OF_VECTOR|GDAL_OF_READONLY, 
+	                    NULL, NULL, NULL);
+#endif
+
+	if ( ! ogr_ds )
+	{
+		CPLError(CE_Failure, CPLE_AppDefined, "Could not connect to source '%s'", source);
+		return OGRERR_FAILURE; 
+	}
+
+	if ( ! ogr_dr )
+		ogr_dr = GDALGetDatasetDriver(ogr_ds);
+
+	/* There should be a nicer way to do this */
+	strcpy(server_name, "myserver");
+
+	ogr_lyr = GDALDatasetGetLayerByName(ogr_ds, layer);
+	if ( ! ogr_lyr )
+	{
+		CPLError(CE_Failure, CPLE_AppDefined, "Could not find layer '%s' in source '%s'", layer, source);
+		return OGRERR_FAILURE; 
+	}
+
+	/* Output SERVER definition */
+	printf("\nCREATE SERVER %s\n" 
+		"  FOREIGN DATA WRAPPER ogr_fdw\n"
+		"  OPTIONS (\n"
+		"    datasource '%s',\n"
+		"    format '%s' );\n",
+		server_name, source, GDALGetDriverShortName(ogr_dr));
+
+	stringbuffer_init(&buf);
+	err = ogrLayerToSQL(ogr_lyr, 
+			server_name, 
+			TRUE, /* launder table names */
+			TRUE, /* launder column names */
+			TRUE, /* use postgis geometry */
+			&buf);
+	
+	GDALClose(ogr_ds);
+
+	if ( err != OGRERR_NONE )
+	{
+		return err;
+	}
+	
+	printf("\n%s\n", stringbuffer_getstring(&buf));
+	stringbuffer_release(&buf);
+	return OGRERR_NONE;
+}
+
diff --git a/output/file.source b/output/file.source
new file mode 100644
index 0000000..ce85102
--- /dev/null
+++ b/output/file.source
@@ -0,0 +1,127 @@
+CREATE EXTENSION ogr_fdw;
+CREATE SERVER myserver
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource '@abs_srcdir@/data',
+    format 'ESRI Shapefile' );
+------------------------------------------------
+CREATE FOREIGN TABLE pt_1 (
+  fid integer,
+  geom bytea,
+  name varchar,
+  age integer,
+  height real,
+  birthdate date )
+  SERVER myserver
+  OPTIONS ( layer 'pt_two' );
+SELECT * FROM pt_1 WHERE fid = 1;
+ fid |                     geom                     | name | age | height | birthdate  
+-----+----------------------------------------------+------+-----+--------+------------
+   1 | \x010100000054e943acd697e2bfc0895ee54a46cf3f | Paul |  33 |   5.84 | 03-25-1971
+(1 row)
+
+------------------------------------------------
+CREATE FOREIGN TABLE pt_2 (
+  fid integer,
+  name varchar )
+  SERVER myserver
+  OPTIONS ( layer 'pt_two' );
+SELECT * FROM pt_2 ORDER BY name;
+ fid | name  
+-----+-------
+   1 | Paul
+   0 | Peter
+(2 rows)
+
+------------------------------------------------
+CREATE FOREIGN TABLE pt_3 (
+  geom bytea,
+  name varchar )
+  SERVER myserver
+  OPTIONS ( layer 'pt_two' );
+SELECT * FROM pt_3 ORDER BY name;
+                     geom                     | name  
+----------------------------------------------+-------
+ \x010100000054e943acd697e2bfc0895ee54a46cf3f | Paul
+ \x0101000000c00497d1162cb93f8cbaef08a080e63f | Peter
+(2 rows)
+
+------------------------------------------------
+-- Laundering and explicit column naming test
+CREATE FOREIGN TABLE column_name_test (
+ fid integer,
+ name varchar OPTIONS (column_name '2ame'),
+ theage integer OPTIONS (column_name 'age'),
+ height real OPTIONS (column_name 'Height'),
+ birthdate date OPTIONS (column_name 'b-rthdate')
+) SERVER myserver
+OPTIONS (layer '2launder');
+SELECT * FROM column_name_test ORDER BY fid;
+ fid | name  | theage | height | birthdate  
+-----+-------+--------+--------+------------
+   0 | Peter |     45 |    5.6 | 04-12-1965
+   1 | Paul  |     33 |   5.84 | 03-25-1971
+(2 rows)
+
+-- Check that columns are reverse-laundered when generating
+-- OGR SQL filters
+SET client_min_messages = debug1;
+SELECT name FROM column_name_test WHERE name = 'Paul';
+DEBUG:  OGR SQL: ("2ame" = 'Paul')
+ name 
+------
+ Paul
+(1 row)
+
+SET client_min_messages = notice;
+------------------------------------------------
+-- GDAL options passing tests
+CREATE SERVER myserver_latin1
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource '@abs_srcdir@/data',
+    format 'ESRI Shapefile',
+    config_options 'SHAPE_ENCODING=LATIN1' );
+CREATE FOREIGN TABLE e_1 (
+  fid integer,
+  name varchar )
+  SERVER myserver_latin1
+  OPTIONS ( layer 'enc' );
+SET client_min_messages = debug1;  
+SELECT * FROM e_1 WHERE fid = 1;
+DEBUG:  GDAL config option 'SHAPE_ENCODING' set to 'LATIN1'
+DEBUG:  OGR SQL: (fid = 1)
+DEBUG:  GDAL config option 'SHAPE_ENCODING' set to 'LATIN1'
+ fid | name 
+-----+------
+   1 | Pàul
+(1 row)
+
+SET client_min_messages = notice;  
+------------------------------------------------
+-- Geometryless test
+CREATE SERVER csvserver
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource '@abs_srcdir@/data/no_geom.csv',
+    format 'CSV' );
+CREATE FOREIGN TABLE no_geom (
+  fid bigint,
+  name varchar,
+  age varchar,
+  value varchar
+) SERVER csvserver
+OPTIONS (layer 'no_geom');
+SELECT c.* 
+  FROM generate_series(1,4) g 
+  JOIN no_geom c 
+  ON (c.fid = g.g);
+ fid |  name   | age | value 
+-----+---------+-----+-------
+   1 | Peter   | 34  | 10.2
+   2 | John    | 77  | 3.4
+   3 | Paul    | 45  | 19.2
+   4 | Matthew | 35  | 18.2
+(4 rows)
+
+------------------------------------------------
diff --git a/output/import.source b/output/import.source
new file mode 100644
index 0000000..a43baae
--- /dev/null
+++ b/output/import.source
@@ -0,0 +1,52 @@
+------------------------------------------------
+CREATE SCHEMA imp1;
+IMPORT FOREIGN SCHEMA ogr_all 
+  LIMIT TO (n2launder) 
+  FROM SERVER myserver 
+  INTO imp1;
+NOTICE:  Number of tables to be created 1
+\d imp1.n2launder
+                    Foreign table "imp1.n2launder"
+  Column   |       Type        | Modifiers |        FDW Options        
+-----------+-------------------+-----------+---------------------------
+ fid       | bigint            |           | 
+ geom      | bytea             |           | 
+ n2ame     | character varying |           | (column_name '2ame')
+ age       | integer           |           | 
+ height    | real              |           | 
+ b_rthdate | date              |           | (column_name 'b-rthdate')
+Server: myserver
+FDW Options: (layer '2launder')
+
+  
+SELECT * FROM imp1.n2launder WHERE fid = 0;
+ fid |                     geom                     | n2ame | age | height | b_rthdate  
+-----+----------------------------------------------+-------+-----+--------+------------
+   0 | \x0101000000c00497d1162cb93f8cbaef08a080e63f | Peter |  45 |    5.6 | 04-12-1965
+(1 row)
+
+------------------------------------------------
+CREATE SCHEMA imp2;
+IMPORT FOREIGN SCHEMA ogr_all 
+  LIMIT TO ("natural") 
+  FROM SERVER myserver 
+  INTO imp2;
+NOTICE:  Number of tables to be created 1
+\d imp2."natural"
+             Foreign table "imp2.natural"
+ Column  |       Type        | Modifiers | FDW Options 
+---------+-------------------+-----------+-------------
+ fid     | bigint            |           | 
+ id      | real              |           | 
+ natural | character varying |           | 
+Server: myserver
+FDW Options: (layer 'natural')
+
+SELECT "natural" FROM imp2."natural";
+ natural 
+---------
+ wood
+ land
+(2 rows)
+
+------------------------------------------------
diff --git a/output/pgsql.source b/output/pgsql.source
new file mode 100644
index 0000000..8843998
--- /dev/null
+++ b/output/pgsql.source
@@ -0,0 +1,117 @@
+CREATE SERVER pgserver
+  FOREIGN DATA WRAPPER ogr_fdw
+  OPTIONS (
+    datasource 'PG:dbname=contrib_regression host=localhost',
+    format 'PostgreSQL' );
+	
+CREATE TABLE bytea_local (
+  fid serial primary key,
+  geom bytea,
+  name varchar,
+  age bigint,
+  value float8,
+  num numeric(6,2),
+  dt date,
+  tm time,
+  dttm timestamp,
+  varch char(8),
+  yn char
+);
+----------------------------------------------------------------------
+INSERT INTO bytea_local (name, geom, age, value, num, dt, tm, dttm, varch, yn) 
+  VALUES ('Jim', '14232'::bytea, 23, 4.3, 5.5, '2010-10-10'::date, '13:23:21'::time, '2010-10-10 13:23:21'::timestamp, 'this', 'y' );
+INSERT INTO bytea_local (name, geom, age, value, num, dt, tm, dttm, varch, yn) 
+  VALUES ('Marvin', '55555'::bytea, 34, 5.4, 10.13, '2011-11-11'::date, '15:21:45'::time, '2011-11-11 15:21:45'::timestamp, 'that', 'n' );
+----------------------------------------------------------------------
+CREATE FOREIGN TABLE bytea_fdw (
+  fid integer,
+  geom bytea,
+  name varchar,
+  age bigint,
+  value float8,
+  num numeric(6,2),
+  dt date,
+  tm time,
+  dttm timestamp,
+  varch char(8),
+  yn char
+) SERVER pgserver OPTIONS (layer 'bytea_local');
+SELECT fid, name, geom, age, value, num, dt, tm, dttm, varch, yn FROM bytea_fdw;
+ fid |  name  |     geom     | age | value |  num  |     dt     |    tm    |           dttm           |  varch   | yn 
+-----+--------+--------------+-----+-------+-------+------------+----------+--------------------------+----------+----
+   1 | Jim    | \x3134323332 |  23 |   4.3 |  5.50 | 10-10-2010 | 13:23:21 | Sun Oct 10 13:23:21 2010 | this     | y
+   2 | Marvin | \x3535353535 |  34 |   5.4 | 10.13 | 11-11-2011 | 15:21:45 | Fri Nov 11 15:21:45 2011 | that     | n
+(2 rows)
+
+SELECT a.name, b.name 
+  FROM bytea_local a 
+  JOIN bytea_fdw b 
+  USING (fid);
+  name  |  name  
+--------+--------
+ Jim    | Jim
+ Marvin | Marvin
+(2 rows)
+
+EXPLAIN VERBOSE 
+  SELECT fid, name, geom, age, value, num, dt, tm, dttm, varch, yn 
+  FROM bytea_fdw;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Foreign Scan on public.bytea_fdw  (cost=25.00..1025.00 rows=1000 width=162)
+   Output: fid, name, geom, age, value, num, dt, tm, dttm, varch, yn
+(2 rows)
+
+----------------------------------------------------------------------
+INSERT INTO bytea_fdw (name, geom, age, value, num, dt, tm, dttm, varch, yn) 
+VALUES ('Margaret', '2222'::bytea, 12, 1.4, 19.13, '2001-11-23'::date, '9:12:34'::time, '2001-02-11 09:23:11'::timestamp, 'them', 'y' ) 
+RETURNING fid, name, geom, age, value, num, dt, tm, dttm, varch, yn;
+ fid |   name   |    geom    | age | value |  num  |     dt     |    tm    |           dttm           |  varch   | yn 
+-----+----------+------------+-----+-------+-------+------------+----------+--------------------------+----------+----
+   3 | Margaret | \x32323232 |  12 |   1.4 | 19.13 | 11-23-2001 | 09:12:34 | Sun Feb 11 09:23:11 2001 | them     | y
+(1 row)
+
+SELECT fid, name, geom, age, value, num, dt, tm, dttm, varch, yn 
+  FROM bytea_fdw
+  WHERE fid = 3;
+ fid |   name   |    geom    | age | value |  num  |     dt     |    tm    |           dttm           |  varch   | yn 
+-----+----------+------------+-----+-------+-------+------------+----------+--------------------------+----------+----
+   3 | Margaret | \x32323232 |  12 |   1.4 | 19.13 | 11-23-2001 | 09:12:34 | Sun Feb 11 09:23:11 2001 | them     | y
+(1 row)
+
+UPDATE bytea_fdw 
+  SET name = 'Maggie', num = 45.34, yn = 'n'
+  WHERE age = 12;
+SELECT fid, name, num, yn
+  FROM bytea_fdw
+  WHERE fid = 3;
+ fid |  name  |  num  | yn 
+-----+--------+-------+----
+   3 | Maggie | 45.34 | n
+(1 row)
+
+UPDATE bytea_fdw 
+  SET dt = '2089-12-13', tm = '01:23:45'
+  WHERE num = 45.34;
+SELECT fid, dt, tm
+  FROM bytea_fdw
+  WHERE fid = 3;
+ fid |     dt     |    tm    
+-----+------------+----------
+   3 | 12-13-2089 | 01:23:45
+(1 row)
+
+DELETE FROM bytea_fdw 
+  WHERE fid = 3;
+  
+SELECT a.fid, a.name, b.name 
+  FROM bytea_local a 
+  JOIN bytea_fdw b 
+  USING (fid);
+ fid |  name  |  name  
+-----+--------+--------
+   1 | Jim    | Jim
+   2 | Marvin | Marvin
+(2 rows)
+
+  
diff --git a/sql/.gitignore b/sql/.gitignore
new file mode 100644
index 0000000..5e7d273
--- /dev/null
+++ b/sql/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/stringbuffer.c b/stringbuffer.c
new file mode 100644
index 0000000..f1a30d8
--- /dev/null
+++ b/stringbuffer.c
@@ -0,0 +1,367 @@
+/*-------------------------------------------------------------------------
+ *
+ * stringbuffer.c
+ *		  simple stringbuffer
+ *
+ * Copyright (c) 2009, Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2002 Thamer Alharbash
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "stringbuffer.h"
+
+#ifdef USE_PG_MEM
+
+void * palloc(size_t sz);
+void pfree(void *ptr);
+void * repalloc(void *ptr, size_t sz);
+
+#define malloc(sz) palloc(sz)
+#define free(ptr) pfree(ptr)
+#define realloc(ptr,sz) repalloc(ptr,sz)
+
+#endif
+
+/**
+* Allocate a new stringbuffer_t. Use stringbuffer_destroy to free.
+*/
+stringbuffer_t* 
+stringbuffer_create(void)
+{
+	return stringbuffer_create_with_size(STRINGBUFFER_STARTSIZE);
+}
+
+static void
+stringbuffer_init_with_size(stringbuffer_t *s, size_t size)
+{
+	s->str_start = malloc(size);
+	s->str_end = s->str_start;
+	s->capacity = size;
+	memset(s->str_start, 0, size);
+}
+
+void
+stringbuffer_release(stringbuffer_t *s)
+{
+	if ( s->str_start ) free(s->str_start);
+}
+
+void
+stringbuffer_init(stringbuffer_t *s)
+{
+	stringbuffer_init_with_size(s, STRINGBUFFER_STARTSIZE);
+}
+
+/**
+* Allocate a new stringbuffer_t. Use stringbuffer_destroy to free.
+*/
+stringbuffer_t* 
+stringbuffer_create_with_size(size_t size)
+{
+	stringbuffer_t *s;
+
+	s = malloc(sizeof(stringbuffer_t));
+	stringbuffer_init_with_size(s, size);
+	return s;
+}
+
+/**
+* Free the stringbuffer_t and all memory managed within it.
+*/
+void 
+stringbuffer_destroy(stringbuffer_t *s)
+{
+	stringbuffer_release(s);
+	if ( s ) free(s);
+}
+
+/**
+* Reset the stringbuffer_t. Useful for starting a fresh string
+* without the expense of freeing and re-allocating a new
+* stringbuffer_t.
+*/
+void 
+stringbuffer_clear(stringbuffer_t *s)
+{
+	s->str_start[0] = '\0';
+	s->str_end = s->str_start;
+}
+
+/**
+* If necessary, expand the stringbuffer_t internal buffer to accomodate the
+* specified additional size.
+*/
+static inline void 
+stringbuffer_makeroom(stringbuffer_t *s, size_t size_to_add)
+{
+	size_t current_size = (s->str_end - s->str_start);
+	size_t capacity = s->capacity;
+	size_t required_size = current_size + size_to_add;
+
+	while (capacity < required_size)
+		capacity *= 2;
+
+	if ( capacity > s->capacity )
+	{
+		s->str_start = realloc(s->str_start, capacity);
+		s->capacity = capacity;
+		s->str_end = s->str_start + current_size;
+	}
+}
+
+/**
+* Return the last character in the buffer.
+*/
+char 
+stringbuffer_lastchar(stringbuffer_t *s)
+{
+	if( s->str_end == s->str_start ) 
+		return 0;
+	
+	return *(s->str_end - 1);
+}
+
+/**
+* Append the specified string to the stringbuffer_t.
+*/
+void 
+stringbuffer_append(stringbuffer_t *s, const char *a)
+{
+	int alen = strlen(a); /* Length of string to append */
+	int alen0 = alen + 1; /* Length including null terminator */
+	stringbuffer_makeroom(s, alen0);
+	memcpy(s->str_end, a, alen0);
+	s->str_end += alen;
+}
+
+/**
+* Append the specified character to the stringbuffer_t.
+*/
+void 
+stringbuffer_append_char(stringbuffer_t *s, char c)
+{
+	stringbuffer_makeroom(s, 1);
+	*(s->str_end) = c; /* add char */
+	s->str_end += 1;
+	*(s->str_end) = 0; /* null terminate */
+}
+
+/**
+* Returns a reference to the internal string being managed by
+* the stringbuffer. The current string will be null-terminated
+* within the internal string.
+*/
+const char* 
+stringbuffer_getstring(stringbuffer_t *s)
+{
+	return s->str_start;
+}
+
+/**
+* Returns a newly allocated string large enough to contain the
+* current state of the string. Caller is responsible for
+* freeing the return value.
+*/
+char* 
+stringbuffer_getstringcopy(stringbuffer_t *s)
+{
+	size_t size = (s->str_end - s->str_start) + 1;
+	char *str = malloc(size);
+	memcpy(str, s->str_start, size);
+	str[size - 1] = '\0';
+	return str;
+}
+
+/**
+* Returns the length of the current string, not including the
+* null terminator (same behavior as strlen()).
+*/
+int 
+stringbuffer_getlength(stringbuffer_t *s)
+{
+	return (s->str_end - s->str_start);
+}
+
+/**
+* Clear the stringbuffer_t and re-start it with the specified string.
+*/
+void 
+stringbuffer_set(stringbuffer_t *s, const char *str)
+{
+	stringbuffer_clear(s);
+	stringbuffer_append(s, str);
+}
+
+/**
+* Copy the contents of src into dst.
+*/
+void 
+stringbuffer_copy(stringbuffer_t *dst, stringbuffer_t *src)
+{
+	stringbuffer_set(dst, stringbuffer_getstring(src));
+}
+
+/**
+* Appends a formatted string to the current string buffer,
+* using the format and argument list provided. Returns -1 on error,
+* check errno for reasons, documented in the printf man page.
+*/
+static int 
+stringbuffer_avprintf(stringbuffer_t *s, const char *fmt, va_list ap)
+{
+	int maxlen = (s->capacity - (s->str_end - s->str_start));
+	int len = 0; /* Length of the output */
+	va_list ap2;
+
+	/* Make a copy of the variadic arguments, in case we need to print twice */
+	/* Print to our buffer */
+	va_copy(ap2, ap);
+	len = vsnprintf(s->str_end, maxlen, fmt, ap2);
+	va_end(ap2);
+
+	/* Propogate errors up */
+	if ( len < 0 ) 
+		#if defined(__MINGW64_VERSION_MAJOR)
+		len = _vscprintf(fmt, ap2);/**Assume windows flaky vsnprintf that returns -1 if initial buffer to small and add more space **/
+		#else
+		return len;
+		#endif
+
+	/* We didn't have enough space! */
+	/* Either Unix vsnprint returned write length larger than our buffer */
+	/*     or Windows vsnprintf returned an error code. */
+	if ( len >= maxlen )
+	{
+		stringbuffer_makeroom(s, len + 1);
+		maxlen = (s->capacity - (s->str_end - s->str_start));
+
+		/* Try to print a second time */
+		len = vsnprintf(s->str_end, maxlen, fmt, ap);
+
+		/* Printing error? Error! */
+		if ( len < 0 ) return len;
+		/* Too long still? Error! */
+		if ( len >= maxlen ) return -1;
+	}
+
+	/* Move end pointer forward and return. */
+	s->str_end += len;
+	return len;
+}
+
+/**
+* Appends a formatted string to the current string buffer,
+* using the format and argument list provided.
+* Returns -1 on error, check errno for reasons,
+* as documented in the printf man page.
+*/
+int 
+stringbuffer_aprintf(stringbuffer_t *s, const char *fmt, ...)
+{
+	int r;
+	va_list ap;
+	va_start(ap, fmt);
+	r = stringbuffer_avprintf(s, fmt, ap);
+	va_end(ap);
+	return r;
+}
+
+/**
+* Trims whitespace off the end of the stringbuffer. Returns
+* the number of characters trimmed.
+*/
+int 
+stringbuffer_trim_trailing_white(stringbuffer_t *s)
+{
+	char *ptr = s->str_end;
+	int dist = 0;
+	
+	/* Roll backwards until we hit a non-space. */
+	while( ptr > s->str_start )
+	{	
+		ptr--;
+		if( (*ptr == ' ') || (*ptr == '\t') )
+		{
+			continue;
+		}
+		else
+		{
+			ptr++;
+			dist = s->str_end - ptr;
+			*ptr = '\0';
+			s->str_end = ptr;
+			return dist;
+		}
+	}
+	return dist;	
+}
+
+/**
+* Trims zeroes off the end of the last number in the stringbuffer.
+* The number has to be the very last thing in the buffer. Only the
+* last number will be trimmed. Returns the number of characters
+* trimmed.
+* 
+* eg: 1.22000 -> 1.22
+*     1.0 -> 1
+*     0.0 -> 0
+*/
+int 
+stringbuffer_trim_trailing_zeroes(stringbuffer_t *s)
+{
+	char *ptr = s->str_end;
+	char *decimal_ptr = NULL;
+	int dist;
+	
+	if ( s->str_end - s->str_start < 2) 
+		return 0;
+
+	/* Roll backwards to find the decimal for this number */
+	while( ptr > s->str_start )
+	{	
+		ptr--;
+		if ( *ptr == '.' )
+		{
+			decimal_ptr = ptr;
+			break;
+		}
+		if ( (*ptr >= '0') && (*ptr <= '9' ) )
+			continue;
+		else
+			break;
+	}
+
+	/* No decimal? Nothing to trim! */
+	if ( ! decimal_ptr )
+		return 0;
+	
+	ptr = s->str_end;
+	
+	/* Roll backwards again, with the decimal as stop point, trimming contiguous zeroes */
+	while( ptr >= decimal_ptr )
+	{
+		ptr--;
+		if ( *ptr == '0' )
+			continue;
+		else
+			break;
+	}
+	
+	/* Huh, we get anywhere. Must not have trimmed anything. */
+	if ( ptr == s->str_end )
+		return 0;
+
+	/* If we stopped at the decimal, we want to null that out. 
+	   It we stopped on a numeral, we want to preserve that, so push the 
+	   pointer forward one space. */
+	if ( *ptr != '.' )
+		ptr++;
+
+	/* Add null terminator re-set the end of the stringbuffer. */
+	*ptr = '\0';
+	dist = s->str_end - ptr;
+	s->str_end = ptr;
+	return dist;
+}
+
diff --git a/stringbuffer.h b/stringbuffer.h
new file mode 100644
index 0000000..00b43f1
--- /dev/null
+++ b/stringbuffer.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * stringbuffer.h
+ *		  simple stringbuffer
+ *
+ * Copyright (c) 2009, Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2002 Thamer Alharbash
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _STRINGBUFFER_H
+#define _STRINGBUFFER_H 1
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+#define STRINGBUFFER_STARTSIZE 128
+
+typedef struct
+{
+	size_t capacity;
+	char *str_end;
+	char *str_start;
+}
+stringbuffer_t;
+
+extern stringbuffer_t *stringbuffer_create_with_size(size_t size);
+extern stringbuffer_t *stringbuffer_create(void);
+extern void stringbuffer_init(stringbuffer_t *s);
+extern void stringbuffer_release(stringbuffer_t *s);
+extern void stringbuffer_destroy(stringbuffer_t *sb);
+extern void stringbuffer_clear(stringbuffer_t *sb);
+void stringbuffer_set(stringbuffer_t *sb, const char *s);
+void stringbuffer_copy(stringbuffer_t *sb, stringbuffer_t *src);
+extern void stringbuffer_append(stringbuffer_t *sb, const char *s);
+extern void stringbuffer_append_char(stringbuffer_t *s, char c);
+extern int stringbuffer_aprintf(stringbuffer_t *sb, const char *fmt, ...);
+extern const char *stringbuffer_getstring(stringbuffer_t *sb);
+extern char *stringbuffer_getstringcopy(stringbuffer_t *sb);
+extern int stringbuffer_getlength(stringbuffer_t *sb);
+extern char stringbuffer_lastchar(stringbuffer_t *s);
+extern int stringbuffer_trim_trailing_white(stringbuffer_t *s);
+extern int stringbuffer_trim_trailing_zeroes(stringbuffer_t *s);
+
+#endif /* _STRINGBUFFER_H */

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pgsql-ogr-fdw.git



More information about the Pkg-grass-devel mailing list