pax_global_header00006660000000000000000000000064132120665670014522gustar00rootroot0000000000000052 comment=4ff63731cf58ea5564d6b3b7dad23023ab3d43a5 pgsql-ogr-fdw-1.0.5/000077500000000000000000000000001321206656700142165ustar00rootroot00000000000000pgsql-ogr-fdw-1.0.5/.editorconfig000066400000000000000000000003751321206656700167000ustar00rootroot00000000000000# http://editorconfig.org # top-most EditorConfig file root = true # these are the defaults [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true # C files want tab indentation [*.{c,h}] indent_style = tab pgsql-ogr-fdw-1.0.5/.gitignore000066400000000000000000000002371321206656700162100ustar00rootroot00000000000000*.o *.a *.so *.dll *.dylib *.pc ogr_fdw_info.dSYM/ ogr_fdw_info* sql/ogr_fdw.sql expected/ogr_fdw.out results/ regression.diffs regression.out tmp_check/ log/ pgsql-ogr-fdw-1.0.5/.travis.yml000066400000000000000000000057211321206656700163340ustar00rootroot00000000000000sudo: 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 - os: linux language: cpp env: - GDAL_VERSION=22 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 libgdal1h libgdal-dev; fi - if test "$GDAL_VERSION" = "20"; then wget http://download.osgeo.org/gdal/2.0.3/gdal-2.0.3.tar.xz; tar xJf gdal-2.0.3.tar.xz; cd gdal-2.0.3; ./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.4/gdal-2.1.4.tar.xz; tar xJf gdal-2.1.4.tar.xz; cd gdal-2.1.4; ./configure --prefix=/usr --enable-debug --without-libtool; make -j4 >/dev/null; sudo make install >/dev/null; cd ..; gdalinfo --version; fi - if test "$GDAL_VERSION" = "22"; then wget http://download.osgeo.org/gdal/2.2.2/gdal-2.2.2.tar.xz; tar xJf gdal-2.2.2.tar.xz; cd gdal-2.2.2; ./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 - sudo chmod 755 $HOME - env - 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 pgsql-ogr-fdw-1.0.5/FAQ.md000066400000000000000000000040321321206656700151460ustar00rootroot00000000000000# 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. pgsql-ogr-fdw-1.0.5/LICENSE.md000066400000000000000000000020721321206656700156230ustar00rootroot00000000000000Copyright (C) 2014 Paul Ramsey 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.pgsql-ogr-fdw-1.0.5/META.json000066400000000000000000000023231321206656700156370ustar00rootroot00000000000000{ "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 " ], "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" ] }pgsql-ogr-fdw-1.0.5/Makefile000066400000000000000000000025761321206656700156700ustar00rootroot00000000000000# 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) PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) PG_VERSION_NUM = $(shell awk '/PG_VERSION_NUM/ { print $$3 }' $(shell $(PG_CONFIG) --includedir-server)/pg_config.h) HAS_IMPORT_SCHEMA = $(shell [ $(PG_VERSION_NUM) -ge 90500 ] && echo yes) # order matters, file first, import last REGRESS = file pgsql ifeq ($(HAS_IMPORT_SCHEMA),yes) REGRESS += import endif ############################################################### # 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 pgsql-ogr-fdw-1.0.5/README.md000066400000000000000000000336521321206656700155060ustar00rootroot00000000000000Travis: [![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' ); Multiple config options can be passed at one time by supplying a **space-separated** list of options. 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' ); ### GDAL Debugging If you are getting odd behavior and you want to see what GDAL is doing behind the scenes, enable debug logging in your server: CREATE SERVER myserver_latin1 FOREIGN DATA WRAPPER ogr_fdw OPTIONS ( datasource '/tmp/test', format 'ESRI Shapefile', config_options 'SHAPE_ENCODING=LATIN1 CPL_DEBUG=ON' ); GDAL-level messages will be logged at the PostgreSQL **DEBUG2** level, so to see them when running a query, alter your `client_min_messages` setting. SET client_min_messages = debug2; Once you've figured out your issue, don't forget to remove the `CPL_DEBUG` option from your server definition, and set your messages back to **NOTICE** level. SET client_min_messages = notice; ALTER SERVER myserver_latin1 OPTIONS (SET config_options 'SHAPE_ENCODING=LATIN1'); pgsql-ogr-fdw-1.0.5/data/000077500000000000000000000000001321206656700151275ustar00rootroot00000000000000pgsql-ogr-fdw-1.0.5/data/2launder.dbf000066400000000000000000000004501321206656700173170ustar00rootroot00000000000000_C2ameC2ageNHeightNb-rthdateD Peter 45 5.6019650412 Paul 33 5.8419710325 pgsql-ogr-fdw-1.0.5/data/2launder.prj000066400000000000000000000002171321206656700173600ustar00rootroot00000000000000GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]pgsql-ogr-fdw-1.0.5/data/2launder.shp000066400000000000000000000002341321206656700173560ustar00rootroot00000000000000' NTC֗^JF?,?? ,?? TC֗^JF?pgsql-ogr-fdw-1.0.5/data/2launder.shx000066400000000000000000000001641321206656700173700ustar00rootroot00000000000000' :TC֗^JF?,??2 @ pgsql-ogr-fdw-1.0.5/data/Querying.zip000066400000000000000000006153361321206656700174740ustar00rootroot00000000000000PK qE Querying.gdb/UT \T\Tux PKqEWᝦ*XQuerying.gdb/a00000001.freelistUT \T\Tux ס |tB K $oPKqE˖Pn!Querying.gdb/a00000001.gdbindexesUT \T\Tux cb```b7xO 3I& qC"CCC*C1C% A10tpЬpW  M8PKqE.aQuerying.gdb/a00000001.gdbtableUT \T\Tux m 0 VT> Mqm|I$9TĜCTys|^‡k+(h+\)|Lj:\wRX 7NErK%kW΄UBo ]̄~L,5MU)1&LFh}_2 6zEQ_*ʖxңa$~ PKqEUH Querying.gdb/a00000001.gdbtablxUT \T\Tux A 0N@ Ph@ V! %M:ED֖\k 9E+yl\{J$I$I|PKqE(2'Querying.gdb/a00000001.TablesByName.atxUT \T\Tux M0G/ b&jᨾ &2ț6ae] 3ǧY2+fd c |^,.DXhhD.qe==u8rW6O?C?id7V֜.:iN] >Z&͸2((NgW/@PKqE0Querying.gdb/a00000002.gdbtableUT \T\Tux Tk@>t*Ht7pI-ߒd$neD+nUؤ^lzSX V~Y7BPB(EzQP@ʑ<5)P.v)!%Zoo7ƔEt1JH9S.4/9qrW?Q]U' ǝd'q_<`ge ֳ{s*GWfj){!zh5s 3Uôp_qhE~[X Qi0\DήeyBy^xsCI:NRꭐЫ8Y$?o%e#Rl/6>Y[ Oۺ3=uq)_\pzQ}qe²&M=O2m\6 8 E36w-,kyH'Qr5ї6ko^ }V5YubaS Ƿ`~x[mC?UM;X q4N{$wtC-}PKqEzA}n Querying.gdb/a00000002.gdbtablxUT \T\Tux ͱ PF<^  8D(! 8}VpT49TJJ6RL.ITGF D&!ғ;yoI$I$IPKqE)!*!Querying.gdb/a00000003.gdbindexesUT \T\Tux cd```b7xO 3I& PKqE9E Querying.gdb/a00000003.gdbtableUT \T\Tux cf```@ʀ,Z`!F&O6&` W T"rR " +!!(* *G/T] 7|T"eAaS3T 6U:/s9s?뱅OPKqE>;D#Querying.gdb/a00000004.FDO_UUID.atxUT \T\Tux M 1ഴWN410goܡ[.\Bu?5_H٪ \%CB"( jZLV:5 t$ġ\,) 8fE}[.gYfNd8q On\8*&Ok([.SR|l7:Ysԛ#k ;zRF*16xb˨Ciɵv?OPKqEY@(GQuerying.gdb/a00000004.gdbtableUT \T\Tux ]}#Y${eHP::ABv/|^ߞ^Y=wgf@A4""QQ>R"T (@jJT>D? TUΌ=k{׻}|;y}'GNO|~?t}6|H8*l&K pԽSº};UvPZdSSCy,B7T*, . 0.M#,L 4udM:"LݱOiܷ. ˭E#%!ԙE(` ȗ!ͧpȠ=VN8^S3i42S !gY2ª$煳N,;,$0-3P8bn֏nA 18 o|?6O^[oFkE&zz'?kړ:1޸7_Z|Gw=Gx o ث~Kŗ#bƿm!؃Ϯ=]ȧA攴iۦri'_ͼi<'nU',q! lEQ FBpZnP0XlQPYK]jT w[l=?jh&Z6XxT( ۮakskH+JihO-(S}E6WthR9eeꥧ2bvQ vTKKy~l7s]z{uw >5'iIu6)k4U%S#yoJby;`K~mGJUdS>rzq?5l&T.ˌ]ˌ43k^fZ_fd&7` 14Ԝ,e5Uq<&|jjXe_m e:!nI/x,5gl8"Ph!._48N-eR]\c*IfMiU|8^CRT}CVa Jﭒܒ}db&ma` f`˝ӶS`q'׷zEOGguy`2#;0{q?svPыVݑ;w.|Wڏ?m6f^:;wnOa/UOjr |L6/9bp`zrDbKL/;ۼ0rpَжn[|ײvH|^6X`$uf;B]y9/ie> ԟI!bm!dumۅY?]س &tz=7ʛ&tIԲ6 ndrY\M+|eT>yN|5ѐ&mĺV:Uc7ō|$: v<N<4{)^y<49,ic~6FѐKahhSeo<( !5zȺ*xzjʧ.k:LbiWIƶ=.o"ZDN!+%ۍ6]ޣ7yS)4`¦|CR =Vj^n_VeJ)IHGR>򑔏|$#)IHGR>򑓜7.^5L2 7T%*)UIJJUTeJUo{DN+I쟢%%()AI JJPR%%()AI JJPR*Aa;m`q+`)r<5x8 bRHo8XHDH$4E#y)]B< E0vD84%bVʔRdJH&O&/OwdO;_ߟ|s_z;}׿R/>+uA>{wܱKyw*b/^d^YYpX̴<唝f2wI<_gUycLۻRªXX!IoXRh*QOJ[hlBfhJA*äfFR*+&lO7RItp IӊkuX8dUS\c&[jʲ KT\*2@HTD [[m]q ր#! q.U 4 7Za96?ҡ~pHI?@ r`p{Vq.2NnkuTRJb ۓ\ 'X3$Dk/g7r ~%n`pAc~:Ԅ=JZ0urJUPV@ 'ώKMJal pXvi&&D$pBpLc0<,n)VM\6]VqL@Њ0ڑD;xRܫ-b tc;N "Cb{/xWȘٔ@nlstId5q}Mjcyg܎6W2)YuRRn_FSϤ|?AChFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFhFh6Xɍy>yщ&^bu_;u{d4!e4! -ID fn WCr#4Bz1W.m]!qFCWwleFN h|qp>hn=ֹjzwFB;h~3fF޹Τx5~tJhG ̴_fxgή͏@hGͻ#^f@h''tgv/B;hnxΘO4_Lucw4Ўޝ0{4m9N<=}vt:vh.k/3p_3h/{axba|`}ƕj8mE28keL࠮Q^uˠlh9 E+]Zp=؟wWWѽmPǙ3L{Q",Wݽ ~ CvмݧzޜR&<F}򥍶;&ގkI ^޵ߕN_:N=&ooc_sGhn?{l޽ݯC:3ۣonj{ɰ7Q4pT9u-otw\3fw[ 8*kﰑ6ݔwn~ۛ]_Ad+]=hq#6ڝm?w#N m7:tDzG<܇עSozI9w:xǶ{ F-us\,֐u^IqK/N>8:=_pƕ<>rxo8hw/ஷBtQxD֎OzwYdywޟw>YZ!{hvąA>}~ޗN:]z' nق~0sEk~ܧkW mW'F^~mT%hOk75H v's\/D0h9eٻG>B s@P .է-쬪5YCFY鰝Lof i8! B8 ž@hL;]vMvW#٪ȈEQh M)4BSh M)4BSh M)4M?*4uоyYMG+n M)4BSh M)4BSh M)4BSh M)4BSh M)4BSh M)4BSh M)4BSh M)4BSh M)4BiMLӛOЍMm%sh֏<;ui4GI4JK?Fw {5`WW4"q\#lI P)U)Hˇ+#+ѬsttY[SpdcMyڿ,c ¦v RXwt=rlȮ&|eMUjZ=͗d]zhl޶] Y 5O}XifқY:7Sa~ sJݗ1D/]ش|FS2WzIavuCi ,PwMeVeҷSǰ٭ȥo2h%в-_%3Lǿ`H\h\ٮr[Zq[r!w_֟qku~xt8jZ];'{tjggԚF8?kn"r]qݶh f)vAt!)-ʖ9=;.mR3S]yKo8D<[ {xKTL/4]>Ac>|oޕM& __WN)RxLv^;mUk{zOvFkt?zSbaa^\Sv֪X*)(ȪAv.(mgh޶|F@6mH,8x~ɄۑGWkG~P4t~W?6ڧI&:ˢWO9n*v9H 9|ӷ.;p ;^6{qݺ>yS}`^'tb9(Fdp>4J#/ րmtxx-MJJ^~⣦G؝M|OB1fdyS}csOk}^!05]Al"p3K|9txz~w>#=_phxna?.{"(u[~=0,4E&MTIm\-'q^GξySXf(2<9|oRޔ.{SANT;>8Dq)E2ׅZ^yCFdV~rsz~wzm#8=88YuS,VIElUP} uEݎ]nzD8m(iꤌШOaV;h=2c 9fཪT  <鄥Ij v(DOC.q"XYeiL! "Ae:*~\TWE' s(y;טQNe_y6E 3m]gA6&rzAD:;#!d Y>BV=S᫏7\+q}؟VѮyP~*'kMƮ65Z{u?/8/!ɴJ%<") |UO9@X,J oZ|x&%x-!I%vi0F4[$Q8Eлt C8nM`$O.F~kl^2ڬ]{5e97+(ؚ>-9&+fN2+Lq 8Ma5u"3o#QV[>Dʊ!G<Lǭz⸜3uS{FʑG 8 ",# 6 #M4QM}uZqx2$;jqGZ=>@A#o엏i2^  rSCLJlaiuet|.dξ$D.M(;M),4_jxm%W*ϼ,ZheȖrl|:pn+Km\p&M>0IF{880XБjˑ9!Y.R:?g~vZO݅ue6="h$B@;#D޾]$ʆռ9W}%7ؔ3qڱ)84C2^5J[^9p<ߥpS >|ʢ \[OWކ]!"G;W\|t8rG.. L&ɳJ&!/QP%s՜ȳ(LOnA(m KЧ]-p CE"x 6fB ӄ;T0yƔ m?]S5wr`)EKwޡLg(B!Am$z?uŇ~6g A}*/?KC21&ѽ-K9Kb\i=۠M˰k^/;FܰA;lɳ9ٳaouŗJm- [v9K>z}'Ka]p0ڶ@z5l6X){fְ.-:V YKS[Hv)0{W蝓Lׂ(=*ɲżEӆ/6Υ[o8c3!טmE+ !e=Ϥ=.@Y9+F{^v&cv٣HJ2N_0;w uG`HN:c.;֠oDDwo* J<-߽?]<jR:g5z--/ջ Kfd&ۚ0"z:}BXYi>W]memJV ز#iG^Z7-l8qe  J ׮s; ۾~BI?1"Z%<ڗc:}a]I.#ēw x)OkĢa;/Z."K3Qād:j6WS?hn9toz#1KB qjWMfw÷  Je/d P F!bN٪ZZ^E)wUj~~d|!iv-Hhʮ 6X4n+n\hܓ$Zʽ)+ ]lVje4ڈ!S;({|W(K$Bg8K\nl/9"1_a&'nEOsGjg^l֛nZVJw& y{F{:"Ԫaï*/`d^Uɱ5doU t֎O=xZ))<=Ut=ڜIQEA, bJ EG0]ˍ̀]m(%wH,HP=rbX,0{#h4nƝl *42&$ǺH(w8ۥ} z>]&1qTaEJT9<F' wB -Oϲ;Nv~zsjK't"&$E>JwZ} Z?PfQVcrV1Ovf#jq$80 *H5 yx I(7g0](mUDy4 %n'<ƷAadgcnI0[d?r^(Oc;Lѵ-RK{asX,핪#EIQDp' E3kTvL6 L--'o0R.?텫R\-/ ʓ֩'^ӑ=x#%NQ2Hqؘ@QGU* ǼvEH`lEOq0FPV!cArbfZ1eߋxowmTWٶk2KXK2.UM't؝ ZTY #(VX([eTDh]-B{ L.'ʱA2!DAH1!S!"(DhG1l$U70y:D>dF^L^Ёޑ8 JUS{੡oul=N08BRI!+unYv+y~KFZ@05yr,uq{bh\xJW1ӀrzchZ|i9d[* |p6tEtRLXl1^@)I5*,ucqϷUAwՎ^9ֻmrZϠJKhkeы)AM۩Rm3ϼE[u{w%"Y@ erfpviN~;E+R%oARfw`b-,\,m,k7-db&ݕٓGʼ?̈ lh #^9/l 2J,+Ϗem#&s~\[^{Ѥlxq~e*W%nSgFR.8['f!-#\ʄ&?lpE1;Zfё|r8!M3ؑ: t eI.N:,+}db皰q/'X!Փ`Ui-LJf\7+y@c1XybM"lt/wug#aU%4O=6O 73Wz#rlsJ?g'OՑŧaf-[f{c7.F[8%wMKxBƽjPc ƃTLgžXpȩKג/όBfyͭ18CROިǍW&|qCGY[uHزHWax%0Bs#B o[~Z=+e lwc?ƥGX TQ!d! ` $ǤXǂKxp}XláYdAn.>#[t{{#'~[`9) KxBa7>Ƒ!+Ouh)upĭ P?׆>$w1%}528K~?xHTڀ c LC΋fP6~^Pwӿ\L*&hJ2 P|: , /:dǸGދ+c)SۃǬ&{c/ҝZJ]aUW\o*/T芦wJ[3xwaNrLnP" %x=jh{xfݨՔ,gpe2 SU=de=7ʝsgQ:r)Qr?-MSD3ke##3 5 ̊v㞉/-ٗwDt&iJZP@:6{($b<+t仮E2 ~pZ&y5}O hD2M\>`م5 -<܂[C}Q_Bqng1"=Ûn[ͻh]X ݼy馉`\~`x"OHЇ)%uL94dSx6 mf9~DRp q6-uz=5@4.&NӢ[g^^#uX#G01Ԫu}fdN^Y2c FMY׏rT3R^eԳt*!8TrBҰZ}`BLeт.چN4_JVPʛVZ( )#p_ƈW5'юn/~0~;@/A/G!E[HcOȟK83Z~zH3r U8'ݩy/¨:G9JNwYU7s~?c:Կ4FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44ڗ~4ZgY4oo=nM +sǼEkѾd4FhMi4)Ww%"?齆z4Fۄ, ΘWֺ=nsFCq_gmjVeFijO -MGS阦Z,44ڧjFfy.t}4ztl]myWKit&m%y6os^ZwC}m=h7En$*Nh>Uvjܥ$T˛F6MT9 x3~Y&A{gO=Jp]i-!Wnck7=5BȥYYwVFEyM~6uF|VYkaλ9蹰]4k䂲0_vQHϏM52hI<Μm|V. ܶOm\ei>d˱vVGƲ_-ʶ|L묇Ztz%D-SH/}vVK/_vV|/ Gۗ4jH)-kBa۶=|I7bB煦$)9_HbI}Gz_ԻuFZۈmrې멾~Iu绯44燶|;m] }ؙ_h_ڪ4i-oTo:^$>yN)O6ҳiY_hC;wk۴ݸ/_pS=UE- -Z1w춹0;g-жK~w/+[=.7XFۆFoKF[>Co]|/~<4"Zzs;>=MQfGKC9/u~wY-en+VsC=IQҶ'o>{/t:_{<?>_Z (}v]}5 k Zg1>츥ϕZӗ@:NCž8~8?Q/bamy:!}6ŗ}$n1w}ۻgI*-|WJo>>=w+>̺oۖx_6ݡFKp7͇wnF61lB>K<]DZz?&}v j4vEkͅASkhVYu=nM݄FhMi44FhMiCIi>TFhm鎺}Ѿ,4u3GY!i4v4u3G[4FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44F5F ]7Mi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44FhMi44Fh(4Ȭlr ǎgaCVn{#fp <`YwWZDZ?Gl*dߟ<c=9OlK̴ޱ3۬X/V*Kb|pxhDNMo\3\93Ǽۜ]?;;4&Df-NLg !v((1?Ԍ3԰UR L2%/k7yX3 ]sl=9,WFOգ+lkR+bѪO`EqњϯtUS3O)3ǗT?OgkT!- F,3A/KhOyC^iĶ3QH'd`T6 pkgs62ݐCt)QlIs|9ImH 9 $ZINR, #X {P]EMF!l8p:\z(ChrE&c 9IU.d'iU$~!Ygus Ry 1rѲKAYv< 2yȼQ0_7 mt{S"ks?Qxr;ΈԕYgg~g3"uegDٙ3 ~բ&37wE|̃ShE?Zf'+%2ֱK>5NўrR:(UKuX7{ǭƓ"NRDmʧB.™=Yay)9ͫ m̀eF niIzJi} MҺ`)Z̛xeykTnRq'w* |AOɂ\~$eB&r88 C1ȆDPJ(tɀJӿ 8١R)?P .F`"׉I57,]x?8H,ИG. \}qa)dz_O*Q ܯGj(P7k{aZ,AI(?1X Sߒr3b#O oש7yoO ED,|msXQpx^G`e. šSҲBj(,T 5#Zc{BDCf\xFbh0~mo\Pk5Gq J?V^ J*Q+lұ KX0a2D7ٳlZRL55|6 _WX/N%dKHㆯ˶OTP*]#?$ IdpRMٓ )3BVRuD1hr05cPp.Yn3@/fDz0Q~]9iWPC6}w?i<} ɀ8a÷bm]Oqc9@RBbbίߦ~ah-(D>AY)֗dD Q#!:?v-^LVֆ2l7?hlȯ#VN rݪ!9p *e 3vr {׀U Dk2`z"DU9rU3E& M>l[)jam2w5rtT,,zp;0fs7>`, (_WO/"gx$rurO#ёaQ`ZQ z n=?O-CJo,dEf0a,AzDJ!LKgfmC"uL%ڏ[G$e@ p<$JBxc: ~WK&xL`*xT٩n`E?MdX b#M ک,cWcN]AbOYi)'..8Z"_,FaEk{@s[-J%\@#EB#3 !iV6zJPwʴVoF\A0򶠥SWNθϔ)0 :dypu%cثAF 5j- 4!7F:E?@klȯڃ?vltsLB\dE] ]dC3گBkE6;iK M56|('[)l}(gЂm+"qӯwQ{b~M&ym*|$'mV\:9YR 3rb 2KJ'O>-%Ro"9xX!Ӳ,IQnҋF>;u,0h̘$*Rcr4&L46;̍-9JN`DsAz BsE%X0g3R+[?~#l)4/!+L`jم2h?><;ZO̹ vib2(pg{HhCl7c0h3ȫs׿cNEuu5Fm/utf dn4f'PR`"MMȾ =#ir03 0gDpm8Ё`RֆjD;ϿrBu4U)~, i٥Ed< 8HF5!gA18EGo#~AsiєMWr ABI<hyB܌,q=F|LgZ ^S +  . 4Ŧ+0 2Vm6AA{02{ou7g]DAB-/EF1mZlValP#q8`lf}Xgn[a:߮Xb|ʰiF~;|"ZX N߰znVEk4!lz;`^ź.$ %ϱfCZ~?no]uZaܜG@j5և^w,)jbcHﶻ0Wgg@E{Fo Ք=i~9ЮwД.ؖ;x{uڄV|W3`['8WS*BƵΆڼ56hQ%^rl0Hv-VK{~m`=@gмaRk}6sJ@ppڝv ]4;82ÞhU0i/~>mC`N}v]wJ-a`::)iaҊ3}T.K`]$VAeB &'q AmqgBrm]j}/CB8ը<p3SVHbl K0'㓩ۘ>kxmh%vZF:{1R ͗ qDɑQ([)xs̥FXxgBN$4U3,r?O}7eiD!rsB9HDMmC *HPJ xso|[y DFv6f D E)l4#eŇ`Tc٪m:ab'=ޏx (g!F~=I0y/35D+h9n$gq(t *.pPN/c/Z=;lPE5cس(Bm1]-9p (uRMz:K]`mU]`Wn[oR%%,??:P?X^)!LVj{T3/gі{E,Yk, Ҳ`ҜvϹ%73|GQWJ,כ;sh6f%`D[0XXX ҮQ%[yMaF\|jÈD[=G|fRh ӡtoE&>Lj0V^ch/-?^_J录ԙ ։CH&2!Ib=mE&zDqj8yG'ksmdK pb,v%_v_M+]mNwyݷU|T][U΃B `+6Ah$fլF,al` V9u+qy{M3W1c9jbqa-:2"}m]&6T h+w:}ex2<-%{RuօVtP\qN poJKϦ7ݕs{N(,!oKnk_#nܟXO:e:iԙFM$Ti}|a/av.ͰǔKi:_= xJCvEnCK!̡l%iqnM@.7&ʎ$v -iT*>:Zly}~t [=*hC)ңDQrtׇc%-L\|Wq'MGU_vcF a|wZYd ZK]8DG hf?2q'8: 1woԻwD-QuGOPpc)7\~*V5  My5_4~ÏӨByS:"Ưq4,RuO Cv&U: SZ`Ms(K=+= 7UmbE!9k4l*+mKz+&Ov+[ϴӜ'o*sj4sHY*g}bo%-\@YuZ U2x^R|:jFebEh҄_oe9{b\%,m/9Z(ڲ{ۉg xh\ Q "w1,}ϵ`l'޲ʓz1'/Q/Nޛ}+DWv&{ %4!>V(!"q'尟T>}&\9fF{J.s'"{ڣ4 Q6&dhtS~v ,abP`6XV;;f\-5 > x;cA=J CM/5T;'ι+ ȃOIGupE8yH*' 1>s %0X5vx lzT?w2TژoFA۴Q=xᎶoߴ| wuXg!`j;жWB5M^I9UT-%#ኆp:AaIXVx;A )m:f M%TuP ϑq.d@tP蓑'ԀV4u Nz ]Pu/ceVW}b g#=Pl9RBqgܔO^^͈STio񷋿+^Yo vE%>G4hD#шF4hD#шF4hD#шF4hD#шF4hD#шF4hD#шF4hD#шF4hD#шF4GZ)߈FQ mKF4Q^]gvã 3Mi(߈F4hD#n3 P)>pW޻{hDF# ָ7euMyZ)n ۋa{FѤ9ɯ̈Jhbff-ȖtVlL#ڨLv{iX=YZhܒ\Q)ɸ1!e&IxT D1axlR{R ]U㋎ b,iR ݤ$*64)nI4mv] Ks&u{hH+Foy6I*oV&Qi̺#H&5y6,5)3ΐ-\~oiNnN#$V[:5Tp^3m.{YbdYվ9"eף2<ӸzpU{RHjh-Ƭe0j|kKla)~Og;կ>g?W~Z|r["V n(3%sm|{{hI=fHsqޛc%ҙedIj.jjǢ^]-7iu]sI7TC6#gGGMi-\-K| ǡMávhH#:4zׄ\;q[ü/1'&-I:S.t&͈/}#]:LmZ4;W"ڐ4\1ivh ]qu6t-:n %̈́HJl-Ji;ɜM;IIڳ踩sa;5< lϳ6m~3[M:QyJඞ)5o.v*l8_T5b vѥp=+UE|{?vW&ݴ/ nm V۽j"ztZ#}wr~]4)n7nnPwL~0?LF=ˊ}!Ҥ%g40kPCHCCIziĨhgZ~U{bNJܾxS) ^cvsbV6<76 ]ZnzV2LfgE\~[<пos?g_}ow޿;O~O+?~׿?Po/'q?fEKǸ=jl[{t?Z<0||icOYF =i>zmpV]Y\?0ž{nEi>w;ա;>.oe%yZUmC1v3&k[fgwܨFɻ QsG .¨֡%@a+DۑHӲ}G]^K߃ŀ_64y:تb`*P<|5Q_· N܌ElglNsZfb7}ۃi^ZF&S݁e`LGcb;Нv}\g;q ٭F#L#nfb}}XBE ͓Zh9=I[N̻4y.hR$+-VKKX+.a{ 56S[,l5\@ZѥV,jwm4'=~<`>&voc<  7~e$jɰE=2;W yzف R:10PM]hw^gcO7>[?1s)1GRB๝vXdxB"cH%8 gC5PB`)Vnra#TYY|Ybe 3Q2Q~J9##nٰ!+\\Kndf23,i-L)[gdiȣ4A!o҄#˛zMG?fͪka3|TY_BPszR) C޸d 3(|G+Ӯ s=F̓Zc;`>;ew77Eu֡ov{b8ϼst̏Wh(Im ~e,LWrAޅY&Lgm f<0qk[F] tg[uTY:7~ \OuFno,*v ||QۃO}w 9bKJՎ:}}e}5:(Vkl~-YʕZ.6NM ǘsj[NJwHڻW[ 5b .e +rs+0wKW{[G\$Qʻwizkfϳ۶R)]R~%Zqox;:Y)-Ɋ bD w ;$sv9; Λ FhTLCȮHJ.]c z5sJ/#Ո˝1.wF\la)vMY+vLdGQ$tF*>c +0/ t=#HƝ-Zvn߳C0bW? h^ܳ/$\CC?n23{Kh W}Ki8b>O%B>*Aq;=܀bq]%6F~ON%(~qPi vhi{~LYBr{S_-G(/p>hC@` B6񿣡4DiDL8_§ѠV2/' Q9 pehr\nPRTYu;g"X`#'ZmnsuɆ7WXs4E5BY.(4KVF\ܿa>aSv~q:~7 b:ΧWV ]Fgj|03 ̽c :_%_s[Zp̴鵙hpzjǶ JZYcڑ鳦 Cwj'X|DZs`:?_bQ^>KLLN&FјXbh@ =tJ2[HHDAl^BzQ N5m1Rq$T>.Aa]gQ~V OL"NcKÍBf˱@^5zGzu01#I0Y  ]8_ȤfmBL~o糦͓13tf0۸h^ɱh|:1Aְ}Bx斂i -V>`MM JcCL\^+dT?`K<ӵ'8{OaAAQ{PneTKJhO6Z3Ee)=`dp\0(a候FM* `w"DP+gtzM}σyvTc`:}=4fpN@JGzQ!-l3;tqsH k 6kͶ ٹRcKޒlsu.<<<~Yku$`HJaYgvZr?] Xt>>PcɭbGgv܀EuHPa"a^}ux }5{;*F'vwmU zXP ]C7D{woziG;tIW`eU\*.Ɉ;7b/f |&7*>ťzK ozǦ%Y 4td$4j?X frC^o ^vK]Jaov2(E]<؎MKe`.,iS&!DS؏jiC&XCW\ E߉=-J^芞CUu  #F=Rah=KSqgql)tH! U4XYqcG#}=mPͅ䳁,CO6~0v!r]Eؕ"ՓH.yN#lA{m_ _o؝JLl8b6%#t Gnu(Q 'QaGQx6rр1Niy ?ƐMd m' -_wa(PzΖ=`Ġ]'.͑ؔ]{-'b@!JL* E#\$GI81qch49 D=q 67b‹HKbj]v+,c_+(M=5y4G =>J9d0ڝx%9^P2˰͐M #"iBrhDd `c6 =@8MUT7?H9ۨ. &>Xac LzHmOD?z a\צSym]@Sf(zyK4!]{v4S2.ubUQ)ʢ\fܪVmʧ(7En |vVz_fEOjݪCͪe~_/ʧu-yܨʵ%YUuqhQ䖨TO-h4El4@-Ӂ1/SqVW~?r֨['/hzѼ8Dz KtX͠7MR8jKB cJځR˔>7.N+Ћ/寖jL.ʧW=o$ՄUgQ>j\VfR_Zw kekfmm`[нVkUZWDOɩ֪PT#qzd1.m`x oy mm[D7MzYA>`ͷz%mYmM isܰZDAf AuqdjVU|j4Z%ОSYӪ FGAW% bUq 8CvbA}_ 5h.8. 悛5-avi!fUrnd (Q55 UJ)"fUIsBCM-h*k4IqIL^J -s īZȦ/ KVMQS-x R>?\[-yR f^OXC2HV S=4\H pn\iH~|?f~`ͨ&^{FP7A6^049A+RCIJ+XΠa#K˫=6[T_ )V.*'T&8?ìqPjZoߤoV2>\~##5\~Rr)/nC\1R+蛥N֨W'U֪t(|f顔^Hhу˄LNųmѰG2dg8ǙǸqAOQ >ram)l@d6z.n7H%vZF:{fg@ qjyXS"a\1l`b;9ܥ3N8)0BeQb/S*: Ax3wO# La%.SEd.¿ֵYNFa)[ZQ*D]ފ2$;Vh"ٍ # 36_q-Npwoo`NV,BQ JC|G6Vhߓwobāaڨ\/!BAعnָ>D1-'pzKBЃb^= jZYRt<SOԱܵM4b%q UR *!4O\%sMy6t7Npæ&8ǹ ,UMKI||+A73D]o3! נ$hV~N{o?Dɂn (A˪ۅ;N^Glm|cFP#jɁyF.ThIN/h@uǑQk%斈mdF*z!zQV; 6.q;gRõ`;h!{iEiuJٗQ֊&Iͨ?]h+e+JmŴG9H"}EgC*͛w Ͻq,QұHWe~V-Hƞi3Eye@V](l(PءJ US6Sy#.QWg֞+]ґ=x#T]*HpNal ߖ6qܵef6QzTSpdj};}q"P4ki nS{9ȧsBeU4$-0ĘeGhG]`UKZҀ٣Q4R!T 9TJEOJpx"Kqrm5@Юt, u.}zO9 U zwo$T_wTˆ' C$#j\ P+1)+(8='"boLht^.2u WMK't؝ ͚H̳$:KPPTѷZhmt+RBȭi0[ 1tk(d~|}PX5}WG*h#*\}LX˩޹%bi4I=J7)c:В(`TO }3mg(/iף'OɧԚA3^Iy2 ȵ;J.5ytxlw}l_]r#Gev <=J:=D1{4/4ӈ$iB%})ahZr{ʝ+/ҷ]s?2t{`("Sclx <n2]WCgC>Wj>jGaߏn=ۺZ9 g\W#D#Nd`p1 0 ғ (y@4( t,(%q9A.gwԕ`PTDSW#Z J %Qh:2M`zGG#+&$*>,XZ;7F|]j8/cXgDK9I[ɤ:I5?JS&Ygvn SP[#Kp<4[5 .Mnz|++ޭwދQK? $RۓDۢ<=Kg~R9FV^ ztئ"jelWBsdPPvҌ1x;J^y{@G/Ȟ D nPz^q fGsK#&8eBAR B!C4烨~ H7%H.U+ؚdn!^9PtGS^t Uf3 1=-Sf ۸l%?0 O\T^Vhˆ By~${hru3. -&13rw 3ȟ랝\5qQm֛}>eĸSTW ~NY2c fM٬8\XkWw4;J%'CJ^_*c)YF:ߠo脺M+YuB30t! ŵPD #p/tLgTГ%K7p/A؂kU&?>蟺g 'Ύ'ނo1̤}Z]|:!mo1/^ŷdq99߾2|ſ=*]oG\^"?-'(;{MYFr,6LF{hYKhhhhЫS^".%yc4F[f, J[g։=g4w:(?}3RD1Miok544Y{WMgreT`6Mbj3) F?O;#%mLSF{hͯ_2ғ:|z|wmoj1eW$lBjeyxLF{hl̽Om|i|Ljm۬ݹyl\Hh}cf:ΊXVVD_ߥO(/vklY#۹ڞzќO#ysϼuCI=sAK#QrֱS[U<,39\ܷU摹MӘ,CqhGmz2eD~f-?:2NoCmYEYe.ِˍ3\GZ?^`*0C<_N۲x}huhtcZ}O4Ӕ~> Y{t}hhF{W󹞼,G?H{(4_Y/sunFl \8uzFORL^:~qb2quNJ>/t6_{χCm|=`F>ܮFa+D,F6/a-{OCilb/7_ecosYC6joeO_'1=?JxY{̑Wh?2Rln[l*5Fˮp_f~46Ġ. i}ޗ4D̽?Z6_`njVTX=5S>h&MBםKI"t}p6OͫT_S4Wqb_"zjB`"a:ؤXvc~xp'L3`D4Xհy~mnkr@>ӭ|wR:&J;H^p*)#K#s2ʸqGDp$,?,[ɔ'xd 6m.?ȸ.hiv ;=$*FF^oPKqEW1 Querying.gdb/a00000004.gdbtablxUT \T\Tux ā E/3EùQcNw^MRVש׵w3'O6PKqE}LrpQuerying.gdb/a00000004.spxUT \T\Tux QAFQ oan.a*x,)Zkv/GmsSkvSUJWZ^^^^^JW*]k#{#{#{#{#{*]t쥫tݮozz A'3$Sm~Td߷PKqE>!Querying.gdb/a00000005.gdbindexesUT \T\Tux m] AFؔ+%$uׯ3&vg3>4}`( rVdυ#^,`,Uqͤ][gT\=)tMR2əZj>MKW|{"q X%6MPKqEAQuerying.gdb/a00000005.gdbtableUT \T\Tux cf```I@ Ğ 0Bk@ y(ϐĐŐʐP p1U1d2qDK(PPq2%`mI @H-+WP0( `dae8*d7Eoq{Yb,cF~V:ϟ} 2}u9_wq. _)<Jxm,wkJtsIV u~:e8 RYzeo#_D7hZTCCd;Ӣ7UPKqE:() Querying.gdb/a00000005.gdbtablxUT \T\Tux  =Dl3#Iuy=|^PKqE$5Querying.gdb/a00000006.CatRelTypesByBackwardLabel.atxUT \T\Tux N0}ƛ_16>C%CL$2ʹEo' uFuES uJ-kꂺckxG>F5=GEZL-.lRؼv)T3[ gD!=j9Y.O# zHev{ >%cVRXJqvYƞc9^yVo/&};D>èۉ ޱ?+LZv:ZۧVR|ѢE-Q _PKqE|6Querying.gdb/a00000006.CatRelTypesByDestItemTypeID.atxUT \T\Tux jE! E}O]ο7} :B]@d@m۞G9w^7޺{p{j[$uH9%HM 4Fj#b/J(X$T::RG[mSnJ(d5 6(2ή6| cqx=0)$4I18b| զXh3n[EASJ\jLM*怜 8XTH" 1L~9 W#`Vɦ--bX,W/PKqETU_4Querying.gdb/a00000006.CatRelTypesByForwardLabel.atxUT \T\Tux Mn0B[~໴E. T.zqCeؖI)hX-qpqw'{:WD[潜ވ}Wm}m6c yF8;{׫o|N| x[™8ϫ~mkل֚REGpi4gFg3,Ԯ%7R}+^b4{f9wU̥u1毦˰ ["mv`M7ϒͲiaT|6{ƴt+hޞv7~;ǗƋD%Jՠ{PKqE/T8Querying.gdb/a00000006.CatRelTypesByOriginItemTypeID.atxUT \T\Tux Ɋ1 @{N|En[%e[}e -~zT A|\.?/x~Kk6}JӏM!㊶u j cQ[m`ű \Kd*egsUZy'p Hyk{ta.RaţbÒ;`͹ .t )56<hD2ǽ*bgPTDz5(jS8fڨѽR}ʇ%GꐛAGJ,}-GՍƀ%*[NOTxeu)چ&Ӂ2UM/V;p8_~>?:߯_PKqE,Querying.gdb/a00000006.CatRelTypesByUUID.atxUT \T\Tux XK- <<*[%c6jH>ja5iw.$kFY}2b!kvmu'("gwsT9&TFk(a ]EYRU|㞥"؜@'.2C-8z6"".PIB&l%kv֐p?c<{ۢRl{~ƙpKG=T{Pl2{ Lu/\*qݰ .\p… ]x_'PKqEdZ!Querying.gdb/a00000006.gdbindexesUT \T\Tux ] @EOCE=VgL #-+L~]WȰr:{̺[R س!$a,:RVnH4bL9w.89W%{:5>1;I|"?zm\%3*iV>Y{7cL Z/o\ZQ&CKPKqEFɗQuerying.gdb/a00000006.gdbtableUT \T\Tux U LSW>Ղ'7pn:i)T <)=D\W,-d2 It č6dFlO64}{s{Ϲk@w ܿq#`~^H|G :85(k룥Z`2pȯr,kA\T82#' hi 23` 4д=rFLz(?5T}(Ċ)'xUmCPWidv]Ek1mv}.z_keet/މ9L) Lp3q 7,服|g8ROZp&8c7@O?5z)%#拼cozp ŮL_1'fڣ5p \k&,fR x9°İE1ڈ=9 p ԌnlL|73m-6nWTs*f\{%j3yM a*BaYVMLJ#_Ey\- 8bNϧj,-øY;-{-~VvKͼB*Ø~8ⳘI@%] T ,0ʗ)W\wM77> NmyA_~v$Cڐ#iJk|\41OMڢP:ewS>y$U"$T7/Um6r2*s!Lk TfKt%PmWPdɶG?}'Y= 1~">hb̵G_;ކw} Oכ8x1rc 3kN>C} iBFk P,GR. GLW6(N|B\w&Xxg/@WfrGC4e=?yOl^`4qAۥe&2{^tg zkyel$*R#4=Jv81犝]PKqE銊S Querying.gdb/a00000006.gdbtablxUT \T\Tux ʻ @D]&إ hdZ X=0BZ*;]lITF?/P+ev.gjPKqEJN0-Querying.gdb/a00000007.CatItemTypesByName.atxUT \T\Tux JPu-"֪Ģil15%I}LNPz#J s* Ru~J1}M Q;"ڙ=)Gag_D-pϜ}] ͙JKQ>qq @1bJI11!%BW6Ӑgp,r]̐|^>.dҙСk\ [QٷG*B<'Y+5i<|Єv,S}4=UuuxކWoF zb(r}v1gvXl[Y|W`M1ɈlfwJRT*?O;PKqEöy5Querying.gdb/a00000007.CatItemTypesByParentTypeID.atxUT \T\Tux j0awy ,TɻM.B7mA?|Ƅ1a'X5؄m؁#88s+;x\\g .`na`v ao{5gW'5&TPI ĭb}W?]Q8Vʍ&I(;J،֑;I4Zrm&z/aUQmó,%̙lĉLrG݇Ff&NS!+jRqB"ޜFJ&*Mʨ~ֿ{^v/W 0?PKqE+]{2-Querying.gdb/a00000007.CatItemTypesByUUID.atxUT \T\Tux XI+7 <ʎ$R$R$!&PFî'UEϏxx | |/3wS}W7]s#C G=m{-+]W=a2=d)ID[Uq^OKKAwx&Y(\G[$EfK.{g'x#+mҙ}eQds ;QUseF{ţ6m,x+$XMwX}tĬBbe =vXvL$:rsIh $-)k-ĩpb);y 71@ely#_7]wu]wu?7z PKqEݦr(!Querying.gdb/a00000007.gdbindexesUT \T\Tux ca``b7x NbbHeHf(a10@1兀ؙ!/20T2Y N@V(BT21$ FK0!(Wea2&[!7rw3-KRKsuZ:V=dݵ;F}[~4ocIX%C&'(1/=U%?713UҷYg9X|ɱu]_a TO_nf~bV,t~SDR RSJK2sA! tK *E#R^Ǥ⒢䒐ĤT5=lgV$}vg* ` 1~ju ?݋/7&ݯЄc #m֣gc~/ѽ-䜟S U+ڼ=w{pO 0zk(wh5R( k@$&_Ӓ{' M.H~HK W䓜mgFFw0ɪ^[k,7/eD/gEqN+<%7gB,^ĒҢTHeT5gَ\N^Ȉn2_piQYj%KMR{,WaS4z_rPaY[U'^t?vt9B _iT\mޝR]X1]X %y\i[J_[ ;78DZfUa)-Э>L/@J-/-JNYYX\NS'"?bjӯŭȥAf's $Pg7,>?wѽtP[b墇ٶE2'oJcp5ÄRsKqFf${ x2 02<`PKqED} Querying.gdb/a00000007.gdbtablxUT \T\Tux ͡ `Eῴ AB:GBa WWM0ڎw60AYW/l0VBEу 8n!.~@-Ɵ/-ƿ'p 8g=:=a7xx64 gIħ`0Q>Ķ]2އYux~֗Ov/MЗ klrV{*'2GB5N=tw|^^`NJʱV/W]rj,:ip ,LTq G7Bp75p"p(ٔ>7OEhx(x8T|%hg'RW K`2fS8#ie7W0P]O貒ֳkGbX:#spcÐCKDTO냉~`^ Qޮ޴),M̜q]|e"fsW؝6Y 5mezɀ+ɕ=Z88687X3u0c[b?y~KN&#I+B[)7ϞG`7F#@QЫȑ " `jYq ^me.<7gHz+/U`wA0 @<.d7]8{ e8FCy~tlr7*wnWnKoݾdzFs3h笡`bLal̵N{ۭ eO3;r|XJsO`xS$H "Kt;(z9t!4˨Լ͒z(r"e:H8ZZs0VzIG<pddh2vN@OzRP1Hs|=8fi/BPWq wW4ˉwmpBU"bgV -sӬ ;kY@0VeQBDV]ޜh{_L!UZ^O%DŽt hyڜ'kZ/}Torwٍ]y鬦CiDyve%3e7PA7IAAr;&~`7 a L>7+p}$0wt֌Qf#aҼUщ!`iZ!Z IgQ-~lW4&;.17 a}MRF6`QEI&}>IG+o׃2lq:*) +SG! 3X+Hj765KxNIvy/VUo-=X\}2=+gbb{-a~m4T'Y@We饕$4JXɔ EO͡kU%5vu X|h;ل-`kOn;@ _jI>KH?:8 L$Ua[ܖs+F싘4s`89K9dAsZ@kIi;l,,dT 㘙tx/D_s ZIah,.Ml5=cq񧕨 ]S=lMLOkU>v5#W?&B0l6ҒFZ:3Ԯ@(4gbuHlc'J.+atC_zX[ͥODd *ecTpG >!J TWbu\ᵴIQo%T[M=(JxWOCB ko 8#⹳?m/b& Q iH[ `ae} !T38k%p.4빧<8Ҳr‘p=WN2BwD5up" *Jm9iB" vuVsyqG;׉x(C \w*XtJZ*^Fmʹ]t֖)ֆvO06Jv'o?P{Ypy3N7in$y7mo>qTvOJԒ2@JvQ&QFc0aLnlWq[2w;Vmg2É˕+ՂJ7J+`Qr0Vf"@\7AO ^ɡD0ݨ$ȝTm`[m2kx`TȊH<{瑻T62K/IL8BRcIeZm=L-Gr6=5fZV2lB΅Bf"[[-GcOMt$+o3I6P*} <ǮmS;şSbhÅ(#o4#:KaG}啭F&"|ZXd@,io\2ʹ[&y!sbfqcH&s.g ? 6PACԹ k5P/$U L=yY2u!ĘŷmȚZ$h `:I"5^.tmD'k?dLӡ^sb202IJEX4T=-bF{)U1,>;!i /,'cۇksZm/lZgko{;T|IUǒT\tH_K̹h8}/M)1y8bbӕH 55o09`haZKjhorju6CmB %_-v%Ipl<9kL sR` I)ѽIQ3ZstDWa2O㸟y½[m5y)݃MPK]rE1}{1M[R-F#˅MǠ+!\ɡ̺XEѱ𕞚_?ϨT=FGimSd\mFx7tD`p-ľrr{s9ԇ|L1YwmM?_]B05~-]y'p I{& 3}sh} rÐibש3@)#VeВ!bĻWw9W׭ef!b{MISX#x|@˜u) м_Mzo-ޡt \X?ܙ|ڙ'͛ёհ<@cà?'?x)w'wz<)ȶU$lT\g|})`tIBmKN6E'r:#/H.;.ڮ#j{3=tkޯ҆|| ci pdoo~ʊ+f7ۖ`km=;=l/"ZZ.yVOLѫ`kUO`m=aCXN~:NOC]KPzQ<9v.GK m #S+bmx^a \Mr<RG_gSjދFuʺ7%I PKqEQr Querying.gdb/a00000009.gdbtablxUT \T\Tux +qfay:p$RB\$#[<\XR"#r[{ƛL&!(/B )xwIB'! Po&aez`,44<6aV)B* *܀.<Ԧ /. 9VC*'!4,! 57ԹQ؄W(t g%3[%k{EW.'!3O ܃#_@  ؃(TrXG(-`[̆`S"6 s<1Oh3ӚUCr<)Mnq\:%,nNs3'8I8OR$`}mm6A4P.RֳAQ~WqJ5i٨ALsEZU>leLVqӟ ZLL1 +j4fu?4kRР%Gۿm| ɓI&d))~?PKqE{+B!Querying.gdb/a0000000a.gdbindexesUT \T\Tux cd``b7x vbbpepfa10@1PKqE^4l> Querying.gdb/a0000000a.gdbtableUT \T\Tux ]Gw;M&M4kk/ҫ݄> 1ޝ1HH(BE"R?N* !Yx+BP @)g^}&:%g(B. ?cgtO ʮZE@5#4IWZC h BjA ~ـG(}.|0^A @ F>NlqxA!頗 {?f\* N.Mxnص1Zɪ; |N =• B+&і-9O;m;/N1Se|z|2B37M7LW,śҷ 4Q۴lc]أ%פu^C}H^R5_J\44ÏBЄ݅3|6f+wK9 +_NK ":Jb`_:"AF:&<&%ϔ1b6z½ۢJ=-ɵJ^' B#HdAK) ;iodF %QQJDK"}4~.Zx8V{ǥ-+=*s-aP`?x z{My^! Z#ubK~?ĝ=YJ =\I"BGZͮQLނ;Y>ewzm q:^ V3I?:` Nbv+ H!z:O,;6qZ> ԉ䃓`9-a)@"-Fd?'-p;vs}.V~(Z[Z ?<='B-lےAHyMU>n_z$qA 6@ Vx$>˅ `K vtS.KX?^΢hBVcЇ_ra.D#N^ytJ|YnDq ODXŮ!cֻ\} $Qµs7^rD U{O({mW(ynuGc!'YY0'0M;i'E?}%?o\?kLf]|cG\DyH%?|GuikexoxW3B\߻up{WW0W?{GNP/ xLg7Eul.5k~qvWsZ%Oo4^aHG^!YX^3&~DmuzIݬ;49Q,kߏ33 i%9 ϻ츁/W̱#+JϿ&YrP*<3rK;Fonnv4X0 bDh{O(gxp!W*k<A8 YXȲafve͗,o bBzFrt=֊g]EMOײS뮧 "Q`LP*0I=}L?xpy39دĘOȱ<&=guH:T!z?ܫAA,L%;3k&y<1}DP::okOzNgQL ܻŚYnS"W`H`Nn/3&Ƞyd|0wA-ɋYbKhb!˱8%bgA҂-Ox߻R^]1nY&ML+ ~ YsF\rnr88-Hoi8;"U] s,3^mh32^~V!uX]a1>zrB{pt2I:zimĻ>yul;SlH}Jf`uXzNfuAfۚ{+C hlZmH۹"&] _T2UA?8*r7ԒԞӉx\J:T7! +5|q-?r& *?.+dV2;i9]2hL9 ڌcF_} |{'vxf*w@:kbi}w /LwEc5+XՅ?y8 /Bf_R(.*6Fl=fNb[؋S/WArւXrld&DؾtFfz,]eO'{Η_Ԇױ;3**=Pfds*.)Vǥs&vBj~с17Js[{>y*5kvT0#X?)(lz"0u7Ϙ~GbCB゚oHE| mb@>T&L 2_Y?1ϲkjԲW",c @Bߞ?THӇk+q0i"l̘I2rR$ړ3p5S51#k'c [#CvRSDn{L3vLgg].3x4EdhD;F0ۜV& }s;dz-4_ C"pv²%W6I|{ͨS1[n9* O +0Ycc a4ԟ>Vjcoxy\>Gv2*X+xLHi}oьH:n/ksd;Ů5ެXwW)sTYټ׬lJ3k!!k}%sk4^kuwh;Jbk! |U@LL|QI^zʘ+ˣ Q}ƒ T=^4\MbA^zq> &pN^P*P}-=-JOM`'5__Yu~+Qw@sb1h>1@ݺɿK=ً^3%+XK8#xJ~V&#S5Xơcq}2kךI/;Ț#xՓ5_~J]n>֒/" ia(؄ȠctVD"i1əi"!O`OfouDm sfb^mb))~K>-OV1IIu1ZP⒩)Di`O;dnMF.U'k:S15yF>(cw=]?{_iP;)x SrԘ7-1V3 o}kfuT\Mm? ;:x>VRvx)J NqKq`l1?j=Sjٙc/a<4 %L@ޞwjj2l6n>].G&C7`N!Ԏc|,e5}ãkyx^[Z}hQuKmZ!} ,(N3,z?S+C3춤I&l'/`?ҔwD3sM_*_ e>.|8ȇe&e`N~Ng^6 Բ۫d݁N;/7ޞ yQٍ1YjzWe{>,5̎]:A.5Y[ hXjjjUWV>b׊}o<>m]xKQv;39q鬩 :zQ9k|iFf^ޯS[ Ӝbevq.G X|,=/wP^4ujCZ0w}4LxtyL˱'?sN7yDZ0`E(22̣^2p.XjL\)aae7>œ-Kr&iɈVnֻ=]Z}\0G8ҁ 1`wa_(uY$.Q*U2E ƍҩ܍^v@f_&oS/`}xGd&,Y[g9ξ krJVjR0mD~@g.pȱ #?/U9g4`?FvHt`44:n|R6,W>.Ż˄+S)3nsӋl/' K1P*"\ĆD'j^z3߽v~D6l7V+|ktL%;ք@}N- :zJ;+g.8F9!vm&l=X9B89BqkP<_ |>b4H$y ԊZA*jӆAA=/K*Ɩ O־OuJ2p{V\NBq4{,ֿ3\CwvY3qc'ltba27#=`\!6 ߟ(GBd>0hX;à/nك7R1;]'v M9&{aӹ>GCuY/ 3+i_e$F\~C/MgExX{3?}iRx'Z)ldKChb3ϸsڬ敥Zvxi Ycm"QhMM3ťujF Zvs֭16Ze\"x9TݻytX9ZVDVx.B(TE3=A9꛷YxÚ %V/le͵z79'8X!KhIO̹P,Ztx Ff,̄V{Umٛ]kˋ 9GɃNHjL+\M؀JYǚ{8;T)}ϧX@:x=Ǔ]P8auPJF f~5/3kّǫ98˵8$̎dhỴVW.ιQ\KbvB ^eJXx0;Qx~ܷʋ2;;[G{PK V7ܞO hjv啺wpػ픶656nYQ;֤f{PkG&F@ 3~uNeV:{~O$|.M BD,4{=^B8SC񜜖/^:SimڭfN&O-QE R2u1H "sSjE >q|W`~L%? |z#x-"N])7-j\\8/5Bd}G߅ptNN_^./YI7svdn`%rН亴;>P=!x64CX2zZ?%nD w^1070Pڵ,X[Qd@ cHzqUI/Z~~=Lʛo^l7;H "vskH h@{&H$b R#d/ańf夡EYv IGt`߾K# ]5>Xb] W)(3!3|ވ\Se5-bL*HD755jY\0D%}Ot+)8rČg.#Dy]X[Qڂg(8BO?SV >kq(&0s'(51rB&1υ|pkzx@!v\jɀO#mHkyR{DSJ @C]pګs<Z. қh1ob9?MXl f{Up`D=&'~OkYeoMw'esHyxOUzg#r{rI]s;O(FjgHh1/Ĵn}>Pnu bGv+M* 5:;:@,ZP4z$e2%D&z2?GfEZlBc#SB ?`kZ{.`|rpnqϻ$xT 8d!~-1m˒D)2G_$@RGC+Hh X:?-~9~Qb;86uo(V:bmfʝ mPO0^鴖XJ,V1Czh+&T;kyN qb\˜ k+pAvQʜ ъ:F+^T`ELօ+Z1 ]okϕuR~mcX<ݏ]*JB7H=IgtTP&oBآXK̎Hɱ(/sf<2,=սGۆ֨ՙ?WPa8L@ kLڵy:Mv*[3z"xWҙ,wf#o-vK]wCw+/OjRb9{M*GmZQRCbsby#D ܏9wkaڻq,յB8$JI}d<\'tq6s96y9b~Bjyc^C<=9h11=x!oy;hm1]ޣ{,&T? @q 0`k5Vd1Q}^f7'7 ,Jȣ'd=n6DŽ[OnkͷXMȗ?xEhl\?-HŖ8Tؒ-gTJ^-] x=T-?ŦZo Ř$bZzNbպ;+Fhy=ٽX ߁645Cr"**U5Z}D;w }ot {#__A bH't~f=15G$G ?Tn%zԷ2BPy9B/ukH G ~:1轞ʟLխkdGEXvM 1`1'Ҫ!4+1Zu% {X5Hqg\OEZm DE.O͑&Jl'L椉FsܒWUN|I[((٩Kd͠X=x\vo,GI3y%<( ź?GŲX) s9o| {&Jy c>wqǞYusg ,1%s {1F`S 1Ɂ(h)*j4d2I-ҥsz0ggkxeWpxC8 _ϻv q[.C wZh bior|.-OFV5Ա< DRIav6NϜgPOpm5pmc5)6_JyzDNTC&ofǕ8=oQ^_ G-ΩsG ț"3ʇ _)l E;{^a#_"BENK t}Ӳ7潌f*O嘟)Ut娍vw٦7h,,OO jzw~'.d(̺֒,i >hhKMfw,` O9%0P?1w@e 6fyyRw j"ueGI1Yb. j'0нwPFanBSrA,푩 A@m4}Z՗\iRנY0 X71e* : 8X6JA@E Rz"v(u<-#O7#& t+!Ϝ:btoĬ)n;;FHm?iY922 A˷$) n*3ų{ =yC(i%i9B؜hAs6WB ӮՀ{svuhxLoi\`4 .ivvĠ͍̓}22cxM~MJ1㨰'W<7k஺5*h=2~׍l>lK)i .c'ޒX_Ҵ1 HXON+/A.0@Vb]Utv#i=$N5 σӕֽi&PJznfcG~U]"Q%Xәϡ ڹ&|%c㉹'LNIE\%W~EGs/Q`i]:u=Pw 5ZFGn蒾&Wc5T[}FHВG.Kq@6WWۂz|-‹ QR^i"#-w۝E@gAzvlb~A_4887gw 3y҈ x;ޥģ@X~MH!uK3zģ=hw<LZZ1z^]LAO4wMCJ$h<]pGLWq<zM}۞ p԰E@b1Y O[ܸK<=J= 3%[X?yZ%fqxr-M{>j m dR{),,=Nhx~z۱h3vZs?0#d*C#O|FS'{%5p>BJ݀?38wnhQrz@SK͞&5x)8ԤqtIܟc΍)~\7|Jɽ.QJǯ<ΞD7xZ=4 W6L]݆&u;< \'1N_El6 OC삷N2Y ozsGjX;ubjVaEm#}TQ[,P#8(bHCm@NBF1@wS _^}'(c95T{6=+>>^ Fw 3>=W22̍bH>b%dc`>ְ͂F[4 pJׇ ^^H~XS:2*3Z3,lZNxG4}!f'o?\HAEC chFAw__Z%{z~_UbUJ{ tfu'ӟ-^\sqV\\[F̠ UB0֘ y{_v:p#OqOP`'uըe2 y>?I][2 wIR#{<511Rj׏PL X0Ȗ.ZiD+qS^uLeTS݃H{_U%p]On whmzCE˃جb3 t]X+SJ1;bPS˵` #8a0WmX}~Mh=z45k\U*7%9|%V*ٛ6iOBP^{p}`O#@L7ږޅ䕑2{;+Є~/h 'W,iMaԧ]GE%fkXLV'5y)(`$EH+ʷ~I?G^^W{n< &"O#`}}>N7ǏTF%͚er.Rp;_h;2RS^87LuB@ڏ LxnDk7 A<ᔄ\^2%@fp`Ϻ'}7#QN)M.rG]=.eT}@1#TT=yT`GGkO]o`FJ/׽kQT)s+Ϝ +)otfr)ErJ󪮜bזHw-s[RkjP[7ʉJ oKl|NVۋh铛a ȣy2Q $]1ON`>@(`zY1@rpHbyDjh-y#E,/A蝁S6[|aI#=jP_:>|Vo]"W9.ˌKt_1!dįQ*uk c1ĨqĨ)ϙ"tm= >Lw¯=%N7/p1{]6:ꪠkiGBRpw\L#3Qܤ2<~( tO@S1ʃ Cүd+-KsoW%Z۬ѿ|ޮv0坆n}[#Ψѐ[[}D8,4cA̮'m 9ΥD|09oj JxUa,ІaC<tL0[TQ* Dn\RI8NHO:a޾wY0:s5u/V)< rciq#Ͷu]}48j+p%1'J𼝒ڝ3`@Ny/q=q,R781o;f{}}w|ü}PglKޜ kH`\M:΁BiVӬT^ oqXF.jk B MPWk敏|f7&!q>)+UF1?Quh*}דzbWjcWդ%t=_WrRj_"]_|xMcLnzbYDn֥5&WUiNc *nghwcf+0OdPabyz3#(*z cQՁI" ^GQ@hzWѕu!3ة<66KZ1 P:.+z8 û C_Lj{σ:|Y-[lAMnr-jD DLyΧ5z1o~8"Ȁ= 1`+]61 > wI«ΤgֶQ>wh7:=d2#w{LNsxLojt{I#5 *_ n繮TUyy} س2p*Bptei|4P?Hk+sYCIՀvPTRvKg|B"OMM>s>)HC%m' ܳޝ]-.yʼnH7N K3 T.g{H!}&ն䣠 q¯5ag&rQJr' #ê+PuߜQB^3 !h윿ќ\[:_LB=7%TbZ6'sbPȋz fLո"TWri+27:]uen.\}!Us0YM `,V5}. LGAhMU{Rjgxhw#7x;T3>6|g O7{ gvM 8cZq./ôv=vop9q,vx1􋽀56}.=*^B&a' 6_I2?4nIF gmy沺 /=cC;{3@7dg; Nn4|IRuRS"<,=s!Qq*%jǧuzxm43RH1ܥj =P2 4+;O0/QZNtn^c︒tJ#TG7UOj H%lA&$Mazioj-}s 5&!n ZzIO0j1^zJ_WHjL-N(LpNϕhhƓIaZU*NI̢ݴԘ2+  *ςؙ((DǺ__\LI!ж;L z/#قI:"tThU<7Q#]œHaB`FޡR-qTyU )Y;o|z4v@+r`K𭹜]$^CE轍+#RCQ{`d b럪UC_ s y5W~O[]TrIKiԺ\ncT_jjBYr ԟ`BxR;';4 ik9q/?*p'z= wj/}SitBߺ|')WzD:$aER7H?Xpz@&<`iYӶt21}| |eM0qvPj}=λS՛N3ܹ5;(U($K9D7Iӧ^h>&;gIs9q)!=Xr6ay[qLosh.F 9G  l$xEot Ig齪 ,Yl5EޞMKC/"mN`+#pK!#:J{ rgo5; \V r۔[w y~O3x@%^ЉXꇚoi>PWe!;z:ĶcEZ =Uw1ϰxvfe|@"<%{$,6ۤ1>: N] hD 2@ 1Q 4л 3,cPóE@r\ChhxQ%G߶(vh`~*\:!M5jSj^cUsD4z^7t'c:KVi f'dROi8՘/Ji?哾~+Ol/t͚D`qbkOl dO- 4$Y qƤN0Hy-QPn)GNMItNhbnj4L5u%WN$NMyK{oiҊ>)z١I(4 ! Gf7*ip˩Ah9u `$Z$+j_UI+\q-OCcka HlNӱ ǫ} SQ̭M#'ϻ'eCʣ$mV1ڎz܍N)%CPRafn|>G9a^ZŁ$ |zxj4aHn\Y3yA0ܕM:)*g6obzKBH N'ՌW=: XBz tҝzz{&{4"Q)\\ơ9ٖۛ4&7Dq,w`I`uN;ő>]1]!sem6rz:J+k l7r֭j@\0Zжn 6=o/%~̔6[B\O\iqjs#  y%v2WM>ٺ?-՜;T2BË\~5('$fnw;{5 ?I[ P:G7?XzX>}%"[Md ԏ0N{j^7?zPP̓NǨNUW@Soxl Bo">+0'M!0Hg0D·h/)+. &Oȋ|[*Ɓ2]6$ t`j3}!b/'@pLdֺ YoL?LC%yY8v&&C$ћ;tZ[|*. VݻO.+P}PG.Ӛzpz#Ξ^{D<yv:t?CP7XTl4eD{BFBcHb?)3}h^pI)fogHXKHU#UAE8;9t0yGl{ Ot&,ǻ5#Q"u3Zw\ʌk4SCvF{'wu BM CeA%h(Ʌt#D#[Q!mJWV{kWj)bO#GLL//驞{g-1?h0.^##][w{!Le0;? %]JeŜk+Ch3iz;Y$,@=HҼ+3 ,н <{a.,uf`k EDG &K|>sNS={<рb7'[P%tR4Z4Lp浲k z҉f9im8{Lr'~q,voMC[tO؛q?^ItSU{_nsg׽@vh !%|5Zfڨr( ^t9z`&8 Ƅ:QŜ7 9LaXG髶qO?R12H/Pz^mgwPIϟ1YhFH̫YjDMΩgb2u}F CEkO`ur5q9:}>l滍ԣžσ萣[aXD'kPerYL%y I8p61}V~u/tU9lናz¢m h<# !vU)/v$%C HPw\: qzRSAGT3po=SNna*cPdZr 'R|񌭁{GL&^ՅƠޘbj-Fq(o]aLƋ4t+=Z%䕩W _ 󂟏ܬEmW+KbULeEv!_g2G]u(ըJ>;4yGih-8>N ~pj!.gŢ1>ГH•47&Iz- Q0o1:i3x!&;:IZGz/wdMvM(Uv﷏HW.if4_|L<[#r#;yĖ;HUv*Y[3ݽ* %z[h:!o>,0+͎9$S.q{qXH W+W3OfMW=﩯Lt c-Gܱ/3^Onڣʁ~'DhpAsm2P+dAwf?rm@e1O՜[0Hz=tL}VX) ykLUj4~i%O'q!k+jr @FS&=a+^11\E𓨘{iȯnVMbx\4a\0"yu_72\ i@N1s%7IL2ƶ4nքXY'O9P[W:XIf4#ibN`zc ?[=cz=y$~@H ~|л/ӐSRzSF0Z!0^|\ZQTC-@x|g EPϿ.+noHLC9^7&4U[ H`6i,351 cVXDm' C?Yej|E󸙑L2 kdؙ'9}f`gh*6(Y*=XHB kN\28=kurS=x=|>c&/DG(!%IqMw/I!Q!#&h D+36t6),!1~4eW߻;F)ga9<{I'Wh@[ C̻ԇ1L\ev_M Y/qt@c}y"f"χl?®<*+3iy`4i{ΜL?,=V&s3D왞󅔡BQ,I3=G)Md؋X,,"lEFeyU ߺ޻6Elio\ykćdl5%*?cUr\ufڎՕ̖Aeɓ-6<TuN7_Ӱ=W3PAl{t;2->(O濽l޸)|8Y] πDy؈&+ޅr78wt6RsdTɬ*18~@kA^!,Xϼڔ<xjěxZtoo{H a+ =JV ZΕPctK'POr_tV QLDlF.j|xv@6#MY?x<+l*![oV=A ڣAo|ؼ& ,fJ9O;grQfyx1㯴;]RWtŔv)- F,ҧOgAH@ސk)-KOCg/n39;aÂ?B]GfS& >0Ǭ*slhfQjjD)?4K0@cQڢP옜Pt:H ۉ\^o~v$e D޼x;vL/~W fԙ\^8kYB51+@oԆ*I,)v[n9ܹtfF u] h@n $؝ʉ5fX<ډ5󁎧 %-&zX*9R2$@w)>ͻ딋uhdsNvt {K?oMؔЭ,noћt&!g kmiݏ]nEh}֦b'?P߇%yysld֒c el0n`- -7$I B東1 t;eǠ?N'kRcD>"YnH孂x3V.iɫyXt=yF/[n'#eG4pA媪=*kÁWa[ I' e|\J.\0t&(Ş u$Tiu"v%m .%<3wi~_j):A1P"MtM΍b2Rt'77t#s MuO%v*;F`s)̦_f&=kEW6C9}]-ΪMC3BM]Ɔh /ކȝ~̱K«pJo%+ʨ|pVVc>+JPӎsr?r>0b+ux  SEh#^iN@tDejũ7˥gPNYGi#~+ *R )IN))DsaS퍬fܓ,h*P'@aEu-JFKՕySpT8d|!9(@޸O.NC" j讚 *$Ur_{he t=`db9=\}ǽ|N|r5 2w"<;$s= ¦æXu vg_'yЖ^Fjnw/r U6[1hnjn\؅ϳmFBe?~QtFyX2j/56"/ꤶYF2N7\ںVXmF/dSmS$"{ӂ>[`&rVV+2sG45m$reOާ[VjQĔ“:-?`ĺ3ZP =2.$Ǝrn+5@Y B}s/܍K}w 䑮fz"#Xv졓O% բո z>>'s_|s˓ҧn2+PxRT@r<ɸ+ogSlC+_O{qa"%GU<2? ?,spIڛP&#I$ co?ϯ (oz9.XOHɎEsYGX5~Pz}~{YT2<_C*NB(7決.5yIOTsք|XכFԅ' FfD~739ha(:f2*mH_n5@tT=cV h,NK]XdPۗ5|eW/?BS z j2nI $w]>/NǚQaVRBa5]ˁ5e_"D.|JC2v2s݇gV?tb;TB&( ,Ϭ@~fuS4OORW,#ci<;mmYo|\4y= w0%qMoNw~tK.{ 폝/ Z^}pqd,^7P_9@P_I(#Zsp4~nQ.wKWjY`#dArOF@ ]ҝ670c_ZȮ7k;3wv +*kѽЦbB GF4eOp%|!&\R#lKmOh 8؃k1sӉU()^LL('|}z~ >5DJ:>WeU0 $"$hޓ{cpr%]ǼqQx?,*REW1V!Y" va®eСD"|xX<c^&]vgkN_n ]}c>ZX.ѵoO 6+&d6V m2F_`Ѐg!vJ6Yl} i-YӞ峕&cx|qDwm2"LƧ kQjm%R዇||*Z.Ϝr)ԄiucY&`Lu<3|oLe-zJ{~uɨcjJXXTȵB!+PNc>Z[JOG=L>r]S_̊uĜ􉞳Y(iP  #S/ X zXˠIo߼3؀ c"͊ Nx:Zo0WiT3܀+t.<*j"OvE"OcWic61}'Qn{tٺAtwY4Ss5(?2E$Jbl=} pl8}TWt3h%/` $G{DqȺxtL41'_؈bz{V2zV؋V+/ЊIAς:Pĕ?{YS{ڣ[׊GGؕGEyey I̙39̙'j/2t$91QORHSS@l'3 "žT *;ȦH@),,VQew_UQ*[{}w^ 5:I^w_lN{K׽#N(, [kCT`.xDee-H?"eG{*C3<~`uK񍷱2T!cW6X̵$A4Ry["dOtmS̍XTT SXՐ'5eN+p-h2 Ҧ}(LxA x.X֞> dIYLQN(PJ8 Px?Uݒ?{~isKt*(S7p}9Z( Y x=5bŎu=i幐)'KO:3 4w6<쵞1%%p$CVu_Ӫ+󧵻F4?;|+v"~׎!o~㋿B0[-ޤ)0b1ui) u| v7M}^ol^)BF`^rzs^ZF%g+ qz;vJ"#:~)Ji z0 I */G159jv-hB,ٷڊō @$ M)m` uw% \lN͌?_ 5;G!@O5@SZ)8iKKDjm&n~/ΟjCgbW_J+@{RmaS.OSjP }NO}rzl~- (wxUfCԅbR^zO ܞٸ7VS521'@"8W(YrEn(Jhtyܙ.؊ʲC O=+? q=6O[x<䇴'Zٓàr6?v7(#t: y?##$cvȬ5M mkkB-= ;ψ=2ϫ0\8Y%cc_ 6Vb6>j¨4YŇ@5$k$%e-c=57:~E,=JJ{9&69 +9Afj3H,2x@}wU WKz5(.{6 s@"II g!Z`+vz40R*4wWzbK\`{p0HP{LEQBvKԣI:X.hU$% t OؠYեĜvՠKiБ*qKOzPKPv~ӜWiVJc5pvA3 +-9M(v԰#W]*s? c{X"?v#``+oOL2``;+{5/K<$<#bi~stvffvߞLRQ/͇\|Ɲ^iQȄ:0.hRc ${!-9p&>28bgc?SL&~,sγa&YodXd/Э0||< [‹0x5K# \H=+L/M|GG^=Xk T#V۩JAG uʞ!h !YWya#14?҆+6ny|tlcG`kx!(27u-(:ds}XM(3wtp6+%nu{*S=yb#r_!0,sEShzWW?#=$VM=YvqKڅׇ*Q^HAfuZ "g$r$ްl*;$n jM*|w/E$;O^0|WsŶʪtFzOBΘϸ֑ӋDHo Y(x|q6\G<_I 4#X}ѓ:86; ŽФH5T.~& gmB6{E?da&nL?B Uqrp^A|ėA:AfK40>ĮwfBZRO09gzȀi,g=oT)%}I3{2s3]Z1Ϊ)q{XXt Y)4gL-4DCS0uN}'>ެ^@_෴]N6W@,«x߳R7}wݕȊ&)buh,l~u |W:r;ҿ@?evݍ+*j[J*Y=!)RϏL )ुZ5|Y)0vxHzRCX -u]HEb.xpc-#H>~?wi,UZʢA~O\;RmƲ߁h^Ofh*Vf*$C|[  }L*ϰH_)QݪbN OQ_isX*=gc04WFE%jYں F c*s BW]駃S1ar%ZhMly>gO{K|\̖kjg]M|apRhH_qZߺU 3sWfvjOhMn@fc^FYXVA{V )^L|8h,Ws-iMD_Q FQwHOHOA,-,W5WsThxzMY7@\|au[&IX "NбTC,T޿K;FB1gN`wޥ4SeUX"mhyzy@H Y+z:CqGq%T n\pZD&#נe_Ib=I{ٷ֕hL}5yTk \O 6[M /`NOiy8Pӳ؞+ڟbp+,}Ǡ@:b*4,HaCOBv14=ShqeOoUy[}2axNNx ve~{ 7"tU,IJ y՘Ȯ"S֜tWTcMn$H|R"$Ocf=q"'M6Y:sY:MŐ8wNv20Jeݮ$jSfA#Fjdjh isZ;`{D| ĎndaK_mtM(QRV]9؋Ac#͹J\*t6"ūn5۩!"1t+o?p=!u܏(Ђ!l*i })W?-U&\_6(^< el_y}>ܴbF yls#Bo=GJfS(4W2px{ݱOprH Xf̹u5=hAѧu546S@(ح7uV4?N[[߱xͯ["Uwp!Ov4L2t3-+!*]V]zߧ'#,\0W%EFSn ^,!B7fkK-f6YW@n-Jȭr?N";isbSJ-SKD@{J{nT6 \N՘?%m`[ʚRG)Tr̭q@:ѷtϭD ^=u ^! N%d̢3׀Gn@r$q>~rCX!IH jomA!k+EpB1yҌndɠ4!"4\\ؤv9(# h˵[Zu^veUP@ 74ؿ&@='jgxg9ݡ@ H sril*?ca5~z)w)Ğ\}]c@k34Ł^ 0Sx\C\ Q3XjXUO|}!؂HW_4Em|wUN.eg6mhչ|z&D\9c 7x| ڥ]$822)5)ˇ Jb1BSB}`2z-S=9BɘC5<7V9` D!Ll{cu0{Yc#k38~vMG=V aY͍De{, V>+׆YK$v#v@Ie۞r)!pތڗQ/6< b>H~'TIrpanei@G w@D]d}lo)kaϚ1Al5M&xyTJlR30pwiðkH:f2 F9f3(6v\,u~(.^ N0:"_/.}=YcM]is36 w'4JQ_YzԓOU(?;\xLO&D4|hϸJ-H3. m7*RkPJAdQ6|-9y+[S2jXVs/c'&hlBj.wDhE iIH(css?/1.nx@jl!Xbv:˶ê4`ZvW&1$$k,3Ndbc f`8cs* #rSa!QQ0ZFtmEj"i`Ji}9kWԃjs*`)7 ݵ蛿lFh ;TJTZn h\7gnt.A-5?®<*+kODdLNwLɤ'֯tDIKډx2L*Z )4izh}-d_}XdUUeAyWO_;W߻ᄏnzws2C;K2fq]\/ 𸃲V<=iDžhO =r5 eX4 uHY߉foV+08*3_{N߆<}ꂃaƆ*{}~ [V4h3'?Ν O@wwׇw+zȠ:?($ yHw:S:vXkzCny\A,* p3z4'#DN_q~fU3 +hʅ7CV?ڬo4{+9cR<ȑ3)\&"g!0[4(_mеI - ˀcKs4w ~Yz$)vFZ6 '?Kn k@5\2( zlY?0O{@vfʝ+$ "U<+)ɖ'=-EwlՅ/BVUl.Džٝ[ޥSɽ/_4 'd/AF n-9X6GSe%oM!st4YF>QRD"@fA^KhsA}NqAϱ׷USMTN2fz*n{jKÓ Url|g-ܸe 3\%sFVǮV8]G^ }οt7~@;]}V*vIg]imo_ht هfwN aȊL tQ{~ZkoEvhPLe\`K *Bӎ_X7lnDR,LwY:@UΎUhorji@\rl9Yy ^ SdM 9YU%hj4 Dxڒ^"3Bᖏzדf dD9|ͱT0#[ӰgX E:]T:rgO' MMʊ/Jõ&/@䨭ً*Wd2-#"Y֢pZF,y ^P)YXBERuʋ;h!nHT.&XVFsK>X$*բ΀1o9;][ļH0!:90W!vD"Ӭ7G Dh6##F[+6>pI7)ʟ|C?y~Em|!?j2"@* # 8>=Zu]h=?7?e<>YA-e k37 u!Snam;Q>|Ԫ+ {` d%FwR`%>GDjPҧ2X|3ح|Є1n~4k#(j@l$X8:YBYt*(s m VCؐX'5pTtm,~]شx҆Z[onGK_3/Wΰ!^}0H[3Y]/UgHFĀ~rlĢCv.N=@d_Noc.< 鮷v ڃ?s{jBiF[>krQVf ΄hm{d'7VK3(4F^<wjg)b 㪼zV48eڛϭ"ҴE:|IZvœz?CaQC}H;,R!Ѕ eUy ǫ &7u+&m]NN' e0цSbt- b +X=/:yt3X2RDd$3fln gK ہLS03t s<3|M6MJD2qpOPi%y F~k!h{Ϋ VӇ~  #$zT#Kq7ߍre&lL(MmfӢflD#gG~ ?ed- n""A˥{Lg?je= 0_@Ȅ zX0Y[QG -- Сۃ|Vq钳-0.[&vrlALr@pzIyUBD0=heD_ D<%.}ߨ^5Qho-EsXڻRk ΟWRR))ӑiIf*{}]$0V'oY!7e9;"?ޕnOX,B'!qܦ__M)G\EnwՐ`,-"d_8ff-Ҧjnů>{4B `<%aܯpj12Xz08vOT( }Κ,6FY0uvyL N s=SCcAUD,d!egs,xp㚫H?}&0}|K O Ht`pWδicbC+ X-ݱҗ 0AsSb }yD.rpvW5wclblgTh~v,/53⭡o69'+=%9 a>?hk YßB,#3ӶndʹL䞘8Sj|%"dJIIɊ%(W/i.\!#b%3O_Q\!M=ԅ:2t[?_Xra[şo繥c}1$eWfUݯFk^^S(Ea4\v?džKƻ/Vb&$c6):̰%O jSؓ '˔FUƕ\5kҲf $[OHNvTqAޥĎ١h5QDLsTj} Z?9s@byI1Kt3eG}v O%xc#|0\Q}׼1>a&V-Rx/`_[<!ҕ{gmh<PbWa"0R3؀P}}<˘3zϤcVuH=Y@ |hcN v =@1/i+y&Z,W瓜P">T\], Yh|~=jXF*x)))3_ ڸQrm&\c X~P@c H!iCw[#JPG瑇VdCawKQ~^JK֢z<]u2ֽqGHV;Wlt4 Y={*({;3m[໐ЦCEݛ$#J,B0\JD|mTof\,ft'߲ \Ltu?[wFSԭ N72Aid{tIOŞiv4x"FrhXn-.kl+ZS}҈g No"g{i6o{ >JRkT2QMOkN4*x9c%#CzL,0 :i?V$5͎&^PX!/#Y<!p`dn}~m];Ҁ6R{҂ɹbA<߲Vm%LD99%U52kj1P'Q4%MW'a/݊Ea5-)eIgB̉up?u=1-_1@\tF&8*gjm"4s=hrs9N_^`e sϨ8vQQ]iY'39;ɫj&=Ml[c&dLә2"tڙ9l"A@Ev٭XTYDd}w߻oj\?VaAj`9*klYgAחC+m3Á71)3v>+WfLevv?AjƢKsrDČ*=h@TX@-9V)̼Wl7,3.j6XLOI;2B0[^tߍƕB4HiD+gY6Fwh Ւ',w~7.jH[V@63ďϹj CQbO8+_ 0G*_a-4LŠjZڰ?39tMY7ft?֗-*QSODᶽnܭM% :ߧHOJ|vJ.*>7G>\.|;z {TN3ˋ>t&Z׌/zW`m͢QO6:>îA3yO|`1)zՌ] -c|óP ս +d^n0$t3N[AQOTjn?A0? k Om?[oڹM;ӹNp:mz8Mb Xdx6=]_#xB-@TI UOz)-j_/c(}U=2O`:{|&vc/9iWܴ_4d_G1s B} YYh)V 1-ś7 -A[Krm.j7Kɰ*kBÚ:!6^5j*zE$l aQEk)*'UӈUwiQϗvNTPjT͠SN^z6BGu.swX&^^i M^C;{yk3K:X2I5}@6σ%M.Y.E,Hc*!Awc[$Rti6NVL\^g֔BLqKһ};D1R+XA~L+^ACfs,݌cP2E˚==? ȍx9HYhۇ2( KS碵\b]^n-*kX7`ڣT5ڟ3=*bt?tCK19X%yd`~Jۙ9;D@QX|>E7{n=3I L,a=.`X w>LK.Ŕw-Əř{` f)xH*lߦѱآTY׵ 2ىuqg7ĢI40qs`K˗"(11T3\%T @9A0#xP$pj/nPM\|v{w@GB[Oq jޢ] >СQ}d tğf{W(?`CR/^%!C;V-JDh!po! 'ͽjJ0t< B#AuLG54(gҷWKpD2f1b UEzoeyη>("3ݫ'֠'du83Bbj*ȸ.VPՙa?Ltx֔m}tKGZ ʻܗ1t|ưQ8CSmϠٽԳ=7 1#URlW:l֠`,?LJ#鬥Z)߁t?;GOY]I–21Hoj|}dtqu%hS $o6lޛ?N^wT}ltMX*:ӑ>16  ΄}h.n@--z X1X| ךTc+4g\XHUh6:dvcՅ Ak7: $2{(|;{4B&zEs}Q:NV(bKA ;t/|R U0XvAc. L_ҧw9r*X {'aH"^ >[}Bg  o(;F ]N;/uOy#g:4QD%r4$=|Dy9,Y )VFFrR;{npXSN[EG?K&Ϊ4(o8,_ٯ'uuhN ̶ORZms(,9Oےꣽ 3cV,~dBn z.K7z-p߷M'w@,XQk 2`I/.^i8קw$KEr|6F״Dqpwln'k>$^v-1/"KswD c zg!_mS)i8Z y 4GvBRrѓ!0\*~,L) ~7I: I f%n̾-1-㷞ݖgMFTeO{H й ^pи8l LT(+4fI$\?i;W0<3$^ք8!۞xW;w:PD|d.LA7lDiʾ 4n|QxX0;](<$w/_kD&QH`FWfՒ :fi9~'%>YV`#0Ra_Y"~]>FTq*(zݏmX&ڱ!䭧+~>i I),8uw 6Yx;j_wwSLV9wS'45H_vǿ{F&FMBcc;3֛#))1aĥV RyE^W :i}+ ;Td#29&<M)/w:CLF]d(n栰.?~ÃDk [Ho.1b!|2BfW ?ʹ7k^?ZBFc^5ZݺNQ]fVؒU"3cxYiW뚚XCZ"=+XH=6imQpuN *:g aȱ`u h)F;5@zXВ^B$Cή,B["}OH>N 9#:+e%@gPpOB5AL|l! cwe2&Jž4*+K2`b4dzIdM O~L-'13 `," " " ǦllJEP}%*}u{ᄏi!;A +.<ür!)r8qĪ4 ژ:8ѫ/$B"N嘕]'0o7зkPT`{8LqZ>[vHP13{%$Ed^{Z`45n"㤳J$5 \l7O Wb$C2rqS&'j Hlw6MF(3 ( nܷxE}`q\-6b0D:/c/vcA,Qb6OS:,N"ݍ |BGjST .Pۋ*ˋfޮ 'vqRtN`{'Wϴp8)/:ߢ⒞<-ˡ˗"Pwttܕ;Ѣ__c./{\: %utFN'7Y-#OT*%Aędp} IB VkQĭ\pE Uz,>ؿkbZZ舋bQ]] 0\An.ݭf/[aQrFGIt\D}*{ g{v5EN]@cs#4NB:JPI5؝!Lm>ؙ.SYiE;_:#3xtkWR03YM*okFnw4#y-&kE:1hq簗ʶF7&=:F,jЩ=*}|13o9C)_bK! Øt6>4E,}njL\u@?Gs(E9K$E9GW`!nJWpT[x |WofB*wlqX,9#gsGY/H>FIJ+x8ڞGtm4M~U;;N[ iמw>]oh ig1.-x ?Ј;ӤuUwebiYi4˙(]~Vՠμz*lySH"P\)'BקGɷ:5>|0,=]ao/iF[)aH֤`#۟cK:B_cl#~ARyitK?Qq59d z;OTgX!v}&*(v&)ōS3wNl5s[;qҍJ oYŝZx\J};JgxݕMhV=7/54 M2,-ftbXGAZ9BTXBHDA۝)KzBZL\Oa psf:)o703 f:l10 Vm!y#daA4/e)D`b| !t-z+e(71a..wK_>[4+\v +“y3_8J:^bu{02WTgs{IH'Fm ѽK9[z{?:`V\9#}ٱȻY`&?>ොH&_Xoy}9ɞ)3̞# d6Tz3my EZ$u^>P 'PW"̥d}͞>]R(|&w?1,PP!rْhh/bѣ m7V;2x[2< EkX3/y #c^v ;۳tҢoVOȢx:M$/}QYŃimZ,Ι<}ɷ0 J63}6VNg,.9/( Clp:?=N뗧h-cM,bMD؁VY4\J1~9ۜt#> 3nK<`ծtu|;3bŕ/܇Glj2vt%+}:wR4Hd̋C@/%ہ!F} ]k%9hK(dd|\Lzߚ/ Uu(;g8{GR~k  %&6 3~l\shvFC#K\dѿD7]#:p"%M-ގ"R!J$ `TV>A@[4 XȲAVǯ񚏨^6wTZN~ݻ77@w\ea-N4ƒzZ􈍌]dfAO |BiF, Aba@@Z h'!  W^TW+-7&EXvfvɤRм鐕R P+.ǃU'(˟sINamC9gDbQ_nVIj[l%1R1#B=0'͗צ&/y.wҗHdPZ1J(:乙Q 6-f苞v-QlFi/yn8R DUU&ܚZ==QPsOQ妈#Mϸ?vLOBFYQK-IZ%᭛ 3o2l5R'>%]cJAbjDdD܅)Coč VvFEk Z/0#{d ̚'cAJWsZ ۾ eg-3i~N}J΍*S+3(ݾ;BFP}ߢPYɓQA JD"mX< nğhs$o|N4boabGK zt9c V^}yDc{ZOXX8 E=sHȝIRk./Ш^\1D N%d0>75ӋV{c4C+zḧ|: gdXLBuOyЎm_ ?%ϦU'xd`f Uh%mg8c]\,C Q^򸩛yzr'@TQ@z;)ljujBΒbvgF !ZֹT&ޢH]\Y 2Wjbdbl^(lvp=n;ř1JPL|FZ,hgD}ꈗFh(ЋֻtTβoͣθ\40htDTtTٔ7J/?9Gr#fUrܥr;.~^*GsNODʋ%di2,.!wѣHA&7Gh;V/ zÆΐC ߫=ֵЎ<.]h!vggkIrֽPWXx/mÌws~0|ҷs v/Pi?--L_hGmSjUu1hS{|@aɕIN֨>tGv?Gݠ@7ZC3S #mĆO>tqxn< ZlSV+€u"1}|Rx + .MI!OgjTX%L55E9ѸTcň};Vv0Z},O 'گj/e':)8 :jQ߸P?A0 SEW9z4+(,3/ΦcGylGVX4z8y|Vt"6`sEdsp+:˺0j w':NklvS[Jvٹd'S^5X&Jm Ц8JAEPΦZnTnCE[Cn|_ۿg}NEFmU+ oiu]e ,>vS86vG3b& X } + ^cRڦGm&X<"@/o>$烻ϚгB"sJRBG?aOǍ.еa8U2ZU J$?w3=s3?FE @e}hFL+}:WKHz( Ie*v]]dy$]j1& H&@) J֗clÑ`;2T#M)vwTd 3}䱓zkj|F]ݏѵi3Ҍ7*L()!9uaV"8v i7B|]ɋ/DQxydYv^B-,WP&]}(H(14Vȏ)|NxSU'R𧴘 t~ 3'Z Ehv@?J/i+7Lesm=Q)&"~ѠEf6>.OگN>@lKGQD/WKS )v1a ㉟[/QǞǘu(MX09xmԛn  ym;2:/^CC+Fu::{`:`lH]|n iʲaa~Dyv^Mk6!m&=LX>f6ۭR5Ն*"%lCZ=L]YM*dG>XƵF\`NWӊpb)ۨA}S]#YCȏP8$bZk4݌(EG-Ԓ5E8RR搜 6t!c:Xsk6GG:Ml(ۑj!e@1u@9t!ftQc%fӾamnLh+`i,QV|)}+cs^GP*C}czx3X5^V)-SzJH~8{3Vvl;36X47η\WPqO*V%,TB8[~ GЁ lW|Xkq1XkB%oo&1|yL?$[z2QHZL玲K!ϣF\ Q S!܎]PFj@,b*-'n5%KZ=,"rU>^9z^}'{lZJvAUK) "@úG~6!r p#uzˆLu%|3b+<=`OfohyʐKk^[|fXB/S>hM5Ŝ,SB3 s JKj%E>5KxTZ,r]4NGgjԫ0g*\gjЦV G4}IFKuqXT: C#Xns= X,.JƧfv>HGMa.)9 }]LߪɱR:~2jNlv1h\YKEqiƼxr<a0:WI<_ Tn b9vMgO{RIy}sP8kU,qۋΓ$n3[V9btKIKz@Q/ nbG+ &Z)q.63Ijui;gsKM)hSv|\ۭQj7OLe-*'ՠڳcaVqg6Y6;c\ЭZ2U![}lp('>}2C/ٰ ut% >WIEFt.<8W̖qPbN!tNAbᲷCEђ#jI@A#MNr{w:%^,r!Vw"TȞHEEOBY,"l|J8K3Z{> z`!2t0K`! ??JE5<݈EbmۯT۪r6:~d\}lOŵ< zZw*̼|#_2\yKZ1]˖` o52{LL1M&srk5k!Asy-L^ Y ]7bM')r'ZO y&g$:~TyR6B@!biu}Yp˲B"%︉d3b3 -޽hUaՠh -pM׃S kECYSvZ;2L ~o12tffHL jW[w b;pjԁJތs~, yAmv9|‚-H?1o@lLtJD="Id.cɔ,E`3F3Ó5h*g}dg;͞ΓOp]?~NkB+Ɍ{9LvP"V(Hj 0رA/R.VF>ZH8@b/gwϜ9+ ;3755^D=,VvV;' HwJ#ZP͊Ңf2`#ۿPQ*-3{i"-J# Zm[ڕ)YK!ci0}.kn=l"D0oAgM.O"?r(Ԓ=f j *BDsZ~&I0X[~쐀De#LRe!wRzI(>F^l09mb'~1Eh\w 2=r_[4]pH=.}'oi*?rH\?{ǝ!CIu˗_TzIe8Ti#ۢ@?ܺ҈̉맞$b۞t{k>66qrqք6+Rzp@T+0{'}!ڰ?`ix~E !iX|] b/硐S/r;"םTc-*_+PJj2  64#8 ߧ״~qH2gjL.iL_uK%$Ӟٱj:ӵݶv;F<ugOy(/ O"Q[ w܏q~܍!)2SbJ*($ٲs?a2ZJCh9ia6"< ۮt2`Iv3ąۍ*Rx'+6,\>\aE_/pRVߤ UZQ `1G[I֬ }1?9433dMTX(SCFk _F70Kx=~Wj[8i"APPou` эPXOI '5Z'`4LٻU!iL`c]$R=1!Cr.`]UfarNUtW|C-J쪾 s\ 抬XvVx2`˔@ {jʪP^LLp`&lo1Nc|) H2-x٧ߧ)uEjl {qːAJ/9&_eƒ{lTd7+nNR}A=a8Zyb~z2G霿lW墐$D8Ƨ{Dj1H-^+㴊iGZNl2^LЬp%Rۍ;k=>h@_S"=`P'nґ\bA?.g c/hQg\qҗGl߯ Wy[4xs 04_xq%O+뉟h$ƉLc)ܤ;/ngoh)l1r#n P,RW #A-:۾J(! [0VT۩mmJG3Yjb]пrp=.,Lb$ےo}}Bs>ML!T,AGVM!- m. h5aQ1eh5%:78"L^'n]&,OETX[M߽ژŠ@bÁ:럘T^f~-joEpoJR} 3$[Tk5.h*f ۃڮq@FXh=SׇU,0}ͨdžJz&im,XRS7Oz!M؆s]9WO B/]u7=QƷgqjufbadPߝvRkmbZ/XEmUWCHH;ӶoQ Zz}(;ڛ2k9ሻ O$0;jӚ^j䳕Nj<ŎHUEv\ed5oBWt@ʋtim{1_߅d]ynopfPu˔PQ>~Jg /vÆe:R' O| Yl!c6f#tTyK=U ssw\h3mB.ݞ&{Syϵ: D."ǽ'$9/לmy i7w eH>R \vv iF0^q _ne(9!|ͯ7܅YٗLŀ82,AHOo;iCk>_狜?갗72KW??S9W1Fj#vcjc'+`VK_WݿҐc4GXJ-ş6&6-R!_cT7YD()$~UgXO=_JRF|@;R½8?1:jlgªUo>Kgl?:`F~L$Єm/i ǹTwUc˦-l5Y{_/bˋg/)fغl*-ԊE}_D`=ll' TP܈Ja9WoZ,y$3qiuIMP 2hDZNC|KNX@Q_5R+[u$,ZUw /GdT'd7ZV[|4b̢y_=(y| ͿV|%MT!mYzTuy!2ǹ; 7cjԠ^I|Fב) Qc 1N̎8Lº/FVF*1`ߦI[ޡE!`__&J),~byPڌ!iWK{F V.CCv-Q$?^ߐ[pЫή;78c5Jk% e' V3^+pCkHZ&P rw ludb^@J3AWtIWlqyw#u-c]$"+e#Ԅ)Z0|O-XvH|)%0Η(ۇF3bs|>h 3mY=tR}HHeL_LĈH-o>şzFSb,p2zlN|_Ӧ\Mis@\9Ҕ?M6}2ҏMʛæLb>=&,_Y 9t _/s2v[y(JW Œ^oW7RARd^2/{Ȫŷhը[*=;عtBbX>h׎#$]N\p9$*9AF< D}[kKt)|߾Ms߼K.^14aY3>!ow[|M^v/A[u,bi2# c*-M#Tv50 D8 ;$ڴ;?;3MѦKcPwSSWY=Vw?S|;@ˇ'OʼIH^xަ(ɿԒ\eQGc$ c{¨)xsŮ_#*É`/ b ՝FO@[m]iճ(5|0;:|pSK;C MWNޮ@3A|x oĞiHAu!ՀI$+ dF~/ 8ws^θ4E}) u}I; J[*rz73|{Ԡ+}Scye7BowWC2Ƅܱ47ll,nD)r."GеyL vT M&L!O3@T#%O28PKj}jP[}v}"q KEosklhVwӅ?[^nEu'2{bo8k&ohRWTFգpA$|wXEwDZ]JNjkaΧu̖VJb3gA]kS7{Lw>i@W} Iyn(KP RqQy~0BF"~r;>Be܋ (3JN&Yݽ4(}ᱫ%)|*\˼OB'TBCL>Bq6v)0Ul7lpLs-:KXԅq7I`. 'ӇS;Uj&|zH9<,NAMlI$-,Tghrpx*N܂$ƪlM:iuΓG DtEvb!u|Ov' ëCfɿ">JH24NՇ>u2>zrY=*ߢ|_@]ʭ'ɃTxhdjSU&3d&ItlMGȂHrJ#݀4 ! (% u{4Lw‹>Ԟ,%vWFuxy]y.NK[rajIt[ %hh>8q1#l#Sƌa.`S*^%芢&Θ;>728_co#.Uҍ b347PQUbB(Iq1|+Y`AxpסqXK{@8>֑>cfy1bJL[V4j+z# ]B)*(o hk ]WJs5AEH0f.)Bgc=z9chRfٓ ugjQf:H;JO;?_?6 n }f{(jymJC 9t [VV!zמff ?/\|("zk׺/ȸOE7gUWR6JZW05H(([ +U!uO +LήRwnGYʣCK8u׫¨&z9>,˾1iG/ЌZn("ٕ{= )d;HßƺM4eYOPUW Arsf1c칛x9:YlDJBq'n6D͋YۄUBphkkF/0̱bʩ_h1sAE1Yms0s92v[4~kW3Cz-N^yX@tQHX)v<*uك!H8? wcRG` 9vbr$&jSNӤukzs`֨ɼ=mnb> [k``Y>. ]9=zW`;Eg?}hvf47I΍-_͚ ya 7~D>~De:m/GE/*A &Qu6K-.[F4W.>=Z@(qTxghgvX72t%0z/tH3䡰x?LiB:Iq".6pAzJB29EB~' tYSd,QtaB{bUԋUH=HN/9;Y&2NY&nRE'#U}*U]N%E%"'̨Y vf{5(/0A$Dty殯 zΝtva޼"Pq`~Ncx8T,`}Fl7)]C-iN9ۀbSVjYPd_qh)W֎jPrky2R TUx05 %ѭI;/v690g=oW#PƇqpxB ;n:]_z*O cvwzscPJrP)f2p\p~VV}.Fc"}Y"zU1N> ؋ u*F,70'}R~> +jSa yF][fЂS4G)BS+/ X=u!] G$8aԳ-k%C úy[N$쳿ʀ;m^lvh|1F111V>{ L)=[7V:?OtU>tՓ6>_k͞Ɩ@XlO,IOWGӯugqFG1%B1 `>Ox'z=Ui@}3?:uԦS$V&"&oWGS̄tuW ԍiõȐ}e"EVȉ̻FTZ@JJa: פ*sE|HvG{z#70vy-)ՁUhVϺDb :"yJB vm=0;zytzL2 -^2\9;IÓn2FY,٤_T7]iԢˊ(I8],UUi_V MTE)s"b:2UT3dRht3|# k%Qf.K Ԇ WGteRT!]Pfqgb6ikϟ@˧ ǘU Ƅ Q:#=a%$=-ON ݥPm`{28?^L;.^ȑnme,n#>ȥᇐE6(jszNj_uFn&*o /hW@-$>bQR~-l+q XXafIJd(hq3:&xⷘPqs* K? b+y ٠C4Yv _tkzQښ"rDǗ%e} nkPpd_~ Uǽ~K)BlzP_]wUH=wo`%<#. siQPhx.:bdצh =& a!~@tQL,0 %MtpZ.Ҡ=k :}Ӷ'7 a٫=r0i9HNB  n2Mg 4~ H `-7#?adV s"Pdl$c 8tTjŰ*(-ne Vi&.^Mעks/cnN4af48}S K4^ B!OJ"m-Y1I㡣gx>%Gžz< 'L*9úQߧO-'a 8@,G KyyLo n,19ÞN_ʯаdZď&('GW1yln=Z]mx2AŅ#cp)D?.u2æ. p O'i>B~- gUFganOS3;-Z\ ,}'UW:ҹj! ];K`ev.e FVD2C_;þ@?53O3^J_ܙɤ|0=_${즍ʳͫZ_쫅e~e*McknߑhM,nf*>E C@h0Ag)+/m^ElRi>~s /P+Z6U?h"h+)ԲqӒ]x&-*k O΄@obY8Vo4.W0aX] ˗ `8.5GgREiM9RzV8tif+T)\wuUW 1)~epqֽ2)2×Idz~*m^˻P<,nOs!s wcPHnH-` EbEy`[/99Q hޖ׮܉@ce7;| w~gKofv߽x?j8ć!샃2>B(:ݙB54wSEiەU܆A^\~C}aT8qIE Xg+ќD &: +pM,R""_h-:T5I6S 8ؘiv ct\rZ\J)-DtXnht'n9e΄^X_ӺwCޒ>i&Z 8q֊qѕV`/tEdF XREXF=P@JYށ/hCI;vNRBJބ.qgO$y1`)6b?l tn W$Au+W{q,,t\(>= ע&ԊsB^ב|:5yA|zj ݔ(пtn<9Nדџ2o[5n9Z u8ӑI-5v^lf6[R1_G5YjBL-V >u88%Aj O'cÛ[0Í]md?7L eh!na qmP'쌀-ҕ[w,qζ =P. cfZ!3745o=>D^3`x2dc:=6~KgI++Fj|]m-nUDJ?%9=b, DǢeFd\ 3Z`4E@WW>zeJC 60VZRexnelY+Õ{q$#x>V z{x{IO,A}'TWUCI\ <;XWN3{=ɏ:EmּUd1z~ \TDd,@/6Ь>Jj&l^#"x"Odv'[Ytm HRs$I-;nƲkDo5x7": _8oNҪxF -,FWA\HRX0 ?g5mg稂Iz:p@=?B7ą$UVVΊB>T׿uWaq~>?ak|uDuoSeKo5rz I0X {+ۈ#^҂T ZSFNDycTIQ0) K; xoq:0*?>;4Bn&$|h0aᳶȎ_ ԜyKéQ 2!2Ovk3ZﵨPHnzGc:̮9HXGh䲮vtj#l:frH}*UdKż'fpelSy]H,taf|@mK\XA#DTWLr;v$C'`fSO*֠4 r VkFTO E+e0)CgHIY$>- | f(o vi'.+ٴgiZԾHk. Iˎ}c2ϽnPjCO-4#5 M#O! c K}fKL+|;ob=8S3; [-; tҎɿ&6^>'eTEs=7FD\[Rd@3:sImMwFUspdt|V-`u7'h%ߧ/IM3}%Kh7 [*v#i$qѱ/b!5Ug\Du$|@c6Ws݊^^F Mεu(@WRm"a$:}h$x\1Vs^9իDFN^JKXCL=~HpPw=&B5&t^ DLWQ9i߶OOin^s=&͌'v>!f;P-ݘ[Nqf;;1i߹۱<*λ{DŽ8|cH <ז[Ř_sB ~ 93 )?nu_Faݛ%a^˄J*I7;_9/]pq:⎕u>|h0mxv1**NO=| ,m5Iʖ WrO7Hb][9>jSni!Fjw`o]Do􇽦p ߤ/ !6Ȉx.;aIf!]aN8sUSTMu"{O;&>&:ځFNj2سPz->1h";:aQ fu\Hc/EU=M?zR z4Syr/ۇ 0s'|ZcrYW֡q& kKH\r۟OhVOt1!+s׋J2- a"č7 H_he"W*eBX'8>>G|4PPVDĶ=Jcm7 ?C^]3t(ɒB Y.C?kn\\"^K4H"ȊK_^..4ҐoBq#{9`SEǿ޲P-d6r >Qiez4 @x?]@@`e$^ ,WɎ ̉ DuZ*s"rcMogԝ~uC> K5C y?v1V1Ϳk&êPtB+C21/%Hdq)Yk(@~Nwʀ^`'u}~”ALU̬z.mfͳ-&v|篤nRoB73y̭6-&Me/L[  yl'{< BD-[GL_2POO0"&m!'#gSOGu]-@Gq3%cvf5&س邀zTe%j/Lqƾ _גPGzv.[IX, q?-;? 44p/c`L2A.r?Ȍ] ,:޳d.GkZe,.s|0TK_G}S &wJ@L?1Fnޜ_Tg[CQߠ[hऎ_쇤Dd{$C<oqYYoihL~b0׶’zaX6PR_OOjJIix"12Qcݼ cIs\ecp/ r|L4kr>HFX)-=iXLj>]SiK7c<+A c\(3uk9Չ Af'W}*{癦Қ5কYui9kZS cugȊdr or2,d6<-͘P4-neC  */dq`!f07j+eihW8 a.uv %T3~'ڬ[ɜMm08K-/&TK{e̡+7ҒW8 V $ i!uq:88ͱcPWv%E(2@vqer  cRVM5S[3ki~omjcԄh⚇neZNbtX!o4Dn 4Pvr.w{" I9ӈ~5xEH:=jy(l-*BBXchH(wLՓu:jt"1b"m2E`G]AS^񽥙޽cI#X$fw *~ Te{&騩 /a_-\2Pd^U4T|)[- ahP1Nlx$%j=wÛySIj?!>BvH, r_W' >ZʙO+g$<#x%8̋o1)FU';j =ˤFds.@[Wa7+ PMONj΄.vIIE^;8kc{v d\S}-s9Fđ$3|t؅ zjfDA_\̠&,7f[b!RE=H@{1>̕v1HRP:/s!f;>|Ju%{3UeT WIg<%m/QJIE.HPnq"9bCGJB6Xjo)RH|e!뷂1>DEWTӵɡk\X A+/ZCPrq[dO|IK QX>tX kwۗ0y#]j0{Ə:oۊaYԁ?NNtwRӼ0>XLOo~]"  -+]ᓞE{APeǤWW$X';ʄ痷uUB~CyRYL|lshLe㳇74#­:qdS>g_l_Į(F$ļ$|d1C<~AZ)ckҗ/57? +/b<āgvZxJ3,fKL 4Od9:п'͐i;Ltݪ T3T*?lbKN0|6sp]܄q슷^/me<\`AQ &' o@p,$$@~ԹY/M7{tThބ[y> U vqHRĬ wt6ǎ^ HkE?VҨ_6g4M= |:瀳G[!' ?͆'05.&YRuhn)N_^n) 1C8R Ds.2[/wI3B5=Uzd?914Hܸ:RnD[ # P'KkCfW/rk٘|64NFv  ^|?W DdT= T.A#0@tj hzyʒܡdz[Уy545{ߐ rr EM[{jg"DeG\KhD~;egՆL57k€|!w{#l[ސ\|_)Z#[is_)a^q7ȲHGF zيˑ ԅ:U9)UMmȻY ? |Oywn?յQcgZ;|sEIv2[LZR܉B " <5SWF,jty' 7mk" Cܯĩaőы:b]e#y} R+d!1f:@AB!˃R}-6pB CQP" XS-+˚k.U/r]%$PX}#@v8~~d?atXR-+_}BCAHL9=+&\G%FTj, heVbR;KDdyՋ#$96%e Y~H#-V~xYgn#d!u0DY:dh*3s`ށP6ejXۅ'39&l&JF֮eA\&LF|ޟ@Æ,-eiv"Z[Gf` q18]˴Ԍ^Е ,p[4=<uN7QıU}$˄-pGŃvON0ڦK:82Ė\Â`(*jtvHRF6&.c DBدbf*78gT_yNV~{e!抎Z2GyPiךwS%  s==(+yS5hƬh%jבAPZγW@D[Z/'EjtdɖP.²GLҼ"-SXwv~p84+j RB:%OfieSX*et*Bm_`aeW܋0a4/oصTE0͐ABlc/ amq+CIg+=7j{^If).gUI?f{7 6c8u7o|T_ȣ^I9wBV( O_Wt3rN߮g騞Sc+0|4.&C'jnf}v &Bo͟# q) IQK dG-ZSG, T'n ܬ7#tTRum}%@Ei^eٟHlWnDAf#U6m"~Uvᅫ:prFJ(elAWdQV'LZLX]3{OD$68*.JNэSgythe6G_oVHCnΌղX52H^ 'Y~"FXOE4&곀BovboykoBwɅ]>72(kC1FJ%;eAhwL׎zg#~ZyF1 +H^{%ȍqWl4.EGgі؉X<3)jOODmS}3鏍=(- O9ŧ&3G`'b!5`80َAGmN 9%K*5Kcg!@qs(0Le B=lZR}d\d0E~u"wک烱}F\@*v9 A qu1`^@7E/Y;pR{zJɤj$H zl&勧eJ|!S &Yn#hgڦ:j)ihoQ|Jt $^jTTI_F!]ZjXhB_N*,.yH !po2##8vJ52/1JW;X9SCZ;Cp6DtݬDߪ. 6߿`\l Y;dGBW_u$J9ԻQ)tz҅fgD[ B-]Wnd;/8D^L'u[s0jr@\Ϗu#L=y<ܖc_8"LOؕEeiʞwAE'fzf 31ѵEG?$隮VҮ,MI+.ͅ%A]v}A 2PQDAKxs}{9\sKlj2N :zYœtxu$-o4jQH8Af r܌x~,(e]][Ȇ\.a7qaâ"=3,R#i@i n /b2,RWvQ!/h vZiK#{\&Y$px$:>=F?/G)';G̢\j0GD@ 7e|?yzIXj.M%|2٧҈ c/-GWrqjUjw#S5^̤RyqF̋ W}NkP4WgOD[{'d]$y&|8-@%YgJV".+e 1_OSƗA젲z-7fMq<`cӅ7%`6^#Rמ¯!ҕR g{h\=<]bLڪ~(v m{.:8ʱK!p ؉MXkkb.%uG)âdOשW׭[|GgH͛{I0?>@+×MP+/ǾA ,0*a!lGom Z4?d}> x&$PnbTF,~_i-pqp"f% [ 8d=$ط Glt"wPƘ840Z] pT(_(xVl!ҧ,\[|g94۴h78n1=(hYYU<{!}HvVNoSGf7B! Ӱ2wHGvIvPu:3יB6~0l֟~(m,{GrXmHTD ʿ]8ewS=:͙|z/ }"&v>ibn8+Ei"N[M}P6OE!g/12=x# ;,]tfsQN ؀b΂'L^A?S`Qn.bB/Ozݔ"^&+s嫴;|`2zִV`3|{@(#<0c9yyQkj.r'xi~)ߌtBrh m.'W~tnx"j5ʝ7q$" /v߱wPeUAK d:c͒C2 E^f-q :J*d \PGxjGi S2Z9YRW`bxGkBnn+-43Ꮿ?mg#,[L2Fl?V}כ%u41WZfT"Z܉ 11 KuHWSN 9GC&x*+~#>NɎ$e 5v[Mcfy_] Cy(O L?"KjRO\uTp"iy`$e] Ϲ.K;{U1UQ5C/51%# EKJIER%Aik:gVFK{8 ad \|9GuБ `:)۩!B-ꇴ8l7 G6|f; xjmUyأ8T[fzX:9IؽB c82Mz+]ѕsQݹ``eCFp,c-6~ kM>!(e/7c,R} :zқCH?{{`{7 pJdR\=6?;|N#I Mhgҽ֞ $x8rmߤ-K.EjIۑW `-ObcQ`Y3G/y0Ghv]oxD2gc= Q.bpXc\z2ڰh 8J v\%F\y˨GZ'҆/aμ9 H{7STxI[Տ,^}Υ@ˡJ ], &"8*yq̊~z0n@X `gJTǰ? ATV,!#8j kOIB)ˏ/3ʭ??v&44~֌WQX6oz@qj Fߦpd%֔׼d`wЯJN l0-8 OG?у֔;Ai△|b_l_]^$/;XiAMߢaػb37;q bW?ėB|D2״9Һ /X*c'3=aB?Q.muoEhͿnK|&}ز {A@fT.%OGv3qi-3 ,P\/_5\(BSAea34k|BYZ$ڠv&j>9nfZsXQ(o:VO2?8lD6H̄emPj9OE_/? ɻ&1C"nPt~Q0oqlmŴeZ6c07cm S1_ qHsi KL,XvsJ#CF,xz0qO7Zz }Q(Ŗ @ik⾚H10a>&:| nA+is1(fu67@kӖGd߫`CuξDC^ow4X= i?\y?50)A7^K<$4K2Vy43ӍUVi[erXTr;vlnт:IWM%G%a u`o4^]ڤ̉ KFї&09'EOָ+}.5OGCZBjmG\Ӓe P{k|_Ƨ`,i qh_ \aYElDrm(jmPʮڴ#Kr!Cşfx6= +Io5U6sˀ׶8r t;[^ 2!b^x; >@RMn®<($Wl%n^N;Aѽ;:vDZ-est3r߷rCIq%r#rH- ʏC2yҚ>ž̪TT|؜>~l2Fc6!>M_ROP#Fܡn3<ޥקYUc1ZXA]5f®;ޣypݨAy1@/ķS -ȨipQLG{ ɛOyD9]lŋ%̈́ PNHcg;0!cЇFz -Mb@ebF"Sȳ1VFpӌa"Or;z0A#mWR%4 xYӿ,?w_DSIup<ws.&er4 jc4}2䙉I}>Mq6{h>>68GUޱW;8;~d|AOKzF^qoEdSGtItyu/AB*<2/Hf̚+n⎲dKܕiIDM1Vs xL`֏2lxԫh5{kdigSB~nˌݮxx2~g)s>$&j|v`9V'./ˤ.'N.rPV' @u]\1 ߈x6a^ÐI&'O[%Z*"mmTqiȸON-{Cwk:fEO.{:LSځ-HPyVsǷ:Ӂ"ԱYsU1S# {e9(~ɖ9h~CSy7M-]HA.~R9'TzⷺW=ߚJK}{(5ٯpKo# }G'5qg;Vk' SRYH{WL+Ʌ|6 \,cXgKw+ctnZ|ѵF=LE}ֵIϻ6+(a aG.+ mgtyLJ\:RJ(qoe]'p^b}fSc8rk_L#)yH&cד z@٤fwgxMtljNՐ:#3LK"k gN628}ùoEJ-B \B?>fy9k:lP)iq~*4\5 TYhf78L* xQ8Qu1֨_P18G1F7_K Ua[!])\ICY:{UNAyd:ruftV5(PS<{v֖ge襤΁ch14UB%>ZN»X1x=ӆXkKR>^ 5y)Đd{ϣTuxh@8VֳQ;}8w^xށfh胾KEh0%iSCX65vqUKo{|e p{,f~EX$~ 7'>v'z<1I c|zd{굆xGNa=JG / i]|;0"nh1ROfqSX4cHʕrZ ӆ vRg3 Q<û1gM>:}1 *7~Bl_}&u0D<9!uֽ>zP]p/[P[ަ"L0p{YUTۭE p>1I @hGIU茒ds@"sKmxP64J L ynѪd#ni%#-bSP2EI!}.&qM ATPhq,j6EҳPHoZB3OjQRm☿HBe֧WK\(+Og*C Dl x/)ط~R4h؆P% !o"xM,W(BS+\* i:f2Ȕ[ ^ (z▻g[X󎒯aqEڣNn {BmO1Wr'gԌ6-/ΞWaY)5[ܲg SoJnk4'}:eE vhITJ sㆃiO> %)y3L=QѵʇRClJt< LLZ'<)@R"m ,JZv*}mQP,r!J U^7qKU C/(-iۨ?5}O*{+2f]K(qb 3EG=uY!mexDnHd*8E'٢ dio9TR;=+9)ilqB+afnp%EIQʩ(=$~ҳ`wQdAF$Jhk&^L̀rv+S^7%!tSm Oۊ|$xǛ<+~Yfa34(zkkmn[l=BC^XL42bsz_M[SzX"HY[aiޓڬ,4ڶHtw}ƳWC^~/ tpu_0'"m_[IE@V Zɮ:_(2CO2 я4 q O-2zDnhϳg EO oC'9IO:BC,ۿdBiOuOUS] ǟ +֦gd>%st_Q۫{٨lm yyN?Ō'3L~F nmmv?y2k PH}@/p+@yy.3{iY}4ր䴯j*m1=4nW3ys֨;/K\rxy!u^b6A$Gmne4[W>~S]yPTwټIT2ejk6&tn^WjgcD8fvwfZѮp8f)܇Ҁr }\P@u rr*ryGշw|=Hg>NPy!|=-$щ:iz)uQ_\6HBHS901fW/eG"Tt|002awLq B[@|G*9I9t]`D,0KW^^^^HfJ,6{_o>YS XdFQhkqjbz|<.mc\)m"?9X0uTBIHPD/ZwR!0POMf͏ 3NQ uRȚ&~ T|L^mowV^~2VEaB%l!JP6-~q%XִPE%yqqNŸ8St)lRgn 6ijfD-c/ߒA=EMo|S$F$~d`kLMѮw[~ }[ۨkI~0;١ }FTBՅ΁o!.'75۩W-pizFl%EP7F;PDMaQW| =>]7j0vw>5W!0o _ZyRFǴT@`KKCr@8l3Ow'מu)zxws 褴1s"J|ajuP> 3q:bIh"}{ȪО6bȔ>n;m'n`EoN@NO"Ҹ7^|eDq3JvIL廿#iTBh[f|wT\jq72ٳ4F̸N燴G#Hβ\D(O@d>mŅ/=Ps-(Z&qp-kPvS_&;x-/G2 g ЁC$b;~y7t\Bazc=H&w/F;3, =Nv!EGdp(52^CsEW[ %GaJ IjiX[( wK.@ym 3DL\-ꠋU$yUpVyy`$f3.RƆ~IK*a.WՂa-* QQ5\D*|&NNM1=픿6t%7v:>GlLc\)W/~2QeOCÁH"UM/ A⿛Uriހb~oܶrLWR^/My줎rV zΪ, mPq2^=i??i 2egtfbg?/<ڕحT3 |.l6bygv7_h;7jp&Ro_;v|oLF/XϦ[ CLik9g)w`kIG+%F/҅R jI=?v'֭e騖p`]@d>Ymm}0_OdHF=U]6;YY_.*)n"k a7ٱOHYp}(Uё|E +/z$wM*d.]{*s fY`Y'*#;_޻飧Vhte{ a|Z$Z˟9ڂo7Dעj:?9+]Μ3şt,<u=.' X08@he2j1)+:~YBQ* c: O#s's{~\ /H.ۋ#p`'=Tʭ0wH\4͍ꨡmڀ\% C`_L֬P{?x").gܹ*+Zgr@XJEeluty~sU WfeW#H,k |S, ֎97 LFSO ӛh^f>mz;F{* kx2)RoֆڇOD=ruU,%bȄ#>xk ?ށ5Kބ[{IaM_CvK{YӤ+A 1U?UXw` kw\ mp^<¬_Γt.! = 1Hn *ԡ_F~'32\SL3${;ߒ3>Bbgopʪ#xg?L-UMu c Ÿp,{ kLެI|,IGujtH>y Vc$4?}^V.1-'CN,7٬hr?L~syY0(gVt)հ, דB} o0ww()o`~`ٳ_|NfTȴ:SNB뉍_|'VBʳJ֠l1 !>!E^~a ˗LB!y$M~ ǓO9Xz,K'%'c)f*7>^d-#fs*4S؛]3ȯ-N|7^CdF?QeV^ȲRw\@; JRSh/\{$Rz /Zb{+4)ڇULnv2X=4 &~r32jn6m=><Ֆ1v|Nx]ڏfSgO?37n]ڑQLuGG4chIC,pENjkB8Ehh6ؼwIT|R &eb_)D;LL~q)^5‟m$=Tiԇ-'WP2|{Yf\sb€taɚmÉsFo5ꂎJ,ddȼYEU0'G,7}fG &poAOxyLiRO&5,CsaXsppnF` >\§踪tR@CЮss$_o LڲFh;sToze vPMlTSF093zV* Wz2  o3 o@Sڥk±AoB`Ytz10>$ʹ+7c?tӆB/[`3(W24MOMFǴ< #=n؜As+[iX,8j`,$ ۍsjO|'x1Sz-4Sd0ubcJpwS9 72޶oxXuf1;ȨEo-h$Bp NC__W-ʋ'cԦO.³DR8{B#026W98tOޯJQZֳ F 2`YϞZ$dwi@_Q0oixx%*BHnhkl\ egdƳrz[K_"G'Tsk$==lqct~u:ZCH岽N*_Xx%|LNVV[3QFqS2 8syT9{/KzyKO 5YhhpT \]R M brM"2 a>JEo6 g&~oQ.pW3RQܽF.b\U9SKGX".9 {E:s[[@{`+) XHٮN(E =g$r^~6[S@^X&R筄 ` ?"3Znҕ%Au!mrM)[IM*GM͙tmۈ&x wΖlՒi|RnjZӸ^_E?aWՕ0FLN3sΜL;EQg2%&Ѵ:;3R*JUBbLR &X.(KUQ&Pl{Ի+x/_w߽ Llm,ONO`$ʐĖqX 8!6^X A_N*'9p\ TF>, Hr>.b9BҫbzͅDJ%{|1^C)!cpGsGj^,Lb%,s0o{Nh ~D6OH0m x|K8$Ѷ?igy^O1P,+K P=3R_I:lUht$)%O Xnh5H`ο,+ɱF`W|_FQZjTHrK>2!)+H>Oچ Yw+}\ /Ų;Ux J]jA+^;ȍC:Uxr>-hMOu>,2x 0VG7嘮ӱ [Cnkox]`s[vZe|ⱻ08> ͥ JVF2e$faIW͑j1~fio+mEw)G}o'<}iSAP*lu>2CkHqR/$g(PL\RbH[eXsK(SWrh[/_;gJ/GdZ(?ٺKP˝$a*ءMyGXRP2gdJ}Y`KY{q݅~h]wxdOc[0#OD~Kqs7rͱ c R%<H{$4&BhHUxn^_|2ɰVF|xPr51 &&<(;<ζzG2O%-W/T(휼L71H*^c*!g_Kͻj''YkD4d[ݡ)yRWt][pN5!p{A.;'C'ڳPs#X;P=~>@n٪&v{|@<Nt/-Cthjn ~}T:,dJ]t{2GAnPUUmT&ق+s&[ܭ$yҵlOh(HD*GR*'a!H4`E uWl ,ѶOjo܉PS|1va(OO_ BR(8{relO{f>HMVxdUBNUU02'ڦ'O rыVԄbPjw Wg)qrkAU*hIm0a{X?},y<;JWܰτtMnͯXE;*xx}!$̓suC:)詀W}'{Cx_yY! xj|"yMoAO&1Y C_5,M" /3'#K2Gn[|}N"cfJ/A~M>vlhфNJ+/%6jKcǿ C%=RΥB@ d.8gzuE]YXyDceK ё"5df8;z [W_`<ƚ.}%7#XmaS^bmS^|*HlG# S3aGR&Cdn"(=FX< 3n>h+gK-TuY΃Lyc p2t1bpDM,%l>tRΟ7KdXYK$`Oib:]{e(Ul.IM1AZOb냳F8+d tys/K;I9"Ik8[D.Wl@*޺44a~V4*ߒro- 0PwbSl &-IlSn]FOWݯ2Y Ap̠'oU qVH-% :+[{ Ԏ.CZHn>N8Ca:Hrϙ}z43x' p33쯗ȨV7gPWo_+sZMWUd(oelFtW&& @wJc^8xxāso?Ka&KpM,D|=|sf C6OG>IMbapWޑ *[/ C`"dSG(ph9e2 q8p.vn٭8‡/xnoqb_MБ׺:gm# (dxi1߬ ?d:+/>J5Wm&oc/pj?d,讹OkӚ.଑L,ܼSlLxy{}y7/`" {%Vaem-<+h-F_fF_iKJd^J͛U9K{D6dN#lhBh+*%Pu!B@Q%7gFJrg^IaՆM?@bZ\0fI%ϊ9~O~Uƌ`Ed@YO\2;ήƃ+%sA#eAzz9K@&y} XYtx7^:%] ]1ؘ/%.@Aٟ pbHVVeh~ #ᨭGB8Ch#Xq, ܘs8}pCipބaH˭Z *Œme@z*+@Ig*kw{1C$lC-v១|L#߷>_:G>`37VM&Kj_;4;k|xޑE Ыk&emK?=uCHh5R~C9~6/U:cSq$?4R]1cKf8\2!׉Xgî"Id]J{2Ik(#+$oHVWД2Bw7/S}nq;҈Y%Ts`،k֑S %N)r/ѳM XWڲ-S_sc<x7@EɯrG!xo CS_ 9B/_@Z> ~@v@>Z3|u#Dv~Fi6u;'Y11~q256;Ӝ҉"GW|2"rG9p9,*!ukv]fp r# gr@eGR,SU P*Jފܿ2{1<lXC3g}f@ q^[~j9/yO!Kg_K?nx _ m7 3_99Jć@I*` z=abi~KbRlݫ__~dQLKSvgoA|{(60h*(+o]&vrO5O<ȡHA9ϭ+ұa+fL}#lT+d ,IվȑN#8 JE-{ϱyk"EdV3MWa O]x1;~MFC /4] #k%E6;3y?3;;ΔHBufvgbǎ'qo7>O#IUt{#z5T1)6# {$Ώí=K /aۨ=/5;A dC| -6y\%g^ O%PMNA-\\q iݩ'="6W问z|kQg#8z$ =H,VOLݭ֑o'0&LV[W{ҒѨ!1dM=pU~4-Û  I_ɨ#_A$PS|dS*L B+ ej?$O![:)<ېмlEI$t h9͚4b Rc˫SrcAhPϿݞ?Q۲ofӑZQJ'{@s2c$)౧Bւ*M[VjLOZѓӵTjbuJ?·Y߆LF,LGik43DW^ԪyUM9v|{Ί/P!1pOGt!2нm%E_Y,! Eg\]eJ M], ZN򭺖U_㖋-'RҖ[fXЄWFd'#u؍QobKKHk?[!9^FA-g#i7Q|x-UH!Dܸ^B:ɳ6Ć݇ǥ\=|My\F%u$sCGJщi5VV_|N[>H]#}p"-̙^iZ^Wlbq!sS($Rc9^D_ՈTz3d]os2>2ݮCfKog{[`Bj w_Ybi5ZDN9e.91W)%.+c\:-g%7jRӮϻ:&P1FqADsLnTy89hX7[u$08GMӧ8яÁ#sc.X+SX,c:{^UZNI֘}dk2utr!!+?0igl"M۲4-l=]te xRډ֔L % 8`6ԅ ?3ӳߣ)HUޝ: ; nj3X;tT:]t`&YqQʝѯu` W?9T!/dT|cIΊ. rG|ȦcN '% ȻnbM?seƐ탶FC4:qN9?ܔuI E>q1oɟwHF+1֭vTL,eftoL`{ϘLgh b/1mS*L8ˆm0E)~'2ؕ@|ۓ±Zr~F{7 N7-+Im!2kPqLVW+Eh1ɣ@'ey-q (?'S\"۫&PMZ"bӮylWcgioq_hƬ?Pm>:@ $dj7ؾYrǎ0 h f&8_bw{J4;EϗyWe5ke:jtps\#qN)]*ܕeOLfӚؓ!VbRePb}Y&@5#bXmu>(8?-(˜X;#hdg Ƿ>x NiZw}_YӇ2'h{βr_@I{L&MGNpwde>y3 |an|mGwȩXFksDdG@Cgjl;cl.,wόaWP5ϭt2Yt^oe#_Я1Ͷ7*fK׎˜u9tS"1V;}R|S#|VB1:ys"t#?e'DfM Nc[`UP1\Xrc98"3ooҚ o4B]Hz}/!2ulk NC*XC%o[C ~~jeuNlRg8&e9|b  hX dpVV_Ie%cxMS a EX[]"z9S;cl?/(TМ Ed(KϓD`үBp7H,ZӪ`vB?7~M4 _14j6sm ? ҙQ@L?׉Oft@ ["U3O3V/Uoɖb$a9|. ["S/a| xecPtǼ;[3U=8ݻr{۟08V3!?[ijM nW3u85/;½vZY9VswJs= ;1Gx"Wr26  e ̥$RmVί=p۱Bsمgƈ~NE%%Q&wDh'gZJ=M?AA+l\Qӱ7"[8Nr=+g4B6Tkj{7uŎdD|#Jϖ|OBST;텒5٣Wt+4'3a.- ~vz6XǙR=;#PV(sڸ94.X֮e7=ՂHC?XB/PWzKߴꃪֻ-@v U70 f?*Y7"8u$|paA~NA :ݚѹFyMggMoyG J a}nPXKEذQAEtߵ:L7fGÑoy}U -Dh}D悧}E>U 2@4'uAB̷6o>eX|Fv2Di@\&#Ā#yUq6J;:pq',87uK!<]|8}Qd;*{ O408Z2%hjقũ/[Y餴| l\Sn:12L/Y@m/Tp=C-Isrϒp*K:DHoUs6kI?6O(?|SJB31;>›E:NcIlI0x|Ј5 1(֫W[r2cGLμlЂ;Wez$3M[Kyh4-pXk*Nyє>mD=-k^oTeYpVn9 $?ǣDGF߻mwӤO Ԓ})&O+/Q;ѩ͂ޑUN^>@\SFT=^7JZ WĪߙi:ʇ7݌|y߽bø|d\,ܹ8=Ǩ5B"To/\;+ @>bsʭEz0Ro 6ʜ\y̕~_YgHE N^w]?wd=忦YEFU!t2,Sg Gz?7zx4rf|Ɂ#`7@a:YrCWBjXaL(Du; ט/Y-P|t\0j[|'QK`Ea=Ƃd/,wSܪY+DS;O+N"vvBaL5M״D@8[m%! Qk=89C mJ2$"YL9{}#;d j,mmT;F8]7١;/@:~ x[[:.PFvyg$5RfsRbi%3F79)u*`BWrd,Dh fu3eqp0ztS:$Blsb"Dge_ qڞO[{j=l(qb : wfཋ4;B.hʧ^H8G bDG复+M Oixz5D\)/T違@ZuTP>Uc{!A`t_h?n`>Mj(-]+;f3YInOy#UH[ai/GtPWSF{a;ft?}?%53'--ueE\)9aIUJPK,`sBB4q[zj-M]4Ӽ9>UDʷQ3C>>rmuP!WK-VVچy*`oS H By1s~(q9S i(p}9 E#A8S;S%0(W=j®3| z[5eVd@C'N$4ráߺ3,%nj?6U5ݬ@49ہ5zBz' $'N%IEu! \*좰gvRڂa'<;QسfB31#r 5xNTEv/b }=heea,^e؄^؁Ӳɜd$g{hUL6.q/4ٳ GSk~BxmM/TQ?@-PO`d$ dΗ X߆-dύ' kEMdsAD [kH}jO g, ) p:[-5(qCgY E P$$AU]W4TK C W>]v'JXd. ûަ|T5)1`Bµ9Rm\1U }aFo},5T][]4B5;x$/pBZ"9HN DىO+JV$ +${o3VR⢡@䛸]G66~$Ws0.s4$M״C=_fU=+I\[R~^7EphgNOI~}^T*|2'V$ _e=jH +[F^#fzt9J۴f* iX|+OD(t j] H${ pٕcA2'郛ZCS=`Ho?e#Jv}Ma-Htjli!k&jŌyx 37zlsR߄u|R CZVX[Y~K+# ])e=QHvwIm)XIl+)kS:5NC3 ?ta]8$ewHc:wJp B>̋?|_YؚEWq*@򜌴؊q. $N.lzHF=C|LüOG>7Qӥ#棓_|pb߯0mVY$ E'aWSXY^zFJcov5`j>> t?'8l{,#agrA,JJ҄-=xB{ 0:cR餲֕0;55+ mM)#la?ISsPƭE!X.|k@es%騣ާrSA\H]%ȶ|1'"x{tjQ сJ}L{AVw*E7!P-CN:>WņdZr{JWK=jg\AWV6ƞiH N._)ٰ~N3MMYR=LD5aH! BH0(mxMe{kH_*)pd?ӮmjD%TN\*="S8} 64\ȡuV޹1F'O!#Px!+ L]p[yp%թ}K7`<-Y?l~x6QsVIɭ2O4@31Ö4 Ҭeqza!xu5nQh>bg:$w^Z{w$aڿu2OTIJ\aŏ={1F??On#pB?}E:p y|䬋KȦe-@F9#/lA7Ru[.pډ1M//|qtH0}m' h?*c}B,@m!Hjouǯ#2h[rn12dR?{ >,9tYuOL: &k>-~ﴏ\'՛ ](R! ?H%k2G cI)b>9ji @,;c]qg-#s 38MwJغ?ۑ4o>\U?cojJ.V ǣܾ+])=\\Lh j";Fk.6MgLBSɓ K03PП?lQZCܮʉC7Jp.:pT~m)Wb1YkKqh<0Aq݊ю.="hqwd5,vA?f˝ a@!g/ ueurOnGxe==Z\/)@_dƘJFoǢ{]$\0S{ƥ5?153C I(w:p 1ٞ9;ϩ# 矅c x4[~\f, mo/;tI0R@#_9}+4O{cX8al3Q iF;>)_eG]Y:BYj%BZ(aC&E }`Vh/\ f$~LѪnlb?(a)߈ vDT&D^Dщvۀ1ʐ5_CPf[y~jd $vc܃/,L^3~,¢qyB5h߀TO)xT̳ g!ҪafUb taD:YQn#t|GF57]P.3 %{.S_eNjD+AHIY::i/OɩЙTdJo &2PD&wxoNub&w^@+a'⽾-rT:e-kvߤG~&I* mI򨌰;(jgH:<TQdxx:D7%NRʄE#x?|O] ѥ4y1ؒq=~yH2VxMg6ul#-Ɏb% N)L@&p>9>DU{a@Ys Z+Rߜ\E3$vq(V ެwhzHxԽ?:-5=ٴ@T6U7a,]\H@&ȣ.) DOo7IUxURiK8k[J޺KabJCT|eX.tOiؗTD p |CY #QדYjG\anbRq7:ALٗ-p4c$? (6NmN z`~FY9g*jN +mNG28oBުZ1D'7b.I77wOsghqڕ]ʶD#Ho>'eQ#M3UjmuJy\hs}GdO{@qGC6Fa1;4=7HWQg%$VbBVp]N.2H;;=6 es)57CQٔЄ~i"[zyb›=JIT_u,<3@4P# siv\ N/ 8ێ/87@@%Z{أ@W+: (^nowҖJ@-sv\ufuI_;[;|]2-urh_H/&ӧ dm m}zmGxuVm> z{Pi _w\@Ք-%/>G+c˓ 5=#:4rTc y͏ yf=}umya=&W5o4;a%ώz;|\NM_yEab 3OK-ƏGOL$pik5 *'" \jD(>_wt CgDXekڌ mTۤh~p~H6郗yUhW, ӗ}d\:]r#! 2tXj3ٯуp4x016E㩝gt7h,I]ƺnD/=k'nk{lKN,hRF눰5EtcqJ6.WrWғg$/`J ?X]oL~s)b7. 8%;ZhbptD#dmtkի0a1斻LXO>TZXjjuMfQD :Tg.[Q0hQL& pJPjyYBkCj- ~Zdru\Z>lOȎ?Sc/ӡJ@Dt䌇Յ6d O|[ڣh艝բYY!U?-{@R #C9BhA}E1 H+Ò3&E~CC"#:+us: V Ȱv-g!:ُ"{=/AlU&˯*F țл._| e !fak*Т:]r`[z9;/.deO* Q&Of(BP>TVvK[pzb bGb2U=G3-00.c,S!q-H1k2FMֹ$0IbG (ܯnYO=Bu˰a}%k4(zd혃B(JG'huv0|NiZX;hţ@ǏbjưȚ*ͽ> HNZ÷rKj܁ Tӭ Փ"nA .iHJSxf!yz 6̶m{eeccA?g5FTܱFH㒑65O\7ܱր3uќz_/)oE񹂟Ro_ąX='#ss(+4?^Eo[cEa/ğhEæGyBlEuE3$JYO/18&s;5#yRV[Fr&-l.DCnIЧ~]whZx#@]9Bێh_f<5-V̺ (W@c ^`g֎`|庄1ǯ22fzـIuN.wUھ[zƒI8h!N 0 +`,R,6_'lDd.ɿ8wìAw##Vb^jI!'F%罘c#ij2i5hDaY7wDY1mYuFD 0W6=H2΄"gKƙ=D/8+dn~-5 O"/z'Ge{^hZyk}5w뢲1R;dn S ItՃφ)h\Rkh..^4FTasEp2׊F,ecR>Y,C׽pc`&f`p\ w0@xv'EOoQe p`ЙͰQ'2nn|*,Ȓ/Pe(V>VړQoPE /FH5mØ8ܻDcъrp+39lR֑A y!04I9$mKSU5x:.sb=rR%NHDbmVk_X1Ül~#<x-/#rnSu1Zug>zbXmoh_?9D,K^&{WK5=7,@@3˽J:b+Z|mAL,p2 "p3)ODwH'¤~Ed~5I `" x֨t*;I|׻I9 r*=&޳j59 }xlXY;U"ADvz_%c1s"wY+ SHtmyӮ.eб8ΚBI]i]E_.&U\dnmѱ9>nH=9q@vDL m>Iw"~u)2V2'IudqG{=Xvn"l}B2,ZI򮜪*o,]Qs"o@#q1nmNH)٭զ*0A3Ux){ݖS WOu9K:UL{kvģs*'nhѥKz>1P!zVc-!F5jTOd(|}:j8 /Us jPRo/7izҞDMiAg`*7C;GTLrBe(;r/Ogj酜 k.!~ON9![/I )xh_(!E\߾D-a+<2Dc{8[<<",v,>Mt")f8h-{NYI Ť?'v:yЏO. "}Kv o k X㐭e$VT*щˢ۞ܟzlZj1Ժ%ɚKÃF'KOcv .(7X!}%_ISA4:?CÍR˩ABF бVۻQ#'W2O jhOE6X mb2$Ff<1LNd^^T߅N;[;9Eހxqk;r2qnlxrM}Fn8㐟 m~T<)4 6T2E9'Z{aDRRZ}BTY-7h1lIܼHVX(:AphwW2l1\WY@esm.  rHYKrqRYS dzs8 2/Sߑ׹3RJ nسδ-Gj2 % d[]F2YW:ٖqu.߽˜)|Kuhl; ձ=GF*ehoȍ,6CW;bu1q%(H#ligx:yϤ0PUюt{# I)Lzʹ~TI>e?5F9zw.kHWfW!~B?%.4#+5+aOQvIa1yI=?$Y47P8wuۀ0d,q" nV9'@-',um*ۨuk Jx; r=L<#ZnO\^}CKJ|]%O1+~SNΤ)+|)^H jLnBfJY:5Tlt٬>%BJ] f1 s6?Ġr*7. x\hDÇю7rj+/tE{y\ܸ*!L'䵑ԧ{" R)qd˕vW`&ez`Pq>RaH;dP Rx3PI(Yu"?M^R{zѯo <;8lOaV!$-R J=|>ۘ ߊh?{2PmYS}\K?{1 ngdralF&Ja5yX2d~@#jwɌ| D< !'ڞg5Um-L*ÛqCHx+&f74AƮ"fy<@,WUDdo0Q->Τ;hs"S.&doPOlɝdqh%)KFi&Ldbġ7hzt= Ntm.']E&(vlE yIuHlL&b MY=ɟ*䣨GQcrj27QH{o0[v7;ArTܔ[Hq-`(!4!0\F>~z6Nz@7:?āBOhPW/O SГ(;ȷش&ZeHOP5ȩ0 gpk(@] rj,5hp6&)3 icCN/MY]ejX~>L3\6/QLf9Mf-$sA+DF3c`nSt!6qauw\E dIN`av Tp}0]]k<ߣu, ̟ ϏcY[х yhǹj\*-?nDNUDT'ipiQ?W23wSuQ ɜ\o M,l&ph2/<2nOU,ls} 񈃿wN⬎p$\v+@!q+WMZc{6}| ӕKj"3|/`s U IA0i8VvSrD*F~>3<XThw4,BJorbFdب19#7ٕOỊ$Q]jj s fB"|0ֽh-\øuߑ鉴XtapEOb ?2TgW%,|2OezvCę4j|hWo\r;94t-#{Z8I9qY+b߱0@6&krlh;h %bL$Y/|}[ n_+k0m[bjnoe˩RzT9ziB/1kFѷdrZtӥ5jsAt$m XixkP0R:w [-[e§d@G݋9l ft+8I WEhoxQI|:Y-Z_o..š? k6-4#Uh{2wwI({x `1qy \cɭ9VeouhPx h/b&L0 ͼFg)3TnZp _=ѿBSA?zʠg ҙ^^) <ίlӠ3d PKqE PQuerying.gdb/a0000000b.gdbtablxUT \T\Tux sUW(`,H $-NNBRRKpC ܹ=ӧg}:3$7qODT(xihQoPH>,AЉ<$n+W1%c5 5hP*1 :D2A8fe" 5b/bJYSNQ`W0hC/*JWr[9p+F4%Bb6 HGO}S3+>WOQ "Dx bDXo.V·;{V+S@5OV[Pg bxSq~3T\CAeCq 0k8Co&&1:<v˟>2$%p }F*ۦT$-0E"f@:3X:rr-Ulې<)6d{ v,j^p]~Qnq-fzę" 4+ ֡D9Pe. zd\CShV /D-R1e:A% KP{ v(܂b*pEZT-M;*UZr" x/ :YK։i()1:;` 6lDކHؤ0bn,[ tjwA6osR E5 i<ȿS{ 'v2fq\Dbgq8Q4ګXcj+3V#`\Fކ=鐝{ꨦp)n ,ANZHCf3uS,CSM3蘫|!5ODD3F5hxja-*! ua]/ EAͨyIPc\6,d!?^$]aUD仦gA&*Op4/3r?mW؉: ~G: A;Q+* VbaMatD 0Ab_q F:|]W۰%J(pY&pu,h(x3kXbQ\Q>0WpWDxO1y+l䯤eEʁ[0Ū K%HxsU- pLxUiqcZԲ؂ڵoa- <kHC$3ɸ B@B'0yX lи^Job1yo h4z HC7 e[Lmp [IIG& Q,D!LUtm<51J;G6jň10'ТSkDbgɄ(Eb:Ko؃7vF7Eh=0аc^1z{9f`@_,B-1%J{LC~hF[S{|Cjx pfo͑wn$e=Ի"}18>4{B@99]F!4y8-#j," <5XSDr IჼOFLDU8A3s[E=(JLB7BKSH} +F(iHx- !8noɛ/exmq]eD|At@eP##Qc-H$0S;? Ũy# "T1U2TlB N7. ~Q;aMhUE - ` ~T?:$/8^E_?:Ƣ h\ lÆ[74p7&:nfq}kǐHet*Xװ+؊Q 3Z $clcf!ϸ@? xx&8d?瑎w'TlZ_,Birr2];#:!z` ϲ`͖G;c(Π\\tj+v'%pB-n[ :.'{qtX c7,(B4\GJW0exFC@5"sQ#ftZE>ӎ}q ``hlnalDV9`Zf9@b#jm?AOv:@.G De_Dݭ{ D~U Ip:ܡ!5pwVp]@ 9N}jhp\b7jP( 1'1EOKEܟk(qZ0x gSCsP[A uHh7V$^S,Ae#LC?&:^ ,EUQ6Ծn&$%sHD (\D(S VDD>kT0:QP d!!2#1*16JӘE/c^2Yǭ%QF1QO`5J.KtB2nqyt sh{).01\`6B+lhT1K|~ʁS%p 6!nUSZ`>k+[#Vn؃WkYEځ?p- =:$"=!2Ya"08RC64.FƆj&#`%xsQF:RmxдEW@JK\ru` x$E_6AtjU,GL،8n ]QNQFdPfqHѿB3#OO+{iu{~~#^p -n E(z LUswmP{X;8 'att309hub/)؅{n7}qta/E(֡Ń!Cϱ eyD ez؆ڏ ~D'`*?xHx*Piˈ T{F-<{P9z|Sd#8/GKfetļ"1 ~ t-;ruU،oED#< h#M> Gg*f#s3ͨ"((TZb"܁q]Ao" Iy֢v+dwl@#?a(rgjjLMA~5l䘆ryG!>t*^ {h"X#c*A8#9 o5 i(>J-w`D˱lCqtĎ<(1!0q =88Ve-.$Oq'{!qSvT񇕈&k13d-͔8ֳ [QsaͱX2sE!b_E} LIILGBQtPe5G&j,/X2t\nAW:c#꤫@U [Z`)fPT_lb(NcS.ͺ؋5 A4an4HCM1gEnjꐁTB,Ela l|r٨-p-v tC= ~{FF>M! +a#Z8nhNtGm~Ĕ  G,Ac~>_4 |L$wH@*pp"<)9 ɧZp-sΡiU;PVT=Ds+s{2PEF..eq 48WP調G&'by`2 ܆(?04(؀ja9* <(z )t,дh~&:oc<(S"XJ%`rLG|XњR)cИȲ1X hae\.0Qu+?9YhЩu3r` *:8T ЪFqM "wbΣ}KPq ¿h8>U؅2PUL7 <(\ aJ܏I(2 _+ IiEX6nRtQHRĤ^fjgk GѴƿ1Y8$Nu+Sh* ,[@wKPGABខbzVQŨzKUA>FT$b5bo LCR)(c6XKA;Pqw`N#al$xH/&"~a;} H"!WaA4|DAT|4,c&:>P[XOn4}J`+̛Xg1yU 195|,G (XCK8/ AW,$*°^ t& *2Q;(HFF:?/֡OgF~XRډ1W7 HGF,RP@⇘qLCP+(9L{3h=\q4؇#3V#/a.HCt;=v(6:۰)cT.Fױ_gb JW1 '8Ka=#u&* ؅bO6aX)'0 JEt&5]Jb0h4z %t"1W؍s Zϓ)A"cQ,ͩ Py|TZdXB#HZ#p)6rLTXᄌ _\c)J{22FՁ(ᘀhvZ*T61 Cp]k{AUr$mcoRð6?A-؏[?v6s؇FmȷC% iOFrhQ@9nvlD}8vv!>;rMH>48CX1 U>ZTPhsBp O:|"pL:<`"&gXZT< QM[Q`/:^dr$]R8F9 ^+V$\XXh!#o@ B |N` tDDoC(O?! |SW80V;R3X0 J!,cJ^C6D~G2XdaOc`?G6^Fܸn\7׍uqݸn\7׍uqݸn\?PKqEAjQuerying.gdb/a0000000b.spxUT \T\Tux T5A0^T@TTJ`.PTL,@DEP0KC3>ٙޙsdddd`EVbeVaUQ=jHҷ-}Kߪr}a:e6?sߐ^>]x̑3e|;y,.-}Kҷ-}KҷmiݢeQߗ؟8nt:ġ`mVfds7buO۾o2l=>wo R5XFҋ~ l.2n.!F3'xxݾBx-̳ rz͖lNBt)qqg鷧)@|.MoqvwcBXdq~O/\j:Y-ؚ8Ӱ R996ӬM֥ hD&9S@!ŔPZc|?c"}fsIGe />_O|ny̢,N$%K,Kqr' 3/U$ R0ɢ$+ȒH.fsKOʗKbeRʢ,N$Iѹ/FTyIW1KSK)sRV1Ke wu:3z2Ǭـ&lFl&lJs6[5hC{۱#ؙ]ٍك=ًþ@7;pӃñdN/я L3 ,ssrs rYIs7r3p+swq#xx 9^d Sl=>G~muVm9oe%ϵ͏:lִfwqӓcũ 1\]iJkҎt r}8A(y^`3'(a=!uO5 ˺v |3|_eyNoe^g:g4K=9 XiAKڲzmhxoy0<+kG|GGv-zͿ ,Kw..fn&$O1[{~c%u"N1~Jg{9:U'-7|m㺧"Ĺ/ÌO+qD^d<:񪼦??YuilAKmykd w0.ګ(k|Ŵdz-q<=O[!NNo-߱b_ [ZXv\m<*^ ep3|ŷ|O-[::|ıOX=}'}}6L&[ܥeC9<&[+ўmNuz9󹘏439Fyݢ$+ɒ*fcU!U)UIfWI%)YXAd"fI:,Ml~Yw( 2{13̯$ *J(, K,K2'/ G(%31)I?%̂ p1($K/2dfJf%SMɼJ`)eaY8ɒl())S2'ܔKJ,dF6ώY8pEVbeQFMjQ:úԥ hm&| (X hBӨ79-2-hvH'vbW  3k#u:79 iK{:Љ9ڎ7s=894q/1i5dmR@ MFl;NԃMfo~Zh%jPY7Ѷjԉ4qbRzWx|74 ىc.벂&w}@/6F]W,8)U{cnmؔfnte8Nҏ L09s\E\\Ռ۸yy'Qg^V?FO_vZxsSv\\m7l{bMϯܢ=_WPʶaц6 HoNTrsy,H~IY1O<6a7 O6}3Dzݢ~U 1 !Zy1<E&o1﩮tP{5s'RLG;fN2ncW~{/q=7p?xW V4Zf ։yE61Ĺo@Es׍mIGL5^ .rFps7s rswrr#Qk~G0)8W!s2qC8\B[j:r,}d*o2Ml?Fpsws#}<Ye&17x5ڳXhD.E><ך?JΚi{=C>>&\}\֜/w0sR27ɼEYT1 Sh)gqǵo&DZUJ2_IT1sKI,a.,dq$$͒dt)$0%dfJfU8s*J2̯b,gY(&T"fF2݌e?zrЋ9SiN?3 L3}q9G]ш|J؆=>mz;\Ìߞ8!ڼ;xM ]1ƘdAc#| mTOZ:80.G[zڱ=2բ1s)y1Qf8\u~#s?8r;wr711e$OE y&1Wx`&xwyÇ|g|OfejTg5jR5XYuK=Ӏ4",!| (bJ(Ƭl@!1)hf`s%[S5Њִ-l=_U3e1Ft|9-G ~'~.RJʹx<8?ƀ$No?ߊVlK'gKJц~3fl}/ѓhC33{GHG>+'Ygcě| ޳"+z|XzdKPJhK4ac"~O=86,agp"'ћՔsCͱ8ўIY´z?>cv6]Zq9<92Q񛏜Cg|礪%wdv?׸t>F3)l>ɖ)rNw618'>7tsY1~?9 ,.rd~fzr4}5~gw};}gwJ-|;Xbڷr'w1Gx7{:vAhƘ\1gI{xdSc$~4`+vy}9{̫ S`eaYg?-9K)d~,Lh137d2μ,әtcY,b/+O)R2,\FYT,NɒrH~Ѧكٟg8\Mʝd cyyyx?O_qN jԧ4CԷNq=.~(Џ `g2󹀋kW;\QGEm;17b6SԊ1k&R7~ˎ4!Qg!bʢMِ.BќhAKdkiO#̞>H7`pzr$Gs,ы)Jҏ`09s\E\e\\5\K97p#p;wq7p/3y8Q,/2Qmb|ؗ1Q̩tsp!p 29[gsr1GxohlP".ˀ)qyn9YqzL7b|D:G|mЁtHFnV^>[szuf1u)1?Ёbh 6ȥ&? }y-?FV1*ɒt3\Y,,N$%Ĭ*d<1+ؾcf$I@24s>Mٌ]8G0,JnVÌeQ׈1v["<^ڬCV+}(ߌ-}Kܖ(/8%Kʢ$R,Iw:NHҷ-}Kҷq* ѩgPK qE./Querying.gdb/gdbUT \T\Tux PKqEs Querying.gdb/timestampsUT \T\Tux @ (+PK qE AQuerying.gdb/UT\Tux PKqEWᝦ*XGQuerying.gdb/a00000001.freelistUT\Tux PKqE˖Pn!Querying.gdb/a00000001.gdbindexesUT\Tux PKqE.auQuerying.gdb/a00000001.gdbtableUT\Tux PKqEUH Querying.gdb/a00000001.gdbtablxUT\Tux PKqE(2'<Querying.gdb/a00000001.TablesByName.atxUT\Tux PKqE0UQuerying.gdb/a00000002.gdbtableUT\Tux PKqEzA}n Querying.gdb/a00000002.gdbtablxUT\Tux PKqE)!*!Querying.gdb/a00000003.gdbindexesUT\Tux PKqE9E Querying.gdb/a00000003.gdbtableUT\Tux PKqE-  Querying.gdb/a00000003.gdbtablxUT\Tux PKqE,+Y1n Querying.gdb/a00000004.CatItemsByPhysicalName.atxUT\Tux PKqEeF8؞)2 Querying.gdb/a00000004.CatItemsByType.atxUT\Tux PKqE>;D#3 Querying.gdb/a00000004.FDO_UUID.atxUT\Tux PKqEWXq. Querying.gdb/a00000004.freelistUT\Tux PKqEӰ 6!`Querying.gdb/a00000004.gdbindexesUT\Tux PKqEY@(G>Querying.gdb/a00000004.gdbtableUT\Tux PKqEW1 Querying.gdb/a00000004.gdbtablxUT\Tux PKqE}LrpIQuerying.gdb/a00000004.spxUT\Tux PKqEczk1hQuerying.gdb/a00000005.CatRelsByDestinationID.atxUT\Tux PKqE Kc=U,>Querying.gdb/a00000005.CatRelsByOriginID.atxUT\Tux PKqEwES(Querying.gdb/a00000005.CatRelsByType.atxUT\Tux PKqEj#Querying.gdb/a00000005.FDO_UUID.atxUT\Tux PKqEr'0XuQuerying.gdb/a00000005.freelistUT\Tux PKqE>!Querying.gdb/a00000005.gdbindexesUT\Tux PKqEA٪Querying.gdb/a00000005.gdbtableUT\Tux PKqE:() NQuerying.gdb/a00000005.gdbtablxUT\Tux PKqE$5ЬQuerying.gdb/a00000006.CatRelTypesByBackwardLabel.atxUT\Tux PKqE|6cQuerying.gdb/a00000006.CatRelTypesByDestItemTypeID.atxUT\Tux PKqETU_4Querying.gdb/a00000006.CatRelTypesByForwardLabel.atxUT\Tux PKqE"',HQuerying.gdb/a00000006.CatRelTypesByName.atxUT\Tux PKqE/T8ղQuerying.gdb/a00000006.CatRelTypesByOriginItemTypeID.atxUT\Tux PKqE,Querying.gdb/a00000006.CatRelTypesByUUID.atxUT\Tux PKqEdZ!Querying.gdb/a00000006.gdbindexesUT\Tux PKqEFɗŷQuerying.gdb/a00000006.gdbtableUT\Tux PKqE銊S ׼Querying.gdb/a00000006.gdbtablxUT\Tux PKqEJN0-Querying.gdb/a00000007.CatItemTypesByName.atxUT\Tux PKqEöy5޿Querying.gdb/a00000007.CatItemTypesByParentTypeID.atxUT\Tux PKqE+]{2-cQuerying.gdb/a00000007.CatItemTypesByUUID.atxUT\Tux PKqEݦr(!Querying.gdb/a00000007.gdbindexesUT\Tux PKqEH 2Querying.gdb/a00000007.gdbtableUT\Tux PKqED} TQuerying.gdb/a00000007.gdbtablxUT\Tux PKqE[:gCt!*Querying.gdb/a00000009.gdbindexesUT\Tux PKqELG&Querying.gdb/a00000009.gdbtableUT\Tux PKqEQr ?Querying.gdb/a00000009.gdbtablxUT\Tux PKqE Querying.gdb/a00000009.spxUT\Tux PKqE{+B! Querying.gdb/a0000000a.gdbindexesUT\Tux PKqE^4l> Querying.gdb/a0000000a.gdbtableUT\Tux PKqE"ar (Querying.gdb/a0000000a.gdbtablxUT\Tux PKqE[:gCt!Querying.gdb/a0000000b.gdbindexesUT\Tux PKqEM/<Querying.gdb/a0000000b.gdbtableUT\Tux PKqE PQuerying.gdb/a0000000b.gdbtablxUT\Tux PKqEAjQuerying.gdb/a0000000b.spxUT\Tux PK qE./Querying.gdb/gdbUT\Tux PKqEs Querying.gdb/timestampsUT\Tux PK77@pgsql-ogr-fdw-1.0.5/data/enc.dbf000066400000000000000000000004471321206656700163560ustar00rootroot00000000000000_CnameC2ageNheightNbirthdateD Peter 45 5.6019650412 Pul 33 5.8419710325pgsql-ogr-fdw-1.0.5/data/enc.prj000066400000000000000000000002171321206656700164110ustar00rootroot00000000000000GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]pgsql-ogr-fdw-1.0.5/data/enc.shp000066400000000000000000000002341321206656700164070ustar00rootroot00000000000000' NTC֗^JF?,?? ,?? TC֗^JF?pgsql-ogr-fdw-1.0.5/data/enc.shx000066400000000000000000000001641321206656700164210ustar00rootroot00000000000000' :TC֗^JF?,??2 @ pgsql-ogr-fdw-1.0.5/data/natural.dbf000066400000000000000000000002211321206656700172450ustar00rootroot00000000000000_aIDNNATURALC 1298372893 wood 239287328 landpgsql-ogr-fdw-1.0.5/data/no_geom.csv000066400000000000000000000001061321206656700172640ustar00rootroot00000000000000name,age,value Peter,34,10.2 John,77,3.4 Paul,45,19.2 Matthew,35,18.2 pgsql-ogr-fdw-1.0.5/data/no_geom_apost.csv000066400000000000000000000001211321206656700204670ustar00rootroot00000000000000name,age,"person's value" Peter,34,10.2 John,77,3.4 Paul,45,19.2 Matthew,35,18.2 pgsql-ogr-fdw-1.0.5/data/pt_two.dbf000066400000000000000000000004471321206656700171250ustar00rootroot00000000000000_CnameC2ageNheightNbirthdateD Peter 45 5.6019650412 Paul 33 5.8419710325pgsql-ogr-fdw-1.0.5/data/pt_two.prj000066400000000000000000000002171321206656700171600ustar00rootroot00000000000000GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]pgsql-ogr-fdw-1.0.5/data/pt_two.qpj000066400000000000000000000004011321206656700171520ustar00rootroot00000000000000GEOGCS["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"]] pgsql-ogr-fdw-1.0.5/data/pt_two.shp000066400000000000000000000002341321206656700171560ustar00rootroot00000000000000' NTC֗^JF?,?? ,?? TC֗^JF?pgsql-ogr-fdw-1.0.5/data/pt_two.shx000066400000000000000000000001641321206656700171700ustar00rootroot00000000000000' :TC֗^JF?,??2 @ pgsql-ogr-fdw-1.0.5/expected/000077500000000000000000000000001321206656700160175ustar00rootroot00000000000000pgsql-ogr-fdw-1.0.5/expected/.gitignore000066400000000000000000000001071321206656700200050ustar00rootroot00000000000000# Ignore everything in this directory * # Except this file !.gitignore pgsql-ogr-fdw-1.0.5/input/000077500000000000000000000000001321206656700153555ustar00rootroot00000000000000pgsql-ogr-fdw-1.0.5/input/file.source000066400000000000000000000045331321206656700175230ustar00rootroot00000000000000 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); ------------------------------------------------ pgsql-ogr-fdw-1.0.5/input/import.source000066400000000000000000000035621321206656700201170ustar00rootroot00000000000000set client_min_messages=NOTICE; ------------------------------------------------ CREATE SCHEMA imp1; IMPORT FOREIGN SCHEMA ogr_all LIMIT TO (n2launder) FROM SERVER myserver INTO imp1; SELECT c.column_name, c.data_type, attfdwoptions FROM information_schema._pg_foreign_table_columns AS fc INNER JOIN information_schema.columns AS c ON fc.nspname = c.table_schema AND fc.relname = c.table_name AND fc.attname = c.column_name WHERE fc.nspname = 'imp1' and fc.relname = 'n2launder' ORDER BY c.ordinal_position; SELECT * FROM imp1.n2launder WHERE fid = 0; ------------------------------------------------ CREATE SCHEMA imp2; IMPORT FOREIGN SCHEMA ogr_all LIMIT TO ("natural") FROM SERVER myserver INTO imp2; SELECT c.column_name, c.data_type, attfdwoptions FROM information_schema._pg_foreign_table_columns AS fc INNER JOIN information_schema.columns AS c ON fc.nspname = c.table_schema AND fc.relname = c.table_name AND fc.attname = c.column_name WHERE fc.nspname = 'imp2' and fc.relname = 'natural' ORDER BY c.ordinal_position; SELECT "natural" FROM imp2."natural"; ------------------------------------------------ CREATE SERVER svr_test_apost FOREIGN DATA WRAPPER ogr_fdw OPTIONS ( datasource '@abs_srcdir@/data/no_geom_apost.csv', format 'CSV' ); CREATE SCHEMA imp3; IMPORT FOREIGN SCHEMA ogr_all LIMIT TO (no_geom_apost) FROM SERVER svr_test_apost INTO imp3; SELECT c.column_name, c.data_type, attfdwoptions FROM information_schema._pg_foreign_table_columns AS fc INNER JOIN information_schema.columns AS c ON fc.nspname = c.table_schema AND fc.relname = c.table_name AND fc.attname = c.column_name WHERE fc.nspname = 'imp3' and fc.relname = 'no_geom_apost' ORDER BY c.ordinal_position; SELECT * FROM imp3.no_geom_apost; ------------------------------------------------ pgsql-ogr-fdw-1.0.5/input/pgsql.source000066400000000000000000000047761321206656700177430ustar00rootroot00000000000000CREATE 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, size integer, value float8, num numeric(6,2), dt date, tm time, dttm timestamp, varch char(8), yn char ); ---------------------------------------------------------------------- INSERT INTO bytea_local (name, geom, age, size, value, num, dt, tm, dttm, varch, yn) VALUES ('Jim', '14232'::bytea, 23, 1, 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, size, value, num, dt, tm, dttm, varch, yn) VALUES ('Marvin', '55555'::bytea, 34, 2, 5.4, 10.13, '2011-11-11'::date, '15:21:45'::time, '2011-11-11 15:21:45'::timestamp, 'that', 'n' ); INSERT INTO bytea_local (name, geom, age, size, value, num, dt, tm, dttm, varch, yn) VALUES (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); ---------------------------------------------------------------------- CREATE FOREIGN TABLE bytea_fdw ( fid integer, geom bytea, name varchar, age bigint, size integer, 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, size, 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, size, value, num, dt, tm, dttm, varch, yn FROM bytea_fdw; ---------------------------------------------------------------------- INSERT INTO bytea_fdw (name, geom, age, size, value, num, dt, tm, dttm, varch, yn) VALUES ('Margaret', '2222'::bytea, 12, 5, 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, size, value, num, dt, tm, dttm, varch, yn; SELECT fid, name, geom, age, size, value, num, dt, tm, dttm, varch, yn FROM bytea_fdw WHERE fid = 4; UPDATE bytea_fdw SET name = 'Maggie', num = 45.34, yn = 'n' WHERE age = 12; SELECT fid, name, num, yn FROM bytea_fdw WHERE fid = 4; 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 = 4; DELETE FROM bytea_fdw WHERE fid = 4; SELECT a.fid, a.name, b.name FROM bytea_local a JOIN bytea_fdw b USING (fid); pgsql-ogr-fdw-1.0.5/ogr_fdw--1.0.sql000066400000000000000000000007171321206656700167440ustar00rootroot00000000000000/* 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; pgsql-ogr-fdw-1.0.5/ogr_fdw.c000066400000000000000000002203471321206656700160210ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * ogr_fdw.c * foreign-data wrapper for GIS data access. * * Copyright (c) 2014-2015, Paul Ramsey * *------------------------------------------------------------------------- */ /* * System */ #include #include #include "postgres.h" /* * Require PostgreSQL >= 9.3 */ #if PG_VERSION_NUM < 90300 #error "OGR FDW requires PostgreSQL version 9.3 or higher" #else /* * Definition of stringToQualifiedNameList */ #if PG_VERSION_NUM >= 100000 #include "utils/regproc.h" #endif /* * 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; #if GDAL_VERSION_MAJOR >= 2 && GDAL_VERSION_MINOR >= 1 static void ogrErrorHandler(CPLErr eErrClass, int err_no, const char *msg) { switch (eErrClass) { case CE_None: elog(NOTICE, "[%d] %s", err_no, msg); break; case CE_Debug: elog(DEBUG2, "[%d] %s", err_no, msg); break; case CE_Warning: elog(WARNING, "[%d] %s", err_no, msg); break; case CE_Failure: case CE_Fatal: default: elog(ERROR, "[%d] %s", err_no, msg); break; } return; } #endif void _PG_init(void) { Oid typoid = TypenameGetTypid("geometry"); if (OidIsValid(typoid) && get_typisdefined(typoid)) { GEOMETRYOID = typoid; } else { GEOMETRYOID = BYTEAOID; } on_proc_exit(&ogr_fdw_exit, PointerGetDatum(NULL)); #if GDAL_VERSION_MAJOR >= 2 && GDAL_VERSION_MINOR >= 1 /* Hook up the GDAL error handlers to PgSQL elog() */ CPLSetErrorHandler(ogrErrorHandler); CPLSetCurrentErrorHandlerCatchDebug(true); #endif } /* * 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?") )); } ogr.lyr_utf8 = OGR_L_TestCapability(ogr.lyr, OLCStringsAsUTF8); 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, ¶ms_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; 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_NUM >= GDAL_COMPUTE_VERSION(1,11,0) 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; #if PG_VERSION_NUM >= 110000 Form_pg_attribute att_tuple = &tupdesc->attrs[i]; #else Form_pg_attribute att_tuple = tupdesc->attrs[i]; #endif 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); value = OidFunctionCall3(pginputfunc, cdata, ObjectIdGetDatum(InvalidOid), Int32GetDatum(pgtypmod)); 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_NUM >= GDAL_COMPUTE_VERSION(1,11,0) 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 ) { #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,2,0) int field_not_null = OGR_F_IsFieldSet(feat, ogrfldnum) && ! OGR_F_IsFieldNull(feat, ogrfldnum); #else int field_not_null = OGR_F_IsFieldSet(feat, ogrfldnum); #endif /* Ensure that the OGR data type fits the destination Pg column */ ogrCanConvertToPg(ogrfldtype, pgtype, pgname, tbl->tblname); /* Only convert non-null fields */ if ( field_not_null ) { 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_in = OGR_F_GetFieldAsString(feat, ogrfldnum); size_t cstr_len = cstr_in ? strlen(cstr_in) : 0; if ( cstr_in && cstr_len > 0 ) { char *cstr_decoded; if(execstate->ogr.lyr_utf8) cstr_decoded = pg_any_to_server(cstr_in, cstr_len, PG_UTF8); else cstr_decoded = pstrdup(cstr_in); nulls[i] = false; values[i] = pgDatumFromCString(cstr_decoded, 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_NUM >= GDAL_COMPUTE_VERSION(1,11,0) 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_NUM >= GDAL_COMPUTE_VERSION(1,11,0) 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 */ feat = OGR_L_GetNextFeature(execstate->ogr.lyr); if ( feat ) { /* 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++ ) { #if PG_VERSION_NUM >= 110000 NameData attname = td->attrs[i].attname; Oid atttypeid = td->attrs[i].atttypid; #else NameData attname = td->attrs[i]->attname; Oid atttypeid = td->attrs[i]->atttypid; #endif 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)); #if PG_VERSION_NUM >= 110000 att = &tupdesc->attrs[fid_column]; #else att = tupdesc->attrs[fid_column]; #endif /* 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]; #if PG_VERSION_NUM >= 110000 fid_type = td->attrs[fid_column].atttypid; #else fid_type = td->attrs[fid_column]->atttypid; #endif 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]; #if PG_VERSION_NUM >= 110000 fid_type = td->attrs[fid_column].atttypid; #else fid_type = td->attrs[fid_column]->atttypid; #endif 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 */ pgsql-ogr-fdw-1.0.5/ogr_fdw.control000066400000000000000000000002301321206656700172420ustar00rootroot00000000000000# ogr_fdw extension comment = 'foreign-data wrapper for GIS data access' default_version = '1.0' module_pathname = '$libdir/ogr_fdw' relocatable = true pgsql-ogr-fdw-1.0.5/ogr_fdw.h000066400000000000000000000114671321206656700160270ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * ogr_fdw.h * foreign-data wrapper for GIS data access. * * Copyright (c) 2014-2015, Paul Ramsey * *------------------------------------------------------------------------- */ #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; bool lyr_utf8; /* OGR layer will return UTF8 strings */ 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 */ pgsql-ogr-fdw-1.0.5/ogr_fdw_common.c000066400000000000000000000220411321206656700173600ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * ogr_fdw_common.c * foreign-data wrapper for GIS data access. * * Copyright (c) 2014-2016, Paul Ramsey * *------------------------------------------------------------------------- */ #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_append(buf, " OPTIONS (column_name "); ogrDeparseStringLiteral(buf, ogrcolname); stringbuffer_append(buf, ")"); } } 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 *fdw_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(fdw_server)); stringbuffer_append(buf, "layer "); ogrDeparseStringLiteral(buf, OGR_L_GetName(ogr_lyr)); stringbuffer_append(buf, ");\n"); return OGRERR_NONE; } pgsql-ogr-fdw-1.0.5/ogr_fdw_common.h000066400000000000000000000016111321206656700173650ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * ogr_fdw_common.h * foreign-data wrapper for GIS data access. * * Copyright (c) 2014-2015, Paul Ramsey * *------------------------------------------------------------------------- */ #ifndef _OGR_FDW_COMMON_H #define _OGR_FDW_COMMON_H 1 #include #include #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 */ pgsql-ogr-fdw-1.0.5/ogr_fdw_deparse.c000066400000000000000000000350501321206656700175170ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * ogr_fdw_deparse.c * foreign-data wrapper for GIS data access. * * Copyright (c) 2014-2015, Paul Ramsey * * 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; } pgsql-ogr-fdw-1.0.5/ogr_fdw_gdal.h000066400000000000000000000024041321206656700170050ustar00rootroot00000000000000 #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 */pgsql-ogr-fdw-1.0.5/ogr_fdw_info.c000066400000000000000000000112701321206656700170250ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * ogr_fdw_info.c * Commandline utility to read an OGR layer and output a * SQL "create table" statement. * * Copyright (c) 2014-2015, Paul Ramsey * *------------------------------------------------------------------------- */ /* getopt */ #include /* * 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 -l \n" " ogr_fdw_info -s \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 connect 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; } pgsql-ogr-fdw-1.0.5/output/000077500000000000000000000000001321206656700155565ustar00rootroot00000000000000pgsql-ogr-fdw-1.0.5/output/file.source000066400000000000000000000067511321206656700177300ustar00rootroot00000000000000CREATE 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) ------------------------------------------------ pgsql-ogr-fdw-1.0.5/output/import.source000066400000000000000000000066211321206656700203170ustar00rootroot00000000000000set client_min_messages=NOTICE; ------------------------------------------------ CREATE SCHEMA imp1; IMPORT FOREIGN SCHEMA ogr_all LIMIT TO (n2launder) FROM SERVER myserver INTO imp1; NOTICE: Number of tables to be created 1 SELECT c.column_name, c.data_type, attfdwoptions FROM information_schema._pg_foreign_table_columns AS fc INNER JOIN information_schema.columns AS c ON fc.nspname = c.table_schema AND fc.relname = c.table_name AND fc.attname = c.column_name WHERE fc.nspname = 'imp1' and fc.relname = 'n2launder' ORDER BY c.ordinal_position; column_name | data_type | attfdwoptions -------------+-------------------+------------------------- fid | bigint | geom | bytea | n2ame | character varying | {column_name=2ame} age | integer | height | real | b_rthdate | date | {column_name=b-rthdate} (6 rows) 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 SELECT c.column_name, c.data_type, attfdwoptions FROM information_schema._pg_foreign_table_columns AS fc INNER JOIN information_schema.columns AS c ON fc.nspname = c.table_schema AND fc.relname = c.table_name AND fc.attname = c.column_name WHERE fc.nspname = 'imp2' and fc.relname = 'natural' ORDER BY c.ordinal_position; column_name | data_type | attfdwoptions -------------+-------------------+--------------- fid | bigint | id | real | natural | character varying | (3 rows) SELECT "natural" FROM imp2."natural"; natural --------- wood land (2 rows) ------------------------------------------------ CREATE SERVER svr_test_apost FOREIGN DATA WRAPPER ogr_fdw OPTIONS ( datasource '@abs_srcdir@/data/no_geom_apost.csv', format 'CSV' ); CREATE SCHEMA imp3; IMPORT FOREIGN SCHEMA ogr_all LIMIT TO (no_geom_apost) FROM SERVER svr_test_apost INTO imp3; NOTICE: Number of tables to be created 1 SELECT c.column_name, c.data_type, attfdwoptions FROM information_schema._pg_foreign_table_columns AS fc INNER JOIN information_schema.columns AS c ON fc.nspname = c.table_schema AND fc.relname = c.table_name AND fc.attname = c.column_name WHERE fc.nspname = 'imp3' and fc.relname = 'no_geom_apost' ORDER BY c.ordinal_position; column_name | data_type | attfdwoptions ----------------+-------------------+-------------------------------- fid | bigint | name | character varying | age | character varying | person_s_value | character varying | {"column_name=person's value"} (4 rows) SELECT * FROM imp3.no_geom_apost; fid | name | age | person_s_value -----+---------+-----+---------------- 1 | Peter | 34 | 10.2 2 | John | 77 | 3.4 3 | Paul | 45 | 19.2 4 | Matthew | 35 | 18.2 (4 rows) ------------------------------------------------ pgsql-ogr-fdw-1.0.5/output/pgsql.source000066400000000000000000000110641321206656700201300ustar00rootroot00000000000000CREATE 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, size integer, value float8, num numeric(6,2), dt date, tm time, dttm timestamp, varch char(8), yn char ); ---------------------------------------------------------------------- INSERT INTO bytea_local (name, geom, age, size, value, num, dt, tm, dttm, varch, yn) VALUES ('Jim', '14232'::bytea, 23, 1, 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, size, value, num, dt, tm, dttm, varch, yn) VALUES ('Marvin', '55555'::bytea, 34, 2, 5.4, 10.13, '2011-11-11'::date, '15:21:45'::time, '2011-11-11 15:21:45'::timestamp, 'that', 'n' ); INSERT INTO bytea_local (name, geom, age, size, value, num, dt, tm, dttm, varch, yn) VALUES (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); ---------------------------------------------------------------------- CREATE FOREIGN TABLE bytea_fdw ( fid integer, geom bytea, name varchar, age bigint, size integer, 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, size, value, num, dt, tm, dttm, varch, yn FROM bytea_fdw; fid | name | geom | age | size | value | num | dt | tm | dttm | varch | yn -----+--------+--------------+-----+------+-------+-------+------------+----------+--------------------------+----------+---- 1 | Jim | \x3134323332 | 23 | 1 | 4.3 | 5.50 | 10-10-2010 | 13:23:21 | Sun Oct 10 13:23:21 2010 | this | y 2 | Marvin | \x3535353535 | 34 | 2 | 5.4 | 10.13 | 11-11-2011 | 15:21:45 | Fri Nov 11 15:21:45 2011 | that | n 3 | | | | | | | | | | | (3 rows) SELECT a.name, b.name FROM bytea_local a JOIN bytea_fdw b USING (fid); name | name --------+-------- Jim | Jim Marvin | Marvin | (3 rows) EXPLAIN VERBOSE SELECT fid, name, geom, age, size, 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=166) Output: fid, name, geom, age, size, value, num, dt, tm, dttm, varch, yn (2 rows) ---------------------------------------------------------------------- INSERT INTO bytea_fdw (name, geom, age, size, value, num, dt, tm, dttm, varch, yn) VALUES ('Margaret', '2222'::bytea, 12, 5, 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, size, value, num, dt, tm, dttm, varch, yn; fid | name | geom | age | size | value | num | dt | tm | dttm | varch | yn -----+----------+------------+-----+------+-------+-------+------------+----------+--------------------------+----------+---- 4 | Margaret | \x32323232 | 12 | 5 | 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, size, value, num, dt, tm, dttm, varch, yn FROM bytea_fdw WHERE fid = 4; fid | name | geom | age | size | value | num | dt | tm | dttm | varch | yn -----+----------+------------+-----+------+-------+-------+------------+----------+--------------------------+----------+---- 4 | Margaret | \x32323232 | 12 | 5 | 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 = 4; fid | name | num | yn -----+--------+-------+---- 4 | 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 = 4; fid | dt | tm -----+------------+---------- 4 | 12-13-2089 | 01:23:45 (1 row) DELETE FROM bytea_fdw WHERE fid = 4; 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 3 | | (3 rows) pgsql-ogr-fdw-1.0.5/sql/000077500000000000000000000000001321206656700150155ustar00rootroot00000000000000pgsql-ogr-fdw-1.0.5/sql/.gitignore000066400000000000000000000001071321206656700170030ustar00rootroot00000000000000# Ignore everything in this directory * # Except this file !.gitignore pgsql-ogr-fdw-1.0.5/stringbuffer.c000066400000000000000000000174151321206656700170720ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * stringbuffer.c * simple stringbuffer * * Copyright (c) 2009, Paul Ramsey * 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, 2); /* space for char + null terminator */ *(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; } pgsql-ogr-fdw-1.0.5/stringbuffer.h000066400000000000000000000031201321206656700170630ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * stringbuffer.h * simple stringbuffer * * Copyright (c) 2009, Paul Ramsey * Copyright (c) 2002 Thamer Alharbash * *------------------------------------------------------------------------- */ #ifndef _STRINGBUFFER_H #define _STRINGBUFFER_H 1 #include #include #include #include #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 */