├── .gitmodules ├── .gitignore ├── sql ├── firebird_fdw--0.3.0--0.4.0.sql ├── firebird_fdw--0.4.0--0.5.0.sql ├── firebird_fdw--0.5.0--1.0.0.sql ├── firebird_fdw--1.2.0--1.3.0.sql ├── firebird_fdw--1.3.0--1.4.0.sql ├── firebird_fdw--1.4.0--1.5.0.sql ├── firebird_fdw--1.0.0--1.1.0.sql ├── firebird_fdw--0.3.0.sql ├── firebird_fdw--1.1.0--1.2.0.sql ├── firebird_fdw--0.4.0.sql ├── firebird_fdw--0.5.0.sql ├── firebird_fdw--1.0.0.sql ├── firebird_fdw--1.1.0.sql ├── firebird_fdw--1.2.0.sql ├── firebird_fdw--1.3.0.sql ├── firebird_fdw--1.4.0.sql └── firebird_fdw--1.5.0.sql ├── firebird_fdw.control ├── BUGS ├── License ├── t ├── 16-functions.pl ├── 13-copy.pl ├── 09-identifier-quoting.pl ├── 11-pushdowns.pl ├── 12-misc.pl ├── 07-query-tables.pl ├── 01-extension.pl ├── 10-generated-columns.pl ├── 05-table-options.pl ├── 17-scans.pl ├── 08-server-options.pl ├── 18-batch-insert.pl ├── 15-implicit-booleans.pl ├── 02-returning.pl ├── 19-truncate.pl ├── 03-triggers.pl ├── 04-data-types.pl ├── 06-import-foreign-schema.pl └── FirebirdFDWNode.pm ├── doc ├── INSTALL-debian-ubuntu.md ├── INSTALL-osx.md ├── INSTALL-centos-redhat.md └── ENCODINGS.md ├── packaging ├── opensuse │ ├── postgresql92-firebird_fdw.spec │ └── postgresql93-firebird_fdw.spec └── redhat │ ├── postgresql10-firebird_fdw.spec │ ├── postgresql11-firebird_fdw.spec │ ├── postgresql13-firebird_fdw.spec │ ├── postgresql14-firebird_fdw.spec │ ├── postgresql15-firebird_fdw.spec │ └── postgresql12-firebird_fdw.spec ├── META.json ├── Makefile ├── CHANGELOG └── src ├── firebird_fdw.h ├── options.c └── connection.c /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp_check/ 2 | log/ 3 | *.o 4 | *.so 5 | *~ 6 | .#* 7 | *dSYM* 8 | *.tgz 9 | *.tar.gz 10 | cov-int 11 | -------------------------------------------------------------------------------- /sql/firebird_fdw--0.3.0--0.4.0.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 2 | RETURNS pg_catalog.int4 STRICT 3 | AS 'MODULE_PATHNAME' LANGUAGE C; 4 | -------------------------------------------------------------------------------- /firebird_fdw.control: -------------------------------------------------------------------------------- 1 | # Firebird FDW 2 | comment = 'foreign data wrapper for Firebird' 3 | default_version = '1.5.0' 4 | module_pathname = '$libdir/firebird_fdw' 5 | relocatable = true 6 | -------------------------------------------------------------------------------- /sql/firebird_fdw--0.4.0--0.5.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | -------------------------------------------------------------------------------- /sql/firebird_fdw--0.5.0--1.0.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | -------------------------------------------------------------------------------- /BUGS: -------------------------------------------------------------------------------- 1 | BUGS 2 | ==== 3 | 4 | - On OS X, during a long running operation (e.g. inserting a large number 5 | of records into a foreign table), pressing CTRL-C in psql results in 6 | the connection to the server being lost 7 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.2.0--1.3.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | 4 | -- this file intentionally left empty 5 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.3.0--1.4.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | 4 | -- this file intentionally left empty 5 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.4.0--1.5.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | 4 | -- this file intentionally left empty 5 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.0.0--1.1.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | 4 | CREATE OR REPLACE FUNCTION firebird_fdw_close_connections() 5 | RETURNS void 6 | AS 'MODULE_PATHNAME' 7 | LANGUAGE C STRICT; 8 | 9 | CREATE OR REPLACE FUNCTION firebird_fdw_diag( 10 | OUT name TEXT, 11 | OUT setting TEXT) 12 | RETURNS SETOF record 13 | AS 'MODULE_PATHNAME' 14 | LANGUAGE C STRICT VOLATILE PARALLEL SAFE; 15 | -------------------------------------------------------------------------------- /sql/firebird_fdw--0.3.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | CREATE FUNCTION firebird_fdw_handler() 18 | RETURNS fdw_handler 19 | AS 'MODULE_PATHNAME' 20 | LANGUAGE C STRICT; 21 | 22 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 23 | RETURNS void 24 | AS 'MODULE_PATHNAME' 25 | LANGUAGE C STRICT; 26 | 27 | CREATE FOREIGN DATA WRAPPER firebird_fdw 28 | HANDLER firebird_fdw_handler 29 | VALIDATOR firebird_fdw_validator; 30 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.1.0--1.2.0.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 3 | 4 | ALTER FUNCTION firebird_fdw_server_options( 5 | IN server_name TEXT, 6 | OUT name TEXT, 7 | OUT value TEXT, 8 | OUT provided BOOL 9 | ) 10 | STABLE PARALLEL UNSAFE; 11 | 12 | CREATE OR REPLACE FUNCTION firebird_fdw_server_options( 13 | IN server_name TEXT, 14 | OUT name TEXT, 15 | OUT value TEXT, 16 | OUT provided BOOL 17 | ) 18 | RETURNS SETOF record 19 | AS 'MODULE_PATHNAME' 20 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 21 | 22 | CREATE OR REPLACE FUNCTION firebird_version( 23 | OUT server_name TEXT, 24 | OUT firebird_version INT, 25 | OUT firebird_version_string TEXT 26 | ) 27 | RETURNS SETOF record 28 | AS 'MODULE_PATHNAME' 29 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 30 | -------------------------------------------------------------------------------- /sql/firebird_fdw--0.4.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2018 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--0.4.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | CREATE FUNCTION firebird_fdw_handler() 18 | RETURNS fdw_handler 19 | AS 'MODULE_PATHNAME' 20 | LANGUAGE C STRICT; 21 | 22 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 23 | RETURNS void 24 | AS 'MODULE_PATHNAME' 25 | LANGUAGE C STRICT; 26 | 27 | CREATE FOREIGN DATA WRAPPER firebird_fdw 28 | HANDLER firebird_fdw_handler 29 | VALIDATOR firebird_fdw_validator; 30 | 31 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 32 | RETURNS pg_catalog.int4 STRICT 33 | AS 'MODULE_PATHNAME' LANGUAGE C; 34 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | This software, PostgreSQL Firebird Foreign Data Wrapper, is released under 2 | the terms of the PostgreSQL License. 3 | 4 | Copyright (c) 2013 - 2020, Ian Barwick 5 | 6 | Permission to use, copy, modify, and distribute this software and its 7 | documentation for any purpose, without fee, and without a written agreement 8 | is hereby granted, provided that the above copyright notice and this paragraph 9 | and the following two paragraphs appear in all copies. 10 | 11 | IN NO EVENT SHALL Ian Barwick BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, 12 | SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING 13 | OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF Ian Barwick 14 | HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | 16 | Ian Barwick SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, 19 | AND Ian Barwick HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, 20 | UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 21 | -------------------------------------------------------------------------------- /sql/firebird_fdw--0.5.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2018 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--0.5.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 STRICT 36 | AS 'MODULE_PATHNAME' LANGUAGE C; 37 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.0.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2018 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--1.0.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 STRICT 36 | AS 'MODULE_PATHNAME' LANGUAGE C; 37 | -------------------------------------------------------------------------------- /t/16-functions.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 16-functions.pl 4 | # 5 | # Checks for functions provided by firebird_fdw 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More tests => 2; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | my ($version, $version_int) = $node->get_firebird_version(); 20 | 21 | # 1. Check firebird_version() output 22 | # ---------------------------------- 23 | 24 | my $version_expected = sprintf( 25 | q/%s|%i|%s/, 26 | $node->server_name(), 27 | $version_int, 28 | $version, 29 | ); 30 | 31 | 32 | my $q1_sql = q|SELECT * FROM firebird_version()|; 33 | my ($q1_res, $q1_stdout, $q1_stderr) = $node->psql($q1_sql); 34 | 35 | is ( 36 | $q1_stdout, 37 | $version_expected, 38 | q|Check firebird_version() output|, 39 | ); 40 | 41 | 42 | 43 | # 2. Check firebird_version() output as non-superuser 44 | # --------------------------------------------------- 45 | 46 | $node->psql(q|CREATE USER foo|); 47 | 48 | my $q2_sql = q|SET session AUTHORIZATION foo; SELECT * FROM firebird_version()|; 49 | my ($q2_res, $q2_stdout, $q2_stderr) = $node->psql($q2_sql); 50 | 51 | is ( 52 | $q2_stdout, 53 | $version_expected, 54 | q|Check firebird_version() output with non-superuser|, 55 | ); 56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/INSTALL-debian-ubuntu.md: -------------------------------------------------------------------------------- 1 | Installing firebird_fdw on Debian/Ubuntu 2 | ======================================== 3 | 4 | Package installation 5 | -------------------- 6 | 7 | Currently no packages are available for Debian/Ubuntu. 8 | 9 | Source installation 10 | ------------------- 11 | 12 | ### Prerequisites 13 | 14 | - `libfq`, a `libpq`-like API wrapper for the Firebird C API; see: 15 | 16 | https://github.com/ibarwick/libfq 17 | 18 | This will need to be built from source too; see the instructions 19 | in the `libfq` repository. 20 | *NOTE* the latest `libfq` version should be used with the current 21 | `firebird_fdw` version, as the two are usually developed in tandem. 22 | 23 | - following packages must be installed: 24 | - `firebird-dev` 25 | - `libfbclient2` 26 | 27 | If PostgreSQL itself is not installed from source, the appropriate 28 | `dev` package is also required: 29 | 30 | - `postgresql-server-dev-{VERSION}` 31 | 32 | where `{VERSION}` corresponds to the PostgreSQL version `firebird_fdw` 33 | is being built against (e.g.`12`, `9.6`). 34 | 35 | *IMPORTANT*: you *must* build `firebird_fdw` against the PostgreSQL version 36 | it will be installed on. 37 | 38 | ### Building 39 | 40 | Ensure the `pg_config` binary for the target PostgreSQL version is in 41 | the shell path; then execute: 42 | 43 | `make && sudo make install` 44 | 45 | which should build and install `firebird_fdw`. 46 | -------------------------------------------------------------------------------- /packaging/opensuse/postgresql92-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql92-firebird_fdw 3 | Version: 0.2.5 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql92-devel 11 | BuildRequires: libfq 12 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 13 | Requires: postgresql92-server libfq 14 | 15 | %description 16 | This is an experimental foreign data wrapper (FDW) to connect PostgreSQL 17 | to Firebird. It provides read-only (SELECT) support, WHERE-clause pushdowns, 18 | connection caching and Firebird transaction support. (INSERT/UPDATE/DELETE 19 | support is available with PostgreSQL 9.3 and later). 20 | 21 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 22 | 23 | %prep 24 | CFLAGS="-I/usr/include/firebird" 25 | %setup 26 | 27 | %build 28 | 29 | make 30 | %install 31 | rm -rf $RPM_BUILD_ROOT 32 | make DESTDIR=$RPM_BUILD_ROOT install%clean 33 | rm -rf $RPM_BUILD_ROOT 34 | %files 35 | %defattr(-, root, root) 36 | /usr/lib/postgresql92/lib64/firebird_fdw.so 37 | /usr/share/postgresql92/extension/firebird_fdw--%{version}.sql 38 | /usr/share/postgresql92/extension/firebird_fdw.control 39 | 40 | %changelog 41 | * Tue Feb 11 2014 Ian Barwick (barwick@gmail.com) 42 | - First draft 43 | -------------------------------------------------------------------------------- /packaging/opensuse/postgresql93-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql93-firebird_fdw 3 | Version: 0.2.5 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql93-devel 11 | BuildRequires: libfq 12 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 13 | Requires: postgresql93-server libfq 14 | 15 | %description 16 | This is an experimental foreign data wrapper (FDW) to connect PostgreSQL 17 | to Firebird. It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 18 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 19 | support. 20 | 21 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 22 | 23 | 24 | %prep 25 | %setup 26 | 27 | %build 28 | 29 | PG_CPPFLAGS="-I/usr/include/firebird" make 30 | 31 | %install 32 | rm -rf $RPM_BUILD_ROOT 33 | export PG_CONFIG=/usr/bin/pg_config 34 | make DESTDIR=$RPM_BUILD_ROOT install 35 | 36 | %clean 37 | rm -rf $RPM_BUILD_ROOT 38 | 39 | %files 40 | %defattr(-, root, root) 41 | /usr/lib/postgresql93/lib64/firebird_fdw.so 42 | /usr/share/postgresql93/extension/firebird_fdw--%{version}.sql 43 | /usr/share/postgresql93/extension/firebird_fdw.control 44 | 45 | %changelog 46 | * Tue Feb 11 2014 Ian Barwick (barwick@gmail.com) 47 | - First draft 48 | 49 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebird_fdw", 3 | "abstract": "A PostgreSQL foreign data wrapper (FDW) for Firebird", 4 | "version": "1.5.0", 5 | "release_status": "stable", 6 | "maintainer": "Ian Barwick ", 7 | "license": { 8 | "PostgreSQL": "https://www.postgresql.org/about/licence" 9 | }, 10 | "prereqs": { 11 | "runtime": { 12 | "requires": { 13 | "PostgreSQL": "10.0" 14 | } 15 | } 16 | }, 17 | "provides": { 18 | "firebird_fdw": { 19 | "abstract": "A PostgreSQL foreign data wrapper (FDW) for Firebird", 20 | "file": "firebird_fdw--1.5.0.sql", 21 | "docfile": "README.md", 22 | "version": "1.5.0" 23 | } 24 | }, 25 | "resources": { 26 | "homepage": "https://sql-info.de/postgresql/firebird-fdw/index.html", 27 | "repository": { 28 | "url": "git://github.com/ibarwick/firebird_fdw.git", 29 | "web": "https://github.com/ibarwick/firebird_fdw", 30 | "type": "git" 31 | }, 32 | "bugtracker": { 33 | "web": "https://github.com/ibarwick/firebird_fdw/issues/" 34 | } 35 | }, 36 | "meta-spec": { 37 | "version": "1.0.0", 38 | "url": "https://pgxn.org/meta/spec.txt" 39 | }, 40 | "generated_by": "Ian Barwick", 41 | "tags": [ 42 | "foreign data wrapper", 43 | "fdw", 44 | "firebird", 45 | "sql med" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.1.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2021 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--1.1.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 36 | AS 'MODULE_PATHNAME' 37 | LANGUAGE C STRICT; 38 | 39 | CREATE OR REPLACE FUNCTION firebird_fdw_close_connections() 40 | RETURNS void 41 | AS 'MODULE_PATHNAME' 42 | LANGUAGE C STRICT; 43 | 44 | CREATE OR REPLACE FUNCTION firebird_fdw_diag( 45 | OUT name TEXT, 46 | OUT setting TEXT) 47 | RETURNS SETOF record 48 | AS 'MODULE_PATHNAME' 49 | LANGUAGE C STRICT VOLATILE PARALLEL SAFE; 50 | -------------------------------------------------------------------------------- /t/13-copy.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 13-copy.pl 4 | # 5 | # Check support for COPY 6 | # 7 | # See also 14-tuple-routing.pl for COPY support with tuple routing 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More; 13 | 14 | use FirebirdFDWNode; 15 | 16 | # Initialize nodes 17 | # ---------------- 18 | 19 | my $node = FirebirdFDWNode->new(); 20 | 21 | my $version = $node->pg_version(); 22 | 23 | if ($version < 110000) { 24 | plan skip_all => sprintf( 25 | q|version is %i, tests for 11 and later|, 26 | $version, 27 | ); 28 | } 29 | else { 30 | plan tests => 1; 31 | } 32 | 33 | 34 | # 1. Check simple COPY 35 | # -------------------- 36 | 37 | my $table_1 = $node->init_table(); 38 | 39 | my $copy_sql_1 = sprintf( 40 | <<'EO_SQL', 41 | COPY %s FROM STDIN WITH (format 'csv'); 42 | xx,Xxxish,Xxxisch 43 | zz,Zzzish,Zzzisch 44 | \. 45 | EO_SQL 46 | $table_1, 47 | ); 48 | 49 | $node->safe_psql($copy_sql_1); 50 | 51 | my $select_q1 = sprintf( 52 | q|SELECT * FROM %s ORDER BY 1|, 53 | $table_1, 54 | ); 55 | 56 | my $q1 = $node->firebird_conn()->prepare($select_q1); 57 | 58 | $q1->execute(); 59 | 60 | my $res1 = $node->firebird_format_results($q1); 61 | 62 | $q1->finish(); 63 | 64 | my $expected1 = <firebird_drop_table($table_1); 78 | 79 | 80 | # Clean up 81 | # -------- 82 | 83 | $node->drop_foreign_server(); 84 | 85 | done_testing(); 86 | -------------------------------------------------------------------------------- /doc/INSTALL-osx.md: -------------------------------------------------------------------------------- 1 | Installing firebird_fdw on OS X 2 | =============================== 3 | 4 | Package installation 5 | -------------------- 6 | 7 | Currently no packages are available for OS X. 8 | 9 | Source installation 10 | ------------------- 11 | 12 | ### Prerequisites 13 | 14 | - `libfq`, a `libpq`-like API wrapper for the Firebird C API; see: 15 | 16 | https://github.com/ibarwick/libfq 17 | 18 | This will need to be built from source too; see the instructions 19 | in the `libfq` repository. 20 | *NOTE* the latest `libfq` version should be used with the current 21 | `firebird_fdw` version, as the two are usually developed in tandem. 22 | 23 | If PostgreSQL itself is not installed from source, the appropriate 24 | `dev` package for the target PostgreSQL version is also required. 25 | 26 | *IMPORTANT*: you *must* build `firebird_fdw` against the PostgreSQL version 27 | it will be installed on. 28 | 29 | ### Building 30 | 31 | Following environment variables should be set so that the PostgreSQL build system 32 | can find the required Firebird files: 33 | 34 | export PG_CPPFLAGS="-I /Library/Frameworks/Firebird.framework/Versions/A/Headers/" 35 | export SHLIB_LINK="-L/Library/Frameworks/Firebird.framework/Versions/A/Libraries/" 36 | 37 | Note that particularly on OS X, tthe Firebird include/library files often end up in 38 | non-standard locations; the Firebird utility [fb_config](https://firebirdsql.org/manual/fbscripts-fb-config.html) 39 | can assist with locating them. 40 | 41 | Ensure the `pg_config` binary for the target PostgreSQL version is in 42 | the shell path; then execute: 43 | 44 | `make && sudo make install` 45 | 46 | which should build and install `firebird_fdw`. 47 | -------------------------------------------------------------------------------- /t/09-identifier-quoting.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 09-identifier-quoting.pl 4 | # 5 | # Check identifier quoting options 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | # 1. Check server option "quote_identifiers" 20 | # ------------------------------------------ 21 | 22 | # Create Firebird table with quoted identifiers 23 | 24 | $node->alter_server_option('quote_identifiers', 'true'); 25 | 26 | my $table_name = $node->make_table_name(); 27 | 28 | my $tbl_sql = sprintf( 29 | <firebird_conn()->prepare($tbl_sql); 41 | $tbl_query->execute(); 42 | $tbl_query->finish(); 43 | 44 | my $ftbl_sql = sprintf( 45 | <{server_name}, 57 | $table_name, 58 | ); 59 | 60 | $node->safe_psql($ftbl_sql); 61 | 62 | my $insert_q1 = sprintf( 63 | <psql($insert_q1); 71 | 72 | 73 | is( 74 | $res_stdout, 75 | '1|2|3|4', 76 | 'Default options OK', 77 | ); 78 | 79 | 80 | $node->firebird_drop_table($table_name, 1); 81 | done_testing(); 82 | -------------------------------------------------------------------------------- /doc/INSTALL-centos-redhat.md: -------------------------------------------------------------------------------- 1 | Installing firebird_fdw on CentOS/Redhat 2 | ======================================== 3 | 4 | RPM package installation 5 | ------------------------ 6 | 7 | Beginning with version 1.2.1, `firebird_fdw` (and `libfq`) packages for 8 | current PostgreSQL versions are available via the PostgreSQL community YUM 9 | repository; for details see here: . 10 | 11 | Source installation 12 | ------------------- 13 | 14 | ### Prerequisites 15 | 16 | - `libfq`, a `libpq`-like API wrapper for the Firebird C API; see: 17 | 18 | https://github.com/ibarwick/libfq 19 | 20 | `libfq` packages are available via the Fedora "copr" build system. 21 | see: 22 | 23 | [Source installation instructions](https://github.com/ibarwick/libfq/blob/master/INSTALL.md) 24 | are also available. 25 | 26 | *NOTE* the latest `libfq` version should be used with the current 27 | `firebird_fdw` version, as the two are usually developed in tandem. 28 | 29 | - following packages must be installed: 30 | - `firebird-devel` 31 | - `libfbclient2` 32 | 33 | If PostgreSQL itself is not installed from source, the appropriate 34 | `dev` package is also required: 35 | 36 | - `postgresql{VERSION}-devel` 37 | 38 | where `{VERSION}` corresponds to the PostgreSQL version `firebird_fdw` 39 | is being built against (e.g.`12`, `96`). 40 | 41 | *IMPORTANT*: you *must* build `firebird_fdw` against the PostgreSQL version 42 | it will be installed on. 43 | 44 | #### Building 45 | 46 | Ensure the `PGXS` build system can locate the Firebird header files with: 47 | 48 | export PG_CPPFLAGS="-I /usr/include/firebird" 49 | 50 | Ensure the `pg_config` binary for the target PostgreSQL version is in 51 | the shell path; then execute: 52 | 53 | make && sudo make install 54 | 55 | which should build and install `firebird_fdw`. 56 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.2.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2021 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--1.2.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 36 | AS 'MODULE_PATHNAME' 37 | LANGUAGE C STRICT; 38 | 39 | CREATE OR REPLACE FUNCTION firebird_fdw_close_connections() 40 | RETURNS void 41 | AS 'MODULE_PATHNAME' 42 | LANGUAGE C STRICT; 43 | 44 | CREATE OR REPLACE FUNCTION firebird_fdw_server_options( 45 | IN server_name TEXT, 46 | OUT name TEXT, 47 | OUT value TEXT, 48 | OUT provided BOOL 49 | ) 50 | RETURNS SETOF record 51 | AS 'MODULE_PATHNAME' 52 | LANGUAGE C STRICT STABLE PARALLEL UNSAFE; 53 | 54 | CREATE OR REPLACE FUNCTION firebird_fdw_diag( 55 | OUT name TEXT, 56 | OUT setting TEXT 57 | ) 58 | RETURNS SETOF record 59 | AS 'MODULE_PATHNAME' 60 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 61 | 62 | CREATE OR REPLACE FUNCTION firebird_version( 63 | OUT server_name TEXT, 64 | OUT firebird_version INT, 65 | OUT firebird_version_string TEXT 66 | ) 67 | RETURNS SETOF record 68 | AS 'MODULE_PATHNAME' 69 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 70 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.3.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2023 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--1.3.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 36 | AS 'MODULE_PATHNAME' 37 | LANGUAGE C STRICT; 38 | 39 | CREATE OR REPLACE FUNCTION firebird_fdw_close_connections() 40 | RETURNS void 41 | AS 'MODULE_PATHNAME' 42 | LANGUAGE C STRICT; 43 | 44 | CREATE OR REPLACE FUNCTION firebird_fdw_server_options( 45 | IN server_name TEXT, 46 | OUT name TEXT, 47 | OUT value TEXT, 48 | OUT provided BOOL 49 | ) 50 | RETURNS SETOF record 51 | AS 'MODULE_PATHNAME' 52 | LANGUAGE C STRICT STABLE PARALLEL UNSAFE; 53 | 54 | CREATE OR REPLACE FUNCTION firebird_fdw_diag( 55 | OUT name TEXT, 56 | OUT setting TEXT 57 | ) 58 | RETURNS SETOF record 59 | AS 'MODULE_PATHNAME' 60 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 61 | 62 | CREATE OR REPLACE FUNCTION firebird_version( 63 | OUT server_name TEXT, 64 | OUT firebird_version INT, 65 | OUT firebird_version_string TEXT 66 | ) 67 | RETURNS SETOF record 68 | AS 'MODULE_PATHNAME' 69 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 70 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.4.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2023 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--1.4.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 36 | AS 'MODULE_PATHNAME' 37 | LANGUAGE C STRICT; 38 | 39 | CREATE OR REPLACE FUNCTION firebird_fdw_close_connections() 40 | RETURNS void 41 | AS 'MODULE_PATHNAME' 42 | LANGUAGE C STRICT; 43 | 44 | CREATE OR REPLACE FUNCTION firebird_fdw_server_options( 45 | IN server_name TEXT, 46 | OUT name TEXT, 47 | OUT value TEXT, 48 | OUT provided BOOL 49 | ) 50 | RETURNS SETOF record 51 | AS 'MODULE_PATHNAME' 52 | LANGUAGE C STRICT STABLE PARALLEL UNSAFE; 53 | 54 | CREATE OR REPLACE FUNCTION firebird_fdw_diag( 55 | OUT name TEXT, 56 | OUT setting TEXT 57 | ) 58 | RETURNS SETOF record 59 | AS 'MODULE_PATHNAME' 60 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 61 | 62 | CREATE OR REPLACE FUNCTION firebird_version( 63 | OUT server_name TEXT, 64 | OUT firebird_version INT, 65 | OUT firebird_version_string TEXT 66 | ) 67 | RETURNS SETOF record 68 | AS 'MODULE_PATHNAME' 69 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 70 | -------------------------------------------------------------------------------- /sql/firebird_fdw--1.5.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Firebird 4 | * 5 | * Copyright (c) 2013-2025 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/sql/firebird_fdw--1.5.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION firebird_fdw" to load this file. \quit 19 | 20 | CREATE FUNCTION firebird_fdw_handler() 21 | RETURNS fdw_handler 22 | AS 'MODULE_PATHNAME' 23 | LANGUAGE C STRICT; 24 | 25 | CREATE FUNCTION firebird_fdw_validator(text[], oid) 26 | RETURNS void 27 | AS 'MODULE_PATHNAME' 28 | LANGUAGE C STRICT; 29 | 30 | CREATE FOREIGN DATA WRAPPER firebird_fdw 31 | HANDLER firebird_fdw_handler 32 | VALIDATOR firebird_fdw_validator; 33 | 34 | CREATE OR REPLACE FUNCTION firebird_fdw_version() 35 | RETURNS pg_catalog.int4 36 | AS 'MODULE_PATHNAME' 37 | LANGUAGE C STRICT; 38 | 39 | CREATE OR REPLACE FUNCTION firebird_fdw_close_connections() 40 | RETURNS void 41 | AS 'MODULE_PATHNAME' 42 | LANGUAGE C STRICT; 43 | 44 | CREATE OR REPLACE FUNCTION firebird_fdw_server_options( 45 | IN server_name TEXT, 46 | OUT name TEXT, 47 | OUT value TEXT, 48 | OUT provided BOOL 49 | ) 50 | RETURNS SETOF record 51 | AS 'MODULE_PATHNAME' 52 | LANGUAGE C STRICT STABLE PARALLEL UNSAFE; 53 | 54 | CREATE OR REPLACE FUNCTION firebird_fdw_diag( 55 | OUT name TEXT, 56 | OUT setting TEXT 57 | ) 58 | RETURNS SETOF record 59 | AS 'MODULE_PATHNAME' 60 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 61 | 62 | CREATE OR REPLACE FUNCTION firebird_version( 63 | OUT server_name TEXT, 64 | OUT firebird_version INT, 65 | OUT firebird_version_string TEXT 66 | ) 67 | RETURNS SETOF record 68 | AS 'MODULE_PATHNAME' 69 | LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; 70 | -------------------------------------------------------------------------------- /t/11-pushdowns.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 11-pushdowns.pl 4 | # 5 | # Check support for generated columns 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | my @boolean_pushdowns = ( 20 | ['IS TRUE' , q|\(\(bool_type IS TRUE\)\)|], 21 | ['IS FALSE', q|\(\(bool_type IS FALSE\)\)|], 22 | ['IS NOT TRUE', q|\(\(bool_type IS FALSE\) OR \(bool_type IS NULL\)\)|], 23 | ['IS NOT FALSE', q|\(\(bool_type IS TRUE\) OR \(bool_type IS NULL\)\)|], 24 | ['IS NULL', q|\(\(bool_type IS NULL\)\)|], 25 | ['IS NOT NULL', q|\(\(bool_type IS NOT NULL\)\)|], 26 | ); 27 | 28 | 29 | 30 | if ($node->{firebird_major_version} < 3) { 31 | plan skip_all => sprintf( 32 | q|No tests for Firebird version %s|, 33 | $node->{firebird_major_version}, 34 | ); 35 | } 36 | 37 | # 1. Boolean pushdowns (Firebird 3+) 38 | # ---------------------------------- 39 | 40 | my $table_name = $node->init_data_type_table(); 41 | 42 | if ($node->{firebird_major_version} >= 3) { 43 | 44 | # Prepare table data 45 | # ------------------ 46 | 47 | 48 | my $bool_insert_sql = sprintf( 49 | <safe_psql($bool_insert_sql); 57 | 58 | foreach my $boolean_pushdown (@boolean_pushdowns) { 59 | my $explain_q = sprintf( 60 | q|EXPLAIN SELECT * FROM %s WHERE bool_type %s|, 61 | $table_name, 62 | $boolean_pushdown->[0], 63 | ); 64 | 65 | my ($explain_res, $explain_stdout, $explain_stderr) = $node->psql( 66 | $explain_q, 67 | ); 68 | 69 | my $explain_expected = sprintf( 70 | q|Firebird query: SELECT.+?WHERE\s+%s|, 71 | $boolean_pushdown->[1], 72 | ); 73 | 74 | like ( 75 | $explain_stdout, 76 | qr/$explain_expected/, 77 | sprintf( 78 | q|Check pushdown for "%s"|, 79 | $boolean_pushdown->[0], 80 | ), 81 | ); 82 | } 83 | 84 | } 85 | 86 | 87 | 88 | # Clean up 89 | # -------- 90 | 91 | $node->drop_foreign_server(); 92 | 93 | done_testing(); 94 | -------------------------------------------------------------------------------- /t/12-misc.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 12-misc.pl 4 | # 5 | # Checks for miscellaneous items which don't fit elsewhere 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | my $version = $node->pg_version(); 20 | 21 | my $tests = 0; 22 | 23 | my @on_conflict_tests = ( 24 | [ 25 | <<'EO_SQL', 26 | INSERT INTO %s 27 | (lang_id, name_english, name_native) 28 | VALUES ('en', 'English', 'English') 29 | ON CONFLICT DO NOTHING 30 | EO_SQL 31 | q|INSERT with ON CONFLICT clause is not supported|, 32 | q|Check INSERT with ON CONFLICT DO NOTHING clause fails|, 33 | ], 34 | [ 35 | <<'EO_SQL', 36 | INSERT INTO %s 37 | (lang_id, name_english, name_native) 38 | VALUES ('en', 'English', 'English') 39 | ON CONFLICT (lang_id) DO NOTHING 40 | EO_SQL 41 | q|there is no unique or exclusion constraint matching the ON CONFLICT specification|, 42 | q|Check INSERT with ON CONFLICT (col) DO NOTHING clause fails|, 43 | ], 44 | [ 45 | <<'EO_SQL', 46 | INSERT INTO %s 47 | (lang_id, name_english, name_native) 48 | VALUES ('en', 'English', 'English') 49 | ON CONFLICT (lang_id) DO UPDATE SET name_english = EXCLUDED.name_english 50 | EO_SQL 51 | q|there is no unique or exclusion constraint matching the ON CONFLICT specification|, 52 | q|Check INSERT with ON CONFLICT (col) DO NOTHING clause fails|, 53 | ], 54 | ); 55 | 56 | 57 | # INSERT ... ON CONFLICT 58 | $tests += scalar @on_conflict_tests; 59 | 60 | 61 | if (!$tests) { 62 | plan skip_all => q|all test(s) skipped|; 63 | } 64 | 65 | 66 | # 1. Check INSERT ... ON CONFLICT 67 | # ------------------------------- 68 | 69 | my $table_name = $node->init_table(); 70 | 71 | foreach my $on_conflict_test (@on_conflict_tests) { 72 | my $insert = sprintf( 73 | $on_conflict_test->[0], 74 | $table_name, 75 | ); 76 | 77 | my ($insert_res, $insert_stdout, $insert_stderr) = $node->psql( 78 | $insert, 79 | ); 80 | 81 | like ( 82 | $insert_stderr, 83 | qr/$on_conflict_test->[1]/, 84 | $on_conflict_test->[2], 85 | ); 86 | } 87 | 88 | done_testing(); 89 | -------------------------------------------------------------------------------- /t/07-query-tables.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 07-query-tables.pl 4 | # 5 | # Test foreign tables created as queries 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | 15 | # Initialize nodes 16 | # ---------------- 17 | 18 | my $node = FirebirdFDWNode->new(); 19 | 20 | # Prepare table 21 | # -------------- 22 | 23 | my $table_name = $node->init_table(firebird_only => 1); 24 | 25 | my $query_table_name = sprintf(q|%s_query|, $table_name); 26 | 27 | # 1) Test basic functionality 28 | # --------------------------- 29 | 30 | my $create_q1 = sprintf( 31 | <<'EO_SQL', 32 | CREATE FOREIGN TABLE %s ( 33 | lang_id CHAR(2), 34 | name_english VARCHAR(64), 35 | name_native VARCHAR(64) 36 | ) 37 | SERVER %s 38 | OPTIONS( 39 | query $$SELECT lang_id, name_english, name_native FROM %s$$ 40 | ) 41 | EO_SQL 42 | $query_table_name, 43 | $node->server_name(), 44 | $table_name, 45 | ); 46 | 47 | $node->safe_psql( $create_q1 ); 48 | 49 | my $insert_q1 = sprintf( 50 | q|INSERT INTO %s (lang_id, name_english, name_native) VALUES('en', 'English', 'English')|, 51 | $table_name, 52 | ); 53 | 54 | my $fb_q1= $node->firebird_conn()->prepare($insert_q1); 55 | 56 | $fb_q1->execute(); 57 | 58 | $fb_q1->finish(); 59 | 60 | my $select_q1 = sprintf( 61 | q|SELECT lang_id, name_english, name_native FROM %s WHERE lang_id = 'en'|, 62 | $query_table_name, 63 | ); 64 | 65 | my ($res, $res_stdout, $res_stderr) = $node->psql( $select_q1 ); 66 | 67 | 68 | is( 69 | $res_stdout, 70 | 'en|English|English', 71 | 'query OK', 72 | ); 73 | 74 | # 2) Verify that insert operations fail 75 | # ------------------------------------- 76 | 77 | my $insert_q2 = sprintf( 78 | q|INSERT INTO %s (lang_id, name_english, name_native) VALUES('de', 'German', 'Deutsch')|, 79 | $query_table_name, 80 | ); 81 | 82 | my ($insert_q2_res, $insert_q2_stdout, $insert_q2_stderr) = $node->psql( 83 | $insert_q2, 84 | ); 85 | 86 | my $expected_q2_stderr = q|unable to modify a foreign table defined as a query|; 87 | like ( 88 | $insert_q2_stderr, 89 | qr/$expected_q2_stderr/, 90 | q|Check INSERT on foreign table defined as query fails|, 91 | ); 92 | 93 | # Clean up 94 | # -------- 95 | 96 | $node->drop_foreign_server(); 97 | $node->firebird_drop_table($table_name); 98 | 99 | done_testing(); 100 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Firebird Foreign Data Wrapper for PostgreSQL 4 | # 5 | # Copyright (c) 2013-2025 Ian Barwick 6 | # 7 | # This software is released under the PostgreSQL Licence 8 | # 9 | # Author: Ian Barwick 10 | # 11 | # IDENTIFICATION 12 | # firebird_fdw/Makefile 13 | # 14 | ############################################################################## 15 | 16 | 17 | EXTENSION = firebird_fdw 18 | EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 19 | 20 | MODULE_big = $(EXTENSION) 21 | 22 | OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) 23 | 24 | FIREBIRD_FDW_DEBUG_BUILD ?= 0 25 | ifneq ($(FIREBIRD_FDW_DEBUG_BUILD),0) 26 | PG_CPPFLAGS += -DFIREBIRD_FDW_DEBUG_BUILD 27 | endif 28 | 29 | +PG_CPPFLAGS += -Werror-missing-prototypes 30 | SHLIB_LINK += -lfq -lfbclient 31 | 32 | DATA = sql/firebird_fdw--0.3.0.sql \ 33 | sql/firebird_fdw--0.3.0--0.4.0.sql \ 34 | sql/firebird_fdw--0.4.0.sql \ 35 | sql/firebird_fdw--0.4.0--0.5.0.sql \ 36 | sql/firebird_fdw--0.5.0.sql \ 37 | sql/firebird_fdw--0.5.0--1.0.0.sql \ 38 | sql/firebird_fdw--1.0.0.sql \ 39 | sql/firebird_fdw--1.0.0--1.1.0.sql \ 40 | sql/firebird_fdw--1.1.0.sql \ 41 | sql/firebird_fdw--1.1.0--1.2.0.sql \ 42 | sql/firebird_fdw--1.2.0.sql \ 43 | sql/firebird_fdw--1.2.0--1.3.0.sql \ 44 | sql/firebird_fdw--1.3.0.sql \ 45 | sql/firebird_fdw--1.3.0--1.4.0.sql \ 46 | sql/firebird_fdw--1.4.0.sql \ 47 | sql/firebird_fdw--1.4.0--1.5.0.sql \ 48 | sql/firebird_fdw--1.5.0.sql 49 | 50 | 51 | ifndef PG_CONFIG 52 | PG_CONFIG = pg_config 53 | endif 54 | 55 | PGXS := $(shell $(PG_CONFIG) --pgxs) 56 | include $(PGXS) 57 | 58 | # Sanity-check supported version 59 | 60 | ifeq (,$(findstring $(MAJORVERSION),10 11 12 13 14 15 16 17 18 19)) 61 | $(error firebird_fdw supports PostgreSQL 10 and later) 62 | endif 63 | 64 | $(info Building against PostgreSQL $(MAJORVERSION)) 65 | 66 | # Fix for OS X and libfq 67 | ifeq (-dead_strip_dylibs, $(findstring -dead_strip_dylibs, $(shell $(PG_CONFIG) --ldflags))) 68 | LDFLAGS := $(subst -dead_strip_dylibs,-flat_namespace,$(LDFLAGS)) 69 | endif 70 | 71 | PG_PROVE_FLAGS += -I $(srcdir)/t 72 | 73 | $(OBJS): src/firebird_fdw.h 74 | 75 | prove_installcheck: all 76 | rm -rf $(srcdir)/tmp_check/log 77 | cd $(srcdir) && PG_VERSION_NUM='$(VERSION_NUM)' TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) 78 | 79 | installcheck: prove_installcheck 80 | 81 | clean: local_clean 82 | 83 | local_clean: 84 | rm -rf tmp_check/ 85 | 86 | -------------------------------------------------------------------------------- /t/01-extension.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 01-extension.pl 4 | # 5 | # Basic sanity check to ensure the extension is installed 6 | # 7 | # Creates a single table on the Firebird server, a corresponding 8 | # foreign table in PostgreSQL and performs some simple DML 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Test::More; 14 | 15 | use FirebirdFDWNode; 16 | 17 | 18 | # Initialize PostgreSQL node 19 | # -------------------------- 20 | 21 | my $node = FirebirdFDWNode->new(); 22 | 23 | 24 | # 1. Check version 25 | # ---------------- 26 | # 27 | # TODO: parse the value from "firebird_fdw.control" and check for a match 28 | 29 | my $version = '10500'; 30 | 31 | my ($res, $res_stdout, $res_stderr) = $node->psql(q|SELECT firebird_fdw_version()|); 32 | 33 | is( 34 | $res_stdout, 35 | $version, 36 | 'version OK', 37 | ); 38 | 39 | 40 | 41 | # Prepare table 42 | # -------------- 43 | 44 | my $table_name = $node->init_table(); 45 | 46 | # 2. Insert a row 47 | # --------------- 48 | 49 | my $insert_q = sprintf( 50 | q|INSERT INTO %s (lang_id, name_english, name_native) VALUES('en', 'English', 'English')|, 51 | $table_name, 52 | ); 53 | 54 | $node->safe_psql( $insert_q ); 55 | 56 | # Check it arrives 57 | 58 | my $queryText = sprintf( 59 | q|SELECT name_english FROM %s WHERE lang_id = 'en'|, 60 | $table_name, 61 | ); 62 | 63 | my $query = $node->firebird_conn()->prepare($queryText); 64 | 65 | $query->execute(); 66 | 67 | $res = $query->fetchrow_array(); 68 | 69 | $query->finish(); 70 | 71 | is( 72 | $res, 73 | 'English', 74 | 'insert OK', 75 | ); 76 | 77 | 78 | # 3. Update the row 79 | # ----------------- 80 | 81 | my $update_q = sprintf( 82 | q|UPDATE %s SET name_native = 'Wibblish' WHERE lang_id = 'en'|, 83 | $table_name, 84 | ); 85 | 86 | $node->safe_psql( $update_q ); 87 | 88 | $queryText = sprintf( 89 | q|SELECT name_native FROM %s WHERE lang_id = 'en'|, 90 | $table_name, 91 | ); 92 | 93 | $query = $node->firebird_conn()->prepare($queryText); 94 | 95 | $query->execute(); 96 | 97 | $res = $query->fetchrow_array(); 98 | 99 | $query->finish(); 100 | 101 | is( 102 | $res, 103 | 'Wibblish', 104 | 'update OK', 105 | ); 106 | 107 | 108 | # 4. Delete the row 109 | # ----------------- 110 | 111 | my $delete_q = sprintf( 112 | q|DELETE FROM %s WHERE lang_id = 'en'|, 113 | $table_name, 114 | ); 115 | 116 | $node->safe_psql( $delete_q ); 117 | 118 | $queryText = sprintf( 119 | q|SELECT COUNT(*) FROM %s WHERE lang_id = 'en'|, 120 | $table_name, 121 | ); 122 | 123 | $query = $node->firebird_conn()->prepare($queryText); 124 | 125 | $query->execute(); 126 | 127 | $res = $query->fetchrow_array(); 128 | 129 | $query->finish(); 130 | 131 | is( 132 | $res, 133 | '0', 134 | 'delete OK', 135 | ); 136 | 137 | 138 | # Clean up 139 | # -------- 140 | 141 | $node->drop_foreign_server(); 142 | $node->firebird_drop_table($table_name); 143 | 144 | done_testing(); 145 | -------------------------------------------------------------------------------- /packaging/redhat/postgresql10-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql10-firebird_fdw 3 | Version: 1.3.0 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql10-devel 11 | BuildRequires: firebird-devel 12 | BuildRequires: libfq 13 | %if 0%{?rhel} && 0%{?rhel} >= 8 14 | BuildRequires: llvm 15 | %else 16 | %if 0%{?rhel} && 0%{?rhel} == 7 17 | BuildRequires: llvm-toolset-7 18 | BuildRequires: llvm5.0 19 | %endif 20 | %endif 21 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 22 | Requires: postgresql10-server libfq 23 | 24 | %define pgsql_path /usr/pgsql-10 25 | 26 | %description 27 | This is a foreign data wrapper (FDW) to connect PostgreSQL to Firebird. 28 | It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 29 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 30 | support. 31 | 32 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 33 | 34 | %prep 35 | %setup 36 | 37 | %build 38 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 39 | PG_CPPFLAGS="-I/usr/include/firebird" USE_PGXS=1 make 40 | 41 | %install 42 | rm -rf $RPM_BUILD_ROOT 43 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 44 | USE_PGXS=1 make DESTDIR=$RPM_BUILD_ROOT install 45 | 46 | %clean 47 | rm -rf $RPM_BUILD_ROOT 48 | 49 | %files 50 | %defattr(-, root, root) 51 | %{pgsql_path}/lib/firebird_fdw.so 52 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0.sql 53 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0--0.4.0.sql 54 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0.sql 55 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0--0.5.0.sql 56 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0.sql 57 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0--1.0.0.sql 58 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0.sql 59 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0--1.1.0.sql 60 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0.sql 61 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0--1.2.0.sql 62 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0.sql 63 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0--1.3.0.sql 64 | %{pgsql_path}/share/extension/firebird_fdw--1.3.0.sql 65 | %{pgsql_path}/share/extension/firebird_fdw.control 66 | 67 | %changelog 68 | * Wed Dec 28 2022 Ian Barwick (barwick@gmail.com) 69 | - 1.3.0 release 70 | * Sun Feb 20 2022 Ian Barwick (barwick@gmail.com) 71 | - 1.2.3 release 72 | * Tue Sep 14 2021 Ian Barwick (barwick@gmail.com) 73 | - 1.2.2 release 74 | * Wed Oct 21 2020 Ian Barwick (barwick@gmail.com) 75 | - 1.2.1 release 76 | * Sat Oct 17 2020 Ian Barwick (barwick@gmail.com) 77 | - 1.2.0 release 78 | * Fri May 31 2019 Ian Barwick (barwick@gmail.com) 79 | - 1.1.0 release 80 | * Fri Nov 9 2018 Ian Barwick (barwick@gmail.com) 81 | - 1.0.0 release 82 | * Fri Oct 12 2018 Ian Barwick (barwick@gmail.com) 83 | - 0.5.0 release 84 | * Tue Oct 2 2018 Ian Barwick (barwick@gmail.com) 85 | - 0.4.0 release 86 | * Sun Apr 22 2018 Ian Barwick (barwick@gmail.com) 87 | - 0.3.0 release 88 | * Sun Feb 2 2014 Ian Barwick (barwick@gmail.com) 89 | - First draft 90 | -------------------------------------------------------------------------------- /t/10-generated-columns.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 10-generated-columns.pl 4 | # 5 | # Check support for generated columns 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | 20 | # Get PostgreSQL version 21 | # ---------------------- 22 | 23 | my $version = $node->pg_version(); 24 | if ($version < 120000) { 25 | plan skip_all => sprintf( 26 | q|version is %i, tests for 12 and later|, 27 | $version, 28 | ); 29 | } 30 | 31 | 32 | # Create tables 33 | # ------------- 34 | 35 | my $table_name = $node->make_table_name(); 36 | 37 | # Note that Firebird accepts "GENERATED ALWAYS AS" but renders 38 | # it as "COMPUTED BY". It does not accept the STORED suffix. 39 | # 40 | # Reference: 41 | # https://firebirdsql.org/file/documentation/html/en/refdocs/fblangref50/firebird-50-language-reference.html#fblangref50-ddl-tbl-computedby 42 | 43 | my $tbl_sql = sprintf( 44 | <firebird_conn()->prepare($tbl_sql); 54 | $tbl_query->execute(); 55 | $tbl_query->finish(); 56 | 57 | my $ftbl_sql = sprintf( 58 | <{server_name}, 68 | $table_name, 69 | ); 70 | 71 | $node->safe_psql($ftbl_sql); 72 | 73 | # 1. Check INSERT 74 | # --------------- 75 | 76 | my $insert_q1 = sprintf( 77 | <safe_psql($insert_q1); 84 | 85 | my $select_q1 = sprintf( 86 | q|SELECT * FROM %s ORDER BY a|, 87 | $table_name, 88 | ); 89 | 90 | my $q1 = $node->firebird_conn()->prepare($select_q1); 91 | 92 | $q1->execute(); 93 | 94 | my $res1 = $node->firebird_format_results($q1); 95 | 96 | $q1->finish(); 97 | 98 | my $expected1 = <safe_psql($update_q2); 124 | 125 | my $select_q2 = sprintf( 126 | q|SELECT * FROM %s ORDER BY a|, 127 | $table_name, 128 | ); 129 | 130 | my $q2 = $node->firebird_conn()->prepare($select_q2); 131 | 132 | $q2->execute(); 133 | 134 | my $res2 = $node->firebird_format_results($q2); 135 | 136 | $q2->finish(); 137 | 138 | my $expected2 = <firebird_drop_table($table_name); 155 | 156 | done_testing(); 157 | -------------------------------------------------------------------------------- /packaging/redhat/postgresql11-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql11-firebird_fdw 3 | Version: 1.3.0 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql11-devel 11 | BuildRequires: firebird-devel 12 | BuildRequires: libfq 13 | %if 0%{?rhel} && 0%{?rhel} >= 8 14 | BuildRequires: llvm 15 | %else 16 | %if 0%{?rhel} && 0%{?rhel} == 7 17 | BuildRequires: llvm-toolset-7 18 | BuildRequires: llvm5.0 19 | %endif 20 | %endif 21 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 22 | Requires: postgresql11-server libfq 23 | 24 | %define pgsql_path /usr/pgsql-11 25 | 26 | %description 27 | This is a foreign data wrapper (FDW) to connect PostgreSQL to Firebird. 28 | It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 29 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 30 | support. 31 | 32 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 33 | 34 | %prep 35 | %setup 36 | 37 | %build 38 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 39 | PG_CPPFLAGS="-I/usr/include/firebird" USE_PGXS=1 make 40 | 41 | %install 42 | rm -rf $RPM_BUILD_ROOT 43 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 44 | USE_PGXS=1 make DESTDIR=$RPM_BUILD_ROOT install 45 | 46 | %clean 47 | rm -rf $RPM_BUILD_ROOT 48 | 49 | %files 50 | %defattr(-, root, root) 51 | %{pgsql_path}/lib/firebird_fdw.so 52 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0.sql 53 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0--0.4.0.sql 54 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0.sql 55 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0--0.5.0.sql 56 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0.sql 57 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0--1.0.0.sql 58 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0.sql 59 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0--1.1.0.sql 60 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0.sql 61 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0--1.2.0.sql 62 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0.sql 63 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0--1.3.0.sql 64 | %{pgsql_path}/share/extension/firebird_fdw--1.3.0.sql 65 | %{pgsql_path}/share/extension/firebird_fdw.control 66 | 67 | %if 0%{?rhel} && 0%{?rhel} >= 7 68 | %exclude %{pgsql_path}/lib/bitcode 69 | %endif 70 | 71 | %changelog 72 | * Wed Dec 28 2022 Ian Barwick (barwick@gmail.com) 73 | - 1.3.0 release 74 | * Sun Feb 20 2022 Ian Barwick (barwick@gmail.com) 75 | - 1.2.3 release 76 | * Tue Sep 14 2021 Ian Barwick (barwick@gmail.com) 77 | - 1.2.2 release 78 | * Wed Oct 21 2020 Ian Barwick (barwick@gmail.com) 79 | - 1.2.1 release 80 | * Sat Oct 17 2020 Ian Barwick (barwick@gmail.com) 81 | - 1.2.0 release 82 | * Fri May 31 2019 Ian Barwick (barwick@gmail.com) 83 | - 1.1.0 release 84 | * Fri Nov 9 2018 Ian Barwick (barwick@gmail.com) 85 | - 1.0.0 release 86 | * Fri Oct 12 2018 Ian Barwick (barwick@gmail.com) 87 | - 0.5.0 release 88 | * Tue Oct 2 2018 Ian Barwick (barwick@gmail.com) 89 | - 0.4.0 release 90 | * Sun Apr 22 2018 Ian Barwick (barwick@gmail.com) 91 | - 0.3.0 release 92 | * Sun Feb 2 2014 Ian Barwick (barwick@gmail.com) 93 | - First draft 94 | -------------------------------------------------------------------------------- /packaging/redhat/postgresql13-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql13-firebird_fdw 3 | Version: 1.3.0 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql13-devel 11 | BuildRequires: firebird-devel 12 | BuildRequires: libfq 13 | %if 0%{?rhel} && 0%{?rhel} >= 8 14 | BuildRequires: llvm 15 | %else 16 | %if 0%{?rhel} && 0%{?rhel} == 7 17 | BuildRequires: llvm-toolset-7 18 | BuildRequires: llvm5.0 19 | %endif 20 | %endif 21 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 22 | Requires: postgresql13-server libfq 23 | 24 | %define pgsql_path /usr/pgsql-13 25 | 26 | %description 27 | This is a foreign data wrapper (FDW) to connect PostgreSQL to Firebird. 28 | It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 29 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 30 | support. 31 | 32 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 33 | 34 | %prep 35 | %setup 36 | 37 | %build 38 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 39 | PG_CPPFLAGS="-I/usr/include/firebird" USE_PGXS=1 make 40 | 41 | %install 42 | rm -rf $RPM_BUILD_ROOT 43 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 44 | USE_PGXS=1 make DESTDIR=$RPM_BUILD_ROOT install 45 | 46 | %clean 47 | rm -rf $RPM_BUILD_ROOT 48 | 49 | %files 50 | %defattr(-, root, root) 51 | %{pgsql_path}/lib/firebird_fdw.so 52 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0.sql 53 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0--0.4.0.sql 54 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0.sql 55 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0--0.5.0.sql 56 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0.sql 57 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0--1.0.0.sql 58 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0.sql 59 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0--1.1.0.sql 60 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0.sql 61 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0--1.2.0.sql 62 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0.sql 63 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0--1.3.0.sql 64 | %{pgsql_path}/share/extension/firebird_fdw--1.3.0.sql 65 | %{pgsql_path}/share/extension/firebird_fdw.control 66 | 67 | %if 0%{?rhel} && 0%{?rhel} >= 7 68 | %exclude %{pgsql_path}/lib/bitcode 69 | %endif 70 | 71 | %changelog 72 | * Wed Dec 28 2022 Ian Barwick (barwick@gmail.com) 73 | - 1.3.0 release 74 | * Sun Feb 20 2022 Ian Barwick (barwick@gmail.com) 75 | - 1.2.3 release 76 | * Tue Sep 14 2021 Ian Barwick (barwick@gmail.com) 77 | - 1.2.2 release 78 | * Wed Oct 21 2020 Ian Barwick (barwick@gmail.com) 79 | - 1.2.1 release 80 | * Sat Oct 17 2020 Ian Barwick (barwick@gmail.com) 81 | - 1.2.0 release 82 | * Fri May 31 2019 Ian Barwick (barwick@gmail.com) 83 | - 1.1.0 release 84 | * Fri Nov 9 2018 Ian Barwick (barwick@gmail.com) 85 | - 1.0.0 release 86 | * Fri Oct 12 2018 Ian Barwick (barwick@gmail.com) 87 | - 0.5.0 release 88 | * Tue Oct 2 2018 Ian Barwick (barwick@gmail.com) 89 | - 0.4.0 release 90 | * Sun Apr 22 2018 Ian Barwick (barwick@gmail.com) 91 | - 0.3.0 release 92 | * Sun Feb 2 2014 Ian Barwick (barwick@gmail.com) 93 | - First draft 94 | -------------------------------------------------------------------------------- /packaging/redhat/postgresql14-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql14-firebird_fdw 3 | Version: 1.3.0 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql14-devel 11 | BuildRequires: firebird-devel 12 | BuildRequires: libfq 13 | %if 0%{?rhel} && 0%{?rhel} >= 8 14 | BuildRequires: llvm 15 | %else 16 | %if 0%{?rhel} && 0%{?rhel} == 7 17 | BuildRequires: llvm-toolset-7 18 | BuildRequires: llvm5.0 19 | %endif 20 | %endif 21 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 22 | Requires: postgresql14-server libfq 23 | 24 | %define pgsql_path /usr/pgsql-14 25 | 26 | %description 27 | This is a foreign data wrapper (FDW) to connect PostgreSQL to Firebird. 28 | It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 29 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 30 | support. 31 | 32 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 33 | 34 | %prep 35 | %setup 36 | 37 | %build 38 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 39 | PG_CPPFLAGS="-I/usr/include/firebird" USE_PGXS=1 make 40 | 41 | %install 42 | rm -rf $RPM_BUILD_ROOT 43 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 44 | USE_PGXS=1 make DESTDIR=$RPM_BUILD_ROOT install 45 | 46 | %clean 47 | rm -rf $RPM_BUILD_ROOT 48 | 49 | %files 50 | %defattr(-, root, root) 51 | %{pgsql_path}/lib/firebird_fdw.so 52 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0.sql 53 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0--0.4.0.sql 54 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0.sql 55 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0--0.5.0.sql 56 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0.sql 57 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0--1.0.0.sql 58 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0.sql 59 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0--1.1.0.sql 60 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0.sql 61 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0--1.2.0.sql 62 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0.sql 63 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0--1.3.0.sql 64 | %{pgsql_path}/share/extension/firebird_fdw--1.3.0.sql 65 | %{pgsql_path}/share/extension/firebird_fdw.control 66 | 67 | %if 0%{?rhel} && 0%{?rhel} >= 7 68 | %exclude %{pgsql_path}/lib/bitcode 69 | %endif 70 | 71 | %changelog 72 | * Wed Dec 28 2022 Ian Barwick (barwick@gmail.com) 73 | - 1.3.0 release 74 | * Sun Feb 20 2022 Ian Barwick (barwick@gmail.com) 75 | - 1.2.3 release 76 | * Tue Sep 14 2021 Ian Barwick (barwick@gmail.com) 77 | - 1.2.2 release 78 | * Wed Oct 21 2020 Ian Barwick (barwick@gmail.com) 79 | - 1.2.1 release 80 | * Sat Oct 17 2020 Ian Barwick (barwick@gmail.com) 81 | - 1.2.0 release 82 | * Fri May 31 2019 Ian Barwick (barwick@gmail.com) 83 | - 1.1.0 release 84 | * Fri Nov 9 2018 Ian Barwick (barwick@gmail.com) 85 | - 1.0.0 release 86 | * Fri Oct 12 2018 Ian Barwick (barwick@gmail.com) 87 | - 0.5.0 release 88 | * Tue Oct 2 2018 Ian Barwick (barwick@gmail.com) 89 | - 0.4.0 release 90 | * Sun Apr 22 2018 Ian Barwick (barwick@gmail.com) 91 | - 0.3.0 release 92 | * Sun Feb 2 2014 Ian Barwick (barwick@gmail.com) 93 | - First draft 94 | -------------------------------------------------------------------------------- /packaging/redhat/postgresql15-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql15-firebird_fdw 3 | Version: 1.3.0 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql15-devel 11 | BuildRequires: firebird-devel 12 | BuildRequires: libfq 13 | %if 0%{?rhel} && 0%{?rhel} >= 8 14 | BuildRequires: llvm 15 | %else 16 | %if 0%{?rhel} && 0%{?rhel} == 7 17 | BuildRequires: llvm-toolset-7 18 | BuildRequires: llvm5.0 19 | %endif 20 | %endif 21 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 22 | Requires: postgresql15-server libfq 23 | 24 | %define pgsql_path /usr/pgsql-15 25 | 26 | %description 27 | This is a foreign data wrapper (FDW) to connect PostgreSQL to Firebird. 28 | It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 29 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 30 | support. 31 | 32 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 33 | 34 | %prep 35 | %setup 36 | 37 | %build 38 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 39 | PG_CPPFLAGS="-I/usr/include/firebird" USE_PGXS=1 make 40 | 41 | %install 42 | rm -rf $RPM_BUILD_ROOT 43 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 44 | USE_PGXS=1 make DESTDIR=$RPM_BUILD_ROOT install 45 | 46 | %clean 47 | rm -rf $RPM_BUILD_ROOT 48 | 49 | %files 50 | %defattr(-, root, root) 51 | %{pgsql_path}/lib/firebird_fdw.so 52 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0.sql 53 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0--0.4.0.sql 54 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0.sql 55 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0--0.5.0.sql 56 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0.sql 57 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0--1.0.0.sql 58 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0.sql 59 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0--1.1.0.sql 60 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0.sql 61 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0--1.2.0.sql 62 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0.sql 63 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0--1.3.0.sql 64 | %{pgsql_path}/share/extension/firebird_fdw--1.3.0.sql 65 | %{pgsql_path}/share/extension/firebird_fdw.control 66 | 67 | %if 0%{?rhel} && 0%{?rhel} >= 7 68 | %exclude %{pgsql_path}/lib/bitcode 69 | %endif 70 | 71 | %changelog 72 | * Wed Dec 28 2022 Ian Barwick (barwick@gmail.com) 73 | - 1.3.0 release 74 | * Sun Feb 20 2022 Ian Barwick (barwick@gmail.com) 75 | - 1.2.3 release 76 | * Tue Sep 14 2021 Ian Barwick (barwick@gmail.com) 77 | - 1.2.2 release 78 | * Wed Oct 21 2020 Ian Barwick (barwick@gmail.com) 79 | - 1.2.1 release 80 | * Sat Oct 17 2020 Ian Barwick (barwick@gmail.com) 81 | - 1.2.0 release 82 | * Fri May 31 2019 Ian Barwick (barwick@gmail.com) 83 | - 1.1.0 release 84 | * Fri Nov 9 2018 Ian Barwick (barwick@gmail.com) 85 | - 1.0.0 release 86 | * Fri Oct 12 2018 Ian Barwick (barwick@gmail.com) 87 | - 0.5.0 release 88 | * Tue Oct 2 2018 Ian Barwick (barwick@gmail.com) 89 | - 0.4.0 release 90 | * Sun Apr 22 2018 Ian Barwick (barwick@gmail.com) 91 | - 0.3.0 release 92 | * Sun Feb 2 2014 Ian Barwick (barwick@gmail.com) 93 | - First draft 94 | -------------------------------------------------------------------------------- /packaging/redhat/postgresql12-firebird_fdw.spec: -------------------------------------------------------------------------------- 1 | Summary: A PostgreSQL foreign data wrapper (FDW) for Firebird 2 | Name: postgresql12-firebird_fdw 3 | Version: 1.3.0 4 | Release: 1 5 | Source: firebird_fdw-%{version}.tar.gz 6 | URL: https://github.com/ibarwick/firebird_fdw 7 | License: PostgreSQL 8 | Group: Productivity/Databases/Tools 9 | Packager: Ian Barwick 10 | BuildRequires: postgresql12-devel 11 | BuildRequires: firebird-devel 12 | BuildRequires: libfq 13 | %if 0%{?rhel} && 0%{?rhel} >= 8 14 | BuildRequires: llvm 15 | %else 16 | %if 0%{?rhel} && 0%{?rhel} == 7 17 | BuildRequires: llvm-toolset-7 18 | BuildRequires: llvm5.0 19 | %endif 20 | %endif 21 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 22 | Requires: postgresql12-server libfq 23 | 24 | %define pgsql_path /usr/pgsql-12 25 | 26 | %description 27 | This is a foreign data wrapper (FDW) to connect PostgreSQL to Firebird. 28 | It provides both read (SELECT) and write (INSERT/UPDATE/DELETE) 29 | support, WHERE-clause pushdowns, connection caching and Firebird transaction 30 | support. 31 | 32 | This code is very much work-in-progress; USE AT YOUR OWN RISK. 33 | 34 | %prep 35 | 36 | %setup 37 | 38 | %build 39 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 40 | PG_CPPFLAGS="-I/usr/include/firebird" USE_PGXS=1 make 41 | 42 | %install 43 | rm -rf $RPM_BUILD_ROOT 44 | export PG_CONFIG=%{pgsql_path}/bin/pg_config 45 | USE_PGXS=1 make DESTDIR=$RPM_BUILD_ROOT install 46 | 47 | %clean 48 | rm -rf $RPM_BUILD_ROOT 49 | 50 | %files 51 | %defattr(-, root, root) 52 | %{pgsql_path}/lib/firebird_fdw.so 53 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0.sql 54 | %{pgsql_path}/share/extension/firebird_fdw--0.3.0--0.4.0.sql 55 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0.sql 56 | %{pgsql_path}/share/extension/firebird_fdw--0.4.0--0.5.0.sql 57 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0.sql 58 | %{pgsql_path}/share/extension/firebird_fdw--0.5.0--1.0.0.sql 59 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0.sql 60 | %{pgsql_path}/share/extension/firebird_fdw--1.0.0--1.1.0.sql 61 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0.sql 62 | %{pgsql_path}/share/extension/firebird_fdw--1.1.0--1.2.0.sql 63 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0.sql 64 | %{pgsql_path}/share/extension/firebird_fdw--1.2.0--1.3.0.sql 65 | %{pgsql_path}/share/extension/firebird_fdw--1.3.0.sql 66 | %{pgsql_path}/share/extension/firebird_fdw.control 67 | 68 | %if 0%{?rhel} && 0%{?rhel} >= 7 69 | %exclude %{pgsql_path}/lib/bitcode 70 | %endif 71 | 72 | %changelog 73 | * Wed Dec 28 2022 Ian Barwick (barwick@gmail.com) 74 | - 1.3.0 release 75 | * Sun Feb 20 2022 Ian Barwick (barwick@gmail.com) 76 | - 1.2.3 release 77 | * Tue Sep 14 2021 Ian Barwick (barwick@gmail.com) 78 | - 1.2.2 release 79 | * Wed Oct 21 2020 Ian Barwick (barwick@gmail.com) 80 | - 1.2.1 release 81 | * Sat Oct 17 2020 Ian Barwick (barwick@gmail.com) 82 | - 1.2.0 release 83 | * Fri May 31 2019 Ian Barwick (barwick@gmail.com) 84 | - 1.1.0 release 85 | * Fri Nov 9 2018 Ian Barwick (barwick@gmail.com) 86 | - 1.0.0 release 87 | * Fri Oct 12 2018 Ian Barwick (barwick@gmail.com) 88 | - 0.5.0 release 89 | * Tue Oct 2 2018 Ian Barwick (barwick@gmail.com) 90 | - 0.4.0 release 91 | * Sun Apr 22 2018 Ian Barwick (barwick@gmail.com) 92 | - 0.3.0 release 93 | * Sun Feb 2 2014 Ian Barwick (barwick@gmail.com) 94 | - First draft 95 | -------------------------------------------------------------------------------- /t/05-table-options.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 05-table-options.pl 4 | # 5 | # Check table-level options (work-in-progress) 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | # Prepare table 20 | # -------------- 21 | 22 | my $estimated_row_count = 100; 23 | 24 | my $table_name = $node->init_table( 25 | 'updatable' => 'FALSE', 26 | 'estimated_row_count' => $estimated_row_count, 27 | ); 28 | 29 | 30 | # 1. Check inserts not allowed 31 | # ---------------------------- 32 | 33 | my $insert_q = sprintf( 34 | q|INSERT INTO %s (lang_id, name_english, name_native) VALUES('en', 'English', 'English')|, 35 | $table_name, 36 | ); 37 | 38 | my ($insert_res, $insert_stdout, $insert_stderr) = $node->psql( 39 | $insert_q, 40 | ); 41 | 42 | my $insert_expected = sprintf( 43 | q|foreign table "%s" does not allow inserts|, 44 | $table_name, 45 | ); 46 | 47 | like ( 48 | $insert_stderr, 49 | qr/$insert_expected/, 50 | q|Check INSERT|, 51 | ); 52 | 53 | 54 | # 2. Check "estimated_row_count" is used 55 | # -------------------------------------- 56 | 57 | # Foreign Scan on qtest (cost=10.00..110.00 rows=100 width=294) 58 | 59 | my $explain_q = sprintf( 60 | q|EXPLAIN SELECT * FROM %s|, 61 | $table_name, 62 | ); 63 | 64 | my ($explain_res, $explain_stdout, $explain_stderr) = $node->psql( 65 | $explain_q, 66 | ); 67 | 68 | my $explain_expected = sprintf( 69 | q|rows=%i\b|, 70 | $estimated_row_count, 71 | ); 72 | 73 | like ( 74 | $explain_stdout, 75 | qr/$explain_expected/, 76 | q|Check "estimated_row_count" is used|, 77 | ); 78 | 79 | 80 | # 3. Check "table_name" and "column_name" options 81 | # ----------------------------------------------- 82 | 83 | my $column_name_table = sprintf( 84 | q|%s_colcheck|, 85 | $table_name, 86 | ); 87 | 88 | my $column_name_table_q = sprintf( 89 | <<'EO_SQL', 90 | CREATE FOREIGN TABLE %s ( 91 | pg_lang_id CHAR(2) OPTIONS (column_name 'lang_id'), 92 | pg_name_english VARCHAR(64) OPTIONS (column_name 'name_english'), 93 | pg_name_native VARCHAR(64) OPTIONS (column_name 'name_native') 94 | ) 95 | SERVER %s 96 | OPTIONS( 97 | table_name '%s' 98 | ) 99 | EO_SQL 100 | $column_name_table, 101 | $node->server_name(), 102 | $table_name, 103 | ); 104 | 105 | $node->safe_psql( $column_name_table_q ); 106 | 107 | my $q3_insert_q = sprintf( 108 | q|INSERT INTO %s (pg_lang_id, pg_name_english, pg_name_native) VALUES('de', 'German', 'Deutsch')|, 109 | $column_name_table, 110 | ); 111 | 112 | $node->safe_psql( $q3_insert_q ); 113 | 114 | # Check it arrives 115 | 116 | my $q3_check_query = sprintf( 117 | q|SELECT lang_id, name_english, name_native FROM %s WHERE lang_id = 'de'|, 118 | $table_name, 119 | ); 120 | 121 | my $query = $node->firebird_conn()->prepare($q3_check_query); 122 | 123 | $query->execute(); 124 | 125 | my @q3_res = $query->fetchrow_array(); 126 | 127 | my $q3_res = join('|', @q3_res); 128 | 129 | $query->finish(); 130 | 131 | is( 132 | $q3_res, 133 | 'de|German|Deutsch', 134 | 'table_name/column_name options OK', 135 | ); 136 | 137 | 138 | # Clean up 139 | # -------- 140 | 141 | $node->drop_foreign_server(); 142 | $node->firebird_drop_table($table_name); 143 | 144 | done_testing(); 145 | -------------------------------------------------------------------------------- /doc/ENCODINGS.md: -------------------------------------------------------------------------------- 1 | PostgreSQL and Firebird character set encoding compatibility 2 | ============================================================ 3 | 4 | Character set mappings 5 | ---------------------- 6 | 7 | The following table provides an overview of available PostgreSQL server 8 | character set encodings and the matching Firebird ones. 9 | 10 | Encodings marked with `-` in the Firebird column are not available in Firebird. 11 | 12 | | PostgreSQL | Firebird | Notes 13 | |---------------|-----------|-------------------------------------------- 14 | | EUC_CN | - | Extended UNIX Code-CN 15 | | EUC_JP | EUCJ_0208 | Compatibility likely but not tested 16 | | EUC_JIS_2004 | - | Extended UNIX Code-JP, JIS X 0213 17 | | EUC_KR | - | Extended UNIX Code-KR 18 | | EUC_TW | - | Extended UNIX Code-TW 19 | | ISO_8859_5 | ISO8859_5 | 20 | | ISO_8859_6 | ISO8859_6 | 21 | | ISO_8859_7 | ISO8859_7 | 22 | | ISO_8859_8 | ISO8859_8 | 23 | | KOI8R | KOI8R | 24 | | KOI8U | KOI8U | 25 | | LATIN1 | LATIN1 | 26 | | LATIN2 | LATIN2 | 27 | | LATIN3 | LATIN3 | 28 | | LATIN4 | LATIN4 | 29 | | LATIN5 | LATIN5 | 30 | | LATIN6 | - | ISO 8859-10 / ECMA 144 "Nordic" 31 | | LATIN7 | LATIN7 | 32 | | LATIN8 | - | ISO 8859-14 "Celtic" 33 | | LATIN9 | - | ISO 8859-15 "LATIN1 with Euro and accents" 34 | | LATIN10 | - | ISO 8859-16 "Romanian" 35 | | MULE_INTERNAL | - | "Multilingual Emacs" 36 | | SQL_ASCII | NONE | 37 | | UTF8 | UTF8 | 38 | | WIN866 | DOS866 | 39 | | WIN874 | - | Windows CP874 "Thai" 40 | | WIN1250 | WIN1250 | 41 | | WIN1251 | WIN1251 | 42 | | WIN1252 | WIN1252 | 43 | | WIN1253 | WIN1253 | 44 | | WIN1254 | WIN1254 | 45 | | WIN1255 | WIN1255 | 46 | | WIN1256 | WIN1256 | 47 | | WIN1257 | WIN1257 | 48 | | WIN1258 | WIN1258 | 49 | 50 | See also: 51 | 52 | - https://www.postgresql.org/docs/current/multibyte.html#MULTIBYTE-CHARSET-SUPPORTED 53 | - https://firebirdsql.org/file/documentation/html/en/refdocs/fblangref40/firebird-40-language-reference.html#fblangref40-appx07-charsets 54 | - https://firebirdsql.org/refdocs/langrefupd25-charsets.html 55 | - https://firebirdsql.org/en/firebird-1-5-character-sets-collations/ 56 | 57 | Databases with the Firebird "NONE" character set 58 | ------------------------------------------------ 59 | 60 | The `NONE` character set is pretty much the equivalent of PostgreSQL's 61 | `SQL_ASCII`, i.e. a pseudo-character set/encoding which enables the user to 62 | store much any data they care to input without any kind of validation. This 63 | means that it's perfectly possible to insert a mix of data in (for example) 64 | `ISO-8859-1` and `UTF8` encoding. 65 | 66 | This does however mean that Firebird can't know what encoding the data is 67 | supposed to be in, so it can't convert the data to whatever encoding the client 68 | is requesting. Conseqeuently, when `firebird_fdw` connects to a Firebird 69 | database configured with the `NONE` character set, the PostgreSQL database's 70 | server encoding has no meaning, and the raw data will be transmitted to 71 | PostgreSQL. 72 | 73 | If the data happens to be in the same encoding as the PostgreSQL databases's server 74 | encoding, this is normally not an issue. However, if the data is in a different 75 | encoding, it will need to be treated as a stream of `bytea` values which need to 76 | be explictly converted using e.g. PostgreSQL's `convert_from()` function, e.g.: 77 | 78 | SELECT convert_from(some_column_name, 'LATIN1') 79 | FROM firebird_table 80 | 81 | where `firebird_table` is a foreign table in a PostgreSQL database with `UTF8` 82 | server encoding which references a table in a Firebird database with `NONE` 83 | pseudo-encoding containing data in `LATIN1` encoding. 84 | 85 | -------------------------------------------------------------------------------- /t/17-scans.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 17-scans.pl 4 | # 5 | # Check that foreign table scans work as expected 6 | 7 | 8 | use strict; 9 | use warnings; 10 | 11 | use Test::More; 12 | 13 | use FirebirdFDWNode; 14 | 15 | # Initialize nodes 16 | # ---------------- 17 | 18 | our $node = FirebirdFDWNode->new(); 19 | 20 | our $version = $node->pg_version(); 21 | 22 | plan tests => 1; 23 | 24 | # Ensure rescans work properly 25 | # ----------------------------- 26 | # 27 | # This test reproduces the situation described in this issue: 28 | # 29 | # https://github.com/ibarwick/firebird_fdw/issues/21 30 | # 31 | # where the provided query - if executed without analyzing the 32 | # foreign table - resulted in a merge join, which triggered an 33 | # error in the table rescan implementation, leading to incorrect 34 | # results. 35 | 36 | # Create a table with some random hierachical-esque data; this 37 | # can be used to craft a query which should cause a rescan to 38 | # occur: 39 | 40 | my $q1_table_name = $node->make_table_name(); 41 | 42 | my $q1_fb_sql = sprintf( 43 | <<'EO_SQL', 44 | CREATE TABLE %s ( 45 | c0 INT NOT NULL, 46 | c1 INT NOT NULL 47 | ) 48 | EO_SQL 49 | $q1_table_name, 50 | ); 51 | 52 | $node->firebird_execute_sql($q1_fb_sql); 53 | 54 | my $q1_pg_sql = sprintf( 55 | <<'EO_SQL', 56 | CREATE FOREIGN TABLE %s ( 57 | c0 INT NOT NULL, 58 | c1 INT NOT NULL 59 | ) 60 | SERVER %s 61 | OPTIONS ( table_name '%s' ); 62 | EO_SQL 63 | $q1_table_name, 64 | $node->server_name(), 65 | $q1_table_name, 66 | ); 67 | 68 | $node->safe_psql( $q1_pg_sql ); 69 | 70 | my $q1_data_sql = sprintf( 71 | <<'EO_SQL', 72 | WITH y AS ( 73 | WITH x AS ( 74 | SELECT x.id 75 | FROM (SELECT pg_catalog.generate_series(1,20000, (pg_catalog.random() * 10)::int + 1) id) x 76 | LIMIT 1500 77 | ) 78 | SELECT x.id AS c0, 79 | (SELECT y.id 80 | FROM x y 81 | WHERE y.id < x.id 82 | ORDER BY pg_catalog.random() 83 | LIMIT 1 84 | ) AS c1 85 | FROM x 86 | ) 87 | INSERT INTO %s (c0, c1) 88 | SELECT y.c0, y.c1 89 | FROM y 90 | WHERE y.c1 IS NOT NULL 91 | EO_SQL 92 | $q1_table_name, 93 | ); 94 | $node->safe_psql( $q1_data_sql ); 95 | 96 | # Fetch a "seed" ID for the query 97 | 98 | my $q1_seed_id_sql = sprintf( 99 | q|SELECT c1 FROM %s WHERE c1 > 1 ORDER BY 1 OFFSET 5 LIMIT 1|, 100 | $q1_table_name, 101 | ); 102 | 103 | my ($q1_res, $q1_stdout, $q1_stderr) = $node->psql( 104 | $q1_seed_id_sql, 105 | ); 106 | 107 | my $seed_id = $q1_stdout; 108 | 109 | # Determine how many rows should be returned 110 | 111 | my $fb_query_sql = sprintf( 112 | <<'EO_SQL', 113 | SELECT COUNT(*) FROM 114 | ( 115 | WITH RECURSIVE r AS ( 116 | SELECT p.c0 117 | FROM %s p 118 | WHERE p.c1 = %i 119 | UNION ALL 120 | SELECT p.c0 121 | FROM %s p 122 | JOIN r ON p.c1 = r.c0 123 | ) 124 | SELECT * FROM r) 125 | EO_SQL 126 | $q1_table_name, 127 | $seed_id, 128 | $q1_table_name, 129 | ); 130 | 131 | my $fb_query = $node->firebird_conn()->prepare($fb_query_sql); 132 | 133 | $fb_query->execute(); 134 | 135 | my $expected_count = $fb_query->fetchrow_array(); 136 | 137 | # We want the following query to use a merge join 138 | 139 | $node->safe_psql( q|SET enable_hashjoin = 'off'| ); 140 | 141 | my $pg_query_sql = sprintf( 142 | <<'EO_SQL', 143 | WITH RECURSIVE r AS ( 144 | SELECT p.c0 145 | FROM %s p 146 | WHERE p.c1 = %i 147 | UNION ALL 148 | SELECT p.c0 149 | FROM %s p 150 | JOIN r ON p.c1 = r.c0 151 | ) 152 | SELECT count(*) FROM r 153 | EO_SQL 154 | $q1_table_name, 155 | $seed_id, 156 | $q1_table_name, 157 | ); 158 | 159 | my ($count_res, $count_stdout, $count_stderr) = $node->psql( 160 | $pg_query_sql, 161 | ); 162 | 163 | is ( 164 | $count_stdout, 165 | $expected_count, 166 | q|Check query results match|, 167 | ); 168 | 169 | # Clean up 170 | # -------- 171 | 172 | $node->drop_foreign_server(); 173 | 174 | $node->firebird_drop_table($q1_table_name); 175 | 176 | done_testing(); 177 | -------------------------------------------------------------------------------- /t/08-server-options.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 08-server-options.pl 4 | # 5 | # Check server-level options (work-in-progress) 6 | # 7 | # Option "quote_identifiers" covered by 09-identifier-quoting.pl 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More; 13 | 14 | use FirebirdFDWNode; 15 | 16 | # Initialize nodes 17 | # ---------------- 18 | 19 | my $node = FirebirdFDWNode->new(); 20 | 21 | 22 | # 1. Check options as reported by "firebird_fdw_server_options()" 23 | # --------------------------------------------------------------- 24 | 25 | # Record order is stable so no need for ORDER BY 26 | my $options_q1 = sprintf( 27 | q|SELECT * FROM firebird_fdw_server_options('%s')|, 28 | $node->{server_name}, 29 | ); 30 | 31 | my $ISC_PORT = $ENV{ISC_PORT} // 3050; 32 | 33 | my $options_e1 = sprintf( 34 | <{firebird_dbname}, 42 | ); 43 | 44 | chomp($options_e1); 45 | 46 | if ($node->pg_version() >= 140000) { 47 | $options_e1 = sprintf( 48 | <psql($options_q1); 72 | 73 | is( 74 | $res_stdout, 75 | $options_e1, 76 | 'Default options OK', 77 | ); 78 | 79 | # 2. Set "updatable" to "false" 80 | # ----------------------------- 81 | 82 | $node->alter_server_option('updatable', 'false'); 83 | 84 | my $options_q2 = sprintf( 85 | q|SELECT * FROM firebird_fdw_server_options('%s') WHERE name = 'updatable'|, 86 | $node->{server_name}, 87 | ); 88 | 89 | my $options_e2 = q/updatable|false|t/; 90 | 91 | ($res, $res_stdout, $res_stderr) = $node->psql($options_q2); 92 | 93 | is( 94 | $res_stdout, 95 | $options_e2, 96 | q|Disable "updatable" option|, 97 | ); 98 | 99 | # 3. Verify table cannot be updated 100 | # --------------------------------- 101 | 102 | my $table_name_3 = $node->init_table(); 103 | my $options_q3 = sprintf( 104 | q|INSERT INTO %s VALUES('xx','yy','zz')|, 105 | $table_name_3, 106 | ); 107 | 108 | my ($insert_res, $insert_stdout, $insert_stderr) = $node->psql( 109 | $options_q3, 110 | ); 111 | 112 | my $options_e3 = sprintf( 113 | q|foreign table "%s" does not allow inserts|, 114 | $table_name_3, 115 | ); 116 | 117 | like( 118 | $insert_stderr, 119 | qr/$options_e3/, 120 | q|Check table cannot be inserted into|, 121 | ); 122 | 123 | $node->firebird_drop_table($table_name_3); 124 | 125 | # 4. Create table with updatable=true, verify can be updated 126 | # ---------------------------------------------------------- 127 | 128 | my $table_name_4 = $node->init_table( 129 | 'updatable' => 'true', 130 | ); 131 | 132 | my $options_q4 = sprintf( 133 | q|INSERT INTO %s VALUES('xx','yy','zz')|, 134 | $table_name_4, 135 | ); 136 | 137 | $node->safe_psql( $options_q4 ); 138 | 139 | 140 | my $check_query_q4 = sprintf( 141 | q|SELECT lang_id, name_english, name_native FROM %s WHERE lang_id = 'xx'|, 142 | $table_name_4, 143 | ); 144 | 145 | my $query_q4 = $node->firebird_conn()->prepare($check_query_q4); 146 | 147 | $query_q4->execute(); 148 | 149 | my @res_q4 = $query_q4->fetchrow_array(); 150 | 151 | my $res_q4 = join('|', @res_q4); 152 | 153 | $query_q4->finish(); 154 | 155 | is( 156 | $res_q4, 157 | 'xx|yy|zz', 158 | q|table option "updatable" overrides server-level option|, 159 | ); 160 | 161 | 162 | $node->firebird_drop_table($table_name_4); 163 | 164 | # 5. drop updateable option 165 | # ------------------------- 166 | 167 | $node->drop_server_option('updatable'); 168 | 169 | my $options_q5 = sprintf( 170 | q|SELECT * FROM firebird_fdw_server_options('%s') WHERE name = 'updatable'|, 171 | $node->{server_name}, 172 | ); 173 | 174 | my $options_e5 = q/updatable|true|f/; 175 | 176 | ($res, $res_stdout, $res_stderr) = $node->psql($options_q5); 177 | 178 | is( 179 | $res_stdout, 180 | $options_e5, 181 | q|Drop "updatable" option|, 182 | ); 183 | 184 | 185 | # Clean up 186 | # -------- 187 | 188 | $node->drop_foreign_server(); 189 | 190 | done_testing(); 191 | -------------------------------------------------------------------------------- /t/18-batch-insert.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 18-batch-insert.pl 4 | # 5 | # Check that batch inserts work as expected (PostgreSQL 14 and later) 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | our $node = FirebirdFDWNode->new(); 18 | 19 | our $version = $node->pg_version(); 20 | 21 | if ($version < 140000) { 22 | plan skip_all => sprintf( 23 | q|version is %i, tests for 14 and later|, 24 | $version, 25 | ); 26 | } 27 | else { 28 | plan tests => 5; 29 | } 30 | 31 | 32 | our $batch_size = 5; 33 | 34 | # 1) Verify that "batch_size" setting works 35 | # ----------------------------------------- 36 | 37 | $node->alter_server_option('batch_size', $batch_size); 38 | 39 | my $batch_q1 = sprintf( 40 | q|SELECT * FROM firebird_fdw_server_options('%s') WHERE name = 'batch_size'|, 41 | $node->{server_name}, 42 | ); 43 | 44 | my $batch_e1 = qq/batch_size|${batch_size}|t/; 45 | 46 | my ($res, $res_stdout, $res_stderr) = $node->psql($batch_q1); 47 | 48 | is( 49 | $res_stdout, 50 | $batch_e1, 51 | qq|Set "batch_size" option to "${batch_size}"|, 52 | ); 53 | 54 | 55 | # Prepare table 56 | # -------------- 57 | 58 | my $table_name = $node->init_table( 59 | definition_pg => [ 60 | ['id', 'INT NOT NULL'], 61 | ['val', 'VARCHAR(32)'], 62 | ], 63 | definition_fb => [ 64 | ['id', 'INT NOT NULL PRIMARY KEY'], 65 | ['val', 'VARCHAR(32)'], 66 | ], 67 | ); 68 | 69 | 70 | my @tbl_data = (); 71 | my @tbl_expected = (); 72 | 73 | 74 | my $cur_val = 1; 75 | 76 | for (my $i = 1; $i < 99; $i++) { 77 | push @tbl_data, sprintf( 78 | q|(%i, 'test_%i')|, 79 | $cur_val, 80 | $i, 81 | ); 82 | 83 | push @tbl_expected, sprintf( 84 | q/%i|test_%i/, 85 | $cur_val, 86 | $i, 87 | ); 88 | 89 | $cur_val = $cur_val + int(rand(10)+1); 90 | } 91 | 92 | 93 | # 2. Verify that INSERT operations work 94 | # ------------------------------------- 95 | # 96 | # Here we assume that if the foreign table reports the values we just inserted, 97 | # everything is running correctly. We could double-check on the Firebird side, 98 | # but there seems little added benefit. 99 | 100 | my $insert_q2 = sprintf( 101 | <safe_psql( $insert_q2 ); 112 | 113 | my $select_q2 = sprintf( 114 | q|SELECT * FROM %s ORDER BY id|, 115 | $table_name, 116 | ); 117 | 118 | my $q2_expected = join("\n", @tbl_expected); 119 | 120 | ($res, $res_stdout, $res_stderr) = $node->psql($select_q2); 121 | 122 | is( 123 | $res_stdout, 124 | $q2_expected, 125 | q|Check inserted values are retrieved|, 126 | ); 127 | 128 | # 3. Verify reported batch size in EXPLAIN output 129 | # ----------------------------------------------- 130 | 131 | $node->truncate_table($table_name); 132 | 133 | my $explain_q3 = sprintf( 134 | <psql($explain_q3); 147 | 148 | 149 | like( 150 | $res_stdout, 151 | qr/Batch Size: ${batch_size}/, 152 | q|Check batch size value reported in EXPLAIN|, 153 | ); 154 | 155 | # 4. Verify behaviour with INSERT ... RETURNING 156 | # --------------------------------------------- 157 | 158 | $node->truncate_table($table_name); 159 | 160 | my $explain_q4 = sprintf( 161 | <psql($explain_q4); 175 | 176 | 177 | like( 178 | $res_stdout, 179 | qr/Batch Size: 1/, 180 | q|Check batch size value reported in EXPLAIN is 1 with INSERT ... RETURNING ...|, 181 | ); 182 | 183 | # 5. Verify table-level batch_size setting 184 | # ---------------------------------------- 185 | 186 | $node->truncate_table($table_name); 187 | 188 | our $table_batch_size = 15; 189 | 190 | $node->add_foreign_table_option( 191 | $table_name, 192 | 'batch_size', 193 | $table_batch_size, 194 | ); 195 | 196 | my $explain_q5 = sprintf( 197 | <psql($explain_q5); 209 | 210 | like( 211 | $res_stdout, 212 | qr/Batch Size: $table_batch_size/, 213 | qq|Check batch size value reported in EXPLAIN is matches table batch_size ${table_batch_size}|, 214 | ); 215 | -------------------------------------------------------------------------------- /t/15-implicit-booleans.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 15-implicit-booleans.pl 4 | # 5 | # Check support for implicit booleans 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | my @implicit_boolean_pushdowns = ( 20 | ['IS TRUE' , q|\(\(implicit_bool_type <> 0\)\)|], 21 | ['IS FALSE', q|\(\(implicit_bool_type = 0\)\)|], 22 | ['IS NOT TRUE', q|\(\(implicit_bool_type = 0\) OR \(implicit_bool_type IS NULL\)\)|], 23 | ['IS NOT FALSE', q|\(\(implicit_bool_type <> 0\) OR \(implicit_bool_type IS NULL\)\)|], 24 | ['IS NULL', q|\(\(implicit_bool_type IS NULL\)\)|], 25 | ['IS NOT NULL', q|\(\(implicit_bool_type IS NOT NULL\)\)|], 26 | ); 27 | 28 | my $test_cnt = scalar @implicit_boolean_pushdowns; 29 | $test_cnt += 3; 30 | 31 | plan tests => $test_cnt; 32 | 33 | $node->alter_server_option('implicit_bool_type', 'true'); 34 | 35 | my $table_name = $node->init_data_type_table(); 36 | 37 | # 1. Implicit Boolean pushdowns 38 | # ----------------------------- 39 | 40 | # Prepare table data 41 | # ------------------ 42 | 43 | 44 | # my $bool_insert_sql = sprintf( 45 | # <safe_psql($bool_insert_sql); 53 | 54 | foreach my $implicit_boolean_pushdown (@implicit_boolean_pushdowns) { 55 | my $explain_q = sprintf( 56 | q|EXPLAIN SELECT * FROM %s WHERE implicit_bool_type %s|, 57 | $table_name, 58 | $implicit_boolean_pushdown->[0], 59 | ); 60 | 61 | my ($explain_res, $explain_stdout, $explain_stderr) = $node->psql( 62 | $explain_q, 63 | ); 64 | 65 | my $explain_expected = sprintf( 66 | q|Firebird query: SELECT.+?WHERE\s+%s|, 67 | $implicit_boolean_pushdown->[1], 68 | ); 69 | 70 | like ( 71 | $explain_stdout, 72 | qr/$explain_expected/, 73 | sprintf( 74 | q|Check implicit pushdown for "%s"|, 75 | $implicit_boolean_pushdown->[0], 76 | ), 77 | ); 78 | } 79 | 80 | 81 | # 2. Check column retrieval 82 | # ------------------------- 83 | 84 | # Value to insert directly into Firebird 85 | 86 | my @values_2 = ( 87 | [1, 1], 88 | [2, 0], 89 | [3, undef], 90 | [4, 2], 91 | [5, -1], 92 | ); 93 | 94 | 95 | my $insert_2 = sprintf( 96 | <firebird_conn()->prepare($insert_2); 105 | 106 | foreach my $value_set (@values_2) { 107 | 108 | $fb_query_2->execute( 109 | $value_set->[0], 110 | $value_set->[1], 111 | ); 112 | } 113 | 114 | $fb_query_2->finish(); 115 | 116 | my $expected_2 = <psql($query_2); 132 | 133 | is ( 134 | $q2_stdout, 135 | $expected_2, 136 | q|Check retrieval of implicit bool column values|, 137 | ); 138 | 139 | # 3. Check INSERT 140 | # --------------- 141 | 142 | my $insert_3 = sprintf( 143 | <safe_psql($insert_3); 154 | 155 | # Check the expected values arrived in Firebird 156 | my $select_q3 = sprintf( 157 | q|SELECT id, implicit_bool_type FROM %s WHERE id >= 6 ORDER BY id|, 158 | $table_name, 159 | ); 160 | 161 | my $fb_query_3 = $node->firebird_conn()->prepare($select_q3); 162 | 163 | $fb_query_3->execute(); 164 | 165 | my $res_3 = $node->firebird_format_results($fb_query_3); 166 | 167 | $fb_query_3->finish(); 168 | 169 | my $expected_3 = <psql($insert_4); 200 | 201 | my $expected_4 = <firebird_drop_table($table_name); 220 | 221 | 222 | $node->drop_foreign_server(); 223 | 224 | done_testing(); 225 | -------------------------------------------------------------------------------- /t/02-returning.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 02-returning.pl 4 | # 5 | # Check "RETURNING" clauses function as expected 6 | 7 | 8 | use strict; 9 | use warnings; 10 | 11 | use Test::More; 12 | 13 | use FirebirdFDWNode; 14 | 15 | # Initialize PostgreSQL node 16 | # -------------------------- 17 | 18 | my $node = FirebirdFDWNode->new(); 19 | 20 | # Prepare table 21 | # -------------- 22 | 23 | my $table_name = $node->init_table(); 24 | 25 | 26 | # 1. INSERT ... RETURNING ... with specified columns 27 | # ================================================== 28 | 29 | my $insert_q = sprintf( 30 | <safe_psql( $insert_q ); 39 | 40 | 41 | is( 42 | $out, 43 | 'de|Deutsch', 44 | q|'INSERT ... RETURNING' with specified columns OK|, 45 | ); 46 | 47 | 48 | # 2. INSERT ... RETURNING ... with all columns 49 | # ============================================ 50 | 51 | $insert_q = sprintf( 52 | <safe_psql( $insert_q ); 62 | 63 | is( 64 | $out, 65 | qq/sv|Swedish|svenska/, 66 | q|'INSERT ... RETURNING' all columns OK|, 67 | ); 68 | 69 | 70 | # 3. INSERT ... RETURNING ... with multiple rows 71 | # ============================================== 72 | 73 | $insert_q = sprintf( 74 | <safe_psql( $insert_q ); 87 | 88 | 89 | is( 90 | $out, 91 | qq/Dansk|da\nNederlands|nl/, 92 | q|'INSERT ... RETURNING' with multiple rows OK|, 93 | ); 94 | 95 | 96 | # 4. UPDATE ... RETURNING ... with specified columns 97 | # ================================================== 98 | 99 | my $update_q = sprintf( 100 | <safe_psql( $update_q ); 109 | 110 | is( 111 | $out, 112 | qq/sv|wibblska/, 113 | q|'UPDATE ... RETURNING' with specified columns OK|, 114 | ); 115 | 116 | 117 | # 5. UPDATE ... RETURNING ... with all columns 118 | # ============================================ 119 | 120 | $update_q = sprintf( 121 | <safe_psql( $update_q ); 131 | 132 | is( 133 | $out, 134 | qq/de|German|Wibblisch/, 135 | q|'UPDATE ... RETURNING *' OK|, 136 | ); 137 | 138 | 139 | # 6. UPDATE ... RETURNING ... with multiple rows 140 | # ============================================== 141 | 142 | $update_q = sprintf( 143 | <safe_psql( $update_q ); 155 | 156 | is( 157 | $out, 158 | qq/Wibblisch (maybe)|de\nwibblska (maybe)|sv/, 159 | q|'UPDATE ... RETURNING' with multiple rows OK|, 160 | ); 161 | 162 | 163 | # 7. DELETE ... RETURNING ... with specified columns 164 | # ================================================== 165 | 166 | my $delete_q = sprintf( 167 | <safe_psql( $delete_q ); 176 | 177 | is( 178 | $out, 179 | qq/sv|wibblska (maybe)/, 180 | q|'DELETE ... RETURNING' with specified columns OK|, 181 | ); 182 | 183 | # 8. DELETE ... RETURNING ... with all columns 184 | # ============================================ 185 | 186 | $delete_q = sprintf( 187 | <safe_psql( $delete_q ); 197 | 198 | is( 199 | $out, 200 | qq/de|German|Wibblisch (maybe)/, 201 | q|'DELETE ... RETURNING *' OK|, 202 | ); 203 | 204 | # 9. DELETE ... RETURNING ... with multiple rows 205 | # ============================================== 206 | 207 | $delete_q = sprintf( 208 | <safe_psql( $delete_q ); 220 | 221 | is( 222 | $out, 223 | qq/Dansk|da\nNederlands|nl/, 224 | q|'DELETE ... RETURNING' with multiple rows OK|, 225 | ); 226 | 227 | 228 | # Clean up 229 | # -------- 230 | 231 | $node->drop_foreign_server(); 232 | $node->firebird_drop_table($table_name); 233 | 234 | 235 | done_testing(); 236 | -------------------------------------------------------------------------------- /t/19-truncate.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 19-truncate.pl 4 | # 5 | # Check that truncate functionality work as expected (PostgreSQL 14 and later) 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | our $node = FirebirdFDWNode->new(); 18 | 19 | our $version = $node->pg_version(); 20 | 21 | if ($version < 140000) { 22 | plan skip_all => sprintf( 23 | q|version is %i, tests for 14 and later|, 24 | $version, 25 | ); 26 | } 27 | 28 | 29 | # Prepare table 30 | # ------------- 31 | 32 | my $table_name = $node->init_table( 33 | definition_pg => [ 34 | ['id', 'INT NOT NULL'], 35 | ['val', 'VARCHAR(32)'], 36 | ], 37 | definition_fb => [ 38 | ['id', 'INT NOT NULL PRIMARY KEY'], 39 | ['val', 'VARCHAR(32)'], 40 | ], 41 | ); 42 | 43 | # 1) check server-level truncatable = false 44 | # ----------------------------------------- 45 | 46 | $node->alter_server_option('truncatable', 'false'); 47 | 48 | my $truncatable_q1 = sprintf( 49 | q|SELECT * FROM firebird_fdw_server_options('%s') WHERE name = 'truncatable'|, 50 | $node->{server_name}, 51 | ); 52 | 53 | my $truncatable_e1 = qq/truncatable|false|t/; 54 | 55 | my ($res, $res_stdout, $res_stderr) = $node->psql($truncatable_q1); 56 | 57 | is( 58 | $res_stdout, 59 | $truncatable_e1, 60 | qq|Set "truncatable" option to "false"|, 61 | ); 62 | 63 | 64 | # 2) check behaviour when truncatable = false (server) 65 | # ---------------------------------------------------- 66 | 67 | my $truncate_q2 = sprintf( 68 | q|TRUNCATE %s|, 69 | $table_name, 70 | ); 71 | 72 | ($res, $res_stdout, $res_stderr) = $node->psql($truncate_q2); 73 | 74 | my $truncate_e2 = sprintf( 75 | q|foreign table "%s" does not allow truncates|, 76 | $table_name, 77 | ); 78 | 79 | like( 80 | $res_stderr, 81 | qr|$truncate_e2|, 82 | qq|Check truncate fails when server "truncatable" option is "false"|, 83 | ); 84 | 85 | 86 | # 3) check behaviour when truncatable = false (server) and true (table) 87 | # --------------------------------------------------------------------- 88 | 89 | 90 | $node->add_foreign_table_option( 91 | $table_name, 92 | 'truncatable', 93 | 'true', 94 | ); 95 | 96 | my $truncate_q3 = sprintf( 97 | q|TRUNCATE %s|, 98 | $table_name, 99 | ); 100 | 101 | ($res, $res_stdout, $res_stderr) = $node->psql($truncate_q2); 102 | 103 | is( 104 | $res, 105 | 0, 106 | qq|Check truncate succeeds when "truncatable" option is "false" for server and "true" for table|, 107 | ); 108 | 109 | # 4) check behaviour when truncatable = true (server) and false (table) 110 | # --------------------------------------------------------------------- 111 | 112 | my $truncate_q4 = sprintf( 113 | q|TRUNCATE %s|, 114 | $table_name, 115 | ); 116 | 117 | $node->alter_server_option('truncatable', 'true'); 118 | 119 | $node->alter_foreign_table_option( 120 | $table_name, 121 | 'truncatable', 122 | 'false', 123 | ); 124 | 125 | ($res, $res_stdout, $res_stderr) = $node->psql($truncate_q4); 126 | 127 | my $truncate_e4 = sprintf( 128 | q|foreign table "%s" does not allow truncates|, 129 | $table_name, 130 | ); 131 | 132 | like( 133 | $res_stderr, 134 | qr|$truncate_e4|, 135 | qq|Check truncate fails when "truncatable" option is "true" (server) and "false" (table)|, 136 | ); 137 | 138 | # Restore table's truncatable status 139 | 140 | $node->alter_foreign_table_option( 141 | $table_name, 142 | 'truncatable', 143 | 'true', 144 | ); 145 | 146 | 147 | # 5. Verify data removed 148 | # ---------------------- 149 | 150 | # Insert some data; we'll assume the standard FDW functionality is working 151 | # at this point, so no need to verify the Firebird side. 152 | 153 | my $insert_sql_q5 = sprintf( 154 | <<'EO_SQL', 155 | INSERT INTO %s 156 | (id, val) 157 | VALUES (1, 'foo'), 158 | (2, 'bar'), 159 | (3, 'baz') 160 | EO_SQL 161 | $table_name, 162 | ); 163 | 164 | $node->safe_psql($insert_sql_q5); 165 | 166 | my $truncate_q5 = sprintf( 167 | q|TRUNCATE %s|, 168 | $table_name, 169 | ); 170 | 171 | $node->safe_psql($truncate_q5); 172 | 173 | my $rows_q5 = sprintf( 174 | q|SELECT COUNT(*) FROM %s|, 175 | $table_name, 176 | ); 177 | 178 | ($res, $res_stdout, $res_stderr) = $node->psql($rows_q5); 179 | 180 | is( 181 | $res_stdout, 182 | q|0|, 183 | qq|Check table contains 0 rows after INSERT and TRUNCATE operation|, 184 | ); 185 | 186 | # 6. Verify TRUNCATE ... CASCADE is rejected 187 | # ------------------------------------------ 188 | 189 | my $truncate_q6 = sprintf( 190 | q|TRUNCATE %s CASCADE|, 191 | $table_name, 192 | ); 193 | 194 | ($res, $res_stdout, $res_stderr) = $node->psql($truncate_q6); 195 | 196 | like( 197 | $res_stderr, 198 | qr|TRUNCATE with CASCADE option not supported by firebird_fdw|, 199 | qq|Check truncate fails when TRUNCATE ... CASCADE is provided|, 200 | ); 201 | 202 | 203 | # 7. Verify TRUNCATE ... RESTART IDENTITY is rejected 204 | # --------------------------------------------------- 205 | 206 | my $truncate_q7 = sprintf( 207 | q|TRUNCATE %s RESTART IDENTITY|, 208 | $table_name, 209 | ); 210 | 211 | ($res, $res_stdout, $res_stderr) = $node->psql($truncate_q7); 212 | 213 | like( 214 | $res_stderr, 215 | qr|TRUNCATE with RESTART IDENTITY option not supported by firebird_fdw|, 216 | qq|Check truncate fails when TRUNCATE ... CASCADE is provided|, 217 | ); 218 | 219 | # 8. Verify TRUNCATE rejected for Firebird tables with foreign key references 220 | # --------------------------------------------------------------------------- 221 | 222 | my $fkey_table = $node->init_table( 223 | firebird_only => 1, 224 | definition_fb => [ 225 | ['id', sprintf('INT NOT NULL REFERENCES %s (id)', $table_name)], 226 | ], 227 | ); 228 | 229 | my $truncate_q8 = sprintf( 230 | q|TRUNCATE %s|, 231 | $table_name, 232 | ); 233 | 234 | ($res, $res_stdout, $res_stderr) = $node->psql($truncate_q8); 235 | 236 | my $truncate_e8 = sprintf(q|foreign table "%s" has foreign key references|, $table_name); 237 | 238 | like( 239 | $res_stderr, 240 | qr|$truncate_e8|, 241 | qq|Check truncate fails when TRUNCATE ... CASCADE is provided|, 242 | ); 243 | 244 | # Clean up 245 | # -------- 246 | 247 | $node->drop_foreign_server(); 248 | 249 | $node->firebird_drop_table($fkey_table); 250 | $node->firebird_drop_table($table_name); 251 | 252 | done_testing(); 253 | -------------------------------------------------------------------------------- /t/03-triggers.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 03-triggers.pl 4 | # 5 | # Check triggers are supported (9.4 and later) 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | 15 | # Initialize nodes 16 | # ---------------- 17 | 18 | my $node = FirebirdFDWNode->new(); 19 | 20 | # Prepare table 21 | # -------------- 22 | 23 | my $table_name = $node->init_table(); 24 | 25 | # Initialize trigger 26 | # ------------------ 27 | 28 | my $results_tbl = <<'EO_SQL'; 29 | CREATE TABLE results ( 30 | subtest SERIAL, 31 | old_val TEXT NULL, 32 | new_val TEXT NULL 33 | ) 34 | EO_SQL 35 | 36 | $node->safe_psql($results_tbl); 37 | 38 | my $after_trigger_func = <<'EO_SQL'; 39 | CREATE OR REPLACE FUNCTION test_after_trigger_func() 40 | RETURNS TRIGGER 41 | LANGUAGE plpgsql 42 | AS $$ 43 | BEGIN 44 | 45 | IF TG_OP = 'INSERT' THEN 46 | INSERT INTO results (subtest, new_val) 47 | VALUES (DEFAULT, NEW.name_native); 48 | RETURN NEW; 49 | ELSIF TG_OP = 'UPDATE' THEN 50 | INSERT INTO results (subtest, old_val, new_val) 51 | VALUES (DEFAULT, OLD.name_native, NEW.name_native); 52 | RETURN NEW; 53 | END IF; 54 | 55 | INSERT INTO results (subtest, old_val) 56 | VALUES (DEFAULT, OLD.name_native); 57 | 58 | RETURN OLD; 59 | END 60 | $$ 61 | EO_SQL 62 | 63 | $node->safe_psql($after_trigger_func); 64 | 65 | my $before_trigger_func = <<'EO_SQL'; 66 | CREATE OR REPLACE FUNCTION test_before_trigger_func() 67 | RETURNS TRIGGER 68 | LANGUAGE plpgsql 69 | AS $$ 70 | BEGIN 71 | IF TG_OP = 'INSERT' THEN 72 | NEW.name_native = NEW.name_english || ' ins'; 73 | ELSIF TG_OP = 'UPDATE' THEN 74 | NEW.name_native = NEW.name_english || ' upd'; 75 | END IF; 76 | RETURN NEW; 77 | END; 78 | $$; 79 | EO_SQL 80 | 81 | $node->safe_psql($before_trigger_func); 82 | 83 | my $after_trigger_def = sprintf( 84 | <<'EO_SQL', 85 | CREATE TRIGGER trigtest_after_stmt 86 | AFTER INSERT OR UPDATE OR DELETE 87 | ON public.%s 88 | FOR EACH ROW 89 | EXECUTE PROCEDURE test_after_trigger_func() 90 | EO_SQL 91 | $table_name, 92 | ); 93 | 94 | $node->safe_psql($after_trigger_def); 95 | 96 | my $before_trigger_def = sprintf( 97 | <<'EO_SQL', 98 | CREATE TRIGGER trigtest_before_stmt 99 | BEFORE INSERT OR UPDATE OR DELETE 100 | ON public.%s 101 | FOR EACH ROW 102 | EXECUTE PROCEDURE test_before_trigger_func() 103 | EO_SQL 104 | $table_name, 105 | ); 106 | 107 | $node->safe_psql($before_trigger_def); 108 | 109 | $node->safe_psql( 110 | sprintf(q|ALTER TABLE public.%s DISABLE TRIGGER trigtest_before_stmt|, $table_name), 111 | ); 112 | 113 | # 1. AFTER ... INSERT test 114 | # ------------------------ 115 | 116 | my $subtest = 1; 117 | 118 | my $after_insert_sql = sprintf( 119 | <<'EO_SQL', 120 | INSERT INTO %s (lang_id, name_english, name_native) 121 | VALUES('aa', 'foo', 'bar'); 122 | EO_SQL 123 | $table_name, 124 | ); 125 | 126 | $node->safe_psql($after_insert_sql); 127 | 128 | my ($after_insert_res, $after_insert_stdout, $after_insert_stderr) = $node->psql( 129 | sprintf(q|SELECT * FROM results WHERE subtest = %i|, $subtest), 130 | ); 131 | 132 | is ( 133 | $after_insert_stdout, 134 | q/1||bar/, 135 | q|Check AFTER ... INSERT|, 136 | ); 137 | 138 | # 2. AFTER ... UPDATE test 139 | # ------------------------ 140 | 141 | $subtest++; 142 | 143 | my $after_update_sql = sprintf( 144 | <<'EO_SQL', 145 | UPDATE %s 146 | SET name_native = 'baz' 147 | WHERE lang_id = 'aa' 148 | EO_SQL 149 | $table_name, 150 | ); 151 | 152 | $node->safe_psql($after_update_sql); 153 | 154 | my ($after_update_res, $after_update_stdout, $after_update_stderr) = $node->psql( 155 | sprintf(q|SELECT * FROM results WHERE subtest = %i|, $subtest), 156 | ); 157 | 158 | is ( 159 | $after_update_stdout, 160 | q/2|bar|baz/, 161 | q|Check AFTER ... UPDATE|, 162 | ); 163 | 164 | # 3. AFTER ... DELETE test 165 | # ------------------------ 166 | 167 | $subtest++; 168 | 169 | my $after_delete_sql = sprintf( 170 | <<'EO_SQL', 171 | DELETE FROM %s 172 | WHERE lang_id = 'aa' 173 | EO_SQL 174 | $table_name, 175 | ); 176 | 177 | $node->safe_psql($after_delete_sql); 178 | 179 | my ($after_delete_res, $after_delete_stdout, $after_delete_stderr)= $node->psql( 180 | sprintf(q|SELECT * FROM results WHERE subtest = %i|, $subtest), 181 | ); 182 | 183 | is ( 184 | $after_delete_stdout, 185 | q/3|baz|/, 186 | q|Check AFTER ... DELETE|, 187 | ); 188 | 189 | # Disable AFTER, enable BEFORE trigger 190 | # ------------------------------------ 191 | 192 | $node->safe_psql( 193 | sprintf(q|ALTER TABLE public.%s DISABLE TRIGGER trigtest_after_stmt|, $table_name), 194 | ); 195 | 196 | $node->safe_psql( 197 | sprintf(q|ALTER TABLE public.%s ENABLE TRIGGER trigtest_before_stmt|, $table_name), 198 | ); 199 | 200 | # 4. BEFORE ... INSERT test 201 | # -------------------------- 202 | 203 | my $before_insert_sql = sprintf( 204 | qq|INSERT INTO %s values('bb','foo')|, 205 | $table_name, 206 | ); 207 | 208 | $node->safe_psql($before_insert_sql); 209 | 210 | my $before_insert_check_sql = sprintf( 211 | q|SELECT name_native FROM %s WHERE lang_id = 'bb'|, 212 | $table_name, 213 | ); 214 | 215 | my $before_insert_query = $node->firebird_conn()->prepare($before_insert_check_sql); 216 | 217 | $before_insert_query->execute(); 218 | 219 | my $before_insert_res = $before_insert_query->fetchrow_array(); 220 | 221 | $before_insert_query->finish(); 222 | 223 | is ( 224 | $before_insert_res, 225 | q|foo ins|, 226 | q|Check BEFORE ... INSERT|, 227 | ); 228 | 229 | 230 | # 5. BEFORE ... UPDATE test 231 | # -------------------------- 232 | 233 | my $before_update_sql = sprintf( 234 | q|UPDATE %s SET name_english = 'bar' WHERE lang_id = 'bb'|, 235 | $table_name, 236 | ); 237 | 238 | $node->safe_psql($before_update_sql); 239 | 240 | my $before_update_check_sql = sprintf( 241 | q|SELECT name_native FROM %s WHERE lang_id = 'bb'|, 242 | $table_name, 243 | ); 244 | 245 | my $before_update_query = $node->firebird_conn()->prepare($before_update_check_sql); 246 | 247 | $before_update_query->execute(); 248 | 249 | my $before_update_res = $before_update_query->fetchrow_array(); 250 | 251 | $before_update_query->finish(); 252 | 253 | is ( 254 | $before_update_res, 255 | q|bar upd|, 256 | q|Check BEFORE ... UPDATE|, 257 | ); 258 | 259 | 260 | 261 | 262 | # Clean up 263 | # -------- 264 | 265 | $node->drop_foreign_server(); 266 | $node->firebird_drop_table($table_name); 267 | 268 | done_testing(); 269 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Revision history for firebird_fdw 2 | 3 | 1.4.0 2024-05-11 4 | - Support for PostgreSQL 17 added 5 | - Basic support for UUID datatype added 6 | - Proper support for Firebird TIME/TIMESTAMP datatypes added 7 | - Support for Firebird TIME WITH TIME ZONE / TIMESTAMP WITH TIME ZONE 8 | datatypes added 9 | - Support for importing Firebird INT128 datatype added 10 | - Import Firebird FLOAT as PostgreSQL REAL 11 | - Fix generated columns support 12 | 13 | 1.3.1 2023-06-22 14 | - Support for PostgreSQL 16 added 15 | 16 | 1.3.0 2022-12-28 17 | - Support for PostgreSQL 15 added 18 | - Support for PostgreSQL 9.3/9.4 removed 19 | - Basic support for TRUNCATE (PostgreSQL 14+) 20 | - Support for batch inserts via COPY added (PostgreSQL 14+) 21 | - Map some PostgreSQL server encodings to the Firebird equivalent 22 | where no Firebird encoding alias exists 23 | - Various fixes and improvements for IMPORT FOREIGN SCHEMA 24 | 25 | 1.2.3 2022-02-11 26 | - Fix bug in rescan handling 27 | - Adapt TAP tests to support PostgreSQL 15 28 | 29 | 1.2.2 2021-09-15 30 | - Invalid Firebird server port numbers will be rejected 31 | - Enable provision of custom Firebird port for TAP tests 32 | - Support PostgreSQL 14 33 | - Adapt for API changes introduced in PostgreSQL 11+ 34 | - Support for versions earlier than PostgreSQL 9.3 removed 35 | - Ensure non-superusers can execute firebird_version() 36 | 37 | 1.2.1 2020-10-21 38 | - Fix builds for recent Fedora versions 39 | 40 | 1.2.0 2020-10-17 41 | - Add support for PostgreSQL 13 42 | - Add support for generated columns (PostgreSQL 12 and later) 43 | - Add support for COPY and partition tuple routing (PostgreSQL 11 and later) 44 | - Add table/column option "quote_identifier" 45 | - Add server option "quote_identifiers" 46 | - Have IMPORT FOREIGN SCHEMA add "quote_identifier" where appropriate 47 | - Add support for implicit boolean columns 48 | - Push down boolean tests in WHERE clauses where possible 49 | - Add function firebird_fdw_server_options() 50 | - Add function firebird_version() 51 | - When analyzing a foreign table, only retrieve the columns defined in 52 | that table 53 | - Have EXPLAIN for a remote modify show the Firebird query used 54 | - If a remote query fails, log the query as CONTEXT 55 | - Explicitly reject INSERT with ON CONFLICT clause 56 | - Fix UPDATE statements where a BEFORE ROW UPDATE trigger is present 57 | - Apply CREATE SERVER's "port" option, if provided 58 | - Prevent tables defined as queries from being set as "updatable = 'true'" 59 | 60 | 1.1.0 2019-05-31 61 | - Add utility function "firebird_fdw_close_connections()" 62 | - Add utility function "firebird_fdw_diag()" 63 | - Adapt code to deal with tuple-level OID removal in PostgreSQL 12 64 | - Support for heap access method changes in PostgreSQL 12 65 | 66 | 1.0.0 2018-11-09 67 | - Support Firebird BOOLEAN datatype (Firebird 3.0 and later) 68 | - Improve "IMPORT FOREIGN SCHEMA" implementation 69 | - Add table option "estimated_row_count" 70 | - Improve handling of foreign tables defined as Firebird queries 71 | - Support PostgreSQL 11 72 | - Adapt code to compile against current PostgreSQL HEAD 73 | - Avoid segfault if not all expected Firebird connection parameters 74 | were supplied 75 | 76 | 0.5.0 2018-10-12 77 | - Support triggers on foreign tables (PostgreSQL 9.4 and later) 78 | - Support Firebird BLOB datatype (TEXT subtype only) 79 | 80 | 0.4.0 2018-10-02 81 | - Add function firebird_fdw_version() 82 | - Add TAP tests 83 | - Fix bug in INSERT ... RETURNING ... 84 | - Miscellaneous minor bug fixes and code cleanup 85 | 86 | 0.3.0 2018-04-22 87 | - Support PostgreSQL 9.6 and 10 88 | - Add support for IMPORT FOREIGN SCHEMA (PostgreSQL 9.5 and later) 89 | - Ensure tables defined with the 'query' option actually work 90 | - Automatically reconnect if Firebird goes away 91 | - Update function calls for changes in libfq 0.2.0 92 | - Improve SIGINT handling 93 | - Improve error message handling 94 | - Fix memory error when generating connection string 95 | - Fix crash when multibyte string overflows (GitHub #5) 96 | - Report transaction errors as actual errors; log harmless 97 | unhandled transaction types as DEBUG rather than as WARNING 98 | - General code cleanup 99 | 100 | 0.2.5 2014-02-11 101 | - Add RPM spec files for openSUSE and Red Hat 102 | 103 | 0.2.4 2014-02-06 104 | - libfq now supports a "client_encoding" connection parameter, 105 | which enables us to cover most reasonably common encoding 106 | combinations by passing the value provided by 107 | GetDatabaseEncodingName() to Firebird. See caveat in code 108 | regarding compatibility between PostgreSQL and Firebird encoding 109 | names. 110 | 111 | 0.2.3 2014-02-02 112 | - Default to PostgreSQL table name if no 'table_name' option 113 | explicitly provided 114 | 115 | 0.2.2 2014-01-25 116 | - Implement basic transaction support 117 | 118 | 0.2.1 2014-01-23 119 | - Make extension compatible with PostgreSQL 9.2 120 | - Fix some regressions 121 | 122 | 0.2.0 2014-01-23 123 | - Implement basic connection caching 124 | - Fix implicit cast handling 125 | 126 | 0.1.6 2014-01-21 127 | - Push down bitwise operators << and >> (FB 2.1 and later) 128 | - Push down two-argument form of SUBSTRING() 129 | - Push down CONCAT() 130 | - Push down POSITION() / STRPOS() 131 | - Don't check for mutable functions - we are explicitly 132 | whitelisting functions which can be pushed down 133 | (need to double-check if that's a valid approach) 134 | 135 | 0.1.5 2014-01-20 136 | - Add basic Firebird version detection to ensure that only 137 | functions available on the remote Firebird server are actually 138 | pushed down 139 | - Push down functions COALESCE(), NULLIF() 140 | - Only push down SUBSTRING() if 2nd and 3rd arguments are INTs 141 | 142 | 0.1.4 2014-01-13 143 | - Prevent implicit casts from being pushed down to Firebird 144 | 145 | 0.1.3 2014-01-13 146 | - Partial pushdown of functions in WHERE clauses to Firebird 147 | - Ensure parameter "disable_pushdowns" is treated as a boolean 148 | - Add CREATE FOREIGN SERVER/CREATE FOREIGN TABLE parameter 149 | "updatable", matching the postgres_fdw parameter 150 | 151 | 0.1.2 2014-01-11 152 | - Partial pushdown of WHERE clauses to Firebird 153 | - new CREATE FOREIGN SERVER parameter "disable_pushdowns" 154 | - CREATE FOREIGN TABLE parameter "table" renamed to "table_name" 155 | (matching the postgres_fdw parameter and for consistency with the 156 | column option "column_name") 157 | 158 | 0.1.1 2014-01-01 159 | - Add support for column aliases (column option "column_name") 160 | 161 | 0.1.0 2014-01-01 162 | - Initial public release 163 | -------------------------------------------------------------------------------- /t/04-data-types.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 04-data-types.pl 4 | # 5 | # Check various data types (work-in-progress) 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | # Initialize nodes 15 | # ---------------- 16 | 17 | my $node = FirebirdFDWNode->new(); 18 | 19 | 20 | # Prepare table 21 | # -------------- 22 | 23 | my $table_name = $node->init_data_type_table(); 24 | 25 | note qq|data type table name: "$table_name"|; 26 | 27 | # 1) Test BLOB type 28 | # ----------------- 29 | 30 | my $blob_insert_sql = sprintf( 31 | <safe_psql($blob_insert_sql); 39 | 40 | my $q1_sql = sprintf( 41 | q|SELECT blob_type FROM %s WHERE id = 1|, 42 | $table_name, 43 | ); 44 | 45 | my $q1 = $node->firebird_conn()->prepare($q1_sql); 46 | 47 | $q1->execute(); 48 | 49 | my $q1_res = $q1->fetchrow_array(); 50 | 51 | $q1->finish(); 52 | 53 | is ( 54 | $q1_res, 55 | qq/foo\nbar\nテスト/, 56 | q|Check BLOB (subtype TEXT)|, 57 | ); 58 | 59 | 60 | if ($node->{firebird_major_version} >= 3) { 61 | 62 | # 2) Test BOOLEAN type (basic) 63 | # ---------------------------- 64 | # 65 | # Check that inserted values will round-trip 66 | 67 | my $bool_insert_sql = sprintf( 68 | <safe_psql($bool_insert_sql); 76 | 77 | my $q2_sql = sprintf( 78 | q|SELECT id, bool_type FROM %s WHERE id IN (2,3,4) ORDER BY id|, 79 | $table_name, 80 | ); 81 | 82 | my ($q2_res, $q2_stdout, $q2_stderr) = $node->psql($q2_sql); 83 | 84 | is ( 85 | $q2_stdout, 86 | qq/2|t\n3|f\n4|/, 87 | q|Check BOOLEAN|, 88 | ); 89 | 90 | # 3) Test BOOLEAN "IS TRUE" 91 | # ------------------------- 92 | 93 | my $q3_sql = sprintf( 94 | q|SELECT id, bool_type FROM %s WHERE id >= 2 AND bool_type IS TRUE|, 95 | $table_name, 96 | ); 97 | 98 | my ($q3_res, $q3_stdout, $q3_stderr) = $node->psql($q3_sql); 99 | 100 | is ( 101 | $q3_stdout, 102 | qq/2|t/, 103 | q|Check BOOLEAN query with "IS TRUE"|, 104 | ); 105 | 106 | # 4) Test BOOLEAN "IS NOT TRUE" 107 | # ----------------------------- 108 | 109 | my $q4_sql = sprintf( 110 | q|SELECT id, bool_type FROM %s WHERE id >= 2 AND bool_type IS NOT TRUE|, 111 | $table_name, 112 | ); 113 | 114 | my ($q4_res, $q4_stdout, $q4_stderr) = $node->psql($q4_sql); 115 | 116 | is ( 117 | $q4_stdout, 118 | qq/3|f\n4|/, 119 | q|Check BOOLEAN query with "IS NOT TRUE"|, 120 | ); 121 | 122 | # 5) Test BOOLEAN "IS FALSE" 123 | # -------------------------- 124 | 125 | my $q5_sql = sprintf( 126 | q|SELECT id, bool_type FROM %s WHERE id >= 2 AND bool_type IS FALSE|, 127 | $table_name, 128 | ); 129 | 130 | my ($q5_res, $q5_stdout, $q5_stderr) = $node->psql($q5_sql); 131 | 132 | is ( 133 | $q5_stdout, 134 | qq/3|f/, 135 | q|Check BOOLEAN query with "IS FALSE"|, 136 | ); 137 | 138 | # 6) Test BOOLEAN "IS NOT FALSE" 139 | # ------------------------------ 140 | 141 | my $q6_sql = sprintf( 142 | q|SELECT id, bool_type FROM %s WHERE id >= 2 AND bool_type IS NOT FALSE|, 143 | $table_name, 144 | ); 145 | 146 | my ($q6_res, $q6_stdout, $q6_stderr) = $node->psql($q6_sql); 147 | 148 | is ( 149 | $q6_stdout, 150 | qq/2|t\n4|/, 151 | q|Check BOOLEAN query with "IS NOT FALSE"|, 152 | ); 153 | 154 | # 7) Test BOOLEAN "IS NULL" 155 | # ------------------------- 156 | 157 | my $q7_sql = sprintf( 158 | q|SELECT id, bool_type FROM %s WHERE id >= 2 AND bool_type IS NULL|, 159 | $table_name, 160 | ); 161 | 162 | my ($q7_res, $q7_stdout, $q7_stderr) = $node->psql($q7_sql); 163 | 164 | is ( 165 | $q7_stdout, 166 | qq/4|/, 167 | q|Check BOOLEAN query with "IS NULL"|, 168 | ); 169 | 170 | # 8) Test BOOLEAN "IS NOT NULL" 171 | # ----------------------------- 172 | 173 | my $q8_sql = sprintf( 174 | q|SELECT id, bool_type FROM %s WHERE id >= 2 AND bool_type IS NOT NULL|, 175 | $table_name, 176 | ); 177 | 178 | my ($q8_res, $q8_stdout, $q8_stderr) = $node->psql($q8_sql); 179 | 180 | is ( 181 | $q8_stdout, 182 | qq/2|t\n3|f/, 183 | q|Check BOOLEAN query with "IS NOT NULL"|, 184 | ); 185 | } 186 | 187 | 188 | # 9) Test UUID type 189 | # ----------------- 190 | # 191 | # Note: currently insertion of UUID values into OCTETS columns is not supported, 192 | # so we need to generate the UUID in Firebird. 193 | 194 | $node->firebird_execute_sql( 195 | sprintf( 196 | <<'EO_SQL', 197 | INSERT INTO %s 198 | (id, uuid_type) 199 | VALUES 200 | (5, (SELECT gen_uuid() FROM rdb$database)) 201 | EO_SQL 202 | $table_name, 203 | ), 204 | ); 205 | 206 | # retrieve UUID from Firebird as formatted text, normalized to lower case 207 | # (so it matches the value PostgreSQL will retrieve via the FDW) 208 | 209 | my $uuid_std = $node->firebird_single_value_query( 210 | sprintf( 211 | <<'EO_SQL', 212 | SELECT lower(uuid_to_char(uuid_type)) 213 | FROM %s 214 | WHERE id = 5 215 | EO_SQL 216 | $table_name, 217 | ), 218 | ); 219 | 220 | note "uuid $uuid_std"; 221 | 222 | my $q9_sql = sprintf( 223 | sprintf( 224 | <<'EO_SQL', 225 | SELECT uuid_type 226 | FROM %s 227 | WHERE id = 5 228 | EO_SQL 229 | $table_name, 230 | ), 231 | ); 232 | 233 | my ($q9_res, $q9_stdout, $q9_stderr) = $node->psql($q9_sql); 234 | 235 | is ( 236 | $q9_stdout, 237 | qq/$uuid_std/, 238 | q|Check UUID value retrieved correctly|, 239 | ); 240 | 241 | # 10) Test UUID type comparison 242 | # ----------------------------- 243 | 244 | # The Perl DBD driver returns OCTET values as raw bytes; 245 | # convert the raw bytes into the hexadecimal equivalent 246 | 247 | my $uuid_raw = $node->firebird_single_value_query( 248 | sprintf( 249 | <<'EO_SQL', 250 | SELECT uuid_type 251 | FROM %s 252 | WHERE id = 5 253 | EO_SQL 254 | $table_name, 255 | ), 256 | ); 257 | 258 | my $uuid_nonstd = ''; 259 | 260 | for (my $i = 0; $i < length($uuid_raw); $i++) { 261 | $uuid_nonstd .= sprintf(q|%02X|, ord(substr($uuid_raw, $i, 1))); 262 | } 263 | 264 | note "uuid $uuid_nonstd"; 265 | 266 | my $q10_sql = sprintf( 267 | sprintf( 268 | <<'EO_SQL', 269 | SELECT id 270 | FROM %s 271 | WHERE uuid_type = '%s'::uuid 272 | EO_SQL 273 | $table_name, 274 | $uuid_nonstd, 275 | ), 276 | ); 277 | 278 | my ($q10_res, $q10_stdout, $q10_stderr) = $node->psql($q10_sql); 279 | 280 | is ( 281 | $q10_stdout, 282 | qq/5/, 283 | q|Check UUID value retrieved correctly|, 284 | ); 285 | 286 | # 11) Retrieve TIME/TIMESTAMP [WITHOUT TIME ZONE] 287 | # ----------------------------------------------- 288 | 289 | $node->firebird_execute_sql( 290 | sprintf( 291 | <<'EO_SQL', 292 | INSERT INTO %s 293 | (id, time_type, timestamp_type) 294 | VALUES 295 | (11, 296 | '09:00:33.1230', 297 | '1999-12-31 23:59:59.1000' 298 | ) 299 | EO_SQL 300 | $table_name, 301 | ), 302 | ); 303 | 304 | my $q11_sql = sprintf( 305 | sprintf( 306 | <<'EO_SQL', 307 | SELECT time_type, timestamp_type 308 | FROM %s 309 | WHERE id = 11 310 | EO_SQL 311 | $table_name, 312 | ), 313 | ); 314 | 315 | my ($q11_res, $q11_stdout, $q11_stderr) = $node->psql($q11_sql); 316 | 317 | is ( 318 | $q11_stdout, 319 | qq/09:00:33.123|1999-12-31 23:59:59.1/, 320 | q|Check TIME/TIMESTAMP values retrieved correctly|, 321 | ); 322 | 323 | 324 | if ($node->{firebird_major_version} >= 4) { 325 | 326 | # 12 Insert TIME/TIMESTAMP WITH TIME ZONE 327 | # --------------------------------------- 328 | 329 | my $tz = $node->get_firebird_session_timezone(); 330 | note "Firebird system time zone is: ".$tz; 331 | 332 | my $timestamp_insert_sql = sprintf( 333 | <<'EO_SQL', 334 | INSERT INTO %s 335 | (id, time_type, timestamp_type) 336 | VALUES 337 | (12, '12:30:15.123456', '2011-12-13 14:15:16.123456') 338 | EO_SQL 339 | $table_name, 340 | ); 341 | 342 | $node->safe_psql($timestamp_insert_sql); 343 | 344 | my $q12_sql = sprintf( 345 | <<'EO_SQL', 346 | SELECT time_type, timestamp_type 347 | FROM %s 348 | WHERE id = 12 349 | EO_SQL 350 | $table_name, 351 | ); 352 | 353 | my ($q12_res, $q12_stdout, $q12_stderr) = $node->psql($q12_sql); 354 | 355 | is ( 356 | $q12_stdout, 357 | qq/12:30:15.1234|2011-12-13 14:15:16.1234/, 358 | q|Check TIME/TIMESTAMP values inserted correctly|, 359 | ); 360 | 361 | # 13) Retrieve TIME/TIMESTAMP WITH TIME ZONE 362 | # ------------------------------------------ 363 | 364 | $node->firebird_execute_sql( 365 | sprintf( 366 | <<'EO_SQL', 367 | INSERT INTO %s 368 | (id, ttz_type, tstz_type) 369 | VALUES 370 | (13, 371 | '14:46:33.1230 Asia/Tokyo', 372 | '1999-12-31 23:59:59.0100 +05:30' 373 | ) 374 | EO_SQL 375 | $table_name, 376 | ), 377 | ); 378 | 379 | my $q13_sql = sprintf( 380 | sprintf( 381 | <<'EO_SQL', 382 | SELECT ttz_type, tstz_type 383 | FROM %s 384 | WHERE id = 13 385 | EO_SQL 386 | $table_name, 387 | ), 388 | ); 389 | 390 | my ($q13_res, $q13_stdout, $q13_stderr) = $node->psql($q13_sql); 391 | 392 | is ( 393 | $q13_stdout, 394 | qq/14:46:33.123+09|2000-01-01 03:29:59.01+09/, 395 | q|Check TIME/TIMESTAMP WITH TIME ZONE values retrieved correctly|, 396 | ); 397 | 398 | # 14 Insert TIME/TIMESTAMP WITH TIME ZONE 399 | # --------------------------------------- 400 | 401 | my $tz_insert_sql = sprintf( 402 | <<'EO_SQL', 403 | INSERT INTO %s 404 | (id, ttz_type, tstz_type) 405 | VALUES 406 | (14, '12:30:15+05:30', '2011-12-13 14:15:16.123456') 407 | EO_SQL 408 | $table_name, 409 | ); 410 | 411 | $node->safe_psql($tz_insert_sql); 412 | 413 | my $q14_sql = sprintf( 414 | <<'EO_SQL', 415 | SELECT ttz_type, tstz_type 416 | FROM %s 417 | WHERE id = 14 418 | EO_SQL 419 | $table_name, 420 | ); 421 | 422 | my ($q14_res, $q14_stdout, $q14_stderr) = $node->psql($q14_sql); 423 | 424 | is ( 425 | $q14_stdout, 426 | qq/12:30:15+05:30|2011-12-13 14:15:16.1234+09/, 427 | q|Check TIME/TIMESTAMP WITH TIME ZONE values inserted correctly|, 428 | ); 429 | } 430 | 431 | 432 | # Clean up 433 | # -------- 434 | 435 | $node->drop_foreign_server(); 436 | $node->firebird_drop_table($table_name); 437 | 438 | done_testing(); 439 | -------------------------------------------------------------------------------- /t/06-import-foreign-schema.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # 06-import-foreign-schema.pl 4 | # 5 | # Test "IMPORT FOREIGN SCHEMA" functionality 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More; 11 | 12 | use FirebirdFDWNode; 13 | 14 | 15 | # Initialize nodes 16 | # ---------------- 17 | 18 | my $node = FirebirdFDWNode->new(); 19 | my $version = $node->pg_version(); 20 | my $min_compat_version = $node->get_min_compat_version(); 21 | 22 | # 1) Test "IMPORT FOREIGN SCHEMA" 23 | # ------------------------------- 24 | 25 | my $q1_table_name = $node->init_data_type_table(firebird_only => 1); 26 | 27 | note "import table name is '$q1_table_name'"; 28 | 29 | my $q1_import_sql = sprintf( 30 | q|IMPORT FOREIGN SCHEMA foo LIMIT TO (%s) FROM SERVER %s INTO public|, 31 | $q1_table_name, 32 | $node->server_name(), 33 | ); 34 | 35 | my ($q1_res, $q1_stdout, $q1_stderr) = $node->psql( 36 | $q1_import_sql, 37 | ); 38 | 39 | is ( 40 | $q1_res, 41 | q|0|, 42 | q|Check "IMPORT FOREIGN SCHEMA" operation succeeds|, 43 | ); 44 | 45 | 46 | # 1a) verify table imported 47 | # ========================= 48 | 49 | my $q1a_sql = sprintf( 50 | q|\d %s|, 51 | $q1_table_name, 52 | ); 53 | 54 | my ($q1a_res, $q1a_stdout, $q1a_stderr) = $node->psql($q1a_sql); 55 | 56 | my $q1a_expected_output = {}; 57 | 58 | $q1a_expected_output->{2} = <{3} = <{4} = <{$min_compat_version}; 88 | 89 | chomp $q1a_expected; 90 | 91 | is ( 92 | $q1a_stdout, 93 | $q1a_expected, 94 | q|Check IMPORT FOREIGN SCHEMA|, 95 | ); 96 | 97 | 98 | # 2) Test "import_not_null" option 99 | # -------------------------------- 100 | 101 | my $q2_table_name = $node->init_table(firebird_only => 1); 102 | 103 | my $q2_import_foreign_schema_sql = sprintf( 104 | <<'EO_SQL', 105 | IMPORT FOREIGN SCHEMA foo 106 | LIMIT TO (%s) 107 | FROM SERVER %s 108 | INTO public 109 | OPTIONS (import_not_null 'false') 110 | EO_SQL 111 | $q2_table_name, 112 | $node->server_name(), 113 | ); 114 | 115 | 116 | $node->safe_psql($q2_import_foreign_schema_sql); 117 | 118 | my $q2_sql = sprintf( 119 | q|\d %s|, 120 | $q2_table_name, 121 | ); 122 | 123 | my $q2_res = $node->safe_psql($q2_sql); 124 | 125 | my $q2_expected = <drop_foreign_table($q2_table_name); 140 | 141 | 142 | # 3) Test "updatable" option 143 | # -------------------------- 144 | 145 | my $q3_import_foreign_schema_sql = sprintf( 146 | <<'EO_SQL', 147 | IMPORT FOREIGN SCHEMA foo 148 | LIMIT TO (%s) 149 | FROM SERVER %s 150 | INTO public 151 | OPTIONS (updatable 'false') 152 | EO_SQL 153 | $q2_table_name, 154 | $node->server_name(), 155 | ); 156 | 157 | 158 | $node->safe_psql($q3_import_foreign_schema_sql); 159 | 160 | my $q3_sql = sprintf( 161 | <<'EO_SQL', 162 | SELECT pg_catalog.unnest(ftoptions) 163 | FROM pg_catalog.pg_foreign_table ft 164 | JOIN pg_catalog.pg_class c 165 | ON c.oid=ft.ftrelid 166 | WHERE c.relname='%s' 167 | EO_SQL 168 | $q2_table_name, 169 | ); 170 | 171 | my ($q3_res, $q3_stdout, $q3_stderr) = $node->psql($q3_sql); 172 | 173 | my $q3_expected = q|updatable=false|; 174 | 175 | is ( 176 | $q3_stdout, 177 | $q3_expected, 178 | q|Check "updatable" option|, 179 | ); 180 | 181 | # 4) Test default quoted identifier handling (1) 182 | # ---------------------------------------------- 183 | 184 | # Here we'll just check the "IMPORT FOREIGN SCHEMA" operation 185 | # succeeds 186 | 187 | my $q4_table_name = $node->make_table_name(uc_prefix => 1); 188 | 189 | my $q4_fb_sql = sprintf( 190 | <<'EO_SQL', 191 | CREATE TABLE "%s" ( 192 | "col1" INT, 193 | "UClc" INT, 194 | unquoted INT, 195 | "lclc" INT 196 | ) 197 | EO_SQL 198 | $q4_table_name, 199 | ); 200 | 201 | $node->firebird_execute_sql($q4_fb_sql); 202 | 203 | my $q4_import_sql = sprintf( 204 | <<'EO_SQL', 205 | IMPORT FOREIGN SCHEMA foo 206 | LIMIT TO ("%s") 207 | FROM SERVER fb_test 208 | INTO public 209 | EO_SQL 210 | $q4_table_name, 211 | ); 212 | 213 | my ($q4_res, $q4_stdout, $q4_stderr) = $node->psql( 214 | $q4_import_sql, 215 | ); 216 | 217 | is ( 218 | $q4_res, 219 | q|0|, 220 | q|Check "IMPORT FOREIGN SCHEMA" operation succeeds|, 221 | ); 222 | 223 | # 5) Test default quoted identifier handling (2) 224 | # ---------------------------------------------- 225 | 226 | # Here we'll check the previous "IMPORT FOREIGN SCHEMA" operation 227 | # created table name and columns correctly 228 | 229 | # Query won't return any results if table name was created incorrectly 230 | my $q5_sql = sprintf( 231 | <<'EO_SQL', 232 | SELECT a.attname 233 | FROM pg_catalog.pg_class c 234 | INNER JOIN pg_catalog.pg_attribute a 235 | ON a.attrelid = c.oid 236 | INNER JOIN pg_catalog.pg_namespace n 237 | ON n.oid = c.relnamespace 238 | WHERE c.relname = '%s' 239 | AND n.nspname = 'public' 240 | AND a.attnum > 0 241 | ORDER BY a.attnum 242 | EO_SQL 243 | $q4_table_name, 244 | ); 245 | 246 | my ($q5_res, $q5_stdout, $q5_stderr) = $node->psql($q5_sql); 247 | 248 | my $q5_expected = <make_table_name()); 267 | 268 | $node->init_table( 269 | firebird_only => 1, 270 | table_name => $q6_table_name, 271 | ); 272 | 273 | 274 | my $q6_import_sql = sprintf( 275 | <<'EO_SQL', 276 | IMPORT FOREIGN SCHEMA foo 277 | LIMIT TO ("%s") 278 | FROM SERVER fb_test 279 | INTO public 280 | EO_SQL 281 | $q6_table_name, 282 | ); 283 | 284 | my ($q6_res, $q6_stdout, $q6_stderr) = $node->psql( 285 | $q6_import_sql, 286 | ); 287 | 288 | is ( 289 | $q6_res, 290 | q|0|, 291 | q|Check "IMPORT FOREIGN SCHEMA" operation succeeds|, 292 | ); 293 | 294 | 295 | # 7) Verify table is actually imported 296 | # ------------------------------------ 297 | 298 | # Check the table specified in the previous test was actually 299 | # imported. If not handled correctly, the PostgreSQL FDW API 300 | # will silently discard table definitions generated by the FDW. 301 | # 302 | # We don't need to check the schema here. 303 | 304 | my $q7_sql = sprintf( 305 | <<'EO_SQL', 306 | SELECT COUNT(*) 307 | FROM pg_catalog.pg_class 308 | WHERE oid = '"%s"'::regclass 309 | EO_SQL 310 | $q6_table_name, 311 | ); 312 | 313 | my ($q7_res, $q7_stdout, $q7_stderr) = $node->psql($q7_sql); 314 | 315 | is ( 316 | $q7_stdout, 317 | q|1|, 318 | q|Check table was imported|, 319 | ); 320 | 321 | 322 | # 8) Test "IMPORT SCHEMA ... EXCEPT" 323 | # ---------------------------------- 324 | 325 | $node->drop_foreign_table($q1_table_name); 326 | $node->drop_foreign_table($q2_table_name); 327 | $node->drop_foreign_table($q4_table_name); 328 | $node->drop_foreign_table($q6_table_name); 329 | 330 | my $q8_import_sql = sprintf( 331 | <<'EO_SQL', 332 | IMPORT FOREIGN SCHEMA foo 333 | EXCEPT (%s, "%s", "%s") 334 | FROM SERVER fb_test 335 | INTO public 336 | EO_SQL 337 | # Default case 338 | $q1_table_name, 339 | # UC prefix 340 | $q4_table_name, 341 | # All upper-case 342 | $q6_table_name, 343 | ); 344 | 345 | my ($q8_res, $q8_stdout, $q8_stderr) = $node->psql( 346 | $q8_import_sql, 347 | ); 348 | 349 | is ( 350 | $q8_res, 351 | q|0|, 352 | q|Check "IMPORT FOREIGN SCHEMA ... EXCEPT" operation succeeds|, 353 | ); 354 | 355 | # 8a) Check expected table imported 356 | # -------------------------------- 357 | 358 | my $q8a_sql = sprintf( 359 | <<'EO_SQL', 360 | SELECT COUNT(*) 361 | FROM pg_catalog.pg_class 362 | WHERE oid = '%s'::regclass 363 | EO_SQL 364 | $q2_table_name, 365 | ); 366 | 367 | my ($q8a_res, $q8a_stdout, $q8a_stderr) = $node->psql($q8a_sql); 368 | 369 | is ( 370 | $q8a_stdout, 371 | q|1|, 372 | q|Check expected table was imported after "IMPORT FOREIGN SCHEMA ... EXCEPT" operation|, 373 | ); 374 | 375 | # 8b) Check other tables not imported 376 | # ----------------------------------- 377 | 378 | my $q8b_sql = sprintf( 379 | <<'EO_SQL', 380 | SELECT COUNT(*) 381 | FROM pg_catalog.pg_class 382 | WHERE relname IN ( 383 | '%s', 384 | '%s', 385 | '%s' 386 | ) 387 | EO_SQL 388 | # Default case 389 | $q1_table_name, 390 | # UC prefix 391 | $q4_table_name, 392 | # All upper-case 393 | $q6_table_name, 394 | ); 395 | 396 | my ($q8b_res, $q8b_stdout, $q8b_stderr) = $node->psql($q8b_sql); 397 | 398 | is ( 399 | $q8b_stdout, 400 | q|0|, 401 | q|Check other tables not imported|, 402 | ); 403 | 404 | 405 | # 9) Check relations with names > 32 characters can be imported 406 | # ------------------------------------------------------------- 407 | 408 | if ($node->{firebird_major_version} >= 4) { 409 | 410 | my $q9_table_name = ''; 411 | 412 | # Maximum identifier size in Firebird 4+ is 63 characters 413 | foreach $a (0..62) { 414 | $q9_table_name .= chr(int(26*rand) + 65); 415 | } 416 | 417 | $node->firebird_execute_sql( 418 | sprintf( 419 | <psql( 437 | $q9_import_sql, 438 | ); 439 | 440 | is ( 441 | $q9_res, 442 | q|0|, 443 | q|Check "IMPORT FOREIGN SCHEMA" succeeds with long table name|, 444 | ); 445 | 446 | $node->firebird_drop_table($q9_table_name); 447 | } 448 | 449 | # Clean up 450 | # -------- 451 | 452 | $node->drop_foreign_server(); 453 | 454 | $node->firebird_drop_table($q1_table_name); 455 | $node->firebird_drop_table($q2_table_name); 456 | $node->firebird_drop_table($q4_table_name, 1); 457 | $node->firebird_drop_table($q6_table_name, 1); 458 | 459 | done_testing(); 460 | -------------------------------------------------------------------------------- /src/firebird_fdw.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------- 2 | * 3 | * firebird_fdw.h 4 | * Primary include file for firebird_fdw .c files 5 | * 6 | * Copyright (c) 2013-2025 Ian Barwick 7 | * 8 | * This software is released under the PostgreSQL Licence 9 | * 10 | * IDENTIFICATION 11 | * firebird_fdw/src/firebird_fdw.h 12 | * 13 | *---------------------------------------------------------------------- 14 | */ 15 | 16 | #ifndef FIREBIRD_FDW_H 17 | #define FIREBIRD_FDW_H 18 | 19 | #include "funcapi.h" 20 | #include "access/reloptions.h" 21 | #include "access/sysattr.h" 22 | #include "catalog/pg_foreign_server.h" 23 | #include "catalog/pg_foreign_table.h" 24 | #include "catalog/pg_user_mapping.h" 25 | #include "catalog/pg_type.h" 26 | #include "commands/defrem.h" 27 | #include "commands/explain.h" 28 | #include "foreign/fdwapi.h" 29 | #include "foreign/foreign.h" 30 | #include "miscadmin.h" 31 | #include "mb/pg_wchar.h" 32 | #include "nodes/makefuncs.h" 33 | #if (PG_VERSION_NUM >= 120000) 34 | #include "nodes/pathnodes.h" 35 | #else 36 | #include "nodes/relation.h" 37 | #endif 38 | #include "optimizer/cost.h" 39 | #include "optimizer/pathnode.h" 40 | #include "optimizer/planmain.h" 41 | #include "optimizer/restrictinfo.h" 42 | #include "parser/parsetree.h" 43 | #include "storage/fd.h" 44 | #include "storage/ipc.h" 45 | #include "utils/array.h" 46 | #include "utils/builtins.h" 47 | #include "utils/lsyscache.h" 48 | #include "utils/rel.h" 49 | 50 | 51 | #include "libfq.h" 52 | 53 | #define FIREBIRD_FDW_VERSION 10500 54 | #define FIREBIRD_FDW_VERSION_STRING "1.5.0a" 55 | 56 | #define FB_FDW_LOGPREFIX "[firebird_fdw] " 57 | #define FB_FDW_LOGPREFIX_LEN strlen(FB_FDW_LOGPREFIX) 58 | 59 | /* http://www.firebirdfaq.org/faq259/ */ 60 | #define FIREBIRD_DEFAULT_PORT 3050 61 | 62 | /* 63 | * In PostgreSQL 11 and earlier, "table_open|close()" were "heap_open|close()"; 64 | * see core commits 4b21acf5 and f25968c4. 65 | */ 66 | #if PG_VERSION_NUM < 120000 67 | #define table_open(x, y) heap_open(x, y) 68 | #define table_close(x, y) heap_close(x, y) 69 | #endif 70 | 71 | #if (PG_VERSION_NUM >= 120000) 72 | #define HAVE_GENERATED_COLUMNS 73 | #endif 74 | 75 | #if (PG_VERSION_NUM >= 140000) 76 | #define NO_BATCH_SIZE_SPECIFIED -1 77 | #endif 78 | 79 | #if (defined(FIREBIRD_FDW_DEBUG_BUILD)) 80 | #define DEBUG_BUILD 81 | #endif 82 | 83 | /* 84 | * Macro to indicate if a given PostgreSQL datatype can be 85 | * converted to a Firebird type 86 | */ 87 | #define canConvertPgType(x) ((x) == TEXTOID || (x) == CHAROID || (x) == BPCHAROID \ 88 | || (x) == VARCHAROID || (x) == NAMEOID || (x) == INT8OID || (x) == INT2OID \ 89 | || (x) == INT4OID || (x) == FLOAT4OID || (x) == FLOAT8OID \ 90 | || (x) == NUMERICOID || (x) == DATEOID || (x) == TIMESTAMPOID \ 91 | || (x) == TIMEOID) 92 | 93 | typedef union opttype { 94 | char **strptr; 95 | int *intptr; 96 | bool *boolptr; 97 | } opttype; 98 | 99 | typedef struct fdwOption { 100 | union opttype opt; 101 | bool provided; 102 | } fdwOption; 103 | 104 | typedef struct fbServerOptions { 105 | fdwOption address; 106 | fdwOption port; 107 | fdwOption database; 108 | fdwOption disable_pushdowns; 109 | fdwOption updatable; 110 | fdwOption quote_identifiers; 111 | fdwOption implicit_bool_type; 112 | #if (PG_VERSION_NUM >= 140000) 113 | fdwOption batch_size; 114 | fdwOption truncatable; 115 | #endif 116 | } fbServerOptions; 117 | 118 | #if (PG_VERSION_NUM >= 140000) 119 | #define fbServerOptions_init { \ 120 | { { NULL }, false }, \ 121 | { { NULL }, false }, \ 122 | { { NULL }, false }, \ 123 | { { NULL }, false }, \ 124 | { { NULL }, false }, \ 125 | { { NULL }, false }, \ 126 | { { NULL }, false }, \ 127 | { { NULL }, false }, \ 128 | { { NULL }, false } \ 129 | } 130 | #else 131 | #define fbServerOptions_init { \ 132 | { { NULL }, false }, \ 133 | { { NULL }, false }, \ 134 | { { NULL }, false }, \ 135 | { { NULL }, false }, \ 136 | { { NULL }, false }, \ 137 | { { NULL }, false }, \ 138 | { { NULL }, false } \ 139 | } 140 | #endif 141 | 142 | 143 | typedef struct fbTableOptions { 144 | fdwOption query; 145 | fdwOption table_name; 146 | fdwOption updatable; 147 | fdwOption estimated_row_count; 148 | fdwOption quote_identifier; 149 | #if (PG_VERSION_NUM >= 140000) 150 | fdwOption batch_size; 151 | fdwOption truncatable; 152 | #endif 153 | } fbTableOptions; 154 | 155 | #if (PG_VERSION_NUM >= 140000) 156 | #define fbTableOptions_init { \ 157 | { { NULL }, false }, \ 158 | { { NULL }, false }, \ 159 | { { NULL }, false }, \ 160 | { { NULL }, false }, \ 161 | { { NULL }, false }, \ 162 | { { NULL }, false }, \ 163 | { { NULL }, false } \ 164 | } 165 | #else 166 | #define fbTableOptions_init { \ 167 | { { NULL }, false }, \ 168 | { { NULL }, false }, \ 169 | { { NULL }, false }, \ 170 | { { NULL }, false }, \ 171 | { { NULL }, false } \ 172 | } 173 | #endif 174 | 175 | typedef struct fbColumnOptions { 176 | char **column_name; 177 | bool *quote_identifier; 178 | bool *implicit_bool_type; 179 | } fbColumnOptions; 180 | 181 | #define fbColumnOptions_init { \ 182 | NULL, \ 183 | NULL, \ 184 | NULL \ 185 | } 186 | 187 | typedef struct fbTableColumn 188 | { 189 | bool isdropped; /* indicate if PostgreSQL column is dropped */ 190 | bool used; /* indicate if column used in current query */ 191 | } fbTableColumn; 192 | 193 | typedef struct fbTable 194 | { 195 | Oid foreigntableid; 196 | int pg_column_total; 197 | char *pg_table_name; 198 | fbTableColumn **columns; 199 | } fbTable; 200 | 201 | 202 | /* 203 | * Describes the valid options for objects that use this wrapper. 204 | */ 205 | struct FirebirdFdwOption 206 | { 207 | const char *optname; 208 | Oid optcontext; /* Oid of catalog in which option may appear */ 209 | }; 210 | 211 | 212 | /* 213 | * FDW-specific information for RelOptInfo.fdw_private and ForeignScanState.fdw_state. 214 | * 215 | * This is what will be set and stashed away in fdw_private and fetched 216 | * for subsequent routines. 217 | */ 218 | typedef struct FirebirdFdwState 219 | { 220 | char *svr_query; 221 | char *svr_table; 222 | bool disable_pushdowns; /* true if server option "disable_pushdowns" supplied */ 223 | int estimated_row_count; /* set if server option "estimated_row_count" provided */ 224 | bool quote_identifier; 225 | bool implicit_bool_type; /* true if server option "implicit_bool_type" supplied */ 226 | #if (PG_VERSION_NUM >= 140000) 227 | int batch_size; 228 | #endif 229 | 230 | FBconn *conn; 231 | int firebird_version; /* cache Firebird version from connection */ 232 | 233 | List *remote_conds; 234 | List *local_conds; 235 | 236 | Bitmapset *attrs_used; /* Bitmap of attr numbers to be fetched from the remote server. */ 237 | Cost startup_cost; /* cost estimate, only needed for planning */ 238 | Cost total_cost; /* cost estimate, only needed for planning */ 239 | int row; 240 | char *query; /* query to send to Firebird */ 241 | } FirebirdFdwState; 242 | 243 | /* 244 | * Execution state of a foreign scan using firebird_fdw. 245 | */ 246 | typedef struct FirebirdFdwScanState 247 | { 248 | FBconn *conn; 249 | /* Foreign table information */ 250 | fbTable *table; 251 | List *retrieved_attrs; /* attr numbers retrieved by RETURNING */ 252 | /* Query information */ 253 | char *query; /* query to send to Firebird */ 254 | bool db_key_used; /* indicate whether RDB$DB_KEY was requested */ 255 | 256 | FBresult *result; 257 | int row; 258 | 259 | } FirebirdFdwScanState; 260 | 261 | /* 262 | * Execution state of a foreign insert/update/delete operation. 263 | */ 264 | typedef struct FirebirdFdwModifyState 265 | { 266 | Relation rel; /* relcache entry for the foreign table */ 267 | AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ 268 | 269 | /* for remote query execution */ 270 | FBconn *conn; /* connection for the scan */ 271 | int firebird_version; /* cache Firebird version from connection */ 272 | /* extracted fdw_private data */ 273 | char *query; /* text of INSERT/UPDATE/DELETE command */ 274 | List *target_attrs; /* list of target attribute numbers */ 275 | bool has_returning; /* is there a RETURNING clause? */ 276 | List *retrieved_attrs; /* attr numbers retrieved by RETURNING */ 277 | 278 | /* info about parameters for prepared statement */ 279 | AttrNumber db_keyAttno_CtidPart; /* attnum of input resjunk rdb$db_key column (CTID part) */ 280 | AttrNumber db_keyAttno_XmaxPart; /* attnum of input resjunk rdb$db_key column (xmax part) */ 281 | 282 | int p_nums; /* number of parameters to transmit */ 283 | FmgrInfo *p_flinfo; /* output conversion functions for them */ 284 | 285 | /* working memory context */ 286 | MemoryContext temp_cxt; /* context for per-tuple temporary data */ 287 | 288 | #if (PG_VERSION_NUM >= 140000) 289 | int batch_size; 290 | #endif 291 | } FirebirdFdwModifyState; 292 | 293 | 294 | extern void fbSigInt(SIGNAL_ARGS); 295 | 296 | /* connection functions (in connection.c) */ 297 | 298 | 299 | extern FBconn *firebirdInstantiateConnection(ForeignServer *server, UserMapping *user); 300 | extern void firebirdCloseConnections(bool verbose); 301 | extern int firebirdCachedConnectionsCount(void); 302 | extern void fbfdw_report_error(int errlevel, int pg_errcode, FBresult *res, FBconn *conn, char *query); 303 | 304 | 305 | /* option functions (in options.c) */ 306 | 307 | extern void firebirdGetServerOptions(ForeignServer *server, 308 | fbServerOptions *options); 309 | 310 | extern void firebirdGetTableOptions(ForeignTable *table, 311 | fbTableOptions *options); 312 | 313 | extern void firebirdGetColumnOptions(Oid foreigntableid, int varattno, 314 | fbColumnOptions *options); 315 | 316 | /* query-building functions (in convert.c) */ 317 | 318 | extern void buildInsertSql(StringInfo buf, 319 | RangeTblEntry *rte, 320 | FirebirdFdwState *fdw_state, 321 | Index rtindex, Relation rel, 322 | List *targetAttrs, List *returningList, 323 | List **retrieved_attrs); 324 | 325 | extern void buildUpdateSql(StringInfo buf, RangeTblEntry *rte, 326 | FirebirdFdwState *fdw_state, 327 | Index rtindex, Relation rel, 328 | List *targetAttrs, List *returningList, 329 | List **retrieved_attrs); 330 | 331 | extern void buildDeleteSql(StringInfo buf, RangeTblEntry *rte, 332 | FirebirdFdwState *fdw_state, 333 | Index rtindex, Relation rel, 334 | List *returningList, 335 | List **retrieved_attrs); 336 | 337 | extern void buildTruncateSQL(StringInfo buf, 338 | FirebirdFdwState *fdw_state, 339 | Relation rel); 340 | 341 | extern void buildSelectSql(StringInfo buf, RangeTblEntry *rte, 342 | FirebirdFdwState *fdw_state, 343 | RelOptInfo *baserel, 344 | Bitmapset *attrs_used, 345 | List **retrieved_attrs, 346 | bool *db_key_used); 347 | 348 | extern void buildWhereClause(StringInfo buf, 349 | PlannerInfo *root, 350 | RelOptInfo *baserel, 351 | List *exprs, 352 | bool is_first, 353 | List **params); 354 | 355 | extern void 356 | identifyRemoteConditions(PlannerInfo *root, 357 | RelOptInfo *baserel, 358 | List **remote_conds, 359 | List **local_conds, 360 | bool disable_pushdowns, 361 | int firebird_version); 362 | 363 | extern bool 364 | isFirebirdExpr(PlannerInfo *root, 365 | RelOptInfo *baserel, 366 | Expr *expr, 367 | int firebird_version); 368 | 369 | void convertColumnRef(StringInfo buf, 370 | Oid relid, 371 | int varattno, 372 | bool quote_identifier); 373 | 374 | const char * 375 | quote_fb_identifier(const char *ident, bool quote_ident); 376 | 377 | void 378 | unquoted_ident_to_upper(char *ident); 379 | 380 | void convertFirebirdObject(char *server_name, 381 | char *schema, 382 | char *object_name, 383 | char object_type, 384 | char *pg_name, 385 | bool import_not_null, 386 | bool updatable, 387 | FBresult *colres, 388 | StringInfoData *create_table); 389 | void generateColumnMetadataQuery(StringInfoData *data_type_sql, char *fb_table_name); 390 | 391 | #endif /* FIREBIRD_FDW_H */ 392 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * Helper functions to validate and parse the FDW options 4 | * 5 | * Copyright (c) 2013-2025 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * Public repository: https://github.com/ibarwick/firebird_fdw 12 | * 13 | * IDENTIFICATION 14 | * firebird_fdw/src/options.c 15 | * 16 | *------------------------------------------------------------------------- 17 | */ 18 | 19 | #include "postgres.h" 20 | #include "utils/builtins.h" 21 | #include "utils/guc.h" 22 | 23 | #include "firebird_fdw.h" 24 | 25 | /* 26 | * Valid options for firebird_fdw 27 | * 28 | */ 29 | static struct FirebirdFdwOption valid_options[] = 30 | { 31 | /* Connection options */ 32 | { "address", ForeignServerRelationId }, 33 | { "port", ForeignServerRelationId }, 34 | { "database", ForeignServerRelationId }, 35 | { "disable_pushdowns", ForeignServerRelationId }, 36 | { "updatable", ForeignServerRelationId }, 37 | { "quote_identifiers", ForeignServerRelationId }, 38 | { "implicit_bool_type", ForeignServerRelationId }, 39 | #if (PG_VERSION_NUM >= 140000) 40 | { "batch_size", ForeignServerRelationId }, 41 | { "truncatable", ForeignServerRelationId }, 42 | 43 | #endif 44 | /* User options */ 45 | { "username", UserMappingRelationId }, 46 | { "password", UserMappingRelationId }, 47 | /* Table options */ 48 | { "query", ForeignTableRelationId }, 49 | { "table_name", ForeignTableRelationId }, 50 | { "updatable", ForeignTableRelationId }, 51 | { "estimated_row_count", ForeignTableRelationId }, 52 | { "quote_identifier", ForeignTableRelationId }, 53 | #if (PG_VERSION_NUM >= 140000) 54 | { "batch_size", ForeignTableRelationId }, 55 | { "truncatable", ForeignTableRelationId }, 56 | #endif 57 | /* Column options */ 58 | { "column_name", AttributeRelationId }, 59 | { "quote_identifier", AttributeRelationId }, 60 | { "implicit_bool_type", AttributeRelationId }, 61 | { NULL, InvalidOid } 62 | }; 63 | 64 | extern Datum firebird_fdw_validator(PG_FUNCTION_ARGS); 65 | PG_FUNCTION_INFO_V1(firebird_fdw_validator); 66 | static bool firebirdIsValidOption(const char *option, Oid context); 67 | 68 | /** 69 | * firebird_fdw_validator() 70 | * 71 | * Validates the options provided in a "CREATE FOREIGN ..." command. 72 | * It does not store the values anywhere. 73 | */ 74 | Datum 75 | firebird_fdw_validator(PG_FUNCTION_ARGS) 76 | { 77 | List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); 78 | Oid catalog = PG_GETARG_OID(1); 79 | ListCell *cell; 80 | 81 | /* 82 | * If an option is specified, store it in one of these variables so 83 | * we can determine if it gets specified more than once. 84 | */ 85 | char *svr_address = NULL; 86 | int svr_port = 0; 87 | char *svr_username = NULL; 88 | char *svr_password = NULL; 89 | char *svr_database = NULL; 90 | char *svr_query = NULL; 91 | char *svr_table = NULL; 92 | #if (PG_VERSION_NUM >= 140000) 93 | int svr_batch_size = NO_BATCH_SIZE_SPECIFIED; 94 | bool truncatable_set = false; 95 | #endif 96 | 97 | bool disable_pushdowns_set = false; 98 | bool updatable_set = false; 99 | 100 | elog(DEBUG2, "entering function %s", __func__); 101 | 102 | /* 103 | * Check that only options supported by firebird_fdw, 104 | * and allowed for the current object type, are given. 105 | */ 106 | foreach (cell, options_list) 107 | { 108 | DefElem *def = (DefElem *) lfirst(cell); 109 | 110 | if (!firebirdIsValidOption(def->defname, catalog)) 111 | { 112 | struct FirebirdFdwOption *opt; 113 | StringInfoData buf; 114 | 115 | /* 116 | * Unknown option specified, complain about it. Provide a hint 117 | * with list of valid options for the object. 118 | */ 119 | initStringInfo(&buf); 120 | for (opt = valid_options; opt->optname; opt++) 121 | { 122 | if (catalog == opt->optcontext) 123 | { 124 | appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", 125 | opt->optname); 126 | } 127 | } 128 | 129 | ereport(ERROR, 130 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 131 | errmsg("invalid option \"%s\"", def->defname), 132 | errhint("valid options in this context are: %s", buf.len ? buf.data : ""))); 133 | 134 | pfree(buf.data); 135 | } 136 | 137 | if (strcmp(def->defname, "address") == 0) 138 | { 139 | if (svr_address) 140 | ereport(ERROR, 141 | (errcode(ERRCODE_SYNTAX_ERROR), 142 | errmsg("conflicting or redundant options: address (%s)", defGetString(def)))); 143 | 144 | svr_address = defGetString(def); 145 | } 146 | else if (strcmp(def->defname, "port") == 0) 147 | { 148 | if (svr_port) 149 | ereport(ERROR, 150 | (errcode(ERRCODE_SYNTAX_ERROR), 151 | errmsg("conflicting or redundant options: port (%s)", defGetString(def)))); 152 | 153 | /* 154 | * parse_int() accepts a pointer which may be set to a hint message, 155 | * but for our use-case the only likely one is a general integer range check, 156 | * which is not really exciting for us. 157 | */ 158 | if (parse_int(defGetString(def), &svr_port, 0, NULL) == false) 159 | { 160 | ereport(ERROR, 161 | (errcode(ERRCODE_SYNTAX_ERROR), 162 | errmsg("an error was encountered when parsing the provided \"port\" value"))); 163 | } 164 | else if (svr_port < 1 || svr_port > 65535) 165 | { 166 | ereport(ERROR, 167 | (errcode(ERRCODE_SYNTAX_ERROR), 168 | errmsg("\"port\" must have a value between 1 and 65535"))); 169 | } 170 | } 171 | 172 | if (strcmp(def->defname, "username") == 0) 173 | { 174 | if (svr_username) 175 | ereport(ERROR, 176 | (errcode(ERRCODE_SYNTAX_ERROR), 177 | errmsg("conflicting or redundant options: username (%s)", defGetString(def)))); 178 | 179 | svr_username = defGetString(def); 180 | } 181 | 182 | if (strcmp(def->defname, "password") == 0) 183 | { 184 | if (svr_password) 185 | ereport(ERROR, 186 | (errcode(ERRCODE_SYNTAX_ERROR), 187 | errmsg("conflicting or redundant options: password"))); 188 | 189 | svr_password = defGetString(def); 190 | } 191 | else if (strcmp(def->defname, "database") == 0) 192 | { 193 | if (svr_database) 194 | ereport(ERROR, 195 | (errcode(ERRCODE_SYNTAX_ERROR), 196 | errmsg("conflicting or redundant options: database (%s)", defGetString(def)))); 197 | 198 | svr_database = defGetString(def); 199 | } 200 | else if (strcmp(def->defname, "query") == 0) 201 | { 202 | if (svr_table) 203 | ereport(ERROR, 204 | (errcode(ERRCODE_SYNTAX_ERROR), 205 | errmsg("conflicting options: 'query' cannot be used with 'table_name'"))); 206 | 207 | if (svr_query) 208 | ereport(ERROR, 209 | (errcode(ERRCODE_SYNTAX_ERROR), 210 | errmsg("conflicting or redundant options: query (%s)", defGetString(def)))); 211 | 212 | svr_query = defGetString(def); 213 | } 214 | else if (strcmp(def->defname, "table_name") == 0) 215 | { 216 | if (svr_query) 217 | ereport(ERROR, 218 | (errcode(ERRCODE_SYNTAX_ERROR), 219 | errmsg("conflicting options: table cannot be used with query") 220 | )); 221 | 222 | if (svr_table) 223 | ereport(ERROR, 224 | (errcode(ERRCODE_SYNTAX_ERROR), 225 | errmsg("conflicting or redundant options: table (%s)", defGetString(def)))); 226 | 227 | svr_table = defGetString(def); 228 | } 229 | else if (strcmp(def->defname, "disable_pushdowns") == 0) 230 | { 231 | if (disable_pushdowns_set) 232 | ereport(ERROR, 233 | (errcode(ERRCODE_SYNTAX_ERROR), 234 | errmsg("redundant option: 'disable_pushdowns' set more than once"))); 235 | (void) defGetBoolean(def); 236 | 237 | disable_pushdowns_set = true; 238 | } 239 | else if (strcmp(def->defname, "updatable") == 0) 240 | { 241 | bool updatable; 242 | 243 | if (updatable_set) 244 | ereport(ERROR, 245 | (errcode(ERRCODE_SYNTAX_ERROR), 246 | errmsg("redundant option: 'updatable' set more than once"))); 247 | 248 | updatable = defGetBoolean(def); 249 | updatable_set = true; 250 | 251 | /* "updatable" not relevant for tables defined as queries */ 252 | if (svr_query && updatable == true) 253 | ereport(ERROR, 254 | (errcode(ERRCODE_SYNTAX_ERROR), 255 | errmsg("foreign tables defined with the \"query\" option cannot be set as \"updatable\""))); 256 | } 257 | #if (PG_VERSION_NUM >= 140000) 258 | else if (strcmp(def->defname, "batch_size") == 0) 259 | { 260 | if (svr_batch_size != NO_BATCH_SIZE_SPECIFIED) 261 | ereport(ERROR, 262 | (errcode(ERRCODE_SYNTAX_ERROR), 263 | errmsg("redundant option: \"batch_size\" set more than once"))); 264 | 265 | if (parse_int(defGetString(def), &svr_batch_size, 0, NULL) == false) 266 | { 267 | ereport(ERROR, 268 | (errcode(ERRCODE_SYNTAX_ERROR), 269 | errmsg("an error was encountered when parsing the provided \"batch_size\" value"))); 270 | } 271 | else if (svr_batch_size < 1) 272 | { 273 | ereport(ERROR, 274 | (errcode(ERRCODE_SYNTAX_ERROR), 275 | errmsg("\"batch_size\" must have a value of 1 or greater"))); 276 | } 277 | } 278 | else if (strcmp(def->defname, "truncatable") == 0) 279 | { 280 | if (truncatable_set) 281 | ereport(ERROR, 282 | (errcode(ERRCODE_SYNTAX_ERROR), 283 | errmsg("redundant option: 'truncatable' set more than once"))); 284 | (void) defGetBoolean(def); 285 | 286 | truncatable_set = true; 287 | } 288 | #endif 289 | } 290 | 291 | PG_RETURN_VOID(); 292 | } 293 | 294 | 295 | /** 296 | * firebirdIsValidOption() 297 | * 298 | * Check if the provided option is valid. 299 | * "context" is the Oid of the catalog holding the object the option is for. 300 | */ 301 | static bool 302 | firebirdIsValidOption(const char *option, Oid context) 303 | { 304 | struct FirebirdFdwOption *opt; 305 | 306 | for (opt = valid_options; opt->optname; opt++) 307 | { 308 | if (context == opt->optcontext) 309 | { 310 | if (strcmp(opt->optname, option) == 0) 311 | return true; 312 | } 313 | } 314 | 315 | return false; 316 | } 317 | 318 | 319 | /** 320 | * firebirdGetServerOptions() 321 | * 322 | * Fetch the requested server-level options, and record whether they 323 | * were explicitly provided. 324 | */ 325 | void 326 | firebirdGetServerOptions(ForeignServer *server, 327 | fbServerOptions *options) 328 | { 329 | ListCell *lc; 330 | 331 | foreach (lc, server->options) 332 | { 333 | DefElem *def = (DefElem *) lfirst(lc); 334 | 335 | elog(DEBUG3, "server option: \"%s\"", def->defname); 336 | 337 | if (options->address.opt.strptr != NULL && strcmp(def->defname, "address") == 0) 338 | { 339 | *options->address.opt.strptr = defGetString(def); 340 | options->address.provided = true; 341 | continue; 342 | } 343 | 344 | if (options->port.opt.intptr != NULL && strcmp(def->defname, "port") == 0) 345 | { 346 | *options->port.opt.intptr = strtod(defGetString(def), NULL); 347 | options->port.provided = true; 348 | continue; 349 | } 350 | 351 | if (options->database.opt.strptr != NULL && strcmp(def->defname, "database") == 0) 352 | { 353 | *options->database.opt.strptr = defGetString(def); 354 | options->database.provided = true; 355 | continue; 356 | } 357 | 358 | if (options->disable_pushdowns.opt.boolptr != NULL && strcmp(def->defname, "disable_pushdowns") == 0) 359 | { 360 | *options->disable_pushdowns.opt.boolptr = defGetBoolean(def); 361 | options->disable_pushdowns.provided = true; 362 | continue; 363 | } 364 | 365 | if (options->updatable.opt.boolptr != NULL && strcmp(def->defname, "updatable") == 0) 366 | { 367 | *options->updatable.opt.boolptr = defGetBoolean(def); 368 | options->updatable.provided = true; 369 | continue; 370 | } 371 | 372 | if (options->quote_identifiers.opt.boolptr != NULL && strcmp(def->defname, "quote_identifiers") == 0) 373 | { 374 | *options->quote_identifiers.opt.boolptr = defGetBoolean(def); 375 | options->quote_identifiers.provided = true; 376 | continue; 377 | } 378 | 379 | if (options->implicit_bool_type.opt.boolptr != NULL && strcmp(def->defname, "implicit_bool_type") == 0 ) 380 | { 381 | *options->implicit_bool_type.opt.boolptr = defGetBoolean(def); 382 | options->implicit_bool_type.provided = true; 383 | continue; 384 | } 385 | #if (PG_VERSION_NUM >= 140000) 386 | if (options->batch_size.opt.intptr != NULL && strcmp(def->defname, "batch_size") == 0 ) 387 | { 388 | *options->batch_size.opt.intptr = strtod(defGetString(def), NULL); 389 | options->batch_size.provided = true; 390 | continue; 391 | } 392 | if (options->truncatable.opt.boolptr != NULL && strcmp(def->defname, "truncatable") == 0 ) 393 | { 394 | *options->truncatable.opt.boolptr = defGetBoolean(def); 395 | options->truncatable.provided = true; 396 | continue; 397 | } 398 | #endif 399 | } 400 | } 401 | 402 | 403 | /** 404 | * firebirdGetTableOptions() 405 | * 406 | * Fetch the options which apply to a firebird_fdw foreign table. 407 | * 408 | * Note that "updatable" is handled in firebirdIsForeignRelUpdatable(). 409 | */ 410 | void 411 | firebirdGetTableOptions(ForeignTable *table, 412 | fbTableOptions *options) 413 | { 414 | ListCell *lc; 415 | 416 | foreach (lc, table->options) 417 | { 418 | DefElem *def = (DefElem *) lfirst(lc); 419 | 420 | elog(DEBUG3, "table option: \"%s\"", def->defname); 421 | 422 | /* table-level options */ 423 | if (options->query.opt.strptr != NULL && strcmp(def->defname, "query") == 0) 424 | { 425 | *options->query.opt.strptr = defGetString(def); 426 | options->query.provided = true; 427 | continue; 428 | } 429 | 430 | if (options->table_name.opt.strptr != NULL && strcmp(def->defname, "table_name") == 0) 431 | { 432 | *options->table_name.opt.strptr = defGetString(def); 433 | options->table_name.provided = true; 434 | continue; 435 | } 436 | 437 | if (options->updatable.opt.boolptr != NULL && strcmp(def->defname, "updatable") == 0) 438 | { 439 | *options->updatable.opt.boolptr = defGetBoolean(def); 440 | options->updatable.provided = true; 441 | continue; 442 | } 443 | 444 | if (options->estimated_row_count.opt.intptr != NULL && strcmp(def->defname, "estimated_row_count") == 0) 445 | { 446 | *options->estimated_row_count.opt.intptr = strtod(defGetString(def), NULL); 447 | options->estimated_row_count.provided = true; 448 | continue; 449 | } 450 | 451 | if (options->quote_identifier.opt.boolptr != NULL && strcmp(def->defname, "quote_identifier") == 0 ) 452 | { 453 | *options->quote_identifier.opt.boolptr = defGetBoolean(def); 454 | options->quote_identifier.provided = true; 455 | continue; 456 | } 457 | 458 | #if (PG_VERSION_NUM >= 140000) 459 | if (options->batch_size.opt.intptr != NULL && strcmp(def->defname, "batch_size") == 0 ) 460 | { 461 | *options->batch_size.opt.intptr = strtod(defGetString(def), NULL); 462 | options->batch_size.provided = true; 463 | continue; 464 | } 465 | 466 | if (options->truncatable.opt.boolptr != NULL && strcmp(def->defname, "truncatable") == 0 ) 467 | { 468 | *options->truncatable.opt.boolptr = defGetBoolean(def); 469 | options->truncatable.provided = true; 470 | continue; 471 | } 472 | #endif 473 | } 474 | 475 | /* 476 | * If no query and no table name specified, default to the PostgreSQL 477 | * table name. 478 | */ 479 | if (options->table_name.opt.strptr != NULL && options->query.opt.strptr != NULL) 480 | { 481 | if (!*options->table_name.opt.strptr && !*options->query.opt.strptr) 482 | *options->table_name.opt.strptr = get_rel_name(table->relid); 483 | } 484 | } 485 | 486 | 487 | void 488 | firebirdGetColumnOptions(Oid foreigntableid, int varattno, 489 | fbColumnOptions *options) 490 | { 491 | List *options_list; 492 | ListCell *lc; 493 | 494 | options_list = GetForeignColumnOptions(foreigntableid, varattno); 495 | 496 | foreach (lc, options_list) 497 | { 498 | DefElem *def = (DefElem *) lfirst(lc); 499 | 500 | if (options->column_name != NULL && strcmp(def->defname, "column_name") == 0) 501 | { 502 | *options->column_name = defGetString(def); 503 | continue; 504 | } 505 | 506 | if (options->quote_identifier != NULL && strcmp(def->defname, "quote_identifier") == 0 ) 507 | { 508 | *options->quote_identifier = defGetBoolean(def); 509 | continue; 510 | } 511 | 512 | if (options->implicit_bool_type != NULL && strcmp(def->defname, "implicit_bool_type") == 0 ) 513 | { 514 | *options->implicit_bool_type = defGetBoolean(def); 515 | continue; 516 | } 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /src/connection.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * Connection management functions for firebird_fdw 4 | * 5 | * Copyright (c) 2013-2025 Ian Barwick 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Ian Barwick 10 | * 11 | * IDENTIFICATION 12 | * firebird_fdw/src/connection.c 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | #include "postgres.h" 18 | #include "firebird_fdw.h" 19 | 20 | #include "access/xact.h" 21 | #include "utils/hsearch.h" 22 | #include "utils/memutils.h" 23 | 24 | 25 | 26 | typedef struct ConnCacheKey 27 | { 28 | Oid serverid; /* OID of foreign server */ 29 | Oid userid; /* OID of local user whose mapping we use */ 30 | } ConnCacheKey; 31 | 32 | typedef struct ConnCacheEntry 33 | { 34 | ConnCacheKey key; /* hash key (must be first) */ 35 | FBconn *conn; /* connection to foreign server, or NULL */ 36 | int xact_depth; /* 0 = no xact open, 1 = main xact open, 2 = 37 | * one level of subxact open, etc */ 38 | bool have_error; /* have any subxacts aborted in this xact? */ 39 | } ConnCacheEntry; 40 | 41 | /* 42 | * Global connection cache (initialized on first use) 43 | */ 44 | static HTAB *ConnectionHash = NULL; 45 | 46 | /* tracks whether any work is needed in callback functions */ 47 | static bool xact_got_connection = false; 48 | 49 | 50 | static char *firebirdDbPath(char **address, char **database, int *port); 51 | static FBconn *firebirdGetConnection(const char *dbpath, const char *svr_username, const char *svr_password); 52 | static void fb_begin_remote_xact(ConnCacheEntry *entry); 53 | static void fb_xact_callback(XactEvent event, void *arg); 54 | static void fb_subxact_callback(SubXactEvent event, 55 | SubTransactionId mySubid, 56 | SubTransactionId parentSubid, 57 | void *arg); 58 | 59 | /** 60 | * firebirdGetConnection() 61 | * 62 | * Establish DB connection 63 | */ 64 | static FBconn * 65 | firebirdGetConnection(const char *dbpath, const char *svr_username, const char *svr_password) 66 | { 67 | FBconn *volatile conn; 68 | const char *kw[5]; 69 | const char *val[5]; 70 | int i = 0; 71 | 72 | if (dbpath != NULL) 73 | { 74 | kw[i] = "db_path"; 75 | val[i] = dbpath; 76 | i++; 77 | } 78 | 79 | if (svr_username != NULL) 80 | { 81 | kw[i] = "user"; 82 | val[i] = svr_username; 83 | i++; 84 | } 85 | 86 | if (svr_password != NULL) 87 | { 88 | kw[i] = "password"; 89 | val[i] = svr_password; 90 | i++; 91 | } 92 | 93 | /* 94 | * Client encoding 95 | * 96 | * There is a broad overlap between the PostgreSQL server character 97 | * sets and the client encodings supported by Firebird. 98 | * 99 | * In many cases the names are a direct match (e.g. "UTF8"), or Firebird 100 | * supports the PostgreSQL name as an alias (e.g. "LATIN1" for "ISO8859_1"). 101 | * 102 | * In some cases there is no direct match or alias (e.g. PostgreSQL's 103 | * "ISO_8859_5", which corresponds to Firebird's "ISO8859_5"), so we'll 104 | * transparently rewrite those. 105 | * 106 | * There are also some cases where the PostgreSQL server character set 107 | * is not supported by Firebird (e.g. "WIN874"). We won't attempt to handle 108 | * those, as an error will be reported on connection, and we don't want 109 | * to hard-code assumptions about what client encodings a future Firebird 110 | * version may provide. 111 | * 112 | * Note that PostgreSQL supports some client character sets (e.g. SJIS) 113 | * which are not available as server character sets; we don't need to worry 114 | * about those. 115 | * 116 | * See also: 117 | * - https://www.postgresql.org/docs/current/multibyte.html#MULTIBYTE-CHARSET-SUPPORTED 118 | * - https://github.com/FirebirdSQL/firebird/blob/master/src/jrd/IntlManager.cpp#L100 119 | */ 120 | 121 | kw[i] = "client_encoding"; 122 | 123 | switch (GetDatabaseEncoding()) 124 | { 125 | case PG_SQL_ASCII: 126 | val[i] = "NONE"; 127 | break; 128 | case PG_ISO_8859_5: 129 | val[i] = "ISO8859_5"; 130 | break; 131 | case PG_ISO_8859_6: 132 | val[i] = "ISO8859_6"; 133 | break; 134 | case PG_ISO_8859_7: 135 | val[i] = "ISO8859_7"; 136 | break; 137 | case PG_ISO_8859_8: 138 | val[i] = "ISO8859_8"; 139 | break; 140 | case PG_WIN866: 141 | val[i] = "DOS866"; 142 | break; 143 | case PG_EUC_JP: 144 | /* 145 | * NOTE: need to verify whether this EUJC_0208 is an exact match for PostgreSQL's 146 | * EUC_JP (which might include JIS X 0212 and JIS X 0201). 147 | */ 148 | val[i] = "EUJC_0208"; 149 | break; 150 | default: 151 | val[i] = GetDatabaseEncodingName(); 152 | } 153 | 154 | elog(DEBUG2, "client_encoding: \"%s\"", val[i]); 155 | i++; 156 | 157 | kw[i] = NULL; 158 | val[i] = NULL; 159 | 160 | conn = FQconnectdbParams(kw, val); 161 | 162 | if (FQstatus(conn) != CONNECTION_OK) 163 | ereport(ERROR, 164 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 165 | errmsg("Unable to to connect to foreign server"), 166 | errdetail("%s", FQerrorMessage(conn)))); 167 | 168 | FQsetAutocommit(conn, false); 169 | 170 | // XXX make this configurable? 171 | conn->client_min_messages = DEBUG2; 172 | 173 | elog(DEBUG2, "%s(): DB connection OK", __func__); 174 | 175 | return conn; 176 | } 177 | 178 | 179 | /** 180 | * firebirdInstantiateConnection() 181 | * 182 | * Connect to the foreign database using the foreign server parameters 183 | */ 184 | FBconn * 185 | firebirdInstantiateConnection(ForeignServer *server, UserMapping *user) 186 | { 187 | bool found; 188 | ConnCacheEntry *entry; 189 | ConnCacheKey key; 190 | 191 | /* set up connection cache */ 192 | if (ConnectionHash == NULL) 193 | { 194 | HASHCTL ctl; 195 | 196 | elog(DEBUG2, "%s(): instantiating conn cache", __func__); 197 | 198 | MemSet(&ctl, 0, sizeof(ctl)); 199 | ctl.keysize = sizeof(ConnCacheKey); 200 | ctl.entrysize = sizeof(ConnCacheEntry); 201 | 202 | ctl.hcxt = CacheMemoryContext; 203 | 204 | ConnectionHash = hash_create("firebird_fdw connections", 8, 205 | &ctl, 206 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); 207 | 208 | /* Set up transaction callbacks */ 209 | RegisterXactCallback(fb_xact_callback, NULL); 210 | RegisterSubXactCallback(fb_subxact_callback, NULL); 211 | } 212 | 213 | /* Set flag that we did GetConnection during the current transaction */ 214 | xact_got_connection = true; 215 | /* Create hash key for the entry. Assume no pad bytes in key struct. */ 216 | key.serverid = server->serverid; 217 | key.userid = user->userid; 218 | 219 | /* Find or create cached entry for requested connection */ 220 | entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); 221 | if (!found) 222 | { 223 | /* initialize new hashtable entry */ 224 | entry->conn = NULL; 225 | entry->xact_depth = 0; 226 | entry->have_error = false; 227 | } 228 | 229 | if (entry->conn == NULL) 230 | { 231 | char *svr_address = NULL; 232 | char *svr_database = NULL; 233 | int svr_port = FIREBIRD_DEFAULT_PORT; 234 | char *svr_username = NULL; 235 | char *svr_password = NULL; 236 | 237 | char *dbpath; 238 | 239 | ListCell *lc; 240 | 241 | fbServerOptions server_options = fbServerOptions_init; 242 | 243 | elog(DEBUG2, "%s(): no cache entry found", __func__); 244 | 245 | entry->xact_depth = 0; /* just to be sure */ 246 | entry->have_error = false; 247 | 248 | server_options.address.opt.strptr = &svr_address; 249 | server_options.database.opt.strptr = &svr_database; 250 | server_options.port.opt.intptr = &svr_port; 251 | 252 | firebirdGetServerOptions( 253 | server, 254 | &server_options); 255 | 256 | foreach (lc, user->options) 257 | { 258 | DefElem *def = (DefElem *) lfirst(lc); 259 | 260 | if (strcmp(def->defname, "username") == 0) 261 | svr_username = defGetString(def); 262 | if (strcmp(def->defname, "password") == 0) 263 | svr_password = defGetString(def); 264 | } 265 | 266 | dbpath = firebirdDbPath(&svr_address, &svr_database, &svr_port); 267 | 268 | entry->conn = firebirdGetConnection( 269 | dbpath, 270 | svr_username, 271 | svr_password 272 | ); 273 | 274 | pfree(dbpath); 275 | elog(DEBUG2, "%s(): new firebird_fdw connection %p for server \"%s\"", 276 | __func__, entry->conn, server->servername); 277 | } 278 | else 279 | { 280 | elog(DEBUG2, "%s(): cache entry %p found", 281 | __func__, entry->conn); 282 | 283 | /* 284 | * Connection is not valid - reconnect. 285 | * 286 | * XXX if we're in a transaction we should roll back as the Firebird state will be lost 287 | */ 288 | if (FQstatus(entry->conn) == CONNECTION_BAD) 289 | { 290 | FBconn *new_conn = FQreconnect(entry->conn); 291 | 292 | elog(WARNING, "Firebird server connection has gone away"); 293 | 294 | /* XXX do we need to reset entry->xact_depth? */ 295 | elog(DEBUG2, "xact_depth: %i", entry->xact_depth); 296 | 297 | new_conn = firebirdGetConnection( 298 | FQdb_path(entry->conn), 299 | FQuname(entry->conn), 300 | FQupass(entry->conn)); 301 | 302 | FQfinish(entry->conn); 303 | entry->conn = new_conn; 304 | ereport(NOTICE, 305 | (errmsg("reconnected to Firebird server"))); 306 | } 307 | } 308 | 309 | 310 | pqsignal(SIGINT, fbSigInt); 311 | 312 | /* Start a new transaction or subtransaction if needed */ 313 | fb_begin_remote_xact(entry); 314 | 315 | return entry->conn; 316 | } 317 | 318 | 319 | 320 | /** 321 | * fb_begin_remote_xact() 322 | * 323 | * Start remote transaction or subtransaction, if needed. 324 | * 325 | * Firebird's transaction levels are somewhat differen to PostgreSQL's. 326 | * Currently we are using "SET TRANSACTION SNAPSHOT", which is roughly 327 | * equivalent to SERIALIZABLE. We'll probably need to reexamine this at 328 | * some point. 329 | * 330 | * XXX need to improve error handling 331 | * 332 | * See also: 333 | * - http://www.firebirdsql.org/manual/isql-transactions.html 334 | * - http://www.firebirdsql.org/refdocs/langrefupd25-set-trans.html 335 | */ 336 | static void 337 | fb_begin_remote_xact(ConnCacheEntry *entry) 338 | { 339 | FBresult *res; 340 | int curlevel = GetCurrentTransactionNestLevel(); 341 | 342 | elog(DEBUG2, "fb_begin_remote_xact(): xact depth: %i", entry->xact_depth); 343 | 344 | /* Start main transaction if we haven't yet */ 345 | if (entry->xact_depth <= 0) 346 | { 347 | elog(DEBUG2, "starting remote transaction on connection %p", 348 | entry->conn); 349 | 350 | res = FQexec(entry->conn, "SET TRANSACTION SNAPSHOT"); 351 | 352 | if (FQresultStatus(res) != FBRES_TRANSACTION_START) 353 | { 354 | /* XXX better error handling here */ 355 | elog(ERROR, "unable to execute SET TRANSACTION SNAPSHOT: %s", FQresultErrorMessage(res)); 356 | } 357 | 358 | FQclear(res); 359 | 360 | entry->xact_depth = 1; 361 | } 362 | else 363 | { 364 | if (FQisActiveTransaction(entry->conn)) 365 | elog(DEBUG2, "%s(): xact_depth > 0, active transaction", 366 | __func__); 367 | else 368 | elog(DEBUG2, "%s(): xact_depth > 0, no active transaction!", 369 | __func__); 370 | } 371 | 372 | /* 373 | * If we're in a subtransaction, stack up savepoints to match our level. 374 | * This ensures we can rollback just the desired effects when a 375 | * subtransaction aborts. 376 | */ 377 | while (entry->xact_depth < curlevel) 378 | { 379 | char sql[64]; 380 | 381 | snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1); 382 | res = FQexec(entry->conn, sql); 383 | elog(DEBUG2, "savepoint:\n%s", sql); 384 | elog(DEBUG2, "res is %s", FQresStatus(FQresultStatus(res))); 385 | FQclear(res); 386 | 387 | entry->xact_depth++; 388 | } 389 | } 390 | 391 | 392 | /** 393 | * fb_xact_callback() 394 | */ 395 | static void 396 | fb_xact_callback(XactEvent event, void *arg) 397 | { 398 | HASH_SEQ_STATUS scan; 399 | ConnCacheEntry *entry; 400 | 401 | elog(DEBUG3, "entering function %s", __func__); 402 | 403 | /* Connection has no transactions - do nothing */ 404 | if (!xact_got_connection) 405 | return; 406 | 407 | /* 408 | * Scan all connection cache entries and close any open remote transactions 409 | */ 410 | hash_seq_init(&scan, ConnectionHash); 411 | while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) 412 | { 413 | FBresult *res = NULL; 414 | elog(DEBUG3, "closing remote transaction on connection %p", 415 | entry->conn); 416 | 417 | /* We only care about connections with open remote transactions */ 418 | if (entry->conn == NULL) 419 | { 420 | elog(DEBUG3, "%s(): no connection", 421 | __func__); 422 | continue; 423 | } 424 | else if (entry->xact_depth == 0) 425 | { 426 | elog(DEBUG3, "%s(): no open transaction", 427 | __func__); 428 | continue; 429 | } 430 | 431 | /* This shouldn't happen, but log just in case */ 432 | if (!FQisActiveTransaction(entry->conn)) 433 | { 434 | elog(DEBUG3, "%s(): no active transaction", 435 | __func__); 436 | continue; 437 | } 438 | 439 | switch (event) 440 | { 441 | case XACT_EVENT_PRE_COMMIT: 442 | elog(DEBUG2, "COMMIT"); 443 | if (FQcommitTransaction(entry->conn) != TRANS_OK) 444 | { 445 | ereport(ERROR, 446 | (errcode(ERRCODE_FDW_ERROR), 447 | errmsg("COMMIT failed"))); 448 | } 449 | break; 450 | case XACT_EVENT_PRE_PREPARE: 451 | /* XXX not sure how to handle this */ 452 | elog(DEBUG2, "PREPARE"); 453 | break; 454 | case XACT_EVENT_PARALLEL_COMMIT: 455 | case XACT_EVENT_PARALLEL_PRE_COMMIT: 456 | case XACT_EVENT_COMMIT: 457 | case XACT_EVENT_PREPARE: 458 | /* Should not get here -- pre-commit should have handled it */ 459 | elog(ERROR, "missed cleaning up connection during pre-commit"); 460 | break; 461 | case XACT_EVENT_PARALLEL_ABORT: 462 | case XACT_EVENT_ABORT: 463 | /* XXX ROLLBACK here is probably ineffective as the FB connection will 464 | * likely have had an implict ROLLBACK; need to verify this... 465 | */ 466 | elog(DEBUG2, "ROLLBACK"); 467 | res = FQexec(entry->conn, "ROLLBACK"); 468 | if (FQresultStatus(res) != FBRES_TRANSACTION_ROLLBACK) 469 | { 470 | elog(DEBUG2, "transaction rollback failed"); 471 | } 472 | FQclear(res); 473 | break; 474 | default: 475 | elog(DEBUG2, "Unhandled unknown XactEvent"); 476 | } 477 | 478 | /* Reset state to show we're out of a transaction */ 479 | entry->xact_depth = 0; 480 | } 481 | elog(DEBUG3, "leaving fb_xact_callback()"); 482 | 483 | xact_got_connection = false; 484 | } 485 | 486 | 487 | /** 488 | * fb_subxact_callback() 489 | */ 490 | static void 491 | fb_subxact_callback(SubXactEvent event, 492 | SubTransactionId mySubid, 493 | SubTransactionId parentSubid, 494 | void *arg) 495 | { 496 | HASH_SEQ_STATUS scan; 497 | ConnCacheEntry *entry; 498 | int curlevel; 499 | 500 | elog(DEBUG3, "entering function %s", __func__); 501 | 502 | /* Nothing to do at subxact start, nor after commit. */ 503 | if (!(event == SUBXACT_EVENT_PRE_COMMIT_SUB || 504 | event == SUBXACT_EVENT_ABORT_SUB)) 505 | return; 506 | 507 | /* Quick exit if no connections were touched in this transaction. */ 508 | if (!xact_got_connection) 509 | return; 510 | 511 | /* 512 | * Scan all connection cache entries to find open remote subtransactions 513 | * of the current level, and close them. 514 | */ 515 | 516 | curlevel = GetCurrentTransactionNestLevel(); 517 | hash_seq_init(&scan, ConnectionHash); 518 | while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) 519 | { 520 | FBresult *res; 521 | char sql[100]; 522 | 523 | /* 524 | * We only care about connections with open remote subtransactions of 525 | * the current level. 526 | */ 527 | if (entry->conn == NULL || entry->xact_depth < curlevel) 528 | continue; 529 | 530 | if (entry->xact_depth > curlevel) 531 | elog(ERROR, "missed cleaning up remote subtransaction at level %d", 532 | entry->xact_depth); 533 | 534 | if (event == SUBXACT_EVENT_PRE_COMMIT_SUB) 535 | { 536 | /* Commit all remote subtransactions during pre-commit */ 537 | snprintf(sql, sizeof(sql), "RELEASE SAVEPOINT s%d", curlevel); 538 | elog(DEBUG2, "%s(): %s", __func__, sql); 539 | res = FQexec(entry->conn, sql); 540 | elog(DEBUG2, "%s(): res %i", __func__, FQresultStatus(res)); 541 | } 542 | else 543 | { 544 | /* Assume we might have lost track of prepared statements */ 545 | entry->have_error = true; 546 | /* Rollback all remote subtransactions during abort */ 547 | snprintf(sql, sizeof(sql), 548 | "ROLLBACK TO SAVEPOINT s%d", 549 | curlevel); 550 | res = FQexec(entry->conn, sql); 551 | if (FQresultStatus(res) != FBRES_COMMAND_OK) 552 | { 553 | elog(WARNING, "%s(): unable to execute '%s'", 554 | __func__, sql); 555 | FQclear(res); 556 | } 557 | else 558 | { 559 | snprintf(sql, sizeof(sql), 560 | "RELEASE SAVEPOINT s%d", 561 | curlevel); 562 | res = FQexec(entry->conn, sql); 563 | if (FQresultStatus(res) != FBRES_COMMAND_OK) 564 | { 565 | elog(WARNING, "%s(): unable to execute '%s'", 566 | __func__, sql); 567 | } 568 | FQclear(res); 569 | } 570 | } 571 | 572 | /* Leaving current subtransaction level */ 573 | entry->xact_depth--; 574 | } 575 | } 576 | 577 | 578 | /** 579 | * firebirdCloseConnections() 580 | * 581 | * Close any open connections before exiting, or if explicitly 582 | * requested by the user. 583 | */ 584 | void 585 | firebirdCloseConnections(bool verbose) 586 | { 587 | HASH_SEQ_STATUS fstat; 588 | ConnCacheEntry *entry; 589 | int closed = 0; 590 | 591 | elog(DEBUG3, "entering function %s", __func__); 592 | 593 | if (ConnectionHash == NULL) 594 | return; 595 | 596 | hash_seq_init(&fstat, ConnectionHash); 597 | while ((entry = (ConnCacheEntry *)hash_seq_search(&fstat)) != NULL) 598 | { 599 | if (entry->conn == NULL) 600 | continue; 601 | elog(DEBUG2, "%s(): closing cached connection %p", __func__, entry->conn); 602 | FQfinish(entry->conn); 603 | entry->conn = NULL; 604 | elog(DEBUG2, "%s(): cached connection closed", __func__); 605 | closed++; 606 | } 607 | 608 | if (verbose) 609 | elog(NOTICE, 610 | _("%i cached connections closed"), 611 | closed); 612 | } 613 | 614 | /** 615 | * firebirdCachedConnectionsCount() 616 | */ 617 | int 618 | firebirdCachedConnectionsCount(void) 619 | { 620 | HASH_SEQ_STATUS fstat; 621 | ConnCacheEntry *entry; 622 | int entry_count = 0; 623 | 624 | elog(DEBUG3, "entering function %s", __func__); 625 | 626 | if (ConnectionHash != NULL) 627 | { 628 | hash_seq_init(&fstat, ConnectionHash); 629 | while ((entry = (ConnCacheEntry *)hash_seq_search(&fstat)) != NULL) 630 | { 631 | if (entry->conn == NULL) 632 | continue; 633 | entry_count++; 634 | } 635 | } 636 | 637 | return entry_count; 638 | } 639 | 640 | 641 | /** 642 | * firebirdDbPath() 643 | * 644 | * Utility function to generate a Firebird database path. 645 | * 646 | * See: http://www.firebirdfaq.org/faq259/ 647 | */ 648 | static char * 649 | firebirdDbPath(char **address, char **database, int *port) 650 | { 651 | StringInfoData buf; 652 | char *path; 653 | int len; 654 | 655 | initStringInfo(&buf); 656 | 657 | if (*address != NULL) 658 | { 659 | appendStringInfoString(&buf, 660 | *address); 661 | 662 | if (*port > 0 && *port != FIREBIRD_DEFAULT_PORT) 663 | { 664 | appendStringInfo(&buf, 665 | "/%i", *port); 666 | } 667 | 668 | appendStringInfoChar(&buf, 669 | ':'); 670 | } 671 | 672 | /* Caller should ensure at least *database is not NULL */ 673 | if (*database != NULL) 674 | { 675 | appendStringInfoString(&buf, 676 | *database); 677 | } 678 | 679 | len = strlen(buf.data) + 1; 680 | path = palloc0(len); 681 | 682 | snprintf(path, len, "%s", buf.data); 683 | pfree(buf.data); 684 | 685 | elog(DEBUG2, "path: %s", path); 686 | 687 | return path; 688 | } 689 | 690 | 691 | void 692 | fbfdw_report_error(int errlevel, int pg_errcode, FBresult *res, FBconn *conn, char *query) 693 | { 694 | char *primary_message = FQresultErrorField(res, FB_DIAG_MESSAGE_PRIMARY); 695 | char *detail_message = FQresultErrorField(res, FB_DIAG_MESSAGE_DETAIL); 696 | 697 | PG_TRY(); 698 | { 699 | ereport(errlevel, 700 | (errcode(pg_errcode), 701 | errmsg("%s", primary_message), 702 | detail_message ? errdetail("%s", detail_message) : 0, 703 | query ? errcontext("remote SQL command: %s", query) : 0)); 704 | } 705 | PG_CATCH(); 706 | { 707 | FQclear(res); 708 | PG_RE_THROW(); 709 | } 710 | PG_END_TRY(); 711 | } 712 | -------------------------------------------------------------------------------- /t/FirebirdFDWNode.pm: -------------------------------------------------------------------------------- 1 | package FirebirdFDWNode; 2 | 3 | # This class extends PostgresNode with firebird_fdw-specific methods. 4 | # 5 | # It has two main purposes: 6 | # 1) to initialise a PostgreSQL node with the firebird_fdw extension 7 | # configured 8 | # 2) to provide a connection to a running Firebird instance. 9 | # 10 | # The Firebird database must be specified with the standard Firebird 11 | # environment variables `ISC_DATABASE`, `ISC_USER` and `ISC_PASSWORD`, 12 | # otherwise each test will exit with an error. 13 | 14 | use strict; 15 | use warnings; 16 | 17 | use v5.10.0; 18 | 19 | use if $ENV{PG_VERSION_NUM} >= 150000, 'PostgreSQL::Test::Cluster'; 20 | 21 | use if $ENV{PG_VERSION_NUM} < 150000, 'PostgresNode'; 22 | 23 | use Exporter 'import'; 24 | use vars qw(@EXPORT @EXPORT_OK); 25 | 26 | use Carp; 27 | use DBI; 28 | use Test::More; 29 | 30 | $SIG{__DIE__} = \&Carp::confess; 31 | 32 | @EXPORT = qw( 33 | get_new_fdw_node 34 | ); 35 | 36 | sub new { 37 | my $class = shift; 38 | my $self = {}; 39 | 40 | bless($self, $class); 41 | 42 | srand(); 43 | 44 | # Initialize PostgreSQL node 45 | # -------------------------- 46 | 47 | my $node_name = 'pg_node'; 48 | if ($ENV{PG_VERSION_NUM} >= 150000) { 49 | $self->{postgres_node} = PostgreSQL::Test::Cluster->new($node_name); 50 | } 51 | else { 52 | $self->{postgres_node} = get_new_node($node_name); 53 | } 54 | 55 | $self->{postgres_node}->init(); 56 | $self->{postgres_node}->start(); 57 | 58 | 59 | # Set up Firebird connection 60 | # -------------------------- 61 | # 62 | # (do this first to catch any problems connecting to Firebird) 63 | 64 | foreach my $envvar (qw(ISC_DATABASE ISC_USER ISC_PASSWORD)) { 65 | croak "please set environment variable $envvar" 66 | unless defined($ENV{$envvar}); 67 | } 68 | 69 | $self->{firebird_dbname} = $ENV{'ISC_DATABASE'}; 70 | 71 | # ISC_PORT is not a defined Interbase/Firebird variable, but we'll 72 | # use it so as not to hard-code the port number. 73 | $self->{firebird_dbport} = $ENV{'ISC_PORT'} // 3050; 74 | 75 | my $dsn = sprintf( 76 | q|dbi:Firebird:host=localhost;dbname=%s;port=%i|, 77 | $self->{firebird_dbname}, 78 | $self->{firebird_dbport}, 79 | ); 80 | 81 | $self->{firebird_dbh} = DBI->connect( 82 | $dsn, 83 | undef, 84 | undef, 85 | { 86 | PrintError => 1, 87 | RaiseError => 1, 88 | AutoCommit => 1, 89 | } 90 | ); 91 | 92 | $self->{firebird_major_version} = $self->get_firebird_major_version(); 93 | 94 | # Set up FDW on PostgreSQL 95 | # ------------------------ 96 | 97 | $self->{dbname} = 'fdw_test'; 98 | $self->{server_name} = 'fb_test'; 99 | 100 | $self->{postgres_node}->safe_psql( 101 | 'postgres', 102 | sprintf( 103 | q|CREATE DATABASE %s|, 104 | $self->{dbname}, 105 | ), 106 | ); 107 | 108 | $self->safe_psql( 109 | <{firebird_dbname}, 130 | $self->{firebird_dbport}, 131 | ); 132 | 133 | if ($ENV{PG_VERSION_NUM} >= 140000) { 134 | $options = sprintf( 135 | <safe_psql( 145 | sprintf( 146 | <{server_name}, 154 | $options, 155 | ) 156 | ); 157 | 158 | $self->safe_psql( 159 | sprintf( 160 | <{server_name}, 170 | $ENV{'ISC_USER'}, 171 | $ENV{'ISC_PASSWORD'}, 172 | ) 173 | ); 174 | 175 | return $self; 176 | } 177 | 178 | #----------------------------------------------------------------------- 179 | # 180 | # General methods 181 | # 182 | #----------------------------------------------------------------------- 183 | 184 | sub server_name { 185 | shift->{server_name}; 186 | } 187 | 188 | 189 | sub make_table_name { 190 | my $self = shift; 191 | my %options = @_; 192 | 193 | $options{uc_prefix} //= 0; 194 | 195 | my $table_name = 'tbl_'; 196 | 197 | if ($options{uc_prefix}) { 198 | $table_name = uc($table_name); 199 | } 200 | 201 | foreach my $i (0..7) { 202 | $table_name .= chr(int(26*rand) + 97); 203 | } 204 | 205 | return $table_name; 206 | } 207 | 208 | 209 | sub init_table { 210 | my $self = shift; 211 | my %table_options = @_; 212 | 213 | $table_options{firebird_only} //= 0; 214 | 215 | $table_options{table_name} //= $self->make_table_name(); 216 | 217 | $table_options{definition_fb} //= [ 218 | ['LANG_ID', 'CHAR(2) NOT NULL PRIMARY KEY'], 219 | ['NAME_ENGLISH', 'VARCHAR(64) NOT NULL'], 220 | ['NAME_NATIVE', 'VARCHAR(64) NOT NULL'], 221 | ]; 222 | 223 | $table_options{definition_pg} //= [ 224 | ['LANG_ID', 'CHAR(2) NOT NULL'], 225 | ['NAME_ENGLISH', 'VARCHAR(64) NOT NULL'], 226 | ['NAME_NATIVE', 'VARCHAR(64) NOT NULL'], 227 | ]; 228 | 229 | # Check if Firebird table exists (might have been left behind from a 230 | # previous test run) and if so, delete it. 231 | my $exists_query = $self->firebird_conn()->prepare( 232 | sprintf( 233 | q|SELECT COUNT(*) FROM rdb$relations WHERE TRIM(rdb$relation_name) = '%s'|, 234 | uc($table_options{table_name}), 235 | ), 236 | ); 237 | 238 | $exists_query->execute(); 239 | my $cnt = $exists_query->fetchrow_array(); 240 | $exists_query->finish(); 241 | 242 | if ($cnt) { 243 | my $drop_query = $self->firebird_conn()->prepare( 244 | sprintf( 245 | q|DROP TABLE %s|, 246 | $table_options{table_name}, 247 | ), 248 | ); 249 | $drop_query->execute(); 250 | $drop_query->finish(); 251 | } 252 | 253 | # Create Firebird table 254 | 255 | my $tbl_query = $self->firebird_conn()->prepare( 256 | sprintf( 257 | <format_table_definition($table_options{definition_fb}), 264 | ), 265 | ); 266 | 267 | $tbl_query->execute(); 268 | $tbl_query->finish(); 269 | 270 | return $table_options{table_name} if $table_options{firebird_only} == 1; 271 | 272 | # Create PostgreSQL foreign table 273 | 274 | my @options = (); 275 | 276 | push @options, sprintf( 277 | q|table_name '%s'|, 278 | $table_options{table_name}, 279 | ); 280 | 281 | if (defined($table_options{updatable})) { 282 | push @options, sprintf( 283 | q|updatable '%s'|, 284 | $table_options{updatable}, 285 | ); 286 | } 287 | 288 | if (defined($table_options{estimated_row_count})) { 289 | push @options, sprintf( 290 | q|estimated_row_count '%s'|, 291 | $table_options{estimated_row_count}, 292 | ); 293 | } 294 | 295 | my $sql = sprintf( 296 | <format_table_definition($table_options{definition_pg}), 305 | $self->{server_name}, 306 | join(",\n", @options), 307 | ); 308 | 309 | $self->safe_psql($sql); 310 | 311 | return $table_options{table_name}; 312 | } 313 | 314 | # Format a table definition from the provided nested arrayref 315 | 316 | sub format_table_definition { 317 | my $self = shift; 318 | my $definition = shift; 319 | 320 | my @columns = (); 321 | 322 | foreach my $column (@{$definition}) { 323 | push @columns, sprintf( 324 | q|%s %s|, 325 | $column->[0], 326 | $column->[1], 327 | ); 328 | } 329 | return join(",\n", @columns); 330 | } 331 | 332 | # Tables for testing data type handling 333 | 334 | sub init_data_type_table { 335 | my $self = shift; 336 | my %params = @_; 337 | 338 | $params{firebird_only} //= 0; 339 | 340 | my $min_compat_version = $self->get_min_compat_version(); 341 | 342 | my $table_name = sprintf( 343 | q|%s_data_type|, 344 | $self->make_table_name(), 345 | ); 346 | 347 | my $fb_tables = { 348 | # Firebird 2.x 349 | 2 => sprintf( 350 | < sprintf( 364 | < sprintf( 379 | <firebird_conn()->prepare( 398 | $fb_tables->{$min_compat_version}, 399 | ); 400 | 401 | $tbl_query->execute(); 402 | $tbl_query->finish(); 403 | 404 | return $table_name if $params{firebird_only} == 1; 405 | 406 | # Create PostgreSQL foreign table 407 | 408 | my $pg_column_defs = { 409 | 2 => sprintf( 410 | < sprintf( 418 | < sprintf( 429 | <safe_psql( 446 | sprintf( 447 | <{$min_compat_version}, 456 | $self->{server_name}, 457 | $table_name, 458 | ), 459 | ); 460 | 461 | return $table_name; 462 | } 463 | 464 | sub truncate_table { 465 | my $self = shift; 466 | my $table_name = shift; 467 | 468 | $self->safe_psql( 469 | sprintf( 470 | q|DELETE FROM %s|, 471 | $table_name, 472 | ), 473 | ); 474 | } 475 | 476 | 477 | #----------------------------------------------------------------------- 478 | # 479 | # PostgreSQL methods 480 | # 481 | #----------------------------------------------------------------------- 482 | 483 | 484 | sub dbname { 485 | shift->{dbname}; 486 | } 487 | 488 | sub psql { 489 | my $self = shift; 490 | my $sql = shift; 491 | 492 | return $self->{postgres_node}->psql( 493 | $self->{dbname}, 494 | $sql, 495 | ); 496 | } 497 | 498 | sub safe_psql { 499 | my $self = shift; 500 | my $sql = shift; 501 | 502 | return $self->{postgres_node}->safe_psql( 503 | $self->{dbname}, 504 | $sql, 505 | ); 506 | } 507 | 508 | 509 | sub pg_version { 510 | my $self = shift; 511 | 512 | my $sql = <safe_psql($sql); 519 | } 520 | 521 | 522 | 523 | sub add_server_option { 524 | my $self = shift; 525 | my $option = shift; 526 | my $value = shift; 527 | 528 | $self->safe_psql( 529 | sprintf( 530 | <{server_name}, 535 | $option, 536 | $value, 537 | ), 538 | ); 539 | } 540 | 541 | sub alter_server_option { 542 | my $self = shift; 543 | my $option = shift; 544 | my $value = shift; 545 | 546 | $self->safe_psql( 547 | sprintf( 548 | <{server_name}, 553 | $option, 554 | $value, 555 | ), 556 | ); 557 | } 558 | 559 | 560 | sub drop_server_option { 561 | my $self = shift; 562 | my $option = shift; 563 | 564 | $self->safe_psql( 565 | sprintf( 566 | <{server_name}, 571 | $option, 572 | ), 573 | ); 574 | } 575 | 576 | 577 | sub drop_foreign_server { 578 | my $self = shift; 579 | 580 | my $drop_foreign_server = sprintf( 581 | q|DROP SERVER IF EXISTS %s CASCADE|, 582 | $self->{server_name}, 583 | ); 584 | 585 | $self->safe_psql( $drop_foreign_server ); 586 | } 587 | 588 | 589 | sub add_foreign_table_option { 590 | my $self = shift; 591 | my $table = shift; 592 | my $option = shift; 593 | my $value = shift; 594 | 595 | $self->safe_psql( 596 | sprintf( 597 | <safe_psql( 615 | sprintf( 616 | <safe_psql( $drop_foreign_table ); 638 | } 639 | 640 | 641 | #----------------------------------------------------------------------- 642 | # 643 | # Firebird methods 644 | # 645 | #----------------------------------------------------------------------- 646 | 647 | sub firebird_conn { 648 | shift->{firebird_dbh}; 649 | } 650 | 651 | sub firebird_reconnect { 652 | my $self = shift; 653 | 654 | $self->firebird_conn()->disconnect(); 655 | 656 | $self->{firebird_dbh} = DBI->connect( 657 | 'dbi:Firebird:host=localhost;dbname='.$self->{firebird_dbname}, 658 | undef, 659 | undef, 660 | { 661 | PrintError => 1, 662 | RaiseError => 1, 663 | AutoCommit => 1 664 | } 665 | ); 666 | } 667 | 668 | sub get_firebird_version { 669 | my $self = shift; 670 | 671 | my $version_sql = q|SELECT CAST(rdb$get_context('SYSTEM', 'ENGINE_VERSION') AS VARCHAR(10)) FROM rdb$database|; 672 | 673 | my $version_q = $self->firebird_conn()->prepare( $version_sql ); 674 | 675 | $version_q->execute(); 676 | 677 | my $version = $version_q->fetchrow_array(); 678 | $version_q->finish(); 679 | 680 | 681 | if ($version !~ m|^(\d+)\.(\d+)\.(\d+)$|) { 682 | return undef; 683 | } 684 | 685 | if (wantarray) { 686 | my $version_int = sprintf( 687 | q|%d%02d%02d|, 688 | $1, $2, $3, 689 | ); 690 | return ($version, $version_int); 691 | } 692 | 693 | return $version; 694 | } 695 | 696 | sub get_firebird_session_timezone { 697 | my $self = shift; 698 | 699 | my $timezone_sql = q|SELECT TRIM(RDB$GET_CONTEXT('SYSTEM', 'SESSION_TIMEZONE')) FROM RDB$DATABASE|; 700 | 701 | my $timezone_q = $self->firebird_conn()->prepare( $timezone_sql ); 702 | 703 | $timezone_q->execute(); 704 | 705 | my $timezone = $timezone_q->fetchrow_array(); 706 | $timezone_q->finish(); 707 | 708 | return $timezone; 709 | } 710 | 711 | sub get_firebird_major_version { 712 | my $self = shift; 713 | 714 | my $version = $self->get_firebird_version(); 715 | 716 | # We're expecting something like "3.0.3" 717 | if ($version =~ m|^(\d)|) { 718 | return $1; 719 | } 720 | 721 | return undef; 722 | } 723 | 724 | 725 | # Some operations and tests depend on Firebird being at least 726 | # a particular version, so determine what that is from the current 727 | # major version. 728 | 729 | sub get_min_compat_version { 730 | my $self = shift; 731 | 732 | # Currently we do not support any functionality specific to Firebird 5 733 | my @min_compat_versions = (4, 3, 2); 734 | 735 | my $min_compat_version = undef; 736 | 737 | foreach my $version (@min_compat_versions) { 738 | if ($self->{firebird_major_version} >= $version) { 739 | $min_compat_version = $version; 740 | last; 741 | } 742 | } 743 | 744 | return $min_compat_version; 745 | } 746 | 747 | 748 | 749 | sub firebird_format_results { 750 | my $self = shift; 751 | my $query = shift; 752 | 753 | my @outp; 754 | 755 | while (my @row = $query->fetchrow_array()) { 756 | push @outp, join('|', @row); 757 | } 758 | 759 | return join("\n", @outp); 760 | } 761 | 762 | 763 | sub firebird_execute_sql { 764 | my $self = shift; 765 | my $sql = shift; 766 | 767 | my $q = $self->firebird_conn()->prepare($sql); 768 | 769 | $q->execute(); 770 | 771 | $q->finish(); 772 | } 773 | 774 | 775 | sub firebird_single_value_query { 776 | my $self = shift; 777 | my $sql = shift; 778 | 779 | my $q = $self->firebird_conn()->prepare($sql); 780 | 781 | $q->execute(); 782 | 783 | my $value = $q->fetchrow_array(); 784 | 785 | $q->finish(); 786 | 787 | return $value; 788 | } 789 | 790 | 791 | sub firebird_drop_table { 792 | my $self = shift; 793 | my $table_name = shift; 794 | my $quote_identifier = @_ ? shift : 0; 795 | 796 | $self->firebird_reconnect(); 797 | 798 | if ($quote_identifier) { 799 | $table_name = qq|"$table_name"|; 800 | } 801 | my $drop_table = sprintf( 802 | q|DROP TABLE %s|, 803 | $table_name, 804 | ); 805 | 806 | my $tbl_query = $self->firebird_conn()->prepare( $drop_table ); 807 | 808 | $tbl_query->execute(); 809 | $tbl_query->finish(); 810 | } 811 | 812 | 1; 813 | --------------------------------------------------------------------------------