pax_global_header00006660000000000000000000000064143246277310014523gustar00rootroot0000000000000052 comment=c9d0cc2e3db73257f29acb8fcad22083cd75e01e tds_fdw-2.0.3/000077500000000000000000000000001432462773100131575ustar00rootroot00000000000000tds_fdw-2.0.3/.gitattributes000066400000000000000000000003361432462773100160540ustar00rootroot00000000000000# Set the default behavior, in case people don't have core.autocrlf set. * text=auto # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. *.c text *.h text tds_fdw-2.0.3/.github/000077500000000000000000000000001432462773100145175ustar00rootroot00000000000000tds_fdw-2.0.3/.github/ISSUE_TEMPLATE.md000066400000000000000000000037441432462773100172340ustar00rootroot00000000000000# Issue report _The following information is very important in order to help us to help you. Omission of the following details cause delays or could receive no attention at all._ ## Operating system _On recent GNU/Linux distributions, you can provide the content of the file `/etc/os-release`_ ``` Replace this with the content ``` ## Version of tds\_fdw _From a `psql` session, paste the outputs of running `\dx`_ _If you built the package from Git sources, also paste the output of running `git log --source -n 1` on your git clone from a console_ ``` Replace this with the output ``` ## Version of PostgreSQL _From a `psql` session, paste the output of running `SELECT version();`_ ``` Replace this with the output ``` ## Version of FreeTDS _How to get it will depend on your Operating System and how you installes FreeTDS_ _From a console:_ * _On RPM based systems: `rpm -qa|grep freetds`_ * _On Deb based systems: `dpkg -l|grep freetds`_ * _If you built your own binaries from source code, then go to the sources, and run: `grep 'AC_INIT' configure.ac`_ ``` Replace this with the output ``` ## Logs _Please capture the logs when the error you are reporting is happening, as well as commands with their outputs if you are reporting a problem build or installing_ _For problems using tds_fdw on PostgreSQL how to do it will depend on your system, but if your PostgreSQL is installed on GNU/Linux, you will want to use `tail -f` with the log of the PostgreSQL cluster_ _For MSSQL you will need to use the SQL Server Audit Log_ ``` Replace this with the commands and outputs ``` ## Sentences, data structures, data _This will depend on the exact problem you are having and data privacy restrictions_ _However the more data you provide, the more likely we will be able to help_ _As a bare minimum, you should provide_ * _The SQL sentence that is failing_ * _The data structure on the PostgreSQL side and on the MSSQL side_ ``` Replace this with the SQL sentences, data structures, etc ``` tds_fdw-2.0.3/.gitignore000066400000000000000000000001111432462773100151400ustar00rootroot00000000000000.deps README.tds_fdw.md sql/*--*.sql *.o *.bc *.so tests/lib/__pycache__ tds_fdw-2.0.3/ForeignSchemaImporting.md000066400000000000000000000011501432462773100201010ustar00rootroot00000000000000# TDS Foreign data wrapper * **Name:** tds_fdw * **File:** tds_fdw/ForeignSchemaImporting.md ## Importing a Foreign Schema ### Options #### Foreign schema parameters accepted: * *import_default* Required: No Default: false Controls whether column DEFAULT expressions are included in the definitions of foreign tables. * *import_not_null* Required: No Default: true Controls whether column NOT NULL constraints are included in the definitions of foreign tables. ### Example ```SQL IMPORT FOREIGN SCHEMA dbo EXCEPT (mssql_table) FROM SERVER mssql_svr INTO public OPTIONS (import_default 'true'); ``` tds_fdw-2.0.3/ForeignServerCreation.md000066400000000000000000000065721432462773100177600ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/ForeignServerCreation.md ## Creating a Foreign Server ### Options #### Foreign server parameters accepted: * *servername* Required: Yes Default: 127.0.0.1 The servername, address or hostname of the foreign server server. This can be a DSN, as specified in *freetds.conf*. See [FreeTDS name lookup](https://www.freetds.org/userguide/name.lookup.html). You can set this option to a comma separated list of server names, then each server is tried until the first connection succeeds. This is useful for automatic fail-over to a secondary server. * *port* Required: No The port of the foreign server. This is optional. Instead of providing a port here, it can be specified in *freetds.conf* (if *servername* is a DSN). * *database* Required: No The database to connect to for this server. * *dbuse* Required: No Default: 0 This option tells tds_fdw to connect directly to *database* if *dbuse* is 0. If *dbuse* is not 0, tds_fdw will connect to the server's default database, and then select *database* by calling DB-Library's dbuse() function. For Azure, *dbuse* currently needs to be set to 0. * *language* Required: No The language to use for messages and the locale to use for date formats. FreeTDS may default to *us_english* on most systems. You can probably also change this in *freetds.conf*. For information related to this for MS SQL Server, see [SET LANGUAGE in MS SQL Server](https://technet.microsoft.com/en-us/library/ms174398.aspx). For information related to Sybase ASE, see [Sybase ASE login options](http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc32300.1570/html/sqlug/X68290.htm) and [SET LANGUAGE in Sybase ASE](http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc36272.1572/html/commands/X64136.htm). * *character_set* Required: No The client character set to use for the connection, if you need to set this for some reason. For TDS protocol versions 7.0+, the connection always uses UCS-2, so this parameter does nothing in those cases. See [Localization and TDS 7.0](https://www.freetds.org/userguide/Localization.html). * *tds_version* Required: No The version of the TDS protocol to use for this server. See [Choosing a TDS protocol version](https://www.freetds.org/userguide/ChoosingTdsProtocol.html) and [History of TDS Versions](https://www.freetds.org/userguide/tdshistory.html). * *msg_handler* Required: No Default: blackhole The function used for the TDS message handler. Options are "notice" and "blackhole." With the "notice" option, TDS messages are turned into PostgreSQL notices. With the "blackhole" option, TDS messages are ignored. * *fdw_startup_cost* Required: No A cost that is used to represent the overhead of using this FDW used in query planning. * *fdw_tuple_cost* Required: No A cost that is used to represent the overhead of fetching rows from this server used in query planning. #### Foreign table parameters accepted in server definition: Some foreign table options can also be set at the server level. Those include: * *use_remote_estimate* * *row_estimate_method* ### Example ```SQL CREATE SERVER mssql_svr FOREIGN DATA WRAPPER tds_fdw OPTIONS (servername '127.0.0.1', port '1433', database 'tds_fdw_test', tds_version '7.1'); ``` tds_fdw-2.0.3/ForeignTableCreation.md000066400000000000000000000053241432462773100175330ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/ForeignTableCreation.md ## Creating a Foreign Table ### Options #### Foreign table parameters accepted: * *query* Required: Yes (mutually exclusive with *table*) The query string to use to query the foreign table. * *schema_name* Required: No The schema that the table is in. The schema name can also be included in *table_name*, so this is not required. * *table_name* Aliases: *table* Required: Yes (mutually exclusive with *query*) The table on the foreign server to query. * *match_column_names* Required: No Whether to match local columns with remote columns by comparing their table names or whether to use the order that they appear in the result set. * *use_remote_estimate* Required: No Whether we estimate the size of the table by performing some operation on the remote server (as defined by *row_estimate_method*), or whether we just use a local estimate, as defined by *local_tuple_estimate*. * *local_tuple_estimate* Required: No A locally set estimate of the number of tuples that is used when *use_remote_estimate* is disabled. * *row_estimate_method* Required: No Default: `execute` This can be one of the following values: * `execute`: Execute the query on the remote server, and get the actual number of rows in the query. * `showplan_all`: This gets the estimated number of rows using [MS SQL Server's SET SHOWPLAN_ALL](https://msdn.microsoft.com/en-us/library/ms187735.aspx). #### Foreign table column parameters accepted: * *column_name* Required: No The name of the column on the remote server. If this is not set, the column's remote name is assumed to be the same as the column's local name. If match_column_names is set to 0 for the table, then column names are not used at all, so this is ignored. ### Example Using a *table_name* definition: ```SQL CREATE FOREIGN TABLE mssql_table ( id integer, data varchar) SERVER mssql_svr OPTIONS (table_name 'dbo.mytable', row_estimate_method 'showplan_all'); ``` Or using a *schema_name* and *table_name* definition: ```SQL CREATE FOREIGN TABLE mssql_table ( id integer, data varchar) SERVER mssql_svr OPTIONS (schema_name 'dbo', table_name 'mytable', row_estimate_method 'showplan_all'); ``` Or using a *query* definition: ```SQL CREATE FOREIGN TABLE mssql_table ( id integer, data varchar) SERVER mssql_svr OPTIONS (query 'SELECT * FROM dbo.mytable', row_estimate_method 'showplan_all'); ``` Or setting a remote column name: ```SQL CREATE FOREIGN TABLE mssql_table ( id integer, col2 varchar OPTIONS (column_name 'data')) SERVER mssql_svr OPTIONS (schema_name 'dbo', table_name 'mytable', row_estimate_method 'showplan_all'); ``` tds_fdw-2.0.3/InstallAlpine.md000066400000000000000000000071641432462773100162500ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** TheRevenantStar * **EditedBy:** Guriy Samarin * **Name:** tds_fdw * **File:** tds_fdw/InstallAlpine.md ## Installing on Alpine Linux This document will show how to install tds_fdw on Alpine Linux 3.10.3. Other Alpine Linux distributions should be similar. ### Install FreeTDS and build dependencies The TDS foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](http://www.freetds.org). ```bash apk add --update freetds-dev ``` Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: ```bash apk add gcc libc-dev make ``` In case you will get `fatal error: stdio.h: No such file or directory` later on (on `make USE_PGXS=1`) - installing `musl-dev` migth help (https://stackoverflow.com/questions/42366739/gcc-cant-find-stdio-h-in-alpine-linux): ```bash apk add musl-dev ``` ### Install PostgreSQL If you need to install PostgreSQL, do so by installing from APK. For example, to install PostgreSQL 11.6 on Alpine Linux: ```bash apk add postgresql=11.6-r0 postgresql-client=11.6-r0 postgresql-dev=11.6-r0 ``` In postgres-alpine docker image you will need only ```bash apk add postgresql-dev ``` ### Install tds_fdw #### Build from release package If you'd like to use one of the release packages, you can download and install them via something like the following: ```bash export TDS_FDW_VERSION="2.0.3" apk add wget wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION}/ make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Build from repository If you would rather use the current development version, you can clone and build the git repository via something like the following: ```bash apk add git git clone https://github.com/tds-fdw/tds_fdw.git cd tds_fdw make USE_PGXS=1 make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Start server If this is a fresh installation, then create the initial cluster and start the server: ```bash mkdir /var/lib/postgresql/data chmod 0700 /var/lib/postgresql/data chown postgres. /var/lib/postgresql/data su postgres -c 'initdb /var/lib/postgresql/data' mkdir /run/postgresql/ chown postgres. /run/postgresql/ su postgres -c 'pg_ctl start -D /var/lib/postgresql/data "-o -c listen_addresses=\"\""' ``` #### Install extension ```bash psql -U postgres postgres=# CREATE EXTENSION tds_fdw; ``` #### Dockerfile Example This Dockerfile will build PostgreSQL 11 in Alpine Linux with tds_fdw from master branch ``` FROM library/postgres:11-alpine RUN apk add --update freetds-dev && \ apk add git gcc libc-dev make && \ apk add postgresql-dev postgresql-contrib && \ git clone https://github.com/tds-fdw/tds_fdw.git && \ cd tds_fdw && \ make USE_PGXS=1 && \ make USE_PGXS=1 install && \ apk del git gcc libc-dev make && \ cd .. && \ rm -rf tds_fdw ``` You can easily adapt the Dockerfile if you want to use a release package. This Dockerfile works just like to official PostgreSQL image, just with tds_fdw added. See [Docker Hub library/postgres](https://hub.docker.com/_/postgres/) for details. tds_fdw-2.0.3/InstallDebian.md000066400000000000000000000054671432462773100162260ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/InstallDebian.md ## Installing on Debian This document will show how to install tds_fdw on Debian 10. Other Debian distributions should be similar. ### Install FreeTDS and build dependencies The TDS foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](http://www.freetds.org). ```bash sudo apt-get update sudo apt-get install libsybdb5 freetds-dev freetds-common ``` Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: ```bash sudo apt-get install gnupg gcc make ``` ### Install PostgreSQL If you need to install PostgreSQL, do so by following the [apt installation directions](https://wiki.postgresql.org/wiki/Apt). For example, to install PostgreSQL 11 on Debian: ```bash sudo bash -c 'source /etc/os-release; echo "deb http://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg main" > /etc/apt/sources.list.d/pgdg.list' sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-keys 0xACCC4CF8 sudo apt-get update sudo apt-get upgrade sudo apt-get install postgresql-11 postgresql-client-11 postgresql-server-dev-11 ``` **NOTE**: If you already have PostgreSQL installed on your system be sure that the package postgresql-server-dev-XX is installed too (where XX stands for your PostgreSQL version). ### Install tds_fdw #### Build from release package If you'd like to use one of the release packages, you can download and install them via something like the following: ```bash export TDS_FDW_VERSION="2.0.3" sudo apt-get install wget wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION}/ make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Build from repository If you would rather use the current development version, you can clone and build the git repository via something like the following: ```bash sudo apt-get install git git clone https://github.com/tds-fdw/tds_fdw.git cd tds_fdw make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Start server If this is a fresh installation, then start the server: ```bash sudo service postgresql start ``` #### Install extension ```bash psql -U postgres postgres=# CREATE EXTENSION tds_fdw; ``` tds_fdw-2.0.3/InstallOSX.md000066400000000000000000000047061432462773100155100ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/InstallOSX.md ## Installing on OSX This document will show how to install tds_fdw on OSX using the [Homebrew](https://brew.sh/) package manager for the required packages. ### Install FreeTDS The TDS foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](http://www.freetds.org). ```bash brew install freetds ``` Note: If you install FreeTDS from another source, e.g. [MacPorts](https://www.macports.org), you might have to adjust the value for `TDS_INCLUDE` in the make calls below (e.g. `-I/opt/local/include/freetds` for MacPorts). ### Install PostgreSQL If you need to install PostgreSQL, do so by following the [apt installation directions](https://wiki.postgresql.org/wiki/Apt). For example, to install PostgreSQL 9.5 on Ubuntu: ```bash brew install postgres ``` Or use Postgres.app: ### Install tds_fdw #### Build from release package If you'd like to use one of the release packages, you can download and install them via something like the following: ```bash export TDS_FDW_VERSION="2.0.3" wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION} make USE_PGXS=1 TDS_INCLUDE=-I/usr/local/include/ sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Build from repository If you would rather use the current development version, you can clone and build the git repository via something like the following: ```bash git clone https://github.com/tds-fdw/tds_fdw.git cd tds_fdw make USE_PGXS=1 TDS_INCLUDE=-I/usr/local/include/ sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Start server If this is a fresh installation, then start the server: ```bash brew services start postgresql ``` Or the equivalent command if you are not using Homebrew. #### Install extension ```bash psql -U postgres postgres=# CREATE EXTENSION tds_fdw; ``` tds_fdw-2.0.3/InstallRHELandClones.md000066400000000000000000000110311432462773100174050ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/InstallRHELandClones.md ## Installing on RHEL and Clones such as CentOS, Rocky Linux, AlmaLinux or Oracle This document will show how to install tds_fdw on Rocky Linux 8.5. RHEL distributions should be similar. NOTE: For the sake of simplicity, we will use `yum` as it works as an alias for `dnf` on newer distributions. ### Option A: yum/dnf (released versions) #### PostgreSQL If you need to install PostgreSQL, do so by following the [RHEL installation instructions](https://www.postgresql.org/download/linux/redhat/). Here is an extract of the instructions: Only for RHEL 8 and clones such as Rocky Linux 8: ```bash sudo sudo dnf -qy module disable postgresql # Not required for RHEL8 and clones ``` Install the PostgreSQL repository and packages: ```bash sudo rpm -i https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm sudo yum install postgresql11 postgresql11-server postgresql11-libs postgresql11-devel ``` #### tds_fdw The PostgreSQL development team packages `tds_fdw`, but they do not provide FreeTDS. First, install the EPEL repository: ```bash sudo yum install epel-release ``` And then install `tds_fdw`: ```bash sudo yum install tds_fdw11.x86_64 ``` ### Option B: Compile tds_fdw #### PostgreSQL If you need to install PostgreSQL, do so by following the [RHEL installation instructions](https://www.postgresql.org/download/linux/redhat/). Here is an extract of the instructions: Only for RHEL 8 and clones such as Rocky Linux 8: ```bash sudo sudo dnf -qy module disable postgresql # Not required for RHEL8 and clones ``` Install the PostgreSQL repository and packages: ```bash sudo rpm -i https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm sudo yum install postgresql11 postgresql11-server postgresql11-libs postgresql11-devel ``` #### Install FreeTDS devel and build dependencies The TDS foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](http://www.freetds.org). **NOTE:** In CentOS, you need the [EPEL repository installed](https://fedoraproject.org/wiki/EPEL) to install FreeTDS ```bash sudo yum install epel-release sudo yum install freetds-devel ``` ## IMPORTANT: CentOS7/Oracle7 and PostgreSQL >= 11 When using the official PostgreSQL packages from postgresql.org, JIT with bitcode is enabled by default and will require llvm5 and `clang` from LLVM5 installed at `/opt/rh/llvm-toolset-7/root/usr/bin/clang` to be able to compile. You have LLVM5 at the EPEL CentOS7 repository, but not LLVM7, so you will need install the CentOS Software collections. You can easily do it with the following commands: ```bash sudo yum install centos-release-scl ``` Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: ```bash sudo yum install gcc make wget ``` ##### Build from release package If you'd like to use one of the release packages, you can download and install them via something like the following: ```bash export TDS_FDW_VERSION="2.0.3" wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION} make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config sudo make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, then adjust `PG_CONFIG` accordingly. ##### Build from repository If you would rather use the current development version, you can clone and build the git repository via something like the following: ```bash yum install git git clone https://github.com/tds-fdw/tds_fdw.git cd tds_fdw make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config sudo make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, then adjust `PG_CONFIG` accordingly. ### Final steps #### Start server If this is a fresh installation, then initialize the data directory and start the server: ```bash sudo /usr/pgsql-11/bin/postgresql11-setup initdb sudo systemctl enable postgresql-11.service sudo systemctl start postgresql-11.service ``` #### Install extension ```bash /usr/pgsql-11/bin/psql -U postgres postgres=# CREATE EXTENSION tds_fdw; ``` tds_fdw-2.0.3/InstallUbuntu.md000066400000000000000000000054721432462773100163220ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/InstallUbuntu.md ## Installing on Ubuntu This document will show how to install tds_fdw on Ubuntu 18.04. Other Ubuntu distributions should be similar. ### Install FreeTDS and build dependencies The TDS foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](http://www.freetds.org). ```bash sudo apt-get update sudo apt-get install libsybdb5 freetds-dev freetds-common ``` Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: ```bash sudo apt-get install gnupg gcc make ``` ### Install PostgreSQL If you need to install PostgreSQL, do so by following the [apt installation directions](https://wiki.postgresql.org/wiki/Apt). For example, to install PostgreSQL 11 on Ubuntu: ```bash sudo bash -c 'source /etc/os-release; echo "deb http://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg main" > /etc/apt/sources.list.d/pgdg.list' sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-keys 0xACCC4CF8 sudo apt-get update sudo apt-get upgrade sudo apt-get install postgresql-11 postgresql-client-11 postgresql-server-dev-11 ``` **NOTE**: If you already have PostgreSQL installed on your system be sure that the package postgresql-server-dev-XX is installed too (where XX stands for your PostgreSQL version). ### Install tds_fdw #### Build from release package If you'd like to use one of the release packages, you can download and install them via something like the following: ```bash export TDS_FDW_VERSION="2.0.3" sudo apt-get install wget wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION}/ make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Build from repository If you would rather use the current development version, you can clone and build the git repository via something like the following: ```bash sudo apt-get install git git clone https://github.com/tds-fdw/tds_fdw.git cd tds_fdw make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Start server If this is a fresh installation, then start the server: ```bash sudo service postgresql start ``` #### Install extension ```bash psql -U postgres postgres=# CREATE EXTENSION tds_fdw; ``` tds_fdw-2.0.3/InstallopenSUSE.md000066400000000000000000000045271432462773100165010ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/InstallopenSUSE.md ## Installing on openSUSE This document will show how to install tds_fdw on openSUSE Leap 15.1. Other openSUSE and SUSE distributions should be similar. ### Install FreeTDS and build dependencies The TDS foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](http://www.freetds.org). ```bash sudo zypper install freetds-devel ``` Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: ```bash sudo zypper install gcc make ``` ### Install PostgreSQL If you need to install PostgreSQL, for example, 10.X: ```bash sudo zypper install postgresql10 postgresql10-server postgresql10-devel ``` **NOTE**: If you already have PostgreSQL installed on your system be sure that the package postgresqlXX-devel is installed too (where XX stands for your PostgreSQL version). ### Install tds_fdw #### Build from release package If you'd like to use one of the release packages, you can download and install them via something like the following: ```bash export TDS_FDW_VERSION="2.0.3" wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION}/ make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Build from repository If you would rather use the current development version, you can clone and build the git repository via something like the following: ```bash zypper in git git clone https://github.com/tds-fdw/tds_fdw.git cd tds_fdw make USE_PGXS=1 sudo make USE_PGXS=1 install ``` **NOTE:** If you have several PostgreSQL versions and you do not want to build for the default one, first locate where the binary for `pg_config` is, take note of the full path, and then append `PG_CONFIG=` after `USE_PGXS=1` at the `make` commands. #### Start server If this is a fresh installation, then start the server: ```bash sudo service postgresql start ``` #### Install extension ```bash psql -U postgres postgres=# CREATE EXTENSION tds_fdw; ``` tds_fdw-2.0.3/LICENSE000066400000000000000000000021451432462773100141660ustar00rootroot00000000000000TDS Foreign Data Wrapper for PostgreSQL Copyright (c) 2011 - 2016, Geoff Montee Portions Copyright (c) 1996-2016, The PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL GEOFF MONTEE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF GEOFF MONTEE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. GEOFF MONTEE SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND GEOFF MONTEE HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. tds_fdw-2.0.3/META.json000066400000000000000000000021661432462773100146050ustar00rootroot00000000000000{ "name": "tds_fdw", "abstract": "TDS Foreign data wrapper", "description": "This library contains a single PostgreSQL extension, a foreign data wrapper called \"tds_fdw\". It can be used to communicate with Microsoft SQL Server and Sybase databases.", "version": "2.0.3", "maintainer": [ "Geoff Montee " ], "license": "postgresql", "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.2.0" } } }, "provides": { "tds_fdw": { "abstract": "TDS Foreign data wrapper", "file": "sql/tds_fdw.sql", "docfile": "README.md", "version": "2.0.3" } }, "resources": { "bugtracker": { "web": "https://github.com/tds-fdw/tds_fdw/issues" }, "repository": { "url": "https://github.com/tds-fdw/tds_fdw.git", "web": "https://github.com/tds-fdw/tds_fdw", "type": "git" } }, "generated_by": "Geoff Montee", "release_status": "stable", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "tds_fdw", "foreign data wrapper", "fdw", "tds", "tabular data stream", "sybase", "microsoft sql server", "sql server" ] } tds_fdw-2.0.3/Makefile000066400000000000000000000031421432462773100146170ustar00rootroot00000000000000#*------------------------------------------------------------------ # # Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) # # Author: Geoff Montee # Name: tds_fdw # File: tds_fdw/Makefile # # Description: # This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, # such as Sybase databases and Microsoft SQL server. # # This foreign data wrapper requires a library that uses the DB-Library interface, # such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not # the proprietary implementations of DB-Library. #---------------------------------------------------------------------------- EXTENSION = tds_fdw MODULE_big = $(EXTENSION) OBJS = src/tds_fdw.o src/options.o src/deparse.o EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\\([^']*\\)'/\\1/") # no tests yet # TESTS = $(wildcard test/sql/*.sql) # REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) # REGRESS_OPTS = --inputdir=test DOCS = README.${EXTENSION}.md DATA = sql/$(EXTENSION)--$(EXTVERSION).sql PG_CONFIG = pg_config # modify these variables to point to FreeTDS, if needed SHLIB_LINK := -lsybdb TDS_INCLUDE := PG_CPPFLAGS := -I./include/ -fvisibility=hidden ${TDS_INCLUDE} # PG_LIBS := all: sql/$(EXTENSION)--$(EXTVERSION).sql README.${EXTENSION}.md sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql cp $< $@ README.${EXTENSION}.md: README.md cp $< $@ EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql README.${EXTENSION}.md PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) tds_fdw-2.0.3/README.md000066400000000000000000000175421432462773100144470ustar00rootroot00000000000000 # TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/README.md ## Logo ![Logo SVG](./logo/tds_fdw.svg) ## About This is a [PostgreSQL foreign data wrapper](https://wiki.postgresql.org/wiki/Foreign_data_wrappers) that can connect to databases that use the [Tabular Data Stream (TDS) protocol](https://en.wikipedia.org/wiki/Tabular_Data_Stream), such as Sybase databases and Microsoft SQL server. This foreign data wrapper requires a library that implements the DB-Library interface, such as [FreeTDS](https://www.freetds.org). This has been tested with FreeTDS, but not the proprietary implementations of DB-Library. This should support PostgreSQL 9.2+. The current version does not yet support JOIN push-down, or write operations. It does support WHERE and column pushdowns when *match_column_names* is enabled. ## Build Status | | CentOS 7 | Rocky Linux 8 | Ubuntu 20.04 | | --------------:|:--------------:|:------------------:|:-----------------:| | **PostgreSQL 9.6** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=9.6,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=9.6,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=9.6,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=9.6,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=9.6,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=9.6,label=docker)| | **PostgreSQL 10** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=10,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=10,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=10,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=10,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=10,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=10,label=docker)| | **PostgreSQL 11** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=11,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=11,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=11,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=11,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=11,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=11,label=docker)| | **PostgreSQL 12** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=12,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=12,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=12,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=12,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=12,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=12,label=docker)| | **PostgreSQL 13** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=13,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=13,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=13,label=docker)| | **PostgreSQL 14** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=centos7,PG_VER=13,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=13,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=14,label=docker)| ## Installing on RHEL and clones (CentOS, Rocky Linux, AlmaLinux, Oracle...) See [installing tds_fdw on CentOS](InstallRHELandClones.md). ## Installing on Ubuntu See [installing tds_fdw on Ubuntu](InstallUbuntu.md). ## Installing on Debian See [installing tds_fdw on Debian](InstallDebian.md). ## Installing on openSUSE See [installing tds_fdw on openSUSE](InstallopenSUSE.md). ## Installing on OSX See [installing tds_fdw on OSX](InstallOSX.md). ## Installing on Alpine (and Docker) See [installing tds_fdw on Alpine](InstallAlpine.md). ## Usage ### Foreign server See [creating a foreign server](ForeignServerCreation.md). ### Foreign table See [creating a foreign table](ForeignTableCreation.md). ### User mapping See [creating a user mapping](UserMappingCreation.md). ### Foreign schema See [importing a foreign schema](ForeignSchemaImporting.md). ### Variables See [variables](Variables.md). ### `EXPLAIN` `EXPLAIN (VERBOSE)` will show the query issued on the remote system. ## Notes about character sets/encoding 1. If you get an error like this with MS SQL Server when working with Unicode data: > NOTICE: DB-Library notice: Msg #: 4004, Msg state: 1, Msg: Unicode data in a Unicode-only > collation or ntext data cannot be sent to clients using DB-Library (such as ISQL) or ODBC > version 3.7 or earlier., Server: PILLIUM\SQLEXPRESS, Process: , Line: 1, Level: 16 > ERROR: DB-Library error: DB #: 4004, DB Msg: General SQL Server error: Check messages from > the SQL Server, OS #: -1, OS Msg: (null), Level: 16 You may have to manually set *tds version* in *freetds.conf* to 7.0 or higher. See [The *freetds.conf* File](https://www.freetds.org/userguide/freetdsconf.html). and [Choosing a TDS protocol version](https://www.freetds.org/userguide/ChoosingTdsProtocol.html). 2. Although many newer versions of the TDS protocol will only use USC-2 to communicate with the server, FreeTDS converts the UCS-2 to the client character set of your choice. To set the client character set, you can set *client charset* in *freetds.conf*. See [The *freetds.conf* File](https://www.freetds.org/userguide/freetdsconf.html) and [Localization and TDS 7.0](https://www.freetds.org/userguide/Localization.html). ## Encrypted connections to MSSQL It is handled by FreeTDS, so this needs to be configured at the `freetds.conf`. Seee [The *freetds.conf* File](https://www.freetds.org/userguide/freetdsconf.html) and at `freetds.conf settings` look for `encryption`. ## Support If you find any bugs, or you would like to request enhancements, please submit your comments on the [project's GitHub Issues page](https://github.com/tds-fdw/tds_fdw/issues). Additionally, I do subscribe to several [PostgreSQL mailing lists](https://www.postgresql.org/list/) including *pgsql-general* and *pgsql-hackers*. If tds_fdw is mentioned in an email sent to one of those lists, I typically see it. ## Debugging See [Debugging](tests/README.md) tds_fdw-2.0.3/UserMappingCreation.md000066400000000000000000000007571432462773100174310ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/UserMappingCreation.md ## Creating a User Mapping ### Options User mapping parameters accepted: * *username* Required: Yes The username of the account on the foreign server. * *password* Required: Yes The password of the account on the foreign server. ### Example ```SQL CREATE USER MAPPING FOR postgres SERVER mssql_svr OPTIONS (username 'sa', password ''); ```tds_fdw-2.0.3/Variables.md000066400000000000000000000012621432462773100154120ustar00rootroot00000000000000# TDS Foreign data wrapper * **Author:** Geoff Montee * **Name:** tds_fdw * **File:** tds_fdw/Variables.md ## Variables ### Available Variables * *tds_fdw.show_before_row_memory_stats* - print memory context stats to the PostgreSQL log before each row is fetched. * *tds_fdw.show_after_row_memory_stats* - print memory context stats to the PostgreSQL log after each row is fetched. * *tds_fdw.show_finished_memory_stats* - print memory context stats to the PostgreSQL log when a query is finished. ### Setting Variables To set a variable, use the [SET command](https://www.postgresql.org/docs/12/sql-set.html). i.e.: ``` postgres=# SET tds_fdw.show_finished_memory_stats=1; SET ```tds_fdw-2.0.3/include/000077500000000000000000000000001432462773100146025ustar00rootroot00000000000000tds_fdw-2.0.3/include/deparse.h000066400000000000000000000113201432462773100163730ustar00rootroot00000000000000/*------------------------------------------------------------------ * * Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) * * Author: Geoff Montee * Name: tds_fdw * File: tds_fdw/include/planner.h * * Description: * This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, * such as Sybase databases and Microsoft SQL server. * * This foreign data wrapper requires requires a library that uses the DB-Library interface, * such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not * the proprietary implementations of DB-Library. *---------------------------------------------------------------------------- */ #ifndef DEPARSE_H #define DEPARSE_H /* * Examine each qual clause in input_conds, and classify them into two groups, * which are returned as two lists: * - remote_conds contains expressions that can be evaluated remotely * - local_conds contains expressions that can't be evaluated remotely */ void classifyConditions(PlannerInfo *root, RelOptInfo *baserel, List *input_conds, List **remote_conds, List **local_conds); /* * Returns true if given expr is safe to evaluate on the foreign server. */ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr); /* * Construct a simple SELECT statement that retrieves desired columns * of the specified foreign table, and append it to "buf". The output * contains just "SELECT ... FROM tablename". * * We also create an integer List of the columns being retrieved, which is * returned to *retrieved_attrs. */ void deparseSelectSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used, List **retrieved_attrs, TdsFdwOptionSet* option_set); /* * deparse remote INSERT statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, bool doNothing, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set); /* * deparse remote UPDATE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set); /* * deparse remote DELETE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set); /* * Construct SELECT statement to acquire size in blocks of given relation. * * Note: we use local definition of block size, not remote definition. * This is perhaps debatable. * * Note: pg_relation_size() exists in 8.1 and later. */ void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); /* * Append a SQL string literal representing "val" to buf. */ void deparseStringLiteral(StringInfo buf, const char *val); /* * Construct SELECT statement to acquire sample rows of given relation. * * SELECT command is appended to buf, and list of columns retrieved * is returned to *retrieved_attrs. */ void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs); /* * Deparse WHERE clauses in given list of RestrictInfos and append them to buf. * * baserel is the foreign table we're planning for. * * If no WHERE clause already exists in the buffer, is_first should be true. * * If params is not NULL, it receives a list of Params and other-relation Vars * used in the clauses; these values must be transmitted to the remote server * as parameter values. * * If params is NULL, we're generating the query for EXPLAIN purposes, * so Params and other-relation Vars should be replaced by dummy values. */ void appendWhereClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *exprs, bool is_first, List **params); /* * Deparse ORDER BY clause according to the given pathkeys for given base * relation. From given pathkeys expressions belonging entirely to the given * base relation are obtained and deparsed. */ void appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *pathkeys); #endiftds_fdw-2.0.3/include/options.h000066400000000000000000000017361432462773100164550ustar00rootroot00000000000000 #ifndef OPTIONS_H #define OPTIONS_H #include "postgres.h" /* valid options follow this format */ typedef struct TdsFdwOption { const char *optname; Oid optcontext; } TdsFdwOption; /* option values will be put here */ typedef struct TdsFdwOptionSet { char *servername; char *language; char *character_set; int port; char *database; int dbuse; char* tds_version; char* msg_handler; char *username; char *password; char *query; char *schema_name; char *table_name; char* row_estimate_method; int match_column_names; int use_remote_estimate; int fdw_startup_cost; int fdw_tuple_cost; int local_tuple_estimate; } TdsFdwOptionSet; void tdsValidateOptions(List *options_list, Oid context, TdsFdwOptionSet* option_set); void tdsGetForeignServerOptionsFromCatalog(Oid foreignserverid, TdsFdwOptionSet* option_set); void tdsGetForeignTableOptionsFromCatalog(Oid foreigntableid, TdsFdwOptionSet* option_set); void tdsValidateOptionSet(TdsFdwOptionSet* option_set); #endif tds_fdw-2.0.3/include/tds_fdw.h000066400000000000000000000146571432462773100164220ustar00rootroot00000000000000/*------------------------------------------------------------------ * * Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) * * Author: Geoff Montee * Name: tds_fdw * File: tds_fdw/include/tds_fdw.h * * Description: * This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, * such as Sybase databases and Microsoft SQL server. * * This foreign data wrapper requires requires a library that uses the DB-Library interface, * such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not * the proprietary implementations of DB-Library. *---------------------------------------------------------------------------- */ #ifndef TDS_FDW_H #define TDS_FDW_H /* postgres headers */ #include "postgres.h" #include "funcapi.h" #include "commands/explain.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" #if (PG_VERSION_NUM >= 90200) #include "optimizer/pathnode.h" #include "optimizer/restrictinfo.h" #include "optimizer/planmain.h" #endif /* DB-Library headers (e.g. FreeTDS) */ #include #include #include "options.h" #if PG_VERSION_NUM >= 90500 #define IMPORT_API #else #undef IMPORT_API #endif /* PG_VERSION_NUM */ /* a column */ typedef union COL_VALUE { DBSMALLINT dbsmallint; DBINT dbint; DBBIGINT dbbigint; DBREAL dbreal; DBFLT8 dbflt8; } COL_VALUE; typedef struct COL { char *name; int srctype; bool useraw; COL_VALUE value; int local_index; Oid attr_oid; } COL; /* This struct is similar to PgFdwRelationInfo from postgres_fdw */ typedef struct TdsFdwRelationInfo { /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */ List *remote_conds; List *local_conds; /* Bitmap of attr numbers we need to fetch from the remote server. */ Bitmapset *attrs_used; /* Cost and selectivity of local_conds. */ QualCost local_conds_cost; Selectivity local_conds_sel; /* Estimated size and cost for a scan with baserestrictinfo quals. */ double rows; int width; Cost startup_cost; Cost total_cost; /* Options extracted from catalogs. */ bool use_remote_estimate; Cost fdw_startup_cost; Cost fdw_tuple_cost; /* tds_fdw won't ship any PostgreSQL extensions. remove this later. */ //List *shippable_extensions; /* OIDs of whitelisted extensions */ /* Cached catalog information. */ ForeignTable *table; ForeignServer *server; UserMapping *user; /* only set in use_remote_estimate mode */ } TdsFdwRelationInfo; /* this maintains state */ typedef struct TdsFdwExecutionState { LOGINREC *login; DBPROCESS *dbproc; AttInMetadata *attinmeta; char *query; List *retrieved_attrs; int first; COL *columns; Datum *datums; bool *isnull; int ncols; int row; MemoryContext mem_cxt; } TdsFdwExecutionState; /* Callback argument for ec_member_matches_foreign */ typedef struct { Expr *current; /* current expr, or NULL if not yet found */ List *already_used; /* expressions already dealt with */ } ec_member_foreign_arg; /* functions called via SQL */ extern Datum tds_fdw_handler(PG_FUNCTION_ARGS); extern Datum tds_fdw_validator(PG_FUNCTION_ARGS); /* FDW callback routines */ void tdsExplainForeignScan(ForeignScanState *node, ExplainState *es); void tdsBeginForeignScan(ForeignScanState *node, int eflags); TupleTableSlot* tdsIterateForeignScan(ForeignScanState *node); void tdsReScanForeignScan(ForeignScanState *node); void tdsEndForeignScan(ForeignScanState *node); /* routines for 9.2.0+ */ #if (PG_VERSION_NUM >= 90200) void tdsGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); void tdsEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid); void tdsGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); bool tdsAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); #if (PG_VERSION_NUM >= 90500) ForeignScan* tdsGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan); #else ForeignScan* tdsGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses); #endif /* routines for versions older than 9.2.0 */ #else FdwPlan* tdsPlanForeignScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel); #endif #ifdef IMPORT_API List *tdsImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid); #endif /* IMPORT_API */ /* compatibility with PostgreSQL 9.6+ */ #ifndef ALLOCSET_DEFAULT_SIZES #define ALLOCSET_DEFAULT_SIZES \ ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE #endif /* compatibility with PostgreSQL v11+ */ #if PG_VERSION_NUM < 110000 /* new in v11 */ #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #else /* removed in v11 */ #define get_relid_attribute_name(relid, varattno) get_attname((relid), (varattno), false) #endif /* Helper functions */ bool is_builtin(Oid objectId); Expr * find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel); bool is_shippable(Oid objectId, Oid classId, TdsFdwRelationInfo *fpinfo); void tdsBuildForeignQuery(PlannerInfo *root, RelOptInfo *baserel, TdsFdwOptionSet* option_set, Bitmapset* attrs_used, List** retrieved_attrs, List* remote_conds, List* remote_join_conds, List* pathkeys); int tdsSetupConnection(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS **dbproc); double tdsGetRowCount(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc); double tdsGetRowCountShowPlanAll(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc); double tdsGetRowCountExecute(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc); double tdsGetStartupCost(TdsFdwOptionSet* option_set); void tdsGetColumnMetadata(ForeignScanState *node, TdsFdwOptionSet *option_set); char* tdsConvertToCString(DBPROCESS* dbproc, int srctype, const BYTE* src, DBINT srclen); #if (PG_VERSION_NUM >= 90400) int tdsDatetimeToDatum(DBPROCESS *dbproc, DBDATETIME *src, Datum *datetime_out); #endif /* Helper functions for DB-Library API */ int tds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); int tds_notice_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *svr_name, char *proc_name, int line); int tds_blackhole_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *svr_name, char *proc_name, int line); #endif tds_fdw-2.0.3/include/visibility.h000066400000000000000000000002101432462773100171330ustar00rootroot00000000000000#ifndef VISIBILITY_H #define VISIBILITY_H #if __GNUC__ >= 4 #define PGDLLEXPORT __attribute__((visibility("default"))) #endif #endif tds_fdw-2.0.3/logo/000077500000000000000000000000001432462773100141175ustar00rootroot00000000000000tds_fdw-2.0.3/logo/tds_fdw.svg000066400000000000000000000641001432462773100162730ustar00rootroot00000000000000 image/svg+xml TDS TDS tds_fdw-2.0.3/sql/000077500000000000000000000000001432462773100137565ustar00rootroot00000000000000tds_fdw-2.0.3/sql/tds_fdw.sql000066400000000000000000000017331432462773100161350ustar00rootroot00000000000000/*------------------------------------------------------------------ # # Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) # # Author: Geoff Montee # Name: tds_fdw # File: tds_fdw/sql/tds_fdw--1.0.0-beta.sql # # Description: # This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, # such as Sybase databases and Microsoft SQL server. # # This foreign data wrapper requires requires a library that uses the DB-Library interface, # such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not # the proprietary implementations of DB-Library. #----------------------------------------------------------------------------*/ CREATE FUNCTION tds_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FUNCTION tds_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FOREIGN DATA WRAPPER tds_fdw HANDLER tds_fdw_handler VALIDATOR tds_fdw_validator; tds_fdw-2.0.3/src/000077500000000000000000000000001432462773100137465ustar00rootroot00000000000000tds_fdw-2.0.3/src/deparse.c000066400000000000000000001531511432462773100155430ustar00rootroot00000000000000/*------------------------------------------------------------------ * * Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) * * Author: Geoff Montee * Name: tds_fdw * File: tds_fdw/src/deparse.c * * Description: * This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, * such as Sybase databases and Microsoft SQL server. * * This foreign data wrapper requires requires a library that uses the DB-Library interface, * such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not * the proprietary implementations of DB-Library. *---------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * * deparse.c * Query deparser for tds_fdw. This file was copied from postgres_fdw * * This file includes functions that examine query WHERE clauses to see * whether they're safe to send to the remote server for execution, as * well as functions to construct the query text to be sent. The latter * functionality is annoyingly duplicative of ruleutils.c, but there are * enough special considerations that it seems best to keep this separate. * One saving grace is that we only need deparse logic for node types that * we consider safe to send. * * We assume that the remote session's search_path is exactly "pg_catalog", * and thus we need schema-qualify all and only names outside pg_catalog. * * We do not consider that it is ever safe to send COLLATE expressions to * the remote server: it might not have the same collation names we do. * (Later we might consider it safe to send COLLATE "C", but even that would * fail on old remote servers.) An expression is considered safe to send * only if all operator/function input collations used in it are traceable to * Var(s) of the foreign table. That implies that if the remote server gets * a different answer than we do, the foreign table's columns are not marked * with collations that match the remote table's columns, which we can * consider to be user error. * * Portions Copyright (c) 2012-2015, PostgreSQL Global Development Group * * IDENTIFICATION * contrib/postgres_fdw/deparse.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #if (PG_VERSION_NUM >= 90300) #include "access/htup_details.h" #endif #include "access/sysattr.h" #include "catalog/pg_collation.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #if (PG_VERSION_NUM < 120000) #include "optimizer/var.h" #else #include "optimizer/optimizer.h" #endif #include "parser/parsetree.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" #include "tds_fdw.h" #include "deparse.h" /* * Global context for foreign_expr_walker's search of an expression tree. */ typedef struct foreign_glob_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ } foreign_glob_cxt; /* * Local (per-tree-level) context for foreign_expr_walker's search. * This is concerned with identifying collations used in the expression. */ typedef enum { FDW_COLLATE_NONE, /* expression is of a noncollatable type, or * it has default collation that is not * traceable to a foreign Var */ FDW_COLLATE_SAFE, /* collation derives from a foreign Var */ FDW_COLLATE_UNSAFE /* collation is non-default and derives from * something other than a foreign Var */ } FDWCollateState; typedef struct foreign_loc_cxt { Oid collation; /* OID of current collation, if any */ FDWCollateState state; /* state of current collation choice */ } foreign_loc_cxt; /* * Context for deparseExpr */ typedef struct deparse_expr_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; /* * Functions to determine whether an expression can be evaluated safely on * remote server. */ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt); static char *deparse_type_name(Oid type_oid, int32 typemod); static char* postgresql_type_to_tds_type(const char* postgresql_type); /* * Functions to construct string representation of a node tree. */ static void deparseTargetList(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, Bitmapset *attrs_used, List **retrieved_attrs, TdsFdwOptionSet* option_set); static void deparseReturningList(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, bool trig_after_row, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set); static void deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root); static void deparseRelation(StringInfo buf, Relation rel); static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void deparseVar(Var *node, deparse_expr_cxt *context); static void deparseConst(Const *node, deparse_expr_cxt *context); static void deparseParam(Param *node, deparse_expr_cxt *context); #if (PG_VERSION_NUM < 120000) static void deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context); #else static void deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context); #endif static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context); static void deparseOpExpr(OpExpr *node, deparse_expr_cxt *context); static void deparseOperatorName(StringInfo buf, Form_pg_operator opform); static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context); static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt *context); static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context); static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context); static void deparseNullTest(NullTest *node, deparse_expr_cxt *context); static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context); static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); const char * tds_quote_identifier(const char *ident); const char * tds_quote_identifier(const char *ident) { const char *ptr; char *result; char *optr; result = (char *) palloc(strlen(ident) + 2 + 1); optr = result; *optr++ = '['; for (ptr = ident; *ptr; ptr++) { char ch = *ptr; *optr++ = ch; } *optr++ = ']'; *optr = '\0'; return result; } /* * Examine each qual clause in input_conds, and classify them into two groups, * which are returned as two lists: * - remote_conds contains expressions that can be evaluated remotely * - local_conds contains expressions that can't be evaluated remotely */ void classifyConditions(PlannerInfo *root, RelOptInfo *baserel, List *input_conds, List **remote_conds, List **local_conds) { ListCell *lc; *remote_conds = NIL; *local_conds = NIL; foreach(lc, input_conds) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); if (is_foreign_expr(root, baserel, ri->clause)) *remote_conds = lappend(*remote_conds, ri); else *local_conds = lappend(*local_conds, ri); } } /* * is_shippable * Is this object (function/operator/type) shippable to foreign server? */ bool is_shippable(Oid objectId, Oid classId, TdsFdwRelationInfo *fpinfo) { bool is_shippable = true; if (classId == OperatorRelationId) { HeapTuple tuple; Form_pg_operator form; /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", objectId); form = (Form_pg_operator) GETSTRUCT(tuple); /* don't ship operators that are not in pg_catalog */ if (form->oprnamespace != PG_CATALOG_NAMESPACE) { is_shippable = false; } ReleaseSysCache(tuple); } return is_shippable; } /* * Returns true if given expr is safe to evaluate on the foreign server. */ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { foreign_glob_cxt glob_cxt; foreign_loc_cxt loc_cxt; /* * Check that the expression consists of nodes that are safe to execute * remotely. */ glob_cxt.root = root; glob_cxt.foreignrel = baserel; loc_cxt.collation = InvalidOid; loc_cxt.state = FDW_COLLATE_NONE; if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt)) return false; /* * If the expression has a valid collation that does not arise from a * foreign var, the expression can not be sent over. */ if (loc_cxt.state == FDW_COLLATE_UNSAFE) return false; /* * An expression which includes any mutable functions can't be sent over * because its result is not stable. For example, sending now() remote * side could cause confusion from clock offsets. Future versions might * be able to make this choice with more granularity. (We check this last * because it requires a lot of expensive catalog lookups.) */ if (contain_mutable_functions((Node *) expr)) return false; /* OK to evaluate on the remote server */ return true; } /* * Check if expression is safe to execute remotely, and return true if so. * * In addition, *outer_cxt is updated with collation information. * * We must check that the expression contains only node types we can deparse, * that all types/functions/operators are safe to send (they are "shippable"), * and that all collations used in the expression derive from Vars of the * foreign table. Because of the latter, the logic is pretty close to * assign_collations_walker() in parse_collate.c, though we can assume here * that the given expression is valid. Note function mutability is not * currently considered here. */ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt) { bool check_type = true; TdsFdwRelationInfo *fpinfo; foreign_loc_cxt inner_cxt; Oid collation; FDWCollateState state; ereport(DEBUG2, (errmsg("tds_fdw: checking if an expression is safe to execute remotely") )); /* Need do nothing for empty subexpressions */ if (node == NULL) return true; /* May need server info from baserel's fdw_private struct */ fpinfo = (TdsFdwRelationInfo *) (glob_cxt->foreignrel->fdw_private); /* Set up inner_cxt for possible recursion to child nodes */ inner_cxt.collation = InvalidOid; inner_cxt.state = FDW_COLLATE_NONE; switch (nodeTag(node)) { /* deleted cases from postgres_fdw for T_ArrayRef/T_SubscriptingRef, T_FuncExpr, T_ScalarArrayOpExpr, T_ArrayExpr, which should never be pushed down to Sybase / MS SQL Server from PostgreSQL */ case T_Var: { Var *var = (Var *) node; ereport(DEBUG3, (errmsg("tds_fdw: it is a var expression") )); /* * If the Var is from the foreign table, we consider its * collation (if any) safe to use. If it is from another * table, we treat its collation the same way as we would a * Param's collation, ie it's not safe for it to have a * non-default collation. */ if (var->varno == glob_cxt->foreignrel->relid && var->varlevelsup == 0) { /* Var belongs to foreign table */ /* * System columns other than ctid should not be sent to * the remote, since we don't make any effort to ensure * that local and remote values match (tableoid, in * particular, almost certainly doesn't match). */ if (var->varattno < 0 && var->varattno != SelfItemPointerAttributeNumber) return false; /* Else check the collation */ collation = var->varcollid; state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; } else { /* Var belongs to some other table */ return false; } } break; case T_Const: { Const *c = (Const *) node; Oid typoutput; bool typIsVarlena; ereport(DEBUG3, (errmsg("tds_fdw: it is a constant expression") )); getTypeOutputInfo(c->consttype, &typoutput, &typIsVarlena); switch (c->consttype) { case BOOLOID: ereport(DEBUG3, (errmsg("tds_fdw: the constant is a boolean value, which is unsupported") )); return false; default: ereport(DEBUG3, (errmsg("tds_fdw: the constant seems to be a supported type") )); } /* * If the constant has nondefault collation, either it's of a * non-builtin type, or it reflects folding of a CollateExpr. * It's unsafe to send to the remote unless it's used in a * non-collation-sensitive context. */ collation = c->constcollid; if (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) state = FDW_COLLATE_NONE; else state = FDW_COLLATE_UNSAFE; } break; case T_Param: { Param *p = (Param *) node; ereport(DEBUG3, (errmsg("tds_fdw: it is a param expression") )); /* * Collation rule is same as for Consts and non-foreign Vars. */ collation = p->paramcollid; if (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) state = FDW_COLLATE_NONE; else state = FDW_COLLATE_UNSAFE; } break; case T_OpExpr: case T_DistinctExpr: /* struct-equivalent to OpExpr */ { OpExpr *oe = (OpExpr *) node; ereport(DEBUG3, (errmsg("tds_fdw: it is an op or distinct expression") )); /* * Similarly, only shippable operators can be sent to remote. * (If the operator is shippable, we assume its underlying * function is too.) */ if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt)) return false; /* * If operator's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (oe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) return false; /* Result-collation handling is same as for functions */ collation = oe->opcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else if (collation == DEFAULT_COLLATION_OID) state = FDW_COLLATE_NONE; else state = FDW_COLLATE_UNSAFE; } break; case T_RelabelType: { RelabelType *r = (RelabelType *) node; ereport(DEBUG3, (errmsg("tds_fdw: it is a relabel type expression") )); /* * Recurse to input subexpression. */ if (!foreign_expr_walker((Node *) r->arg, glob_cxt, &inner_cxt)) return false; /* * RelabelType must not introduce a collation not derived from * an input foreign Var (same logic as for a real function). */ collation = r->resultcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else if (collation == DEFAULT_COLLATION_OID) state = FDW_COLLATE_NONE; else state = FDW_COLLATE_UNSAFE; } break; case T_BoolExpr: { BoolExpr *b = (BoolExpr *) node; ereport(DEBUG3, (errmsg("tds_fdw: it is a boolean expression") )); /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) b->args, glob_cxt, &inner_cxt)) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_NullTest: { NullTest *nt = (NullTest *) node; ereport(DEBUG3, (errmsg("tds_fdw: it is a null test expression") )); /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) nt->arg, glob_cxt, &inner_cxt)) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_List: { List *l = (List *) node; ListCell *lc; ereport(DEBUG3, (errmsg("tds_fdw: it is a list expression") )); /* * Recurse to component subexpressions. */ foreach(lc, l) { if (!foreign_expr_walker((Node *) lfirst(lc), glob_cxt, &inner_cxt)) return false; } /* * When processing a list, collation state just bubbles up * from the list elements. */ collation = inner_cxt.collation; state = inner_cxt.state; /* Don't apply exprType() to the list. */ check_type = false; } break; default: ereport(DEBUG3, (errmsg("tds_fdw: it is an unsupported expression") )); /* * If it's anything else, assume it's unsafe. This list can be * expanded later, but don't forget to add deparse support below. */ return false; } /* * If result type of given expression is not shippable, it can't be sent * to remote because it might have incompatible semantics on remote side. */ if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo)) return false; /* * Now, merge my collation information into my parent's state. */ if (state > outer_cxt->state) { /* Override previous parent state */ outer_cxt->collation = collation; outer_cxt->state = state; } else if (state == outer_cxt->state) { /* Merge, or detect error if there's a collation conflict */ switch (state) { case FDW_COLLATE_NONE: /* Nothing + nothing is still nothing */ break; case FDW_COLLATE_SAFE: if (collation != outer_cxt->collation) { /* * Non-default collation always beats default. */ if (outer_cxt->collation == DEFAULT_COLLATION_OID) { /* Override previous parent state */ outer_cxt->collation = collation; } else if (collation != DEFAULT_COLLATION_OID) { /* * Conflict; show state as indeterminate. We don't * want to "return false" right away, since parent * node might not care about collation. */ outer_cxt->state = FDW_COLLATE_UNSAFE; } } break; case FDW_COLLATE_UNSAFE: /* We're still conflicted ... */ break; } } /* It looks OK */ return true; } #if (PG_VERSION_NUM >= 150000) bool is_builtin(Oid objectId) { return (objectId < FirstUnpinnedObjectId); } #elif (PG_VERSION_NUM >= 90600) bool is_builtin(Oid objectId) { return (objectId < FirstBootstrapObjectId); } #endif /* * Convert type OID + typmod info into a type name we can ship to the remote * server. Someplace else had better have verified that this type name is * expected to be known on the remote end. * * This is almost just format_type_with_typemod(), except that if left to its * own devices, that function will make schema-qualification decisions based * on the local search_path, which is wrong. We must schema-qualify all * type names that are not in pg_catalog. We assume here that built-in types * are all in pg_catalog and need not be qualified; otherwise, qualify. */ static char * deparse_type_name(Oid type_oid, int32 typemod) { #if (PG_VERSION_NUM >= 90600) if (is_builtin(type_oid)) return format_type_with_typemod(type_oid, typemod); else #if (PG_VERSION_NUM >= 110000) return format_type_extended(type_oid, typemod, FORMAT_TYPE_FORCE_QUALIFY); #else return format_type_with_typemod_qualified(type_oid, typemod); #endif #else return format_type_with_typemod(type_oid, typemod); #endif } static char* postgresql_type_to_tds_type(const char* postgresql_type) { char* tds_type; if (strcmp(postgresql_type, "timestamp") == 0 || strcmp(postgresql_type, "timestamp with time zone") == 0 || strcmp(postgresql_type, "timestamp without time zone") == 0) { const char* tds_type_local = "datetime2"; size_t len = strlen(tds_type_local); tds_type = palloc(len); strncpy(tds_type, tds_type_local, len); } /* if no mapping defined, just copy postgresql type */ else { size_t len = strlen(postgresql_type); tds_type = palloc(len); strncpy(tds_type, postgresql_type, len); } return tds_type; } /* * Construct a simple SELECT statement that retrieves desired columns * of the specified foreign table, and append it to "buf". The output * contains just "SELECT ... FROM tablename". * * We also create an integer List of the columns being retrieved, which is * returned to *retrieved_attrs. */ void deparseSelectSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used, List **retrieved_attrs, TdsFdwOptionSet* option_set) { RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); Relation rel; /* * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ #if PG_VERSION_NUM < 120000 rel = heap_open(rte->relid, NoLock); #else rel = table_open(rte->relid, NoLock); #endif /* * Construct SELECT list */ appendStringInfoString(buf, "SELECT "); deparseTargetList(buf, root, baserel->relid, rel, attrs_used, retrieved_attrs, option_set); /* * Construct FROM clause */ appendStringInfoString(buf, " FROM "); deparseRelation(buf, rel); #if PG_VERSION_NUM < 120000 heap_close(rel, NoLock); #else table_close(rel, NoLock); #endif } /* * Emit a target list that retrieves the columns specified in attrs_used. * This is used for both SELECT and RETURNING targetlists. * * The tlist text is appended to buf, and we also create an integer List * of the columns being retrieved, which is returned to *retrieved_attrs. */ static void deparseTargetList(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, Bitmapset *attrs_used, List **retrieved_attrs, TdsFdwOptionSet* option_set) { TupleDesc tupdesc = RelationGetDescr(rel); bool have_wholerow; bool first; int i; *retrieved_attrs = NIL; /* If we are not matching remote and local column names, then we can't push down attributes */ if (!option_set->match_column_names) { appendStringInfoString(buf, "* "); return; } /* If there's a whole-row reference, we'll need all the columns. */ have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); first = true; for (i = 1; i <= tupdesc->natts; i++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); /* Ignore dropped attributes. */ if (attr->attisdropped) continue; if (have_wholerow || bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used)) { if (!first) appendStringInfoString(buf, ", "); first = false; deparseColumnRef(buf, rtindex, i, root); *retrieved_attrs = lappend_int(*retrieved_attrs, i); } } /* * Add ctid if needed. We currently don't support retrieving any other * system columns. */ if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, attrs_used)) { if (!first) appendStringInfoString(buf, ", "); first = false; appendStringInfoString(buf, "ctid"); *retrieved_attrs = lappend_int(*retrieved_attrs, SelfItemPointerAttributeNumber); } /* Don't generate bad syntax if no undropped columns */ if (first) appendStringInfoString(buf, "NULL"); } /* * Deparse WHERE clauses in given list of RestrictInfos and append them to buf. * * baserel is the foreign table we're planning for. * * If no WHERE clause already exists in the buffer, is_first should be true. * * If params is not NULL, it receives a list of Params and other-relation Vars * used in the clauses; these values must be transmitted to the remote server * as parameter values. * * If params is NULL, we're generating the query for EXPLAIN purposes, * so Params and other-relation Vars should be replaced by dummy values. */ void appendWhereClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *exprs, bool is_first, List **params) { deparse_expr_cxt context; /*int nestlevel;*/ ListCell *lc; if (params) *params = NIL; /* initialize result list to empty */ /* Set up context struct for recursion */ context.root = root; context.foreignrel = baserel; context.buf = buf; context.params_list = params; /* Make sure any constants in the exprs are printed portably */ /*nestlevel = set_transmission_modes();*/ foreach(lc, exprs) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); /* Connect expressions with "AND" and parenthesize each condition. */ if (is_first) appendStringInfoString(buf, " WHERE "); else appendStringInfoString(buf, " AND "); appendStringInfoChar(buf, '('); deparseExpr(ri->clause, &context); appendStringInfoChar(buf, ')'); is_first = false; } /*reset_transmission_modes(nestlevel);*/ } /* * deparse remote INSERT statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, bool doNothing, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set) { AttrNumber pindex; bool first; ListCell *lc; appendStringInfoString(buf, "INSERT INTO "); deparseRelation(buf, rel); if (targetAttrs) { appendStringInfoChar(buf, '('); first = true; foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); if (!first) appendStringInfoString(buf, ", "); first = false; deparseColumnRef(buf, rtindex, attnum, root); } appendStringInfoString(buf, ") VALUES ("); pindex = 1; first = true; foreach(lc, targetAttrs) { if (!first) appendStringInfoString(buf, ", "); first = false; appendStringInfo(buf, "$%d", pindex); pindex++; } appendStringInfoChar(buf, ')'); } else appendStringInfoString(buf, " DEFAULT VALUES"); if (doNothing) appendStringInfoString(buf, " ON CONFLICT DO NOTHING"); deparseReturningList(buf, root, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_insert_after_row, returningList, retrieved_attrs, option_set); } /* * deparse remote UPDATE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set) { AttrNumber pindex; bool first; ListCell *lc; appendStringInfoString(buf, "UPDATE "); deparseRelation(buf, rel); appendStringInfoString(buf, " SET "); pindex = 2; /* ctid is always the first param */ first = true; foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); if (!first) appendStringInfoString(buf, ", "); first = false; deparseColumnRef(buf, rtindex, attnum, root); appendStringInfo(buf, " = $%d", pindex); pindex++; } appendStringInfoString(buf, " WHERE ctid = $1"); deparseReturningList(buf, root, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_update_after_row, returningList, retrieved_attrs, option_set); } /* * deparse remote DELETE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set) { appendStringInfoString(buf, "DELETE FROM "); deparseRelation(buf, rel); appendStringInfoString(buf, " WHERE ctid = $1"); deparseReturningList(buf, root, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_delete_after_row, returningList, retrieved_attrs, option_set); } /* * Add a RETURNING clause, if needed, to an INSERT/UPDATE/DELETE. */ static void deparseReturningList(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, bool trig_after_row, List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set) { Bitmapset *attrs_used = NULL; if (trig_after_row) { /* whole-row reference acquires all non-system columns */ attrs_used = bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); } if (returningList != NIL) { /* * We need the attrs, non-system and system, mentioned in the local * query's RETURNING list. */ pull_varattnos((Node *) returningList, rtindex, &attrs_used); } if (attrs_used != NULL) { appendStringInfoString(buf, " RETURNING "); deparseTargetList(buf, root, rtindex, rel, attrs_used, retrieved_attrs, option_set); } else *retrieved_attrs = NIL; } /* * Construct SELECT statement to acquire size in blocks of given relation. * * Note: we use local definition of block size, not remote definition. * This is perhaps debatable. * * Note: pg_relation_size() exists in 8.1 and later. */ void deparseAnalyzeSizeSql(StringInfo buf, Relation rel) { StringInfoData relname; /* We'll need the remote relation name as a literal. */ initStringInfo(&relname); deparseRelation(&relname, rel); appendStringInfoString(buf, "SELECT pg_catalog.pg_relation_size("); deparseStringLiteral(buf, relname.data); appendStringInfo(buf, "::pg_catalog.regclass) / %d", BLCKSZ); } /* * Construct SELECT statement to acquire sample rows of given relation. * * SELECT command is appended to buf, and list of columns retrieved * is returned to *retrieved_attrs. */ void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs) { Oid relid = RelationGetRelid(rel); TupleDesc tupdesc = RelationGetDescr(rel); int i; char *colname; List *options; ListCell *lc; bool first = true; *retrieved_attrs = NIL; appendStringInfoString(buf, "SELECT "); for (i = 0; i < tupdesc->natts; i++) { /* Ignore dropped columns. */ if (TupleDescAttr(tupdesc, i)->attisdropped) continue; if (!first) appendStringInfoString(buf, ", "); first = false; /* Use attribute name or column_name option. */ colname = NameStr(TupleDescAttr(tupdesc, i)->attname); options = GetForeignColumnOptions(relid, i + 1); foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "column_name") == 0) { colname = defGetString(def); break; } } appendStringInfoString(buf, tds_quote_identifier(colname)); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); } /* Don't generate bad syntax for zero-column relation. */ if (first) appendStringInfoString(buf, "NULL"); /* * Construct FROM clause */ appendStringInfoString(buf, " FROM "); deparseRelation(buf, rel); } /* * Construct name to use for given column, and emit it into buf. * If it has a column_name FDW option, use that instead of attribute name. */ static void deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root) { RangeTblEntry *rte; char *colname = NULL; List *options; ListCell *lc; /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ Assert(!IS_SPECIAL_VARNO(varno)); /* Get RangeTblEntry from array in PlannerInfo. */ rte = planner_rt_fetch(varno, root); /* * If it's a column of a foreign table, and it has the column_name FDW * option, use that value. */ options = GetForeignColumnOptions(rte->relid, varattno); foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "column_name") == 0) { colname = defGetString(def); break; } } /* * If it's a column of a regular table or it doesn't have column_name FDW * option, use attribute name. */ if (colname == NULL) colname = get_relid_attribute_name(rte->relid, varattno); appendStringInfoString(buf, tds_quote_identifier(colname)); } /* * Append remote name of specified foreign table to buf. * Use value of table_name FDW option (if any) instead of relation's name. * Similarly, schema_name FDW option overrides schema name. */ static void deparseRelation(StringInfo buf, Relation rel) { ForeignTable *table; const char *nspname = NULL; const char *relname = NULL; ListCell *lc; /* obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); /* * Use value of FDW options if any, instead of the name of object itself. */ foreach(lc, table->options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "schema_name") == 0) nspname = defGetString(def); else if (strcmp(def->defname, "table_name") == 0) relname = defGetString(def); else if (strcmp(def->defname, "table") == 0) relname = defGetString(def); } /* * Note: we could skip printing the schema name if it's pg_catalog, but * that doesn't seem worth the trouble. */ #if PG_VERSION_NUM >= 120000 /* nspname is PostgreSQL schema, and should not be appended */ /* if (nspname == NULL) nspname = get_namespace_name(RelationGetNamespace(rel)); */ if (relname == NULL) relname = RelationGetRelationName(rel); #endif if (nspname == NULL) appendStringInfo(buf, "%s", relname); else appendStringInfo(buf, "%s.%s", tds_quote_identifier(nspname), tds_quote_identifier(relname)); } /* * Append a SQL string literal representing "val" to buf. */ void deparseStringLiteral(StringInfo buf, const char *val) { const char *valptr; /* * Rather than making assumptions about the remote server's value of * standard_conforming_strings, always use E'foo' syntax if there are any * backslashes. This will fail on remote servers before 8.1, but those * are long out of support. */ if (strchr(val, '\\') != NULL) appendStringInfoChar(buf, ESCAPE_STRING_SYNTAX); appendStringInfoChar(buf, '\''); for (valptr = val; *valptr; valptr++) { char ch = *valptr; if (SQL_STR_DOUBLE(ch, true)) appendStringInfoChar(buf, ch); appendStringInfoChar(buf, ch); } appendStringInfoChar(buf, '\''); } /* * Deparse given expression into context->buf. * * This function must support all the same node types that foreign_expr_walker * accepts. * * Note: unlike ruleutils.c, we just use a simple hard-wired parenthesization * scheme: anything more complex than a Var, Const, function call or cast * should be self-parenthesized. */ static void deparseExpr(Expr *node, deparse_expr_cxt *context) { ereport(DEBUG2, (errmsg("tds_fdw: deparsing an expression") )); if (node == NULL) return; switch (nodeTag(node)) { case T_Var: deparseVar((Var *) node, context); break; case T_Const: deparseConst((Const *) node, context); break; case T_Param: deparseParam((Param *) node, context); break; #if (PG_VERSION_NUM < 120000) case T_ArrayRef: deparseArrayRef((ArrayRef *) node, context); break; #else case T_SubscriptingRef: deparseSubscriptingRef((SubscriptingRef *) node, context); break; #endif case T_FuncExpr: deparseFuncExpr((FuncExpr *) node, context); break; case T_OpExpr: deparseOpExpr((OpExpr *) node, context); break; case T_DistinctExpr: deparseDistinctExpr((DistinctExpr *) node, context); break; case T_ScalarArrayOpExpr: deparseScalarArrayOpExpr((ScalarArrayOpExpr *) node, context); break; case T_RelabelType: deparseRelabelType((RelabelType *) node, context); break; case T_BoolExpr: deparseBoolExpr((BoolExpr *) node, context); break; case T_NullTest: deparseNullTest((NullTest *) node, context); break; case T_ArrayExpr: deparseArrayExpr((ArrayExpr *) node, context); break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int) nodeTag(node)); break; } } /* * Deparse given Var node into context->buf. * * If the Var belongs to the foreign relation, just print its remote name. * Otherwise, it's effectively a Param (and will in fact be a Param at * run time). Handle it the same way we handle plain Params --- see * deparseParam for comments. */ static void deparseVar(Var *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a var") )); if (node->varno == context->foreignrel->relid && node->varlevelsup == 0) { /* Var belongs to foreign table */ deparseColumnRef(buf, node->varno, node->varattno, context->root); } else { /* Treat like a Param */ if (context->params_list) { int pindex = 0; ListCell *lc; /* find its index in params_list */ foreach(lc, *context->params_list) { pindex++; if (equal(node, (Node *) lfirst(lc))) break; } if (lc == NULL) { /* not in list, so add it */ pindex++; *context->params_list = lappend(*context->params_list, node); } printRemoteParam(pindex, node->vartype, node->vartypmod, context); } else { printRemotePlaceholder(node->vartype, node->vartypmod, context); } } } /* * Deparse given constant value into context->buf. * * This function has to be kept in sync with ruleutils.c's get_const_expr. */ static void deparseConst(Const *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; Oid typoutput; bool typIsVarlena; char *extval; bool isfloat = false; bool needlabel; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a constant") )); if (node->constisnull) { appendStringInfo(buf, "CAST(NULL AS %s)", postgresql_type_to_tds_type(deparse_type_name(node->consttype, node->consttypmod))); return; } getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena); extval = OidOutputFunctionCall(typoutput, node->constvalue); /* * Append "CAST(" typename unless the constant will be implicitly typed as the * right type when it is read in. * * XXX this code has to be kept in sync with the behavior of the parser, * especially make_const. */ switch (node->consttype) { case BOOLOID: case INT4OID: case UNKNOWNOID: needlabel = false; break; case NUMERICOID: needlabel = !isfloat || (node->consttypmod >= 0); break; default: needlabel = false; break; } if (needlabel) appendStringInfo(buf, " CAST("); switch (node->consttype) { case INT2OID: case INT4OID: case INT8OID: case OIDOID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: { /* * No need to quote unless it's a special value such as 'NaN'. * See comments in get_const_expr(). */ if (strspn(extval, "0123456789+-eE.") == strlen(extval)) { if (extval[0] == '+' || extval[0] == '-') appendStringInfo(buf, "(%s)", extval); else appendStringInfoString(buf, extval); if (strcspn(extval, "eE.") != strlen(extval)) isfloat = true; /* it looks like a float */ } else appendStringInfo(buf, "'%s'", extval); } break; case BITOID: case VARBITOID: appendStringInfo(buf, "B'%s'", extval); break; case BOOLOID: if (strcmp(extval, "t") == 0) appendStringInfoString(buf, "true"); else appendStringInfoString(buf, "false"); break; default: deparseStringLiteral(buf, extval); break; } if (needlabel) appendStringInfo(buf, " AS %s)", postgresql_type_to_tds_type(deparse_type_name(node->consttype, node->consttypmod))); } /* * Deparse given Param node. * * If we're generating the query "for real", add the Param to * context->params_list if it's not already present, and then use its index * in that list as the remote parameter number. During EXPLAIN, there's * no need to identify a parameter number. */ static void deparseParam(Param *node, deparse_expr_cxt *context) { ereport(DEBUG2, (errmsg("tds_fdw: deparsing a param") )); if (context->params_list) { int pindex = 0; ListCell *lc; /* find its index in params_list */ foreach(lc, *context->params_list) { pindex++; if (equal(node, (Node *) lfirst(lc))) break; } if (lc == NULL) { /* not in list, so add it */ pindex++; *context->params_list = lappend(*context->params_list, node); } printRemoteParam(pindex, node->paramtype, node->paramtypmod, context); } else { printRemotePlaceholder(node->paramtype, node->paramtypmod, context); } } /* * Deparse an array subscript expression. */ static void #if (PG_VERSION_NUM < 120000) deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context) #else deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context) #endif { StringInfo buf = context->buf; ListCell *lowlist_item; ListCell *uplist_item; ereport(DEBUG2, (errmsg("tds_fdw: deparsing an array ref") )); /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* * Deparse referenced array expression first. If that expression includes * a cast, we have to parenthesize to prevent the array subscript from * being taken as typename decoration. We can avoid that in the typical * case of subscripting a Var, but otherwise do it. */ if (IsA(node->refexpr, Var)) deparseExpr(node->refexpr, context); else { appendStringInfoChar(buf, '('); deparseExpr(node->refexpr, context); appendStringInfoChar(buf, ')'); } /* Deparse subscript expressions. */ lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */ foreach(uplist_item, node->refupperindexpr) { appendStringInfoChar(buf, '['); if (lowlist_item) { deparseExpr(lfirst(lowlist_item), context); appendStringInfoChar(buf, ':'); #if PG_VERSION_NUM < 130000 lowlist_item = lnext(lowlist_item); #else lowlist_item = lnext(node->reflowerindexpr, lowlist_item); #endif } deparseExpr(lfirst(uplist_item), context); appendStringInfoChar(buf, ']'); } appendStringInfoChar(buf, ')'); } /* * Deparse a function call. */ static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple proctup; Form_pg_proc procform; const char *proname; bool use_variadic = false; bool first; ListCell *arg; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a function expression") )); /* * If the function call came from an implicit coercion, then just show the * first argument. */ if (node->funcformat == COERCE_IMPLICIT_CAST) { deparseExpr((Expr *) linitial(node->args), context); return; } /* * If the function call came from a cast, then show the first argument * plus an explicit cast operation. */ if (node->funcformat == COERCE_EXPLICIT_CAST) { Oid rettype = node->funcresulttype; int32 coercedTypmod; /* Get the typmod if this is a length-coercion function */ (void) exprIsLengthCoercion((Node *) node, &coercedTypmod); deparseExpr((Expr *) linitial(node->args), context); appendStringInfo(buf, " as %s", deparse_type_name(rettype, coercedTypmod)); return; } /* * Normal function: display as proname(args). */ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(node->funcid)); if (!HeapTupleIsValid(proctup)) elog(ERROR, "cache lookup failed for function %u", node->funcid); procform = (Form_pg_proc) GETSTRUCT(proctup); #if (PG_VERSION_NUM >= 90300) /* Check if need to print VARIADIC (cf. ruleutils.c) */ use_variadic = node->funcvariadic; #endif /* Print schema name only if it's not pg_catalog */ if (procform->pronamespace != PG_CATALOG_NAMESPACE) { const char *schemaname; schemaname = get_namespace_name(procform->pronamespace); appendStringInfo(buf, "%s.", tds_quote_identifier(schemaname)); } /* Deparse the function name ... */ proname = NameStr(procform->proname); appendStringInfo(buf, "%s(", tds_quote_identifier(proname)); /* ... and all the arguments */ first = true; foreach(arg, node->args) { if (!first) appendStringInfoString(buf, ", "); #if PG_VERSION_NUM < 130000 if (use_variadic && lnext(arg) == NULL) appendStringInfoString(buf, "VARIADIC "); #else if (use_variadic && lnext(node->args, arg) == NULL) appendStringInfoString(buf, "VARIADIC "); #endif deparseExpr((Expr *) lfirst(arg), context); first = false; } appendStringInfoChar(buf, ')'); ReleaseSysCache(proctup); } /* * Deparse given operator expression. To avoid problems around * priority of operations, we always parenthesize the arguments. */ static void deparseOpExpr(OpExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple tuple; Form_pg_operator form; char oprkind; ListCell *arg; ereport(DEBUG2, (errmsg("tds_fdw: deparsing an operator expression") )); /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", node->opno); form = (Form_pg_operator) GETSTRUCT(tuple); oprkind = form->oprkind; /* Sanity check. */ Assert((oprkind == 'r' && list_length(node->args) == 1) || (oprkind == 'l' && list_length(node->args) == 1) || (oprkind == 'b' && list_length(node->args) == 2)); /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* Deparse left operand. */ if (oprkind == 'r' || oprkind == 'b') { arg = list_head(node->args); deparseExpr(lfirst(arg), context); appendStringInfoChar(buf, ' '); } /* Deparse operator name. */ deparseOperatorName(buf, form); /* Deparse right operand. */ if (oprkind == 'l' || oprkind == 'b') { arg = list_tail(node->args); appendStringInfoChar(buf, ' '); deparseExpr(lfirst(arg), context); } appendStringInfoChar(buf, ')'); ReleaseSysCache(tuple); } static void deparseTdsOperatorNameFromPgOp(StringInfo buf, char* opname) { if (strncmp(opname, "!~~", NAMEDATALEN) == 0 || strncmp(opname, "!~~*", NAMEDATALEN) == 0) { appendStringInfoString(buf, "NOT LIKE"); } else if (strncmp(opname, "~~", NAMEDATALEN) == 0 || strncmp(opname, "~~*", NAMEDATALEN) == 0) { appendStringInfoString(buf, "LIKE"); } else { appendStringInfoString(buf, opname); } } /* * Print the name of an operator. */ static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { char *opname; /* opname is not a SQL identifier, so we should not quote it. */ opname = NameStr(opform->oprname); /* Just print operator name. */ deparseTdsOperatorNameFromPgOp(buf, opname); } /* * Deparse IS DISTINCT FROM. */ static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a distinct expression") )); Assert(list_length(node->args) == 2); appendStringInfoChar(buf, '('); deparseExpr(linitial(node->args), context); appendStringInfoString(buf, " IS DISTINCT FROM "); deparseExpr(lsecond(node->args), context); appendStringInfoChar(buf, ')'); } /* * Deparse given ScalarArrayOpExpr expression. To avoid problems * around priority of operations, we always parenthesize the arguments. */ static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple tuple; Form_pg_operator form; Expr *arg1; Expr *arg2; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a scalar operator expression") )); /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", node->opno); form = (Form_pg_operator) GETSTRUCT(tuple); /* Sanity check. */ Assert(list_length(node->args) == 2); /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* Deparse left operand. */ arg1 = linitial(node->args); deparseExpr(arg1, context); appendStringInfoChar(buf, ' '); /* Deparse operator name plus decoration. */ deparseOperatorName(buf, form); appendStringInfo(buf, " %s (", node->useOr ? "ANY" : "ALL"); /* Deparse right operand. */ arg2 = lsecond(node->args); deparseExpr(arg2, context); appendStringInfoChar(buf, ')'); /* Always parenthesize the expression. */ appendStringInfoChar(buf, ')'); ReleaseSysCache(tuple); } /* * Deparse a RelabelType (binary-compatible cast) node. */ static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context) { deparseExpr(node->arg, context); // Pretty sure this only causes issues. //if (node->relabelformat != COERCE_IMPLICIT_CAST) // appendStringInfo(context->buf, " as %s", // deparse_type_name(node->resulttype, // node->resulttypmod)); } /* * Deparse a BoolExpr node. */ static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; const char *op = NULL; /* keep compiler quiet */ bool first; ListCell *lc; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a boolean expression") )); switch (node->boolop) { case AND_EXPR: op = "AND"; break; case OR_EXPR: op = "OR"; break; case NOT_EXPR: appendStringInfoString(buf, "(NOT "); deparseExpr(linitial(node->args), context); appendStringInfoChar(buf, ')'); return; } appendStringInfoChar(buf, '('); first = true; foreach(lc, node->args) { if (!first) appendStringInfo(buf, " %s ", op); deparseExpr((Expr *) lfirst(lc), context); first = false; } appendStringInfoChar(buf, ')'); } /* * Deparse IS [NOT] NULL expression. */ static void deparseNullTest(NullTest *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; ereport(DEBUG2, (errmsg("tds_fdw: deparsing a NULL test expression") )); appendStringInfoChar(buf, '('); deparseExpr(node->arg, context); if (node->nulltesttype == IS_NULL) appendStringInfoString(buf, " IS NULL)"); else appendStringInfoString(buf, " IS NOT NULL)"); } /* * Deparse ARRAY[...] construct. */ static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; bool first = true; ListCell *lc; ereport(DEBUG2, (errmsg("tds_fdw: deparsing an array expression") )); appendStringInfoString(buf, "ARRAY["); foreach(lc, node->elements) { if (!first) appendStringInfoString(buf, ", "); deparseExpr(lfirst(lc), context); first = false; } appendStringInfoChar(buf, ']'); /* If the array is empty, we need an explicit cast to the array type. */ if (node->elements == NIL) appendStringInfo(buf, " as %s", deparse_type_name(node->array_typeid, -1)); } /* * Print the representation of a parameter to be sent to the remote side. * * Note: we always label the Param's type explicitly rather than relying on * transmitting a numeric type OID in PQexecParams(). This allows us to * avoid assuming that types have the same OIDs on the remote side as they * do locally --- they need only have the same names. */ static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context) { StringInfo buf = context->buf; char *ptypename = deparse_type_name(paramtype, paramtypmod); appendStringInfo(buf, "CAST($%d as %s)", paramindex, ptypename); } /* * Print the representation of a placeholder for a parameter that will be * sent to the remote side at execution time. * * This is used when we're just trying to EXPLAIN the remote query. * We don't have the actual value of the runtime parameter yet, and we don't * want the remote planner to generate a plan that depends on such a value * anyway. Thus, we can't do something simple like "CAST($1 as paramtype)". * Instead, we emit "(CAST((SELECT CAST(null as paramtype)) as paramtype))". * In all extant versions of Postgres, the planner will see that as an unknown * constant value, which is what we want. This might need adjustment if we * ever make the planner flatten scalar subqueries. Note: the reason for the * apparently useless outer cast is to ensure that the representation as a * whole will be parsed as an a_expr and not a select_with_parens; the latter * would do the wrong thing in the context "x = ANY(...)". */ static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context) { StringInfo buf = context->buf; char *ptypename = deparse_type_name(paramtype, paramtypmod); appendStringInfo(buf, "(CAST((SELECT CAST(null as %s)) as %s))", ptypename, ptypename); } /* * Deparse ORDER BY clause according to the given pathkeys for given base * relation. From given pathkeys expressions belonging entirely to the given * base relation are obtained and deparsed. */ void appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *pathkeys) { ListCell *lcell; deparse_expr_cxt context; /*int nestlevel;*/ char *delim = " "; /* Set up context struct for recursion */ context.root = root; context.foreignrel = baserel; context.buf = buf; context.params_list = NULL; /* Make sure any constants in the exprs are printed portably */ /*nestlevel = set_transmission_modes();*/ appendStringInfo(buf, " ORDER BY"); foreach(lcell, pathkeys) { PathKey *pathkey = lfirst(lcell); Expr *em_expr; em_expr = find_em_expr_for_rel(pathkey->pk_eclass, baserel); Assert(em_expr != NULL); appendStringInfoString(buf, delim); deparseExpr(em_expr, &context); if (pathkey->pk_strategy == BTLessStrategyNumber) appendStringInfoString(buf, " ASC"); else appendStringInfoString(buf, " DESC"); /* NULLS FIRST is not supported by SQL Server if (pathkey->pk_nulls_first) appendStringInfoString(buf, " NULLS FIRST"); */ delim = ", "; } /*reset_transmission_modes(nestlevel);*/ } tds_fdw-2.0.3/src/options.c000066400000000000000000000561441432462773100156170ustar00rootroot00000000000000 /* postgres headers */ #include "postgres.h" #include "funcapi.h" #include "access/reloptions.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" #include "catalog/pg_user_mapping.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/explain.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" #include "miscadmin.h" #include "mb/pg_wchar.h" #include "optimizer/cost.h" #include "storage/fd.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/rel.h" #include "utils/memutils.h" #if (PG_VERSION_NUM >= 90200) #include "optimizer/pathnode.h" #include "optimizer/restrictinfo.h" #include "optimizer/planmain.h" #endif /* DB-Library headers (e.g. FreeTDS */ #include #include #include "tds_fdw.h" #include "options.h" void tdsGetForeignServerOptions(List *options_list, TdsFdwOptionSet *option_set); void tdsGetForeignServerTableOptions(List *options_list, TdsFdwOptionSet *option_set); void tdsGetForeignTableOptions(List *options_list, TdsFdwOptionSet *option_set); void tdsGetUserMappingOptions(List *options_list, TdsFdwOptionSet *option_set); void tdsValidateForeignTableOptionSet(TdsFdwOptionSet *option_set); void tdsSetDefaultOptions(TdsFdwOptionSet *option_set); bool tdsIsValidOption(const char *option, Oid context); void tdsOptionSetInit(TdsFdwOptionSet* option_set); /* these are valid options */ static struct TdsFdwOption valid_options[] = { { "servername", ForeignServerRelationId }, { "language", ForeignServerRelationId }, { "character_set", ForeignServerRelationId }, { "port", ForeignServerRelationId }, { "database", ForeignServerRelationId }, { "dbuse", ForeignServerRelationId }, { "tds_version", ForeignServerRelationId }, { "msg_handler", ForeignServerRelationId }, { "row_estimate_method", ForeignServerRelationId }, { "use_remote_estimate", ForeignServerRelationId }, { "fdw_startup_cost", ForeignServerRelationId }, { "fdw_tuple_cost", ForeignServerRelationId }, { "username", UserMappingRelationId }, { "password", UserMappingRelationId }, { "query", ForeignTableRelationId }, { "table", ForeignTableRelationId }, { "schema_name", ForeignTableRelationId }, { "table_name", ForeignTableRelationId }, { "row_estimate_method", ForeignTableRelationId }, { "match_column_names", ForeignTableRelationId }, { "use_remote_estimate", ForeignTableRelationId }, { "local_tuple_estimate", ForeignTableRelationId }, { "column_name", AttributeRelationId }, { NULL, InvalidOid } }; /* default IP address */ static const char *DEFAULT_SERVERNAME = "127.0.0.1"; /* default method to use to estimate rows in results */ static const char *DEFAULT_ROW_ESTIMATE_METHOD = "execute"; /* default function used to handle TDS messages */ static const char *DEFAULT_MSG_HANDLER = "blackhole"; /* whether to match on column names by default. if not, we use column order. */ static const int DEFAULT_MATCH_COLUMN_NAMES = 1; /* by default we use remote estimates */ static const int DEFAULT_USE_REMOTE_ESTIMATE = 1; /* by default we use remote estimates */ static const int DEFAULT_FDW_STARTUP_COST = 100; /* by default we use remote estimates */ static const int DEFAULT_FDW_TUPLE_COST = 100; /* conservative default tuple count */ static const int DEFAULT_LOCAL_TUPLE_ESTIMATE = 1000; void tdsValidateOptions(List *options_list, Oid context, TdsFdwOptionSet* option_set) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsValidateOptions") )); #endif tdsOptionSetInit(option_set); if (context == ForeignServerRelationId) { tdsGetForeignServerOptions(options_list, option_set); tdsGetForeignServerTableOptions(options_list, option_set); } else if (context == ForeignTableRelationId) { tdsGetForeignTableOptions(options_list, option_set); tdsValidateForeignTableOptionSet(option_set); } else if (context == UserMappingRelationId) { tdsGetUserMappingOptions(options_list, option_set); } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsValidateOptions") )); #endif } /* get options for FOREIGN SERVER objects using this module */ void tdsGetForeignServerOptionsFromCatalog(Oid foreignserverid, TdsFdwOptionSet* option_set) { ForeignServer *f_server; UserMapping *f_mapping; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignServerOptionsFromCatalog") )); #endif tdsOptionSetInit(option_set); f_server = GetForeignServer(foreignserverid); f_mapping = GetUserMapping(GetUserId(), foreignserverid); tdsGetForeignServerOptions(f_server->options, option_set); tdsGetForeignServerTableOptions(f_server->options, option_set); tdsGetUserMappingOptions(f_mapping->options, option_set); tdsSetDefaultOptions(option_set); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignTableOptionsFromCatalog") )); #endif } /* get options for FOREIGN TABLE and FOREIGN SERVER objects using this module */ void tdsGetForeignTableOptionsFromCatalog(Oid foreigntableid, TdsFdwOptionSet* option_set) { ForeignTable *f_table; ForeignServer *f_server; UserMapping *f_mapping; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignTableOptionsFromCatalog") )); #endif tdsOptionSetInit(option_set); f_table = GetForeignTable(foreigntableid); f_server = GetForeignServer(f_table->serverid); f_mapping = GetUserMapping(GetUserId(), f_table->serverid); tdsGetForeignServerOptions(f_server->options, option_set); tdsGetForeignServerTableOptions(f_server->options, option_set); tdsGetForeignTableOptions(f_table->options, option_set); tdsGetUserMappingOptions(f_mapping->options, option_set); tdsSetDefaultOptions(option_set); tdsValidateOptionSet(option_set); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignTableOptionsFromCatalog") )); #endif } void tdsGetForeignServerOptions(List *options_list, TdsFdwOptionSet *option_set) { ListCell *cell; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignServerOptions") )); #endif foreach (cell, options_list) { DefElem *def = (DefElem *) lfirst(cell); #ifdef DEBUG ereport(NOTICE, (errmsg("Working on option %s", def->defname) )); #endif if (!tdsIsValidOption(def->defname, ForeignServerRelationId)) { TdsFdwOption *opt; StringInfoData buf; initStringInfo(&buf); for (opt = valid_options; opt->optname; opt++) { if (ForeignServerRelationId == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("Invalid option \"%s\"", def->defname), errhint("Valid options in this context are: %s", buf.len ? buf.data : "") )); } if (strcmp(def->defname, "servername") == 0) { if (option_set->servername) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: servername (%s)", defGetString(def)) )); option_set->servername = defGetString(def); } else if (strcmp(def->defname, "language") == 0) { if (option_set->language) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: language (%s)", defGetString(def)) )); option_set->language = defGetString(def); } else if (strcmp(def->defname, "character_set") == 0) { if (option_set->character_set) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: character_set (%s)", defGetString(def)) )); option_set->character_set = defGetString(def); } else if (strcmp(def->defname, "port") == 0) { if (option_set->port) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: port (%s)", defGetString(def)) )); option_set->port = atoi(defGetString(def)); } else if (strcmp(def->defname, "database") == 0) { if (option_set->database) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: database (%s)", defGetString(def)) )); option_set->database = defGetString(def); } else if (strcmp(def->defname, "dbuse") == 0) { if (option_set->dbuse) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: dbuse (%s)", defGetString(def)) )); option_set->dbuse = atoi(defGetString(def)); } else if (strcmp(def->defname, "tds_version") == 0) { int tds_version_test = 0; if (option_set->tds_version) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: database (%s)", defGetString(def)) )); option_set->tds_version = defGetString(def); if (strcmp(option_set->tds_version, "4.2") == 0) { tds_version_test = 1; } else if (strcmp(option_set->tds_version, "5.0") == 0) { tds_version_test = 1; } else if (strcmp(option_set->tds_version, "7.0") == 0) { tds_version_test = 1; } #ifdef DBVERSION_71 else if (strcmp(option_set->tds_version, "7.1") == 0) { tds_version_test = 1; } #endif #ifdef DBVERSION_72 else if (strcmp(option_set->tds_version, "7.2") == 0) { tds_version_test = 1; } #endif #ifdef DBVERSION_73 else if (strcmp(option_set->tds_version, "7.3") == 0) { tds_version_test = 1; } #endif #ifdef DBVERSION_74 else if (strcmp(option_set->tds_version, "7.4") == 0) { tds_version_test = 1; } #endif if (!tds_version_test) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown tds version: %s.", option_set->tds_version) )); } } else if (strcmp(def->defname, "msg_handler") == 0) { int msg_handler_test = 0; if (option_set->msg_handler) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: msg_handler (%s)", defGetString(def)) )); option_set->msg_handler = defGetString(def); if (strcmp(option_set->msg_handler, "notice") == 0) { msg_handler_test = 1; } else if (strcmp(option_set->msg_handler, "blackhole") == 0) { msg_handler_test = 1; } if (!msg_handler_test) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown msg handler: %s.", option_set->msg_handler) )); } } else if (strcmp(def->defname, "fdw_startup_cost") == 0) { if (option_set->fdw_startup_cost) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: fdw_startup_cost (%s)", defGetString(def)) )); option_set->fdw_startup_cost = atoi(defGetString(def)); } else if (strcmp(def->defname, "fdw_tuple_cost") == 0) { if (option_set->fdw_tuple_cost) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: fdw_tuple_cost (%s)", defGetString(def)) )); option_set->fdw_tuple_cost = atoi(defGetString(def)); } } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignServerOptions") )); #endif } void tdsGetForeignServerTableOptions(List *options_list, TdsFdwOptionSet *option_set) { ListCell *cell; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignServerTableOptions") )); #endif foreach (cell, options_list) { DefElem *def = (DefElem *) lfirst(cell); #ifdef DEBUG ereport(NOTICE, (errmsg("Working on option %s", def->defname) )); #endif if (!tdsIsValidOption(def->defname, ForeignServerRelationId)) { TdsFdwOption *opt; StringInfoData buf; initStringInfo(&buf); for (opt = valid_options; opt->optname; opt++) { if (ForeignServerRelationId == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("Invalid option \"%s\"", def->defname), errhint("Valid options in this context are: %s", buf.len ? buf.data : "") )); } if (strcmp(def->defname, "row_estimate_method") == 0) { if (option_set->row_estimate_method) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: row_estimate_method (%s)", defGetString(def)) )); option_set->row_estimate_method = defGetString(def); if ((strcmp(option_set->row_estimate_method, "execute") != 0) && (strcmp(option_set->row_estimate_method, "showplan_all") != 0)) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("row_estimate_method should be set to \"execute\" or \"showplan_all\". Currently set to %s", option_set->row_estimate_method) )); } } else if (strcmp(def->defname, "use_remote_estimate") == 0) { if (option_set->use_remote_estimate) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: use_remote_estimate (%s)", defGetString(def)) )); option_set->use_remote_estimate = atoi(defGetString(def)); } } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignServerTableOptions") )); #endif } void tdsGetForeignTableOptions(List *options_list, TdsFdwOptionSet *option_set) { ListCell *cell; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignTableOptions") )); #endif foreach (cell, options_list) { DefElem *def = (DefElem *) lfirst(cell); #ifdef DEBUG ereport(NOTICE, (errmsg("Working on option %s", def->defname) )); #endif if (!tdsIsValidOption(def->defname, ForeignTableRelationId)) { TdsFdwOption *opt; StringInfoData buf; initStringInfo(&buf); for (opt = valid_options; opt->optname; opt++) { if (ForeignTableRelationId == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("Invalid option \"%s\"", def->defname), errhint("Valid options in this context are: %s", buf.len ? buf.data : "") )); } if (strcmp(def->defname, "query") == 0) { if (option_set->query) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: query (%s)", defGetString(def)) )); option_set->query = defGetString(def); } else if (strcmp(def->defname, "schema_name") == 0) { if (option_set->schema_name) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: schema_name (%s)", defGetString(def)) )); option_set->schema_name = defGetString(def); } else if (strcmp(def->defname, "table") == 0 || strcmp(def->defname, "table_name") == 0) { if (option_set->table_name) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: %s (%s)", def->defname, defGetString(def)) )); option_set->table_name = defGetString(def); } else if (strcmp(def->defname, "row_estimate_method") == 0) { if (option_set->row_estimate_method) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: row_estimate_method (%s)", defGetString(def)) )); option_set->row_estimate_method = defGetString(def); if ((strcmp(option_set->row_estimate_method, "execute") != 0) && (strcmp(option_set->row_estimate_method, "showplan_all") != 0)) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("row_estimate_method should be set to \"execute\" or \"showplan_all\". Currently set to %s", option_set->row_estimate_method) )); } } else if (strcmp(def->defname, "match_column_names") == 0) { option_set->match_column_names = atoi(defGetString(def)); } else if (strcmp(def->defname, "use_remote_estimate") == 0) { option_set->use_remote_estimate = atoi(defGetString(def)); } else if (strcmp(def->defname, "local_tuple_estimate") == 0) { if (option_set->local_tuple_estimate) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: local_tuple_estimate (%s)", defGetString(def)) )); option_set->local_tuple_estimate = atoi(defGetString(def)); } } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignTableOptions") )); #endif } void tdsGetUserMappingOptions(List *options_list, TdsFdwOptionSet *option_set) { ListCell *cell; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetUserMappingOptions") )); #endif foreach (cell, options_list) { DefElem *def = (DefElem *) lfirst(cell); #ifdef DEBUG ereport(NOTICE, (errmsg("Working on option %s", def->defname) )); #endif if (!tdsIsValidOption(def->defname, UserMappingRelationId)) { TdsFdwOption *opt; StringInfoData buf; initStringInfo(&buf); for (opt = valid_options; opt->optname; opt++) { if (UserMappingRelationId == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("Invalid option \"%s\"", def->defname), errhint("Valid options in this context are: %s", buf.len ? buf.data : "") )); } if (strcmp(def->defname, "username") == 0) { if (option_set->username) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: username (%s)", defGetString(def)) )); option_set->username = defGetString(def); } else if (strcmp(def->defname, "password") == 0) { if (option_set->password) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Redundant option: password (%s)", defGetString(def)) )); option_set->password = defGetString(def); } } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetUserMappingOptions") )); #endif } void tdsSetDefaultOptions(TdsFdwOptionSet *option_set) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsSetDefaultOptions") )); #endif if (!option_set->servername) { if ((option_set->servername = palloc((strlen(DEFAULT_SERVERNAME) + 1) * sizeof(char))) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to allocate memory for server name") )); } sprintf(option_set->servername, "%s", DEFAULT_SERVERNAME); #ifdef DEBUG ereport(NOTICE, (errmsg("Set servername to default: %s", option_set->servername) )); #endif } if (!option_set->row_estimate_method) { if ((option_set->row_estimate_method = palloc((strlen(DEFAULT_ROW_ESTIMATE_METHOD) + 1) * sizeof(char))) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to allocate memory for row estimate method") )); } sprintf(option_set->row_estimate_method, "%s", DEFAULT_ROW_ESTIMATE_METHOD); #ifdef DEBUG ereport(NOTICE, (errmsg("Set row_estimate_method to default: %s", option_set->row_estimate_method) )); #endif } if (!option_set->msg_handler) { if ((option_set->msg_handler= palloc((strlen(DEFAULT_MSG_HANDLER) + 1) * sizeof(char))) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to allocate memory for msg handler") )); } sprintf(option_set->msg_handler, "%s", DEFAULT_MSG_HANDLER); #ifdef DEBUG ereport(NOTICE, (errmsg("Set msg_handler to default: %s", option_set->msg_handler) )); #endif } if (!option_set->use_remote_estimate) { option_set->use_remote_estimate = DEFAULT_USE_REMOTE_ESTIMATE; #ifdef DEBUG ereport(NOTICE, (errmsg("Set use_remote_estimate to default: %d", option_set->use_remote_estimate) )); #endif } if (!option_set->local_tuple_estimate) { option_set->local_tuple_estimate = DEFAULT_LOCAL_TUPLE_ESTIMATE; #ifdef DEBUG ereport(NOTICE, (errmsg("Set local_tuple_estimate to default: %d", option_set->local_tuple_estimate) )); #endif } if (!option_set->fdw_startup_cost) { option_set->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST; #ifdef DEBUG ereport(NOTICE, (errmsg("Set fdw_startup_cost to default: %d", option_set->fdw_startup_cost) )); #endif } if (!option_set->fdw_tuple_cost) { option_set->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST; #ifdef DEBUG ereport(NOTICE, (errmsg("Set fdw_tuple_cost to default: %d", option_set->fdw_tuple_cost) )); #endif } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsSetDefaultOptions") )); #endif } void tdsValidateOptionSet(TdsFdwOptionSet *option_set) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsValidateOptionSet") )); #endif tdsValidateForeignTableOptionSet(option_set); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsValidateOptionSet") )); #endif } void tdsValidateForeignTableOptionSet(TdsFdwOptionSet *option_set) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsValidateForeignTableOptionSet") )); #endif /* Check conflicting options */ if (option_set->table_name && option_set->query) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Conflicting options: table and query options can't be used together.") )); } /* Check required options */ if (!option_set->table_name && !option_set->query) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Required options: either a table or a query must be specified") )); } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsValidateForeignTableOptionSet") )); #endif } /* validate options for FOREIGN TABLE and FOREIGN SERVER objects using this module */ bool tdsIsValidOption(const char *option, Oid context) { TdsFdwOption *opt; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsIdValidOption") )); #endif for (opt = valid_options; opt->optname; opt++) { if (context == opt->optcontext && strcmp(opt->optname, option) == 0) return true; } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsIdValidOption") )); #endif return false; } /* initialize the option set */ void tdsOptionSetInit(TdsFdwOptionSet* option_set) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsOptionSetInit") )); #endif option_set->servername = NULL; option_set->language = NULL; option_set->character_set = NULL; option_set->port = 0; option_set->database = NULL; option_set->dbuse = 0; option_set->tds_version = NULL; option_set->msg_handler = NULL; option_set->username = NULL; option_set->password = NULL; option_set->query = NULL; option_set->schema_name = NULL; option_set->table_name = NULL; option_set->row_estimate_method = NULL; option_set->match_column_names = DEFAULT_MATCH_COLUMN_NAMES; option_set->use_remote_estimate = 0; option_set->fdw_startup_cost = 0; option_set->fdw_tuple_cost = 0; option_set->local_tuple_estimate = 0; #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsOptionSetInit") )); #endif } tds_fdw-2.0.3/src/tds_fdw.c000066400000000000000000003212041432462773100155460ustar00rootroot00000000000000/*------------------------------------------------------------------ * * Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) * * Author: Geoff Montee * Name: tds_fdw * File: tds_fdw/src/tds_fdw.c * * Description: * This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, * such as Sybase databases and Microsoft SQL server. * * This foreign data wrapper requires requires a library that uses the DB-Library interface, * such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not * the proprietary implementations of DB-Library. *---------------------------------------------------------------------------- */ #include #include #include #include /* Override PGDLLEXPORT for visibility */ #include "visibility.h" /* postgres headers */ #include "postgres.h" #include "funcapi.h" #include "access/reloptions.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" #include "catalog/pg_user_mapping.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/explain.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" #include "miscadmin.h" #include "mb/pg_wchar.h" #include "optimizer/cost.h" #include "optimizer/paths.h" #include "optimizer/prep.h" #if (PG_VERSION_NUM < 120000) #include "optimizer/var.h" #else #include "optimizer/optimizer.h" #endif #include "storage/fd.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/rel.h" #include "utils/memutils.h" #include "utils/guc.h" #include "utils/timestamp.h" #if (PG_VERSION_NUM >= 90300) #include "access/htup_details.h" #else #include "access/htup.h" #endif #include "optimizer/pathnode.h" #include "optimizer/restrictinfo.h" #include "optimizer/planmain.h" /* DB-Library headers (e.g. FreeTDS */ #include #include /* #define DEBUG */ PG_MODULE_MAGIC; #include "tds_fdw.h" #include "options.h" #include "deparse.h" /* run on module load */ extern PGDLLEXPORT void _PG_init(void); static const bool DEFAULT_SHOW_FINISHED_MEMORY_STATS = false; static bool show_finished_memory_stats = false; static const bool DEFAULT_SHOW_BEFORE_ROW_MEMORY_STATS = false; static bool show_before_row_memory_stats = false; static const bool DEFAULT_SHOW_AFTER_ROW_MEMORY_STATS = false; static bool show_after_row_memory_stats = false; static const double DEFAULT_FDW_SORT_MULTIPLIER=1.2; /* error handling */ static char* last_error_message = NULL; static int tds_err_capture(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); static char *tds_err_msg(int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); /* * Indexes of FDW-private information stored in fdw_private lists. * * We store various information in ForeignScan.fdw_private to pass it from * planner to executor. Currently we store: * * 1) SELECT statement text to be sent to the remote server * 2) Integer list of attribute numbers retrieved by the SELECT * * These items are indexed with the enum FdwScanPrivateIndex, so an item * can be fetched with list_nth(). For example, to get the SELECT statement: * sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); */ enum FdwScanPrivateIndex { /* SQL statement to execute remotely (as a String node) */ FdwScanPrivateSelectSql, /* Integer list of attribute numbers retrieved by the SELECT */ FdwScanPrivateRetrievedAttrs }; PG_FUNCTION_INFO_V1(tds_fdw_handler); PG_FUNCTION_INFO_V1(tds_fdw_validator); PGDLLEXPORT Datum tds_fdw_handler(PG_FUNCTION_ARGS) { FdwRoutine *fdwroutine = makeNode(FdwRoutine); #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tds_fdw_handler") )); #endif #if (PG_VERSION_NUM >= 90200) fdwroutine->GetForeignRelSize = tdsGetForeignRelSize; fdwroutine->GetForeignPaths = tdsGetForeignPaths; fdwroutine->AnalyzeForeignTable = tdsAnalyzeForeignTable; fdwroutine->GetForeignPlan = tdsGetForeignPlan; #else fdwroutine->PlanForeignScan = tdsPlanForeignScan; #endif fdwroutine->ExplainForeignScan = tdsExplainForeignScan; fdwroutine->BeginForeignScan = tdsBeginForeignScan; fdwroutine->IterateForeignScan = tdsIterateForeignScan; fdwroutine->ReScanForeignScan = tdsReScanForeignScan; fdwroutine->EndForeignScan = tdsEndForeignScan; #ifdef IMPORT_API fdwroutine->ImportForeignSchema = tdsImportForeignSchema; #endif /* IMPORT_API */ #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tds_fdw_handler") )); #endif PG_RETURN_POINTER(fdwroutine); } PGDLLEXPORT Datum tds_fdw_validator(PG_FUNCTION_ARGS) { List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); TdsFdwOptionSet option_set; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tds_fdw_validator") )); #endif tdsValidateOptions(options_list, catalog, &option_set); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tds_fdw_validator") )); #endif PG_RETURN_VOID(); } void _PG_init(void) { DefineCustomBoolVariable("tds_fdw.show_finished_memory_stats", "Show finished memory stats", "Set to true to show memory stats after a query finishes", &show_finished_memory_stats, DEFAULT_SHOW_FINISHED_MEMORY_STATS, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("tds_fdw.show_before_row_memory_stats", "Show before row memory stats", "Set to true to show memory stats before fetching each row", &show_before_row_memory_stats, DEFAULT_SHOW_BEFORE_ROW_MEMORY_STATS, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("tds_fdw.show_after_row_memory_stats", "Show after row memory stats", "Set to true to show memory stats after fetching each row", &show_after_row_memory_stats, DEFAULT_SHOW_AFTER_ROW_MEMORY_STATS, PGC_SUSET, 0, NULL, NULL, NULL); } /* * Find an equivalence class member expression, all of whose Vars, come from * the indicated relation. */ Expr * find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel) { ListCell *lc_em; foreach(lc_em, ec->ec_members) { EquivalenceMember *em = lfirst(lc_em); if (bms_equal(em->em_relids, rel->relids)) { /* * If there is more than one equivalence member whose Vars are * taken entirely from this relation, we'll be content to choose * any one of those. */ return em->em_expr; } } /* We didn't find any suitable equivalence class expression */ return NULL; } /* This is used for JOIN pushdowns, so it is only needed on 9.5+ */ #if (PG_VERSION_NUM >= 90500) /* * Detect whether we want to process an EquivalenceClass member. * * This is a callback for use by generate_implied_equalities_for_column. */ static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *ec, EquivalenceMember *em, void *arg) { ec_member_foreign_arg *state = (ec_member_foreign_arg *) arg; Expr *expr = em->em_expr; /* * If we've identified what we're processing in the current scan, we only * want to match that expression. */ if (state->current != NULL) return equal(expr, state->current); /* * Otherwise, ignore anything we've already processed. */ if (list_member(state->already_used, expr)) return false; /* This is the new target to process. */ state->current = expr; return true; } #endif /* build query that gets sent to remote server */ void tdsBuildForeignQuery(PlannerInfo *root, RelOptInfo *baserel, TdsFdwOptionSet* option_set, Bitmapset* attrs_used, List** retrieved_attrs, List* remote_conds, List* remote_join_conds, List* pathkeys) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsBuildForeignQuery") )); #endif ereport(DEBUG3, (errmsg("tds_fdw: Getting query") )); if (option_set->query) { ereport(DEBUG3, (errmsg("tds_fdw: Query is explicitly set") )); if (option_set->match_column_names) { /* do this, so that retrieved_attrs is filled in */ StringInfoData sql; initStringInfo(&sql); deparseSelectSql(&sql, root, baserel, attrs_used, retrieved_attrs, option_set); } } else { StringInfoData sql; initStringInfo(&sql); deparseSelectSql(&sql, root, baserel, attrs_used, retrieved_attrs, option_set); if (remote_conds) appendWhereClause(&sql, root, baserel, remote_conds, true, NULL); if (remote_join_conds) appendWhereClause(&sql, root, baserel, remote_join_conds, (remote_conds == NIL), NULL); if (pathkeys) appendOrderByClause(&sql, root, baserel, pathkeys); /* * Add FOR UPDATE/SHARE if appropriate. We apply locking during the * initial row fetch, rather than later on as is done for local tables. * The extra roundtrips involved in trying to duplicate the local * semantics exactly don't seem worthwhile (see also comments for * RowMarkType). * * Note: because we actually run the query as a cursor, this assumes that * DECLARE CURSOR ... FOR UPDATE is supported, which it isn't before 8.3. */ if (baserel->relid == root->parse->resultRelation && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ appendStringInfoString(&sql, " FOR UPDATE"); } #if (PG_VERSION_NUM >= 90500) else { PlanRowMark *rc = get_plan_rowmark(root->rowMarks, baserel->relid); if (rc) { /* * Relation is specified as a FOR UPDATE/SHARE target, so handle * that. (But we could also see LCS_NONE, meaning this isn't a * target relation after all.) * * For now, just ignore any [NO] KEY specification, since (a) it's * not clear what that means for a remote table that we don't have * complete information about, and (b) it wouldn't work anyway on * older remote servers. Likewise, we don't worry about NOWAIT. */ switch (rc->strength) { case LCS_NONE: /* No locking needed */ break; case LCS_FORKEYSHARE: case LCS_FORSHARE: appendStringInfoString(&sql, " FOR SHARE"); break; case LCS_FORNOKEYUPDATE: case LCS_FORUPDATE: appendStringInfoString(&sql, " FOR UPDATE"); break; } } } #endif /* now copy it to option_set->query */ if ((option_set->query = palloc((sql.len + 1) * sizeof(char))) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to allocate memory for query") )); } strcpy(option_set->query, sql.data); } ereport(DEBUG3, (errmsg("tds_fdw: Value of query is %s", option_set->query) )); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsBuildForeignQuery") )); #endif } /* set up connection */ int tdsSetupConnection(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS **dbproc) { char *servers; RETCODE erc; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsSetupConnection") )); #endif ereport(DEBUG3, (errmsg("tds_fdw: Setting login user to %s", option_set->username) )); DBSETLUSER(login, option_set->username); ereport(DEBUG3, (errmsg("tds_fdw: Setting login password to %s", option_set->password) )); DBSETLPWD(login, option_set->password); if (option_set->character_set) { ereport(DEBUG3, (errmsg("tds_fdw: Setting login character set to %s", option_set->character_set) )); DBSETLCHARSET(login, option_set->character_set); } if (option_set->language) { DBSETLNATLANG(login, option_set->language); ereport(DEBUG3, (errmsg("tds_fdw: Setting login language to %s", option_set->language) )); } if (option_set->tds_version) { BYTE tds_version = DBVERSION_UNKNOWN; if (strcmp(option_set->tds_version, "4.2") == 0) { tds_version = DBVER42; } else if (strcmp(option_set->tds_version, "5.0") == 0) { tds_version = DBVERSION_100; } else if (strcmp(option_set->tds_version, "7.0") == 0) { tds_version = DBVER60; } #ifdef DBVERSION_71 else if (strcmp(option_set->tds_version, "7.1") == 0) { tds_version = DBVERSION_71; } #endif #ifdef DBVERSION_72 else if (strcmp(option_set->tds_version, "7.2") == 0) { tds_version = DBVERSION_72; } #endif #ifdef DBVERSION_73 else if (strcmp(option_set->tds_version, "7.3") == 0) { tds_version = DBVERSION_73; } #endif #ifdef DBVERSION_74 else if (strcmp(option_set->tds_version, "7.4") == 0) { tds_version = DBVERSION_74; } #endif if (tds_version == DBVERSION_UNKNOWN) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown tds version: %s.", option_set->tds_version) )); } dbsetlversion(login, tds_version); ereport(DEBUG3, (errmsg("tds_fdw: Setting login tds version to %s", option_set->tds_version) )); } if (option_set->database && !option_set->dbuse) { DBSETLDBNAME(login, option_set->database); ereport(DEBUG3, (errmsg("tds_fdw: Setting login database to %s", option_set->database) )); } /* set an error handler that does not abort */ dberrhandle(tds_err_capture); /* try all server names until we find a good one */ servers = option_set->servername; last_error_message = NULL; while (servers != NULL) { /* find the length of the next server name */ char *next_server = strchr(servers, ','); int server_len = next_server == NULL ? strlen(servers) : next_server - servers; /* construct a connect string */ char *conn_string = palloc(server_len + 10); strncpy(conn_string, servers, server_len); if (option_set->port) sprintf(conn_string + server_len, ":%i", option_set->port); else conn_string[server_len] = '\0'; ereport(DEBUG3, (errmsg("tds_fdw: Connection string is %s", conn_string) )); ereport(DEBUG3, (errmsg("tds_fdw: Connecting to server") )); /* try to connect */ if ((*dbproc = dbopen(login, conn_string)) == NULL) { /* failure, will continue with the next server */ ereport(DEBUG3, (errmsg("Failed to connect using connection string %s with user %s", conn_string, option_set->username) )); pfree(conn_string); } else { /* success, break the loop */ ereport(DEBUG3, (errmsg("tds_fdw: Connected successfully") )); pfree(conn_string); break; } /* skip the comma if appropriate */ servers = next_server ? next_server + 1 : NULL; } /* report an error if all connections fail */ if (*dbproc == NULL) { if (last_error_message) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), errmsg("%s", last_error_message) )); else ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), errmsg("Failed to connect to server %s with user %s", option_set->servername, option_set->username) )); } /* set the normal error handler again */ dberrhandle(tds_err_handler); if (option_set->database && option_set->dbuse) { ereport(DEBUG3, (errmsg("tds_fdw: Selecting database %s", option_set->database) )); if ((erc = dbuse(*dbproc, option_set->database)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), errmsg("Failed to select database %s", option_set->database) )); } ereport(DEBUG3, (errmsg("tds_fdw: Selected database") )); } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsSetupConnection") )); #endif return 0; } double tdsGetRowCountShowPlanAll(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc) { double rows = 0; RETCODE erc; int ret_code; char* show_plan_query = "SET SHOWPLAN_ALL ON"; char* show_plan_query_off = "SET SHOWPLAN_ALL OFF"; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetRowCountShowPlanAll") )); #endif ereport(DEBUG3, (errmsg("tds_fdw: Setting database command to %s", show_plan_query) )); if ((erc = dbcmd(dbproc, show_plan_query)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set current query to %s", show_plan_query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Executing the query") )); if ((erc = dbsqlexec(dbproc)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to execute query %s", show_plan_query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Query executed correctly") )); ereport(DEBUG3, (errmsg("tds_fdw: Getting results") )); erc = dbresults(dbproc); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get results from query %s", show_plan_query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Setting database command to %s", option_set->query) )); if ((erc = dbcmd(dbproc, option_set->query)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set current query to %s", option_set->query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Executing the query") )); if ((erc = dbsqlexec(dbproc)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to execute query %s", option_set->query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Query executed correctly") )); ereport(DEBUG3, (errmsg("tds_fdw: Getting results") )); erc = dbresults(dbproc); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get results from query %s", option_set->query) )); } else if (erc == NO_MORE_RESULTS) { ereport(DEBUG3, (errmsg("tds_fdw: There appears to be no results from query %s", option_set->query) )); goto cleanup_after_show_plan; } else if (erc == SUCCEED) { int ncol; int ncols; int parent = 0; double estimate_rows = 0; ncols = dbnumcols(dbproc); ereport(DEBUG3, (errmsg("tds_fdw: %i columns", ncols) )); for (ncol = 0; ncol < ncols; ncol++) { char *col_name; col_name = dbcolname(dbproc, ncol + 1); if (strcmp(col_name, "Parent") == 0) { ereport(DEBUG3, (errmsg("tds_fdw: Binding column %s (%i)", col_name, ncol + 1) )); erc = dbbind(dbproc, ncol + 1, INTBIND, sizeof(int), (BYTE *)&parent); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column %s to a variable.", col_name) )); } } if (strcmp(col_name, "EstimateRows") == 0) { ereport(DEBUG3, (errmsg("tds_fdw: Binding column %s (%i)", col_name, ncol + 1) )); erc = dbbind(dbproc, ncol + 1, FLT8BIND, sizeof(double), (BYTE *)&estimate_rows); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column %s to a variable.", col_name) )); } } } ereport(DEBUG3, (errmsg("tds_fdw: Successfully got results") )); while ((ret_code = dbnextrow(dbproc)) != NO_MORE_ROWS) { switch (ret_code) { case REG_ROW: ereport(DEBUG3, (errmsg("tds_fdw: Parent is %i. EstimateRows is %g.", parent, estimate_rows) )); if (parent == 0) { rows += estimate_rows; } break; case BUF_FULL: ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Buffer filled up while getting plan for query") )); case FAIL: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row while getting plan for query") )); default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get plan for query. Unknown return code.") )); } } ereport(DEBUG3, (errmsg("tds_fdw: We estimated %g rows.", rows) )); } else { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Unknown return code getting results from query %s", option_set->query) )); } cleanup_after_show_plan: ereport(DEBUG3, (errmsg("tds_fdw: Setting database command to %s", show_plan_query_off) )); if ((erc = dbcmd(dbproc, show_plan_query_off)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set current query to %s", show_plan_query_off) )); } ereport(DEBUG3, (errmsg("tds_fdw: Executing the query") )); if ((erc = dbsqlexec(dbproc)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to execute query %s", show_plan_query_off) )); } ereport(DEBUG3, (errmsg("tds_fdw: Query executed correctly") )); ereport(DEBUG3, (errmsg("tds_fdw: Getting results") )); erc = dbresults(dbproc); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get results from query %s", show_plan_query) )); } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetRowCountShowPlanAll") )); #endif return rows; } /* get the number of rows returned by a query */ double tdsGetRowCountExecute(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc) { int rows_report = 0; long long int rows_increment = 0; RETCODE erc; int ret_code; int iscount = 0; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetRowCountExecute") )); #endif ereport(DEBUG3, (errmsg("tds_fdw: Setting database command to %s", option_set->query) )); if ((erc = dbcmd(dbproc, option_set->query)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set current query to %s", option_set->query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Executing the query") )); if ((erc = dbsqlexec(dbproc)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to execute query %s", option_set->query) )); } ereport(NOTICE, (errmsg("tds_fdw: Query executed correctly") )); ereport(NOTICE, (errmsg("tds_fdw: Getting results") )); erc = dbresults(dbproc); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get results from query %s", option_set->query) )); } else if (erc == NO_MORE_RESULTS) { ereport(DEBUG3, (errmsg("tds_fdw: There appears to be no results from query %s", option_set->query) )); goto cleanup; } else if (erc == SUCCEED) { ereport(DEBUG3, (errmsg("tds_fdw: Successfully got results") )); while ((ret_code = dbnextrow(dbproc)) != NO_MORE_ROWS) { switch (ret_code) { case REG_ROW: rows_increment++; break; case BUF_FULL: ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Buffer filled up while getting plan for query") )); case FAIL: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row while getting plan for query") )); default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get plan for query. Unknown return code.") )); } } rows_report = DBCOUNT(dbproc); iscount = dbiscount(dbproc); ereport(DEBUG3, (errmsg("tds_fdw: We counted %lli rows, and dbcount says %i rows.", rows_increment, rows_report) )); ereport(DEBUG3, (errmsg("tds_fdw: dbiscount says %i.", iscount) )); } else { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Unknown return code getting results from query %s", option_set->query) )); } cleanup: #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetRowCountExecute") )); #endif if (iscount) { return rows_report; } else { return rows_increment; } } double tdsGetRowCount(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc) { double rows = 0; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetRowCount") )); #endif if (strcmp(option_set->row_estimate_method, "execute") == 0) { rows = tdsGetRowCountExecute(option_set, login, dbproc); } else if (strcmp(option_set->row_estimate_method, "showplan_all") == 0) { rows = tdsGetRowCountShowPlanAll(option_set, login, dbproc); } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetRowCount") )); #endif return rows; } /* get the startup cost for the query */ double tdsGetStartupCost(TdsFdwOptionSet* option_set) { double startup_cost; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetStartupCost") )); #endif if (strcmp(option_set->servername, "127.0.0.1") == 0 || strcmp(option_set->servername, "localhost") == 0) startup_cost = 0; else startup_cost = 25; #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetStartupCost") )); #endif return startup_cost; } #if (PG_VERSION_NUM >= 90400) int tdsDatetimeToDatum(DBPROCESS *dbproc, DBDATETIME *src, Datum *datetime_out) { DBDATEREC datetime_in; RETCODE erc = dbdatecrack(dbproc, &datetime_in, src); if (erc == SUCCEED) { float8 seconds; #ifdef MSDBLIB seconds = (float8)datetime_in.second + ((float8)datetime_in.millisecond/1000); ereport(DEBUG3, (errmsg("tds_fdw: Datetime value: year=%i, month=%i, day=%i, hour=%i, minute=%i, second=%i, millisecond=%i, timezone=%i,", datetime_in.year, datetime_in.month, datetime_in.day, datetime_in.hour, datetime_in.minute, datetime_in.second, datetime_in.millisecond, datetime_in.tzone) )); ereport(DEBUG3, (errmsg("tds_fdw: Seconds=%f", seconds) )); *datetime_out = DirectFunctionCall6(make_timestamp, Int64GetDatum(datetime_in.year), Int64GetDatum(datetime_in.month), Int64GetDatum(datetime_in.day), Int64GetDatum(datetime_in.hour), Int64GetDatum(datetime_in.minute), Float8GetDatum(seconds)); #else seconds = (float8)datetime_in.datesecond + ((float8)datetime_in.datemsecond/1000); ereport(DEBUG3, (errmsg("tds_fdw: Datetime value: year=%i, month=%i, day=%i, hour=%i, minute=%i, second=%i, millisecond=%i, timezone=%i,", datetime_in.dateyear, datetime_in.datemonth + 1, datetime_in.datedmonth, datetime_in.datehour, datetime_in.dateminute, datetime_in.datesecond, datetime_in.datemsecond, datetime_in.datetzone) )); ereport(DEBUG3, (errmsg("tds_fdw: Seconds=%f", seconds) )); /* Sybase uses different field names, and it uses 0-11 for the month */ *datetime_out = DirectFunctionCall6(make_timestamp, Int64GetDatum(datetime_in.dateyear), Int64GetDatum(datetime_in.datemonth + 1), Int64GetDatum(datetime_in.datedmonth), Int64GetDatum(datetime_in.datehour), Int64GetDatum(datetime_in.dateminute), Float8GetDatum(seconds)); #endif } return erc; } #endif char* tdsConvertToCString(DBPROCESS* dbproc, int srctype, const BYTE* src, DBINT srclen) { char* dest = NULL; int real_destlen; DBINT destlen; int desttype; int ret_value; #if (PG_VERSION_NUM >= 90400) Datum datetime_out; RETCODE erc; #endif int use_tds_conversion = 1; switch(srctype) { case SYBCHAR: case SYBVARCHAR: case SYBTEXT: real_destlen = srclen + 1; /* the size of the array */ destlen = -2; /* the size to pass to dbconvert (-2 means to null terminate it) */ desttype = SYBCHAR; break; case SYBBINARY: case SYBVARBINARY: real_destlen = srclen; destlen = srclen; desttype = SYBBINARY; break; #if (PG_VERSION_NUM >= 90400) case SYBDATETIME: erc = tdsDatetimeToDatum(dbproc, (DBDATETIME *)src, &datetime_out); if (erc == SUCCEED) { const char *datetime_str = timestamptz_to_str(DatumGetTimestamp(datetime_out)); dest = palloc(strlen(datetime_str) * sizeof(char)); strcpy(dest, datetime_str); use_tds_conversion = 0; } #endif default: real_destlen = 1000; /* Probably big enough */ destlen = -2; desttype = SYBCHAR; break; } ereport(DEBUG3, (errmsg("tds_fdw: Source type is %i. Destination type is %i", srctype, desttype) )); ereport(DEBUG3, (errmsg("tds_fdw: Source length is %i. Destination length is %i. Real destination length is %i", srclen, destlen, real_destlen) )); if (use_tds_conversion) { if (dbwillconvert(srctype, desttype) != FALSE) { dest = palloc(real_destlen * sizeof(char)); ret_value = dbconvert(dbproc, srctype, src, srclen, desttype, (BYTE *) dest, destlen); if (ret_value == FAIL) { ereport(DEBUG3, (errmsg("tds_fdw: Failed to convert column") )); } else if (ret_value == -1) { ereport(DEBUG3, (errmsg("tds_fdw: Failed to convert column. Could have been a NULL pointer or bad data type.") )); } } else { ereport(DEBUG3, (errmsg("tds_fdw: Column cannot be converted to this type.") )); } } return dest; } /* get output for EXPLAIN */ void tdsExplainForeignScan(ForeignScanState *node, ExplainState *es) { TdsFdwExecutionState *festate = (TdsFdwExecutionState *) node->fdw_state; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsExplainForeignScan") )); #endif if (es->verbose) ExplainPropertyText("Remote query", festate->query, es); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsExplainForeignScan") )); #endif } /* initiate access to foreign server and database */ void tdsBeginForeignScan(ForeignScanState *node, int eflags) { TdsFdwOptionSet option_set; LOGINREC *login; DBPROCESS *dbproc; TdsFdwExecutionState *festate; ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; EState *estate = node->ss.ps.state; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsBeginForeignScan") )); #endif tdsGetForeignTableOptionsFromCatalog(RelationGetRelid(node->ss.ss_currentRelation), &option_set); ereport(DEBUG3, (errmsg("tds_fdw: Initiating DB-Library") )); if (dbinit() == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to initialize DB-Library environment") )); } dberrhandle(tds_err_handler); if (option_set.msg_handler) { if (strcmp(option_set.msg_handler, "notice") == 0) { dbmsghandle(tds_notice_msg_handler); } else if (strcmp(option_set.msg_handler, "blackhole") == 0) { dbmsghandle(tds_blackhole_msg_handler); } else { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown msg handler: %s.", option_set.msg_handler) )); } } ereport(DEBUG3, (errmsg("tds_fdw: Getting login structure") )); if ((login = dblogin()) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to initialize DB-Library login structure") )); } if (tdsSetupConnection(&option_set, login, &dbproc) != 0) { goto cleanup; } festate = (TdsFdwExecutionState *) palloc(sizeof(TdsFdwExecutionState)); node->fdw_state = (void *) festate; festate->login = login; festate->dbproc = dbproc; festate->query = strVal(list_nth(fsplan->fdw_private, FdwScanPrivateSelectSql)); festate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private, FdwScanPrivateRetrievedAttrs); festate->first = 1; festate->row = 0; festate->mem_cxt = AllocSetContextCreate(estate->es_query_cxt, "tds_fdw data", ALLOCSET_DEFAULT_SIZES); cleanup: ; #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsBeginForeignScan") )); #endif } void tdsGetColumnMetadata(ForeignScanState *node, TdsFdwOptionSet *option_set) { MemoryContext old_cxt; int ncol; char* local_columns_found = NULL; TdsFdwExecutionState *festate = (TdsFdwExecutionState *)node->fdw_state; int num_retrieved_attrs = list_length(festate->retrieved_attrs); Oid relOid = RelationGetRelid(node->ss.ss_currentRelation); old_cxt = MemoryContextSwitchTo(festate->mem_cxt); festate->attinmeta = TupleDescGetAttInMetadata(node->ss.ss_currentRelation->rd_att); if (option_set->match_column_names && festate->ncols < num_retrieved_attrs) { ereport(ERROR, (errcode(ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION), errmsg("Table definition mismatch: Foreign source returned %d column(s)," " but query expected %d column(s)", festate->ncols, num_retrieved_attrs) )); } else if (!option_set->match_column_names && festate->ncols < festate->attinmeta->tupdesc->natts) { ereport(ERROR, (errcode(ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION), errmsg("Table definition mismatch: Foreign source returned %d column(s)," " but local table has %d column(s)", festate->ncols, festate->attinmeta->tupdesc->natts) )); } festate->columns = palloc(festate->ncols * sizeof(COL)); festate->datums = palloc(festate->attinmeta->tupdesc->natts * sizeof(*festate->datums)); festate->isnull = palloc(festate->attinmeta->tupdesc->natts * sizeof(*festate->isnull)); if (option_set->match_column_names) { local_columns_found = palloc0(festate->attinmeta->tupdesc->natts); } for (ncol = 0; ncol < festate->ncols; ncol++) { COL* column; column = &festate->columns[ncol]; column->name = dbcolname(festate->dbproc, ncol + 1); ereport(DEBUG3, (errmsg("tds_fdw: Fetching column %i (%s)", ncol, column->name) )); column->srctype = dbcoltype(festate->dbproc, ncol + 1); ereport(DEBUG3, (errmsg("tds_fdw: Type is %i", column->srctype) )); if (option_set->match_column_names) { ListCell *lc; ereport(DEBUG3, (errmsg("tds_fdw: Matching foreign column with local column by name.") )); column->local_index = -1; //for (local_ncol = 0; local_ncol < festate->attinmeta->tupdesc->natts; local_ncol++) foreach(lc, festate->retrieved_attrs) { /* this is indexed starting from 1, not 0 */ int local_ncol = lfirst_int(lc) - 1; char* local_name = NULL; List *options; ListCell *inner_lc; ereport(DEBUG3, (errmsg("tds_fdw: Comparing it to the following retrived column: %i", local_ncol) )); options = GetForeignColumnOptions(relOid, local_ncol + 1); foreach(inner_lc, options) { DefElem *def = (DefElem *) lfirst(inner_lc); ereport(DEBUG3, (errmsg("tds_fdw: Checking if column_name is set as an option:%s => %s", def->defname, defGetString(def)) )); if (strcmp(def->defname, "column_name") == 0 && strncmp(defGetString(def), column->name, NAMEDATALEN) == 0) { ereport(DEBUG3, (errmsg("tds_fdw: It matches!") )); local_name = defGetString(def); column->local_index = local_ncol; column->attr_oid = TupleDescAttr(festate->attinmeta->tupdesc, local_ncol)->atttypid; local_columns_found[local_ncol] = 1; break; } } if (!local_name) { local_name = TupleDescAttr(festate->attinmeta->tupdesc, local_ncol)->attname.data; ereport(DEBUG3, (errmsg("tds_fdw: Comparing retrieved column name to the following local column name: %s", local_name) )); if (strncmp(local_name, column->name, NAMEDATALEN) == 0) { ereport(DEBUG3, (errmsg("tds_fdw: It matches!") )); column->local_index = local_ncol; column->attr_oid = TupleDescAttr(festate->attinmeta->tupdesc, local_ncol)->atttypid; local_columns_found[local_ncol] = 1; break; } } } if (column->local_index == -1) { ereport(WARNING, (errcode(ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION), errmsg("Table definition mismatch: Foreign source has column named %s," " but target table does not. Column will be ignored.", column->name) )); } } else { ereport(DEBUG3, (errmsg("tds_fdw: Matching foreign column with local column by position.") )); column->local_index = ncol; column->attr_oid = TupleDescAttr(festate->attinmeta->tupdesc, ncol)->atttypid; } ereport(DEBUG3, (errmsg("tds_fdw: Local index = %i, local type OID = %i", column->local_index, column->attr_oid) )); } if (option_set->match_column_names) { for (ncol = 0; ncol < festate->attinmeta->tupdesc->natts; ncol++) { if (local_columns_found[ncol] == 0) { ereport(DEBUG3, (errcode(ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION), errmsg("Table definition mismatch: Could not match local column %s" " with column from foreign table. It probably was not selected.", TupleDescAttr(festate->attinmeta->tupdesc, ncol)->attname.data) )); /* pretend this is NULL, so Pg won't try to access an invalid Datum */ festate->isnull[ncol] = 1; } } pfree(local_columns_found); } MemoryContextSwitchTo(old_cxt); } /* get next row from foreign table */ TupleTableSlot* tdsIterateForeignScan(ForeignScanState *node) { TdsFdwOptionSet option_set; RETCODE erc; int ret_code; HeapTuple tuple; TdsFdwExecutionState *festate = (TdsFdwExecutionState *) node->fdw_state; EState *estate = node->ss.ps.state; TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; int ncol; /* Cleanup */ ExecClearTuple(slot); #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsIterateForeignScan") )); #endif if (festate->first) { ereport(DEBUG3, (errmsg("tds_fdw: This is the first iteration") )); ereport(DEBUG3, (errmsg("tds_fdw: Setting database command to %s", festate->query) )); festate->first = 0; /* the following option is needed to get a proper size for blobs */ if ((erc = dbsetopt(festate->dbproc, DBTEXTSIZE, "2147483647", -1)) == FAIL) { ereport(WARNING, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set DBTEXTLIMIT server option, blob sizes may be truncated!") )); } if ((erc = dbcmd(festate->dbproc, festate->query)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set current query to %s", festate->query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Executing the query") )); if ((erc = dbsqlexec(festate->dbproc)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to execute query %s", festate->query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Query executed correctly") )); ereport(DEBUG3, (errmsg("tds_fdw: Getting results") )); erc = dbresults(festate->dbproc); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get results from query %s", festate->query) )); } else if (erc == NO_MORE_RESULTS) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("There appears to be no results from query %s", festate->query) )); } else if (erc == SUCCEED) { Oid relOid; ereport(DEBUG3, (errmsg("tds_fdw: Successfully got results") )); ereport(DEBUG3, (errmsg("tds_fdw: Getting column info") )); festate->ncols = dbnumcols(festate->dbproc); ereport(DEBUG3, (errmsg("tds_fdw: %i columns", festate->ncols) )); MemoryContextReset(festate->mem_cxt); relOid = RelationGetRelid(node->ss.ss_currentRelation); ereport(DEBUG3, (errmsg("tds_fdw: Table OID is %i", relOid) )); tdsGetForeignTableOptionsFromCatalog(relOid, &option_set); tdsGetColumnMetadata(node, &option_set); for (ncol = 0; ncol < festate->ncols; ncol++) { COL* column = &festate->columns[ncol]; const int srctype = column->srctype; const Oid attr_oid = column->attr_oid; if (column->local_index == -1) { continue; } erc = SUCCEED; column->useraw = false; ereport(DEBUG3, (errmsg("tds_fdw: The foreign type is %i. The local type is %i.", srctype, attr_oid) )); if (srctype == SYBINT2 && attr_oid == INT2OID) { erc = dbbind(festate->dbproc, ncol + 1, SMALLBIND, sizeof(DBSMALLINT), (BYTE *)(&column->value.dbsmallint)); column->useraw = true; } else if (srctype == SYBINT4 && attr_oid == INT4OID) { erc = dbbind(festate->dbproc, ncol + 1, INTBIND, sizeof(DBINT), (BYTE *)(&column->value.dbint)); column->useraw = true; } else if (srctype == SYBINT8 && attr_oid == INT8OID) { erc = dbbind(festate->dbproc, ncol + 1, BIGINTBIND, sizeof(DBBIGINT), (BYTE *)(&column->value.dbbigint)); column->useraw = true; } else if (srctype == SYBREAL && attr_oid == FLOAT4OID) { erc = dbbind(festate->dbproc, ncol + 1, REALBIND, sizeof(DBREAL), (BYTE *)(&column->value.dbreal)); column->useraw = true; } else if (srctype == SYBFLT8 && attr_oid == FLOAT8OID) { erc = dbbind(festate->dbproc, ncol + 1, FLT8BIND, sizeof(DBFLT8), (BYTE *)(&column->value.dbflt8)); column->useraw = true; } else if ((srctype == SYBCHAR || srctype == SYBVARCHAR || srctype == SYBTEXT) && (attr_oid == TEXTOID)) { column->useraw = true; } else if ((srctype == SYBBINARY || srctype == SYBVARBINARY || srctype == SYBIMAGE) && (attr_oid == BYTEAOID)) { column->useraw = true; } #if (PG_VERSION_NUM >= 90400) else if (srctype == SYBDATETIME && attr_oid == TIMESTAMPOID) { column->useraw = true; } #endif if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column %s to a variable.", dbcolname(festate->dbproc, ncol + 1)))); } } } else { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Unknown return code getting results from query %s", festate->query) )); } } ereport(DEBUG3, (errmsg("tds_fdw: Fetching next row") )); if ((ret_code = dbnextrow(festate->dbproc)) != NO_MORE_ROWS) { int ncol; switch (ret_code) { case REG_ROW: festate->row++; ereport(DEBUG3, (errmsg("tds_fdw: Row %i fetched", festate->row) )); if (show_before_row_memory_stats) { fprintf(stderr,"Showing memory statistics before row %d.\n", festate->row); MemoryContextStats(estate->es_query_cxt); } for (ncol = 0; ncol < festate->ncols; ncol++) { COL* column; DBINT srclen; BYTE* src; char *cstring; Oid attr_oid; bytea *bytes; column = &festate->columns[ncol]; attr_oid = column->attr_oid; if (column->local_index == -1) { ereport(DEBUG3, (errmsg("tds_fdw: Skipping column %s because it is not present in local table", column->name) )); continue; } srclen = dbdatlen(festate->dbproc, ncol + 1); ereport(DEBUG3, (errmsg("tds_fdw: %s: Data length is %i", column->name, srclen) )); src = dbdata(festate->dbproc, ncol + 1); if (srclen == 0 && src == NULL) { ereport(DEBUG3, (errmsg("tds_fdw: %s: Column value is NULL", column->name) )); festate->isnull[column->local_index] = true; continue; } else if (src == NULL) { ereport(DEBUG3, (errmsg("tds_fdw: %s: Column value pointer is NULL, but probably shouldn't be", column->name) )); } else { festate->isnull[column->local_index] = false; } if (column->useraw) { switch (attr_oid) { case INT2OID: festate->datums[column->local_index] = Int16GetDatum(column->value.dbsmallint); break; case INT4OID: festate->datums[column->local_index] = Int32GetDatum(column->value.dbint); break; case INT8OID: festate->datums[column->local_index] = Int64GetDatum(column->value.dbbigint); break; case FLOAT4OID: festate->datums[column->local_index] = Float4GetDatum(column->value.dbreal); break; case FLOAT8OID: festate->datums[column->local_index] = Float8GetDatum(column->value.dbflt8); break; case TEXTOID: festate->datums[column->local_index] = PointerGetDatum(cstring_to_text_with_len((char *)src, srclen)); break; case BYTEAOID: bytes = palloc(srclen + VARHDRSZ); SET_VARSIZE(bytes, srclen + VARHDRSZ); memcpy(VARDATA(bytes), src, srclen); festate->datums[column->local_index] = PointerGetDatum(bytes); break; #if (PG_VERSION_NUM >= 90400) case TIMESTAMPOID: erc = tdsDatetimeToDatum(festate->dbproc, (DBDATETIME *)src, &festate->datums[column->local_index]); if (erc != SUCCEED) { ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg("Possibly invalid date value"))); } break; #endif default: ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("%s marked useraw but wrong type (internal tds_fdw error)", dbcolname(festate->dbproc, ncol+1)))); break; } } else { cstring = tdsConvertToCString(festate->dbproc, column->srctype, src, srclen); festate->datums[column->local_index] = InputFunctionCall(&festate->attinmeta->attinfuncs[column->local_index], cstring, festate->attinmeta->attioparams[column->local_index], festate->attinmeta->atttypmods[column->local_index]); } } if (show_after_row_memory_stats) { fprintf(stderr,"Showing memory statistics after row %d.\n", festate->row); MemoryContextStats(estate->es_query_cxt); } tuple = heap_form_tuple(node->ss.ss_currentRelation->rd_att, festate->datums, festate->isnull); #if PG_VERSION_NUM < 120000 ExecStoreTuple(tuple, slot, InvalidBuffer, false); #else ExecStoreHeapTuple(tuple, slot, false); #endif break; case BUF_FULL: ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Buffer filled up during query") )); break; case FAIL: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row during query") )); break; default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row during query. Unknown return code.") )); } } else { ereport(DEBUG3, (errmsg("tds_fdw: No more rows") )); } #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsIterateForeignScan") )); #endif return slot; } /* rescan foreign table */ void tdsReScanForeignScan(ForeignScanState *node) { TdsFdwExecutionState *festate = (TdsFdwExecutionState *) node->fdw_state; int ret_code = NO_MORE_ROWS; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsReScanForeignScan") )); #endif /* * Consume any remaining result rows. * This might be necessary if the scan stopped before all * rows were consumed. */ if (!festate->first) while ((ret_code = dbnextrow(festate->dbproc)) == REG_ROW) ; if (ret_code != NO_MORE_ROWS) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row during query") )); /* reset the state for the next scan */ festate->first = 1; #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsReScanForeignScan") )); #endif } /* cleanup objects related to scan */ void tdsEndForeignScan(ForeignScanState *node) { MemoryContext old_cxt; TdsFdwExecutionState *festate = (TdsFdwExecutionState *) node->fdw_state; EState *estate = node->ss.ps.state; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsEndForeignScan") )); #endif old_cxt = MemoryContextSwitchTo(festate->mem_cxt); if (show_finished_memory_stats) { fprintf(stderr,"Showing memory statistics after query finished.\n"); MemoryContextStats(estate->es_query_cxt); } ereport(DEBUG3, (errmsg("tds_fdw: Closing database connection") )); dbclose(festate->dbproc); ereport(DEBUG3, (errmsg("tds_fdw: Freeing login structure") )); dbloginfree(festate->login); ereport(DEBUG3, (errmsg("tds_fdw: Closing DB-Library") )); dbexit(); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsEndForeignScan") )); #endif MemoryContextSwitchTo(old_cxt); MemoryContextReset(festate->mem_cxt); } /* * estimate_path_cost_size * Get cost and size estimates for a foreign scan * * We assume that all the baserestrictinfo clauses will be applied, plus * any join clauses listed in join_conds. */ static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *baserel, List *join_conds, List *pathkeys, double *p_rows, int *p_width, Cost *p_startup_cost, Cost *p_total_cost, TdsFdwOptionSet *option_set) { TdsFdwRelationInfo *fpinfo = (TdsFdwRelationInfo *) baserel->fdw_private; double rows = 0.0; double retrieved_rows = 0.0; int width = 0; Cost startup_cost = 0; Cost total_cost = 0; Cost run_cost; Cost cpu_per_tuple; /* * If the table or the server is configured to use remote estimates, * connect to the foreign server and execute EXPLAIN to estimate the * number of rows selected by the restriction+join clauses. Otherwise, * estimate rows using whatever statistics we have locally, in a way * similar to ordinary tables. */ if (fpinfo->use_remote_estimate) { LOGINREC *login; DBPROCESS *dbproc; Selectivity local_sel; QualCost local_cost; List *remote_join_conds; List *local_join_conds; List *usable_pathkeys = NIL; ListCell *lc; List *retrieved_attrs; /* * join_conds might contain both clauses that are safe to send across, * and clauses that aren't. */ classifyConditions(root, baserel, baserel->baserestrictinfo, &remote_join_conds, &local_join_conds); /* * Determine whether we can potentially push query pathkeys to the remote * side, avoiding a local sort. */ foreach(lc, pathkeys) { PathKey *pathkey = (PathKey *) lfirst(lc); EquivalenceClass *pathkey_ec = pathkey->pk_eclass; Expr *em_expr; /* * is_foreign_expr would detect volatile expressions as well, but * ec_has_volatile saves some cycles. */ if (!pathkey_ec->ec_has_volatile && (em_expr = find_em_expr_for_rel(pathkey_ec, baserel)) && is_foreign_expr(root, baserel, em_expr)) usable_pathkeys = lappend(usable_pathkeys, pathkey); else { /* * The planner and executor don't have any clever strategy for * taking data sorted by a prefix of the query's pathkeys and * getting it to be sorted by all of those pathekeys. We'll just * end up resorting the entire data set. So, unless we can push * down all of the query pathkeys, forget it. */ list_free(usable_pathkeys); usable_pathkeys = NIL; break; } } tdsBuildForeignQuery(root, baserel, option_set, fpinfo->attrs_used, &retrieved_attrs, fpinfo->remote_conds, remote_join_conds, usable_pathkeys); /* Get the remote estimate */ ereport(DEBUG3, (errmsg("tds_fdw: Initiating DB-Library") )); if (dbinit() == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to initialize DB-Library environment") )); goto cleanup_before_init; } dberrhandle(tds_err_handler); if (option_set->msg_handler) { if (strcmp(option_set->msg_handler, "notice") == 0) { dbmsghandle(tds_notice_msg_handler); } else if (strcmp(option_set->msg_handler, "blackhole") == 0) { dbmsghandle(tds_blackhole_msg_handler); } else { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown msg handler: %s.", option_set->msg_handler) )); } } ereport(DEBUG3, (errmsg("tds_fdw: Getting login structure") )); if ((login = dblogin()) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to initialize DB-Library login structure") )); goto cleanup_before_login; } if (tdsSetupConnection(option_set, login, &dbproc) != 0) { goto cleanup; } rows = tdsGetRowCount(option_set, login, dbproc); retrieved_rows = rows; width = option_set->fdw_tuple_cost; startup_cost = option_set->fdw_startup_cost; total_cost = 0; /* Factor in the selectivity of the locally-checked quals */ local_sel = clauselist_selectivity(root, join_conds, baserel->relid, JOIN_INNER, NULL); local_sel *= fpinfo->local_conds_sel; rows = clamp_row_est(rows * local_sel); /* Add in the eval cost of the locally-checked quals */ startup_cost += fpinfo->local_conds_cost.startup; total_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; cost_qual_eval(&local_cost, join_conds, root); startup_cost += local_cost.startup; total_cost += local_cost.per_tuple * retrieved_rows; cleanup: dbclose(dbproc); dbloginfree(login); cleanup_before_login: dbexit(); cleanup_before_init: ; } else { /* * We don't support join conditions in this mode (hence, no * parameterized paths can be made). */ Assert(join_conds == NIL); /* Use rows/width estimates made by set_baserel_size_estimates. */ rows = baserel->rows; #if (PG_VERSION_NUM < 90600) width = baserel->width; #else width = baserel->reltarget->width; #endif /* PG_VERSION_NUM < 90600 */ /* * Back into an estimate of the number of retrieved rows. Just in * case this is nuts, clamp to at most baserel->tuples. */ retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); retrieved_rows = Min(retrieved_rows, baserel->tuples); /* * Cost as though this were a seqscan, which is pessimistic. We * effectively imagine the local_conds are being evaluated remotely, * too. */ startup_cost = 0; run_cost = 0; run_cost += seq_page_cost * baserel->pages; startup_cost += baserel->baserestrictcost.startup; cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple; run_cost += cpu_per_tuple * baserel->tuples; /* * Without remote estimates, we have no real way to estimate the cost * of generating sorted output. It could be free if the query plan * the remote side would have chosen generates properly-sorted output * anyway, but in most cases it will cost something. Estimate a value * high enough that we won't pick the sorted path when the ordering * isn't locally useful, but low enough that we'll err on the side of * pushing down the ORDER BY clause when it's useful to do so. */ if (pathkeys != NIL) { startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER; run_cost *= DEFAULT_FDW_SORT_MULTIPLIER; } total_cost = startup_cost + run_cost; } /* * Add some additional cost factors to account for connection overhead * (fdw_startup_cost), transferring data across the network * (fdw_tuple_cost per retrieved row), and local manipulation of the data * (cpu_tuple_cost per retrieved row). */ startup_cost += fpinfo->fdw_startup_cost; total_cost += fpinfo->fdw_startup_cost; total_cost += fpinfo->fdw_tuple_cost * retrieved_rows; total_cost += cpu_tuple_cost * retrieved_rows; /* Return results. */ *p_rows = rows; *p_width = width; *p_startup_cost = startup_cost; *p_total_cost = total_cost; } void tdsGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { TdsFdwRelationInfo *fpinfo; ListCell *lc; TdsFdwOptionSet option_set; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignRelSize") )); #endif /* * We use PgFdwRelationInfo to pass various information to subsequent * functions. */ fpinfo = (TdsFdwRelationInfo *) palloc0(sizeof(TdsFdwRelationInfo)); baserel->fdw_private = (void *) fpinfo; /* Look up foreign-table catalog info. */ fpinfo->table = GetForeignTable(foreigntableid); fpinfo->server = GetForeignServer(fpinfo->table->serverid); tdsGetForeignTableOptionsFromCatalog(foreigntableid, &option_set); fpinfo->use_remote_estimate = option_set.use_remote_estimate; fpinfo->fdw_startup_cost = option_set.fdw_startup_cost; fpinfo->fdw_tuple_cost = option_set.fdw_tuple_cost; /* * Identify which baserestrictinfo clauses can be sent to the remote * server and which can't. */ classifyConditions(root, baserel, baserel->baserestrictinfo, &fpinfo->remote_conds, &fpinfo->local_conds); /* * Identify which attributes will need to be retrieved from the remote * server. These include all attrs needed for joins or final output, plus * all attrs used in the local_conds. (Note: if we end up using a * parameterized scan, it's possible that some of the join clauses will be * sent to the remote and thus we wouldn't really need to retrieve the * columns used in them. Doesn't seem worth detecting that case though.) */ fpinfo->attrs_used = NULL; #if (PG_VERSION_NUM < 90600) pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &fpinfo->attrs_used); #else pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &fpinfo->attrs_used); #endif /* PG_VERSION_NUM < 90600 */ foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); pull_varattnos((Node *) rinfo->clause, baserel->relid, &fpinfo->attrs_used); } /* * Compute the selectivity and cost of the local_conds, so we don't have * to do it over again for each path. The best we can do for these * conditions is to estimate selectivity on the basis of local statistics. */ fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, baserel->relid, JOIN_INNER, NULL); cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); /* * If the table or the server is configured to use remote estimates, * connect to the foreign server and execute EXPLAIN to estimate the * number of rows selected by the restriction clauses, as well as the * average row width. Otherwise, estimate using whatever statistics we * have locally, in a way similar to ordinary tables. */ if (fpinfo->use_remote_estimate) { ereport(DEBUG3, (errmsg("tds_fdw: Using remote estimate") )); /* * Get cost/size estimates with help of remote server. Save the * values in fpinfo so we don't need to do it again to generate the * basic foreign path. */ estimate_path_cost_size(root, baserel, NIL, NIL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, &fpinfo->total_cost, &option_set); /* Report estimated baserel size to planner. */ baserel->rows = fpinfo->rows; #if (PG_VERSION_NUM < 90600) baserel->width = fpinfo->width; #else baserel->reltarget->width = fpinfo->width; #endif /* PG_VERSION_NUM < 90600 */ } else { ereport(DEBUG3, (errmsg("tds_fdw: Using local estimate") )); /* * If the foreign table has never been ANALYZEd, it will have relpages * and reltuples equal to zero, which most likely has nothing to do * with reality. We can't do a whole lot about that if we're not * allowed to consult the remote server, but we can use a hack similar * to plancat.c's treatment of empty relations: use a minimum size * estimate of 10 pages, and divide by the column-datatype-based width * estimate to get the corresponding number of tuples. */ if (baserel->tuples == 0) { baserel->tuples = option_set.local_tuple_estimate; } /* Estimate baserel size as best we can with local statistics. */ set_baserel_size_estimates(root, baserel); /* Fill in basically-bogus cost estimates for use later. */ estimate_path_cost_size(root, baserel, NIL, NIL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, &fpinfo->total_cost, &option_set); } #if (PG_VERSION_NUM < 90600) ereport(DEBUG3, (errmsg("tds_fdw: Estimated rows = %f, estimated width = %d", baserel->rows, baserel->width) )); #else ereport(DEBUG3, (errmsg("tds_fdw: Estimated rows = %f, estimated width = %d", baserel->rows, baserel->reltarget->width) )); #endif /* PG_VERSION_NUM < 90600 */ #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignRelSize") )); #endif } void tdsEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid) { TdsFdwOptionSet option_set; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsEstimateCosts") )); #endif tdsGetForeignTableOptionsFromCatalog(foreigntableid, &option_set); *startup_cost = tdsGetStartupCost(&option_set); *total_cost = baserel->rows + *startup_cost; #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsEstimateCosts") )); #endif } void tdsGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { TdsFdwOptionSet option_set; TdsFdwRelationInfo *fpinfo = (TdsFdwRelationInfo *) baserel->fdw_private; ForeignPath *path; #if (PG_VERSION_NUM >= 90500) List *ppi_list; #endif ListCell *lc; List *usable_pathkeys = NIL; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignPaths") )); #endif tdsGetForeignTableOptionsFromCatalog(foreigntableid, &option_set); /* * Create simplest ForeignScan path node and add it to baserel. This path * corresponds to SeqScan path of regular tables (though depending on what * baserestrict conditions we were able to send to remote, there might * actually be an indexscan happening there). We already did all the work * to estimate cost and size of this path. */ #if PG_VERSION_NUM < 90500 path = create_foreignscan_path(root, baserel, fpinfo->rows, fpinfo->startup_cost, fpinfo->total_cost, NIL, /* no pathkeys */ NULL, /* no outer rel either */ NIL); /* no fdw_private list */ #elif PG_VERSION_NUM < 90600 path = create_foreignscan_path(root, baserel, fpinfo->rows, fpinfo->startup_cost, fpinfo->total_cost, NIL, /* no pathkeys */ NULL, /* no outer rel either */ NULL, /* no extra plan */ NIL); /* no fdw_private list */ #else path = create_foreignscan_path(root, baserel, NULL, fpinfo->rows, fpinfo->startup_cost, fpinfo->total_cost, NIL, /* no pathkeys */ NULL, /* no outer rel either */ NULL, /* no extra plan */ NIL); /* no fdw_private list */ #endif /* PG_VERSION_NUM < 90500 */ add_path(baserel, (Path *) path); /* * Determine whether we can potentially push query pathkeys to the remote * side, avoiding a local sort. */ foreach(lc, root->query_pathkeys) { PathKey *pathkey = (PathKey *) lfirst(lc); EquivalenceClass *pathkey_ec = pathkey->pk_eclass; Expr *em_expr; /* * is_foreign_expr would detect volatile expressions as well, but * ec_has_volatile saves some cycles. */ if (!pathkey_ec->ec_has_volatile && (em_expr = find_em_expr_for_rel(pathkey_ec, baserel)) && is_foreign_expr(root, baserel, em_expr)) usable_pathkeys = lappend(usable_pathkeys, pathkey); else { /* * The planner and executor don't have any clever strategy for * taking data sorted by a prefix of the query's pathkeys and * getting it to be sorted by all of those pathekeys. We'll just * end up resorting the entire data set. So, unless we can push * down all of the query pathkeys, forget it. */ list_free(usable_pathkeys); usable_pathkeys = NIL; break; } } /* Create a path with useful pathkeys, if we found one. */ if (usable_pathkeys != NULL) { double rows; int width; Cost startup_cost; Cost total_cost; estimate_path_cost_size(root, baserel, NIL, usable_pathkeys, &rows, &width, &startup_cost, &total_cost, &option_set); add_path(baserel, (Path *) #if PG_VERSION_NUM < 90500 create_foreignscan_path(root, baserel, rows, startup_cost, total_cost, usable_pathkeys, NULL, NIL)); #elif PG_VERSION_NUM < 90600 create_foreignscan_path(root, baserel, rows, startup_cost, total_cost, usable_pathkeys, NULL, NULL, NIL)); #else create_foreignscan_path(root, baserel, NULL, rows, startup_cost, total_cost, usable_pathkeys, NULL, NULL, NIL)); #endif /* PG_VERSION_NUM < 90500 */ } /* Don't worry about join pushdowns unless this is PostgreSQL 9.5+ */ #if (PG_VERSION_NUM >= 90500) /* * If we're not using remote estimates, stop here. We have no way to * estimate whether any join clauses would be worth sending across, so * don't bother building parameterized paths. */ if (!fpinfo->use_remote_estimate) return; /* * Thumb through all join clauses for the rel to identify which outer * relations could supply one or more safe-to-send-to-remote join clauses. * We'll build a parameterized path for each such outer relation. * * It's convenient to manage this by representing each candidate outer * relation by the ParamPathInfo node for it. We can then use the * ppi_clauses list in the ParamPathInfo node directly as a list of the * interesting join clauses for that rel. This takes care of the * possibility that there are multiple safe join clauses for such a rel, * and also ensures that we account for unsafe join clauses that we'll * still have to enforce locally (since the parameterized-path machinery * insists that we handle all movable clauses). */ ppi_list = NIL; foreach(lc, baserel->joininfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); Relids required_outer; ParamPathInfo *param_info; /* Check if clause can be moved to this rel */ if (!join_clause_is_movable_to(rinfo, baserel)) continue; /* See if it is safe to send to remote */ if (!is_foreign_expr(root, baserel, rinfo->clause)) continue; /* Calculate required outer rels for the resulting path */ required_outer = bms_union(rinfo->clause_relids, baserel->lateral_relids); /* We do not want the foreign rel itself listed in required_outer */ required_outer = bms_del_member(required_outer, baserel->relid); /* * required_outer probably can't be empty here, but if it were, we * couldn't make a parameterized path. */ if (bms_is_empty(required_outer)) continue; /* Get the ParamPathInfo */ param_info = get_baserel_parampathinfo(root, baserel, required_outer); Assert(param_info != NULL); /* * Add it to list unless we already have it. Testing pointer equality * is OK since get_baserel_parampathinfo won't make duplicates. */ ppi_list = list_append_unique_ptr(ppi_list, param_info); } /* * The above scan examined only "generic" join clauses, not those that * were absorbed into EquivalenceClauses. See if we can make anything out * of EquivalenceClauses. */ if (baserel->has_eclass_joins) { /* * We repeatedly scan the eclass list looking for column references * (or expressions) belonging to the foreign rel. Each time we find * one, we generate a list of equivalence joinclauses for it, and then * see if any are safe to send to the remote. Repeat till there are * no more candidate EC members. */ ec_member_foreign_arg arg; arg.already_used = NIL; for (;;) { List *clauses; /* Make clauses, skipping any that join to lateral_referencers */ arg.current = NULL; clauses = generate_implied_equalities_for_column(root, baserel, ec_member_matches_foreign, (void *) &arg, baserel->lateral_referencers); /* Done if there are no more expressions in the foreign rel */ if (arg.current == NULL) { Assert(clauses == NIL); break; } /* Scan the extracted join clauses */ foreach(lc, clauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); Relids required_outer; ParamPathInfo *param_info; /* Check if clause can be moved to this rel */ if (!join_clause_is_movable_to(rinfo, baserel)) continue; /* See if it is safe to send to remote */ if (!is_foreign_expr(root, baserel, rinfo->clause)) continue; /* Calculate required outer rels for the resulting path */ required_outer = bms_union(rinfo->clause_relids, baserel->lateral_relids); required_outer = bms_del_member(required_outer, baserel->relid); if (bms_is_empty(required_outer)) continue; /* Get the ParamPathInfo */ param_info = get_baserel_parampathinfo(root, baserel, required_outer); Assert(param_info != NULL); /* Add it to list unless we already have it */ ppi_list = list_append_unique_ptr(ppi_list, param_info); } /* Try again, now ignoring the expression we found this time */ arg.already_used = lappend(arg.already_used, arg.current); } } /* * Now build a path for each useful outer relation. */ foreach(lc, ppi_list) { ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc); double rows; int width; Cost startup_cost; Cost total_cost; /* Get a cost estimate from the remote */ estimate_path_cost_size(root, baserel, param_info->ppi_clauses, NIL, &rows, &width, &startup_cost, &total_cost, &option_set); /* * ppi_rows currently won't get looked at by anything, but still we * may as well ensure that it matches our idea of the rowcount. */ param_info->ppi_rows = rows; /* Make the path */ #if PG_VERSION_NUM < 90500 path = create_foreignscan_path(root, baserel, rows, startup_cost, total_cost, NIL, /* no pathkeys */ param_info->ppi_req_outer, NIL); /* no fdw_private list */ #elif PG_VERSION_NUM < 90600 path = create_foreignscan_path(root, baserel, rows, startup_cost, total_cost, NIL, /* no pathkeys */ param_info->ppi_req_outer, NULL, NIL); /* no fdw_private list */ #else path = create_foreignscan_path(root, baserel, NULL, rows, startup_cost, total_cost, NIL, /* no pathkeys */ param_info->ppi_req_outer, NULL, NIL); /* no fdw_private list */ #endif /* PG_VERSION_NUM < 90500 */ add_path(baserel, (Path *) path); } #endif #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignPaths") )); #endif } bool tdsAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsAnalyzeForeignTable") )); #endif #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsAnalyzeForeignTable") )); #endif return false; } #if (PG_VERSION_NUM >= 90500) ForeignScan* tdsGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) #else ForeignScan* tdsGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses) #endif { TdsFdwRelationInfo *fpinfo = (TdsFdwRelationInfo *) baserel->fdw_private; TdsFdwOptionSet option_set; Index scan_relid = baserel->relid; List *fdw_private; List *remote_conds = NIL; List *remote_exprs = NIL; List *local_exprs = NIL; List *params_list = NIL; List *retrieved_attrs = NIL; ListCell *lc; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsGetForeignPlan") )); #endif tdsGetForeignTableOptionsFromCatalog(foreigntableid, &option_set); /* * Separate the scan_clauses into those that can be executed remotely and * those that can't. baserestrictinfo clauses that were previously * determined to be safe or unsafe by classifyConditions are shown in * fpinfo->remote_conds and fpinfo->local_conds. Anything else in the * scan_clauses list will be a join clause, which we have to check for * remote-safety. * * Note: the join clauses we see here should be the exact same ones * previously examined by postgresGetForeignPaths. Possibly it'd be worth * passing forward the classification work done then, rather than * repeating it here. * * This code must match "extract_actual_clauses(scan_clauses, false)" * except for the additional decision about remote versus local execution. * Note however that we don't strip the RestrictInfo nodes from the * remote_conds list, since appendWhereClause expects a list of * RestrictInfos. */ foreach(lc, scan_clauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); Assert(IsA(rinfo, RestrictInfo)); /* Ignore any pseudoconstants, they're dealt with elsewhere */ if (rinfo->pseudoconstant) continue; if (list_member_ptr(fpinfo->remote_conds, rinfo)) { remote_conds = lappend(remote_conds, rinfo); remote_exprs = lappend(remote_exprs, rinfo->clause); } else if (list_member_ptr(fpinfo->local_conds, rinfo)) local_exprs = lappend(local_exprs, rinfo->clause); else if (is_foreign_expr(root, baserel, rinfo->clause)) { remote_conds = lappend(remote_conds, rinfo); remote_exprs = lappend(remote_exprs, rinfo->clause); } else local_exprs = lappend(local_exprs, rinfo->clause); } /* * Build the query string to be sent for execution, and identify * expressions to be sent as parameters. */ tdsBuildForeignQuery(root, baserel, &option_set, fpinfo->attrs_used, &retrieved_attrs, remote_conds, NULL, best_path->path.pathkeys); /* * Build the fdw_private list that will be available to the executor. * Items in the list must match enum FdwScanPrivateIndex, above. */ fdw_private = list_make2(makeString(option_set.query), retrieved_attrs); /* * Create the ForeignScan node from target list, filtering expressions, * remote parameter expressions, and FDW private information. * * Note that the remote parameter expressions are stored in the fdw_exprs * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsGetForeignPlan") )); #endif #if (PG_VERSION_NUM >= 90500) return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, NIL, /* no custom tlist */ remote_exprs, outer_plan); #else return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private); #endif } static bool tdsExecuteQuery(char *query, DBPROCESS *dbproc) { RETCODE erc; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsExecuteQuery") )); #endif ereport(DEBUG3, (errmsg("tds_fdw: Setting database command to %s", query) )); if ((erc = dbcmd(dbproc, query)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to set current query to %s", query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Executing the query") )); if ((erc = dbsqlexec(dbproc)) == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to execute query %s", query) )); } ereport(DEBUG3, (errmsg("tds_fdw: Query executed correctly") )); ereport(DEBUG3, (errmsg("tds_fdw: Getting results") )); erc = dbresults(dbproc); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get results from query %s", query) )); } else if (erc == NO_MORE_RESULTS) { ereport(DEBUG3, (errmsg("tds_fdw: There appears to be no results from query %s", query) )); goto cleanup; } else if (erc == SUCCEED) { ereport(DEBUG3, (errmsg("tds_fdw: Successfully got results") )); goto cleanup; } else { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Unknown return code getting results from query %s", query) )); } cleanup: #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsExecuteQuery") )); #endif return (erc == SUCCEED); } #ifdef IMPORT_API static List * tdsImportSqlServerSchema(ImportForeignSchemaStmt *stmt, DBPROCESS *dbproc, TdsFdwOptionSet option_set, bool import_default, bool import_not_null) { List *commands = NIL; ListCell *lc; StringInfoData buf; RETCODE erc; int ret_code; initStringInfo(&buf); /* Check that the schema really exists */ appendStringInfoString(&buf, "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE schema_name = "); deparseStringLiteral(&buf, stmt->remote_schema); if (!tdsExecuteQuery(buf.data, dbproc)) ereport(ERROR, (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND), errmsg("schema \"%s\" is not present on foreign server \"%s\"", stmt->remote_schema, option_set.servername))); else /* Process results */ while ((ret_code = dbnextrow(dbproc)) != NO_MORE_ROWS) { /* Do nothing */ } resetStringInfo(&buf); /* * Fetch all table data from this schema, possibly restricted by * EXCEPT or LIMIT TO. (We don't actually need to pay any attention * to EXCEPT/LIMIT TO here, because the core code will filter the * statements we return according to those lists anyway. But it * should save a few cycles to not process excluded tables in the * first place.) */ appendStringInfoString(&buf, "SELECT t.table_name," " c.column_name, " " c.data_type, " " c.column_default, " " c.is_nullable, " " c.character_maximum_length, " " c.numeric_precision, " " c.numeric_precision_radix, " " c.numeric_scale, " " c.datetime_precision " "FROM INFORMATION_SCHEMA.TABLES t " " LEFT JOIN INFORMATION_SCHEMA.COLUMNS c ON " " t.table_schema = c.table_schema " " AND t.table_name = c.table_name " "WHERE t.table_type IN ('BASE TABLE','VIEW') " " AND t.table_schema = "); deparseStringLiteral(&buf, stmt->remote_schema); /* Apply restrictions for LIMIT TO and EXCEPT */ if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO || stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) { bool first_item = true; appendStringInfoString(&buf, " AND t.table_name "); if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) appendStringInfoString(&buf, "NOT "); appendStringInfoString(&buf, "IN ("); /* Append list of table names within IN clause */ foreach(lc, stmt->table_list) { RangeVar *rv = (RangeVar *) lfirst(lc); if (first_item) first_item = false; else appendStringInfoString(&buf, ", "); deparseStringLiteral(&buf, rv->relname); } appendStringInfoChar(&buf, ')'); } /* Append ORDER BY at the end of query to ensure output ordering */ appendStringInfoString(&buf, " ORDER BY t.table_name, c.ordinal_position"); if (tdsExecuteQuery(buf.data, dbproc)) { char table_name[255], prev_table[255], column_name[255], data_type[255], column_default[4000], is_nullable[10]; int char_len, numeric_precision, numeric_precision_radix, numeric_scale, datetime_precision; bool first_column = true; bool first_table = true; /* Check if there's rows in resultset and if not do not execute the rest */ erc = dbrows(dbproc); if (erc == FAIL) { ereport(NOTICE, (errmsg("tds_fdw: No table were found in schema %s", stmt->remote_schema)) ); return commands; } prev_table[0] = '\0'; erc = dbbind(dbproc, 1, NTBSTRINGBIND, sizeof(table_name), (BYTE *) table_name); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"table_name\" to a variable.") )); } erc = dbbind(dbproc, 2, NTBSTRINGBIND, sizeof(column_name), (BYTE *) column_name); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"column_name\" to a variable.") )); } erc = dbbind(dbproc, 3, NTBSTRINGBIND, sizeof(data_type), (BYTE *) data_type); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"data_type\" to a variable.") )); } erc = dbbind(dbproc, 4, NTBSTRINGBIND, sizeof(column_default), (BYTE *) column_default); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"column_default\" to a variable.") )); } erc = dbbind(dbproc, 5, NTBSTRINGBIND, sizeof(is_nullable), (BYTE *) is_nullable); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"is_nullable\" to a variable.") )); } erc = dbbind(dbproc, 6, INTBIND, sizeof(int), (BYTE *) &char_len); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"character_maximum_length\" to a variable.") )); } erc = dbbind(dbproc, 7, INTBIND, sizeof(int), (BYTE *) &numeric_precision); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"numeric_precision\" to a variable.") )); } erc = dbbind(dbproc, 8, INTBIND, sizeof(int), (BYTE *) &numeric_precision_radix); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"numeric_precision_radix\" to a variable.") )); } erc = dbbind(dbproc, 9, INTBIND, sizeof(int), (BYTE *) &numeric_scale); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"numeric_scale\" to a variable.") )); } erc = dbbind(dbproc, 10, INTBIND, sizeof(int), (BYTE *) &datetime_precision); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"datetime_precision\" to a variable.") )); } /* Process results */ while ((ret_code = dbnextrow(dbproc)) != NO_MORE_ROWS) { switch (ret_code) { case REG_ROW: ereport(DEBUG3, (errmsg("tds_fdw: column \"%s.%s\"", table_name, column_name) )); /* Build query for the new table */ if (first_table || strcmp(prev_table, table_name) != 0) { if (!first_table) { /* * Add server name and table-level options. We specify remote * schema and table name as options (the latter to ensure that * renaming the foreign table doesn't break the association). */ appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (", quote_identifier(stmt->server_name)); appendStringInfoString(&buf, "schema_name "); deparseStringLiteral(&buf, stmt->remote_schema); appendStringInfoString(&buf, ", table_name "); deparseStringLiteral(&buf, prev_table); appendStringInfoString(&buf, ");"); commands = lappend(commands, pstrdup(buf.data)); } resetStringInfo(&buf); appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n", quote_identifier(table_name)); first_column = true; first_table = false; } if (first_column) first_column = false; else appendStringInfoString(&buf, ",\n"); /* Print column name */ appendStringInfo(&buf, " %s", quote_identifier(column_name)); /* Print column type */ /* Numeric types */ if (strcmp(data_type, "bit") == 0 || strcmp(data_type, "smallint") == 0 || strcmp(data_type, "tinyint") == 0) appendStringInfoString(&buf, " smallint"); else if (strcmp(data_type, "int") == 0) appendStringInfoString(&buf, " integer"); else if (strcmp(data_type, "bigint") == 0) appendStringInfoString(&buf, " bigint"); else if (strcmp(data_type, "decimal") == 0) { if (numeric_scale == 0) appendStringInfo(&buf, " decimal(%d)", numeric_precision); else appendStringInfo(&buf, " decimal(%d, %d)", numeric_precision, numeric_scale); } else if (strcmp(data_type, "numeric") == 0) { if (numeric_scale == 0) appendStringInfo(&buf, " numeric(%d)", numeric_precision); else appendStringInfo(&buf, " numeric(%d, %d)", numeric_precision, numeric_scale); } else if (strcmp(data_type, "money") == 0 || strcmp(data_type, "smallmoney") == 0) appendStringInfoString(&buf, " money"); /* Floating-point types */ else if (strcmp(data_type, "float") == 0) appendStringInfo(&buf, " float(%d)", numeric_precision); else if (strcmp(data_type, "real") == 0) appendStringInfoString(&buf, " real"); /* Date/type types */ else if (strcmp(data_type, "date") == 0) appendStringInfoString(&buf, " date"); else if (strcmp(data_type, "datetime") == 0 || strcmp(data_type, "datetime2") == 0 || strcmp(data_type, "smalldatetime") == 0) appendStringInfo(&buf, " timestamp(%d) without time zone", (datetime_precision > 6) ? 6 : datetime_precision); else if (strcmp(data_type, "datetimeoffset") == 0) appendStringInfo(&buf, " timestamp(%d) with time zone", (datetime_precision > 6) ? 6 : datetime_precision); else if (strcmp(data_type, "time") == 0) appendStringInfoString(&buf, " time"); /* Character types */ else if (strcmp(data_type, "char") == 0 || strcmp(data_type, "nchar") == 0) appendStringInfo(&buf, " char(%d)", char_len); else if (strcmp(data_type, "varchar") == 0 || strcmp(data_type, "nvarchar") == 0) { if (char_len == -1) appendStringInfoString(&buf, " text"); else appendStringInfo(&buf, " varchar(%d)", char_len); } else if (strcmp(data_type, "text") == 0 || strcmp(data_type, "ntext") == 0) appendStringInfoString(&buf, " text"); /* Binary types */ else if (strcmp(data_type, "binary") == 0 || strcmp(data_type, "varbinary") == 0 || strcmp(data_type, "image") == 0 || strcmp(data_type, "rowversion") == 0 || strcmp(data_type, "timestamp") == 0 ) appendStringInfoString(&buf, " bytea"); /* Other types */ else if (strcmp(data_type, "xml") == 0) appendStringInfoString(&buf, " xml"); else if (strcmp(data_type, "uniqueidentifier") == 0) appendStringInfoString(&buf, " uuid"); else { ereport(DEBUG3, (errmsg("tds_fdw: column \"%s\" of table \"%s\" has an untranslatable data type", column_name, table_name) )); appendStringInfoString(&buf, " text"); } /* * Add column_name option so that renaming the foreign table's * column doesn't break the association to the underlying * column. */ appendStringInfoString(&buf, " OPTIONS (column_name "); deparseStringLiteral(&buf, column_name); appendStringInfoChar(&buf, ')'); /* Add DEFAULT if needed */ if (import_default && column_default[0] != '\0') appendStringInfo(&buf, " DEFAULT %s", column_default); /* Add NOT NULL if needed */ if (import_not_null && strcmp(is_nullable, "NO") == 0) appendStringInfoString(&buf, " NOT NULL"); strcpy(prev_table, table_name); break; case BUF_FULL: ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Buffer filled up while getting plan for query") )); case FAIL: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row while getting plan for query") )); default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get plan for query. Unknown return code.") )); } } /* * Add server name and table-level options. We specify remote * schema and table name as options (the latter to ensure that * renaming the foreign table doesn't break the association). */ appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (", quote_identifier(stmt->server_name)); appendStringInfoString(&buf, "schema_name "); deparseStringLiteral(&buf, stmt->remote_schema); appendStringInfoString(&buf, ", table_name "); deparseStringLiteral(&buf, prev_table); appendStringInfoString(&buf, ");"); commands = lappend(commands, pstrdup(buf.data)); } return commands; } static List * tdsImportSybaseSchema(ImportForeignSchemaStmt *stmt, DBPROCESS *dbproc, TdsFdwOptionSet option_set, bool import_default, bool import_not_null) { List *commands = NIL; ListCell *lc; StringInfoData buf; RETCODE erc; int ret_code; initStringInfo(&buf); /* Check that the schema really exists */ appendStringInfoString(&buf, "SELECT name FROM sysusers WHERE name = "); deparseStringLiteral(&buf, stmt->remote_schema); if (!tdsExecuteQuery(buf.data, dbproc)) ereport(ERROR, (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND), errmsg("schema \"%s\" is not present on foreign server \"%s\"", stmt->remote_schema, option_set.servername))); else /* Process results */ while ((ret_code = dbnextrow(dbproc)) != NO_MORE_ROWS) { /* Do nothing */ } resetStringInfo(&buf); /* * Fetch all table data from this schema, possibly restricted by * EXCEPT or LIMIT TO. (We don't actually need to pay any attention * to EXCEPT/LIMIT TO here, because the core code will filter the * statements we return according to those lists anyway. But it * should save a few cycles to not process excluded tables in the * first place.) */ appendStringInfoString(&buf, "SELECT so.name AS table_name, " " sc.name AS column_name, " " st.name AS data_type, " " SUBSTRING(sm.text, 10, 255) AS column_default, " " CASE (sc.status & 0x08) " " WHEN 8 THEN 'YES' ELSE 'NO' " " END AS is_nullable, " " sc.length, " " sc.prec, " " sc.scale " "FROM dbo.sysobjects so " " INNER JOIN dbo.sysusers su ON su.uid = so.uid " " LEFT JOIN dbo.syscolumns sc ON sc.id = so.id " " LEFT JOIN dbo.systypes st ON st.usertype = sc.usertype " " LEFT JOIN dbo.syscomments sm ON sm.id = sc.cdefault " "WHERE so.type IN ('U','V') AND su.name = "); deparseStringLiteral(&buf, stmt->remote_schema); /* Apply restrictions for LIMIT TO and EXCEPT */ if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO || stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) { bool first_item = true; appendStringInfoString(&buf, " AND so.name "); if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) appendStringInfoString(&buf, "NOT "); appendStringInfoString(&buf, "IN ("); /* Append list of table names within IN clause */ foreach(lc, stmt->table_list) { RangeVar *rv = (RangeVar *) lfirst(lc); if (first_item) first_item = false; else appendStringInfoString(&buf, ", "); deparseStringLiteral(&buf, rv->relname); } appendStringInfoChar(&buf, ')'); } /* Append ORDER BY at the end of query to ensure output ordering */ appendStringInfoString(&buf, " ORDER BY so.name, sc.colid"); if (tdsExecuteQuery(buf.data, dbproc)) { char table_name[255], prev_table[255], column_name[255], data_type[255], column_default[4000], is_nullable[10]; int char_len, numeric_precision, numeric_scale; bool first_column = true; bool first_table = true; /* Check if there's rows in resultset and if not do not execute the rest */ erc = dbrows(dbproc); if (erc == FAIL) { ereport(NOTICE, (errmsg("tds_fdw: No table were found in schema %s", stmt->remote_schema)) ); return commands; } prev_table[0] = '\0'; erc = dbbind(dbproc, 1, NTBSTRINGBIND, sizeof(table_name), (BYTE *) table_name); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"table_name\" to a variable.") )); } erc = dbbind(dbproc, 2, NTBSTRINGBIND, sizeof(column_name), (BYTE *) column_name); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"column_name\" to a variable.") )); } erc = dbbind(dbproc, 3, NTBSTRINGBIND, sizeof(data_type), (BYTE *) data_type); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"data_type\" to a variable.") )); } erc = dbbind(dbproc, 4, NTBSTRINGBIND, sizeof(column_default), (BYTE *) column_default); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"column_default\" to a variable.") )); } erc = dbbind(dbproc, 5, NTBSTRINGBIND, sizeof(is_nullable), (BYTE *) is_nullable); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"is_nullable\" to a variable.") )); } erc = dbbind(dbproc, 6, INTBIND, sizeof(int), (BYTE *) &char_len); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"length\" to a variable.") )); } erc = dbbind(dbproc, 7, INTBIND, sizeof(int), (BYTE *) &numeric_precision); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"prec\" to a variable.") )); } erc = dbbind(dbproc, 8, INTBIND, sizeof(int), (BYTE *) &numeric_scale); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"scale\" to a variable.") )); } /* Process results */ while ((ret_code = dbnextrow(dbproc)) != NO_MORE_ROWS) { switch (ret_code) { case REG_ROW: ereport(DEBUG3, (errmsg("tds_fdw: column \"%s.%s\"", table_name, column_name) )); /* Build query for the new table */ if (first_table || strcmp(prev_table, table_name) != 0) { if (!first_table) { /* * Add server name and table-level options. We specify remote * schema and table name as options (the latter to ensure that * renaming the foreign table doesn't break the association). */ appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (", quote_identifier(stmt->server_name)); appendStringInfoString(&buf, "schema_name "); deparseStringLiteral(&buf, stmt->remote_schema); appendStringInfoString(&buf, ", table_name "); deparseStringLiteral(&buf, prev_table); appendStringInfoString(&buf, ");"); commands = lappend(commands, pstrdup(buf.data)); } resetStringInfo(&buf); appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n", quote_identifier(table_name)); first_column = true; first_table = false; } if (first_column) first_column = false; else appendStringInfoString(&buf, ",\n"); /* Print column name */ appendStringInfo(&buf, " %s", quote_identifier(column_name)); /* Print column type */ /* Numeric types */ if (strcmp(data_type, "bit") == 0 || strcmp(data_type, "smallint") == 0 || strcmp(data_type, "tinyint") == 0) appendStringInfoString(&buf, " smallint"); else if (strcmp(data_type, "int") == 0) appendStringInfoString(&buf, " integer"); else if (strcmp(data_type, "bigint") == 0) appendStringInfoString(&buf, " bigint"); else if (strcmp(data_type, "decimal") == 0) { if (numeric_scale == 0) appendStringInfo(&buf, " decimal(%d)", numeric_precision); else appendStringInfo(&buf, " decimal(%d, %d)", numeric_precision, numeric_scale); } else if (strcmp(data_type, "numeric") == 0) { if (numeric_scale == 0) appendStringInfo(&buf, " numeric(%d)", numeric_precision); else appendStringInfo(&buf, " numeric(%d, %d)", numeric_precision, numeric_scale); } else if (strcmp(data_type, "money") == 0 || strcmp(data_type, "smallmoney") == 0) appendStringInfoString(&buf, " money"); /* Floating-point types */ else if (strcmp(data_type, "float") == 0) appendStringInfo(&buf, " float(%d)", numeric_precision); else if (strcmp(data_type, "real") == 0) appendStringInfoString(&buf, " real"); /* Date/type types */ else if (strcmp(data_type, "date") == 0) appendStringInfoString(&buf, " date"); else if (strcmp(data_type, "datetime") == 0 || strcmp(data_type, "smalldatetime") == 0 || strcmp(data_type, "bigdatetime") == 0) appendStringInfoString(&buf, " timestamp without time zone"); else if (strcmp(data_type, "time") == 0 || strcmp(data_type, "bigtime") == 0) appendStringInfoString(&buf, " time"); /* Character types */ else if (strcmp(data_type, "char") == 0 || strcmp(data_type, "nchar") == 0 || strcmp(data_type, "unichar") == 0) appendStringInfo(&buf, " char(%d)", char_len); else if (strcmp(data_type, "varchar") == 0 || strcmp(data_type, "nvarchar") == 0 || strcmp(data_type, "univarchar") == 0) { if (char_len == -1) appendStringInfoString(&buf, " text"); else appendStringInfo(&buf, " varchar(%d)", char_len); } else if (strcmp(data_type, "text") == 0 || strcmp(data_type, "unitext") == 0) appendStringInfoString(&buf, " text"); /* Binary types */ else if (strcmp(data_type, "binary") == 0 || strcmp(data_type, "varbinary") == 0 || strcmp(data_type, "image") == 0 || strcmp(data_type, "timestamp") == 0 ) appendStringInfoString(&buf, " bytea"); /* Other types */ else if (strcmp(data_type, "xml") == 0) appendStringInfoString(&buf, " xml"); else { ereport(DEBUG3, (errmsg("tds_fdw: column \"%s\" of table \"%s\" has an untranslatable data type", column_name, table_name) )); appendStringInfoString(&buf, " text"); } /* * Add column_name option so that renaming the foreign table's * column doesn't break the association to the underlying * column. */ appendStringInfoString(&buf, " OPTIONS (column_name "); deparseStringLiteral(&buf, column_name); appendStringInfoChar(&buf, ')'); /* Add DEFAULT if needed */ if (import_default && column_default[0] != '\0') appendStringInfo(&buf, " DEFAULT %s", column_default); /* Add NOT NULL if needed */ if (import_not_null && strcmp(is_nullable, "NO") == 0) appendStringInfoString(&buf, " NOT NULL"); strcpy(prev_table, table_name); break; case BUF_FULL: ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Buffer filled up while getting plan for query") )); case FAIL: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row while getting plan for query") )); default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get plan for query. Unknown return code.") )); } } /* * Add server name and table-level options. We specify remote * schema and table name as options (the latter to ensure that * renaming the foreign table doesn't break the association). */ appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (", quote_identifier(stmt->server_name)); appendStringInfoString(&buf, "schema_name "); deparseStringLiteral(&buf, stmt->remote_schema); appendStringInfoString(&buf, ", table_name "); deparseStringLiteral(&buf, prev_table); appendStringInfoString(&buf, ");"); commands = lappend(commands, pstrdup(buf.data)); } return commands; } List *tdsImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) { TdsFdwOptionSet option_set; List *commands = NIL; bool import_default = false; bool import_not_null = true; bool is_sql_server = true; StringInfoData buf; ListCell *lc; LOGINREC *login; DBPROCESS *dbproc; #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tdsImportForeignSchema") )); #endif /* Parse statement options */ foreach(lc, stmt->options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "import_default") == 0) import_default = defGetBoolean(def); else if (strcmp(def->defname, "import_not_null") == 0) import_not_null = defGetBoolean(def); else ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname))); } tdsGetForeignServerOptionsFromCatalog(serverOid, &option_set); ereport(DEBUG3, (errmsg("tds_fdw: Initiating DB-Library") )); if (dbinit() == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to initialize DB-Library environment") )); goto cleanup_before_init; } dberrhandle(tds_err_handler); if (option_set.msg_handler) { if (strcmp(option_set.msg_handler, "notice") == 0) { dbmsghandle(tds_notice_msg_handler); } else if (strcmp(option_set.msg_handler, "blackhole") == 0) { dbmsghandle(tds_blackhole_msg_handler); } else { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown msg handler: %s.", option_set.msg_handler) )); } } ereport(DEBUG3, (errmsg("tds_fdw: Getting login structure") )); if ((login = dblogin()) == NULL) { ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Failed to initialize DB-Library login structure") )); goto cleanup_before_login; } if (tdsSetupConnection(&option_set, login, &dbproc) != 0) { goto cleanup; } /* Create workspace for strings */ initStringInfo(&buf); /* Determine server: is MS Sql Server or Sybase */ appendStringInfoString(&buf, "SELECT CHARINDEX('Microsoft', @@version) AS is_sql_server"); if (!tdsExecuteQuery(buf.data, dbproc)) ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Failed to check server version") )); else { RETCODE erc; int ret_code, is_sql_server_pos; erc = dbbind(dbproc, 1, INTBIND, sizeof(int), (BYTE *) &is_sql_server_pos); if (erc == FAIL) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to bind results for column \"is_sql_server\" to a variable.") )); } /* Process result */ ret_code = dbnextrow(dbproc); if (ret_code == NO_MORE_ROWS) ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Failed to check server version") )); switch (ret_code) { case REG_ROW: ereport(DEBUG3, (errmsg("tds_fdw: is_sql_server %d", is_sql_server_pos) )); if (is_sql_server_pos == 0) is_sql_server = false; break; case BUF_FULL: ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("Buffer filled up while getting plan for query") )); case FAIL: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get row while getting plan for query") )); default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("Failed to get plan for query. Unknown return code.") )); } } if (is_sql_server) commands = tdsImportSqlServerSchema(stmt, dbproc, option_set, import_default, import_not_null); else commands = tdsImportSybaseSchema(stmt, dbproc, option_set, import_default, import_not_null); cleanup: dbclose(dbproc); dbloginfree(login); cleanup_before_login: dbexit(); cleanup_before_init: ; #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tdsImportForeignSchema") )); #endif return commands; } #endif /* IMPORT_API */ char *tds_err_msg(int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) { StringInfoData buf; initStringInfo(&buf); appendStringInfo( &buf, "DB-Library error: DB #: %i, DB Msg: %s, OS #: %i, OS Msg: %s, Level: %i", dberr, dberrstr ? dberrstr : "", oserr, oserrstr ? oserrstr : "", severity ); return buf.data; } int tds_err_capture(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) { last_error_message = tds_err_msg(severity, dberr, oserr, dberrstr, oserrstr); return INT_CANCEL; } int tds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tds_err_handler") )); #endif ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("%s", tds_err_msg(severity, dberr, oserr, dberrstr, oserrstr)) )); return INT_CANCEL; } int tds_notice_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *svr_name, char *proc_name, int line) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tds_notice_msg_handler") )); #endif ereport(NOTICE, (errmsg("DB-Library notice: Msg #: %ld, Msg state: %i, Msg: %s, Server: %s, Process: %s, Line: %i, Level: %i", (long)msgno, msgstate, msgtext, svr_name, proc_name, line, severity) )); #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tds_notice_msg_handler") )); #endif return 0; } int tds_blackhole_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *svr_name, char *proc_name, int line) { #ifdef DEBUG ereport(NOTICE, (errmsg("----> starting tds_blackhole_msg_handler") )); #endif #ifdef DEBUG ereport(NOTICE, (errmsg("----> finishing tds_blackhole_msg_handler") )); #endif return 0; } tds_fdw-2.0.3/tds_fdw.control000066400000000000000000000015311432462773100162130ustar00rootroot00000000000000#*------------------------------------------------------------------ # # Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) # # Author: Geoff Montee # Name: tds_fdw # File: tds_fdw/tds_fdw.control # # Description: # This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, # such as Sybase databases and Microsoft SQL server. # # This foreign data wrapper requires requires a library that uses the DB-Library interface, # such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not # the proprietary implementations of DB-Library. #---------------------------------------------------------------------------- comment = 'Foreign data wrapper for querying a TDS database (Sybase or Microsoft SQL Server)' default_version = '2.0.3' module_pathname = '$libdir/tds_fdw' relocatable = true tds_fdw-2.0.3/tests/000077500000000000000000000000001432462773100143215ustar00rootroot00000000000000tds_fdw-2.0.3/tests/.gitignore000066400000000000000000000000121432462773100163020ustar00rootroot00000000000000lib/*.pyc tds_fdw-2.0.3/tests/README.md000066400000000000000000000074611432462773100156100ustar00rootroot00000000000000# Testing scripts Testing should follow that workflow : * First build a MSSQL Server 1. Create the server (local, container, VM or azure) 2. Create a testing database with a proper user and access privileges 3. Run `mssql-tests.py` against that server. * Next, build a PostgreSQL Server 1. Create the server (on your machine, with docker o rVM) 2. Compile and install `tds_fdw` extension 3. Create a testing database and schema with proper user and access privilege 4. On that database you'll have first to install `tds_fdw`: `CREATE EXTENSION tds_fdw;` 5. You can run `postgresql_test.py` # Debugging It may be interesting to build a full setup for debugging purpose and use tests to check if anythong regresses. For this, you can use `--debugging` parameter at `postgresql-tests.py` launch time. The test program will stop just after connection creation and give you the backend PID used for testing. This will allow you to connect gdb in another shell session (`gdb --pid=`). Once connected with gdb, just put breakpoints where you need and run `cont`. Then you can press any key in the test shell script to start testing. Also, in case of test failure, `--debugging` will allow to define quite precisely where the script crashed using psycopg2 `Diagnostics` class information and will give the corresponding SQL injected in PostgreSQL. # Adding or modifying tests There are two folders where tests are added: * `tests/mssql` contains the tests to interact with a MSSQL server using `mssql-tests.py`. Such tests are, normally, used to create stuff required for the PostgreSQL test themselves. * `tests/postgresql` contains the test to interact with a PostgreSQL server using `postgresql-tests.py`. Such tests are, normally, used to test the `tds_fdw` functionalities. Each test is made up of two files, with the same name and different extension in this format: ``` XXX_description ``` For example: `000_my_test.json` and `000_my_test.sql`. **Rule1:** `XXX` is used to provide an order to the scripts. **Rule2:** If a script creates an item, or adds a row, it must assume that such item or row exists already, and handle it (for example dropping the table before creating it) ## The JSON file Always has the following format: ``` { "test_desc" : "", "server" : { "version" : { "min" : "", "max" : "" } } } ``` * `test_desc` can be any arbitrary string describing the test. * `min` and `max` are version the for mat `X.W[.Y[.Z]]` for MSSQL and PostgreSQL respectively. * `min` is mandatory, as minimum `7.0.623` for MSSQL (MSSQL 7.0 RTM) and `9.2.0` for PostgreSQL. * `max` is also mandatory, but it can be an empty string if the test supports up to the most recent MSSQL or PostgreSQL version. You can check the list of versions for [MSSQL](https://sqlserverbuilds.blogspot.com/) (format `X.Y.Z` and `X.Y.W.Z`), [PostgreSQL](https://www.postgresql.org/docs/release/) (formats `X.Y.Z` and `X.Y`), to adjust the `min` and `max` values as needed. To validate the JSON file, you can use the script `validate-test-json`. ## The SQL file It is a regular SQL file for one or more queries for either MSSQL or PostgreSQL. There are several variables that can be used and will be placed by the testing scripts. For the MSSQL scripts, the values come from the `mssql-tests.py` parameters: * `@SCHEMANAME`: The MSSQL schema name. For the PostgreSQL scripts the values come from the `postgresql-tests.py` parameters: * `@PSCHEMANAME`: The PostgreSQL schema name * `@PUSER`: The PostgreSQL user * `@MSERVER`: The MSSQL Server * `@MPORT`: The MSSQL port * `@MUSER`: The MSSQL user * `@MPASSWORD`: The MSSQL password * `@MDATABASE`: The MSSQL database * `@MSCHEMANAME`: The MSSQL schema name * `@TDSVERSION`: The TDS version tds_fdw-2.0.3/tests/lib/000077500000000000000000000000001432462773100150675ustar00rootroot00000000000000tds_fdw-2.0.3/tests/lib/__init__.py000066400000000000000000000000001432462773100171660ustar00rootroot00000000000000tds_fdw-2.0.3/tests/lib/messages.py000066400000000000000000000026751432462773100172620ustar00rootroot00000000000000class bcolors: BOLDRED = '\033[1;31m' BOLDGREEN = '\033[1;32m' BOLDYELLOW = '\033[1;33m' BOLDCYAN = '\033[1;36m' BOLDPURPLE = '\033[1;35m' RESET = '\033[0m' def print_error(msg): """Print an error message in red""" print(bcolors.BOLDRED + "[ERROR] %s" % msg + bcolors.RESET) def print_warning(msg): """Print a warning message in yellow""" print(bcolors.BOLDYELLOW + "[WARNING] %s" % msg + bcolors.RESET) def print_ok(msg): """Print an ok message in green""" print(bcolors.BOLDGREEN + "[OK] %s" % msg + bcolors.RESET) def print_info(msg): """Print an info message in cyan""" print(bcolors.BOLDCYAN + "[INFO] %s" % msg + bcolors.RESET) def print_report(total, ok, error): """Print a test report""" if error != 0: print_error("=========== TEST REPORT ==============") print_error(" OK : %s" % ok) print_error(" ERROR: %s" % error) print_error(" Total: %s" % total) else: print_ok("=========== TEST REPORT ==============") print_ok(" OK : %s" % ok) print_ok(" ERROR: %s" % error) print_ok(" Total: %s" % total) def print_usage_error(script, error): """Print script's usage and an error Keyword arguments: script -- A string with the script filename (without path) error -- A string with an error """ print('Usage: %s ' % script) print('') print('%s: error: %s' % (script, error)) tds_fdw-2.0.3/tests/lib/tests.py000066400000000000000000000146241432462773100166120ustar00rootroot00000000000000from glob import glob from json import load from lib.messages import print_error, print_info from os import listdir from os.path import basename, isfile, realpath from re import match from psycopg2.extensions import Diagnostics def version_to_array(version, dbtype): """ Convert a version string to a version array, or return an empty string if the original string was empty Keyword arguments: version -- A string with a dot separated version dbtype -- A string with the database type (postgresql|mssql) """ if version != '': try: version = version.decode('utf-8') except (UnicodeDecodeError, AttributeError): pass # Cleanup version, since Ubuntu decided to add their own # versioning starting with PostgreSQL 10.2: # '10.2 (Ubuntu 10.2-1.pgdg14.04+1' instead of '10.2' version = match('(\d[\.\d]+).*', version).group(1) version = version.split('.') for i in range(0, len(version)): version[i] = int(version[i]) # To be able to work with MSSQL 2000 and 7.0, # see https://sqlserverbuilds.blogspot.ru/ if dbtype == 'mssql': if len(version) == 3: version.append(0) else: version = [] return(version) def check_ver(conn, min_ver, max_ver, dbtype): """ Check SQL server version against test required max and min versions. Keyword arguments: conn -- A db connection (according to Python DB API v2.0) min_ver -- A string with the minimum version required max_ver -- A string with the maximum version required dbtype -- A string with the database type (postgresql|mssql) """ cursor = conn.cursor() if dbtype == 'postgresql': sentence = 'SELECT setting FROM pg_settings WHERE name = \'server_version\';' elif dbtype == 'mssql': sentence = 'SELECT serverproperty(\'ProductVersion\');' cursor.execute(sentence) server_ver = cursor.fetchone()[0] cursor.close() min_ver = version_to_array(min_ver, dbtype) max_ver = version_to_array(max_ver, dbtype) server_ver = version_to_array(server_ver, dbtype) if server_ver >= min_ver and (server_ver <= max_ver or len(max_ver) == 0): return(True) else: return(False) def get_logs_path(conn, dbtype): """ Get PostgreSQL logs Keyword arguments: conn -- A db connection (according to Python DB API v2.0) dbtye -- A string with the database type (postgresql|mssql). mssql will return an empty a array. """ logs = [] if dbtype == 'mssql': return(logs) cursor = conn.cursor() try: cursor.execute("SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_directory';") data_dir = cursor.fetchone()[0] except TypeError: print_error("The user does not have SUPERUSER access to PostgreSQL.") print_error("Cannot access pg_catalog.pg_settings required values, so logs cannot be found") return(logs) cursor.execute("SELECT setting FROM pg_catalog.pg_settings WHERE name = 'log_directory';") log_dir = cursor.fetchone()[0] if log_dir[0] != '/': log_dir = "%s/%s" % (data_dir, log_dir) cursor.execute("SELECT setting FROM pg_catalog.pg_settings WHERE name = 'logging_collector';") # No logging collector, add stdout from postmaster (assume stderr is redirected to stdout) if cursor.fetchone()[0] == 'off': with open("%s/postmaster.pid" % data_dir, "r") as f: postmaster_pid = f.readline().rstrip('\n') postmaster_log = "/proc/%s/fd/1" % postmaster_pid if isfile(postmaster_log): logs.append(realpath(postmaster_log)) # Logging collector enabled else: # Add stdout from logger (assume stderr is redirected to stdout) pids = [pid for pid in listdir('/proc') if pid.isdigit()] for pid in pids: try: cmdline = open('/proc/' + pid + '/cmdline', 'r').read() if 'postgres: logger' in cmdline: logger_log = "/proc/%s/fd/2" % pid if isfile(logger_log): logs.append(realpath(logger_log)) except IOError: # proc has already terminated continue # Add all files from log_dir for f in listdir(log_dir): logs.append(realpath(log_dir + '/' + f)) return(logs) def run_tests(path, conn, replaces, dbtype, debugging=False, unattended_debugging=False): """Run SQL tests over a connection, returns a dict with results. Keyword arguments: path -- String with the path having the SQL files for tests conn -- A db connection (according to Python DB API v2.0) replaces -- A dict with replaces to perform at testing code dbtype -- A string with the database type (postgresql|mssql) """ files = sorted(glob(path)) tests = {'total': 0, 'ok': 0, 'errors': 0} for fname in files: test_file = open('%s.json' % fname.rsplit('.', 1)[0], 'r') test_properties = load(test_file) test_desc = test_properties['test_desc'] test_number = basename(fname).split('_')[0] req_ver = test_properties['server']['version'] if check_ver(conn, req_ver['min'], req_ver['max'], dbtype): tests['total'] += 1 f = open(fname, 'r') sentence = f.read() for key, elem in replaces.items(): sentence = sentence.replace(key, elem) print_info("%s: Testing %s" % (test_number, test_desc)) if debugging or unattended_debugging: print_info("Query:") print(sentence) try: cursor = conn.cursor() cursor.execute(sentence) conn.commit() cursor.close() tests['ok'] += 1 except Exception as e: print_error("Error running %s (%s)" % (test_desc, fname)) print_error("Query:") print(sentence) try: print_error(e.pgcode) print_error(e.pgerror) for att in [member for member in dir(Diagnostics) if not member.startswith("__")]: print_error("%s: %s"%(att, getattr(e.diag,att))) except: print_error(e) conn.rollback() tests['errors'] += 1 f.close() return(tests) tds_fdw-2.0.3/tests/mssql-tests.py000077500000000000000000000055651432462773100172100ustar00rootroot00000000000000#!/usr/bin/env python from lib.messages import print_error, print_info, print_ok, print_report from lib.messages import print_usage_error, print_warning from lib.tests import run_tests from optparse import OptionParser from os import path DEFAULT_TDS_VERSION="7.1" try: from pymssql import connect except: print_error( "pymssql library not available, please install it before usage!") exit(1) def parse_options(): """Parse and validate options. Returns a dict with all the options.""" usage = "%prog " description = ('Run MSSQL tests from sql files') parser = OptionParser(usage=usage, description=description) parser.add_option('--server', action='store', help='MSSQL/Azure server') parser.add_option('--port', action='store', help='MSSQL/Azure TCP port') parser.add_option('--database', action='store', help='Database name') parser.add_option('--schema', action='store', help='Schema to use (and create if needed)') parser.add_option('--username', action='store', help='Username to connect') parser.add_option('--password', action='store', help='Passord to connect') parser.add_option('--azure', action='store_true', default=False, help='If present, will connect as Azure, otherwise as ' 'standard MSSQL') parser.add_option('--tds_version', action="store", default=DEFAULT_TDS_VERSION, help='Specifies th TDS protocol version, default="%s"'%DEFAULT_TDS_VERSION) (options, args) = parser.parse_args() # Check for test parameters if (options.server is None or options.port is None or options.database is None or options.schema is None or options.username is None or options.password is None): print_error("Insufficient parameters, check help (-h)") exit(4) else: if options.azure is True: options.username = '%s@%s' % ( options.username, options.server.split('.')[0]) return(options) def main(): try: args = parse_options() except Exception as e: print_usage_error(path.basename(__file__), e) exit(2) try: conn = connect(server=args.server, user=args.username, password=args.password, database=args.database, port=args.port, tds_version=args.tds_version) replaces = {'@SCHEMANAME': args.schema} tests = run_tests('tests/mssql/*.sql', conn, replaces, 'mssql') print_report(tests['total'], tests['ok'], tests['errors']) if tests['errors'] != 0: exit(5) else: exit(0) except Exception as e: print_error(e) exit(3) if __name__ == "__main__": main() tds_fdw-2.0.3/tests/postgresql-tests.py000077500000000000000000000131601432462773100202420ustar00rootroot00000000000000#!/usr/bin/env python from lib.messages import print_error, print_info, print_ok, print_report from lib.messages import print_usage_error, print_warning from lib.tests import get_logs_path, run_tests from optparse import OptionParser from os import path try: from psycopg2 import connect except: print_error( "psycopg2 library not available, please install it before usage!") exit(1) DEFAULT_TDS_VERSION="7.1" def parse_options(): """Parse and validate options. Returns a dict with all the options.""" usage = "%prog " description = ('Run PostgreSQL tests from sql files') parser = OptionParser(usage=usage, description=description) parser.add_option('--postgres_server', action='store', help='PostgreSQL server') parser.add_option('--postgres_port', action='store', help='PostgreSQL TCP port') parser.add_option('--postgres_database', action='store', help='Database name') parser.add_option('--postgres_schema', action='store', help='Schema to use (and create if needed)') parser.add_option('--postgres_username', action='store', help='Username to connect') parser.add_option('--postgres_password', action='store', help='Passord to connect') parser.add_option('--mssql_server', action='store', help='MSSQL/Azure server') parser.add_option('--mssql_port', action='store', help='MSSQL/Azure TCP port') parser.add_option('--mssql_database', action='store', help='Database name') parser.add_option('--mssql_schema', action='store', help='Schema to use (and create if needed)') parser.add_option('--mssql_username', action='store', help='Username to connect') parser.add_option('--mssql_password', action='store', help='Passord to connect') parser.add_option('--azure', action="store_true", default=False, help='If present, will connect as Azure, otherwise as ' 'standard MSSQL') parser.add_option('--debugging', action="store_true", default=False, help='If present, will pause after backend PID and before ' 'launching tests (so gdb can be attached. It will also ' 'display contextual SQL queries') parser.add_option('--unattended_debugging', action="store_true", default=False, help='Same as --debugging, but without pausing and printing ' 'PostgreSQL logs at the end (useful for CI)') parser.add_option('--tds_version', action="store", default=DEFAULT_TDS_VERSION, help='Specifies th TDS protocol version, default="%s"'%DEFAULT_TDS_VERSION) (options, args) = parser.parse_args() # Check for test parameters if (options.postgres_server is None or options.postgres_port is None or options.postgres_database is None or options.postgres_schema is None or options.postgres_username is None or options.postgres_password is None or options.mssql_server is None or options.mssql_port is None or options.mssql_database is None or options.mssql_schema is None or options.mssql_username is None or options.mssql_password is None): print_error("Insufficient parameters, check help (-h)") exit(4) else: if options.azure is True: options.mssql_username = '%s@%s' % (options.mssql_username, options.mssql_server.split('.')[0]) return(options) def main(): try: args = parse_options() except Exception as e: print_usage_error(path.basename(__file__), e) exit(2) try: conn = connect(host=args.postgres_server, user=args.postgres_username, password=args.postgres_password, database=args.postgres_database, port=args.postgres_port) if args.debugging or args.unattended_debugging: curs = conn.cursor() curs.execute("SELECT pg_backend_pid()") print("Backend PID = %d"%curs.fetchone()[0]) if not args.unattended_debugging: print("Press any key to launch tests.") raw_input() replaces = {'@PSCHEMANAME': args.postgres_schema, '@PUSER': args.postgres_username, '@MSERVER': args.mssql_server, '@MPORT': args.mssql_port, '@MUSER': args.mssql_username, '@MPASSWORD': args.mssql_password, '@MDATABASE': args.mssql_database, '@MSCHEMANAME': args.mssql_schema, '@TDSVERSION' : args.tds_version} tests = run_tests('tests/postgresql/*.sql', conn, replaces, 'postgresql', args.debugging, args.unattended_debugging) print_report(tests['total'], tests['ok'], tests['errors']) logs = get_logs_path(conn, 'postgresql') if (tests['errors'] != 0 or args.unattended_debugging) and not args.debugging: for fpath in logs: print_info("=========== Content of %s ===========" % fpath) with open(fpath, "r") as f: print(f.read()) if tests['errors'] != 0: exit(5) else: exit(0) except Exception as e: print_error(e) exit(3) if __name__ == "__main__": main() tds_fdw-2.0.3/tests/tests/000077500000000000000000000000001432462773100154635ustar00rootroot00000000000000tds_fdw-2.0.3/tests/tests/mssql/000077500000000000000000000000001432462773100166225ustar00rootroot00000000000000tds_fdw-2.0.3/tests/tests/mssql/000_create_schema.json000066400000000000000000000002261432462773100226570ustar00rootroot00000000000000{ "test_desc" : "schema creation", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/000_create_schema.sql000066400000000000000000000002471432462773100225100ustar00rootroot00000000000000IF NOT EXISTS ( SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE schema_name = '@SCHEMANAME') BEGIN EXEC sp_executesql N'CREATE SCHEMA @SCHEMANAME' END tds_fdw-2.0.3/tests/tests/mssql/001_create_tinyint_min_table.json000066400000000000000000000002611432462773100251270ustar00rootroot00000000000000{ "test_desc" : "table creation with tinyint, minimum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/001_create_tinyint_min_table.sql000066400000000000000000000004021432462773100247520ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.tinyint_min', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.tinyint_min; CREATE TABLE @SCHEMANAME.tinyint_min ( id int primary key, value tinyint ); INSERT INTO @SCHEMANAME.tinyint_min (id, value) VALUES (1, 0); tds_fdw-2.0.3/tests/tests/mssql/002_create_tinyint_max_table.json000066400000000000000000000002611432462773100251320ustar00rootroot00000000000000{ "test_desc" : "table creation with tinyint, maximum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/002_create_tinyint_max_table.sql000066400000000000000000000004041432462773100247570ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.tinyint_max', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.tinyint_max; CREATE TABLE @SCHEMANAME.tinyint_max ( id int primary key, value tinyint ); INSERT INTO @SCHEMANAME.tinyint_max (id, value) VALUES (1, 255); tds_fdw-2.0.3/tests/tests/mssql/003_create_smallint_min_table.json000066400000000000000000000002621432462773100252570ustar00rootroot00000000000000{ "test_desc" : "table creation with smallint, minimum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/003_create_smallint_min_table.sql000066400000000000000000000004141432462773100251040ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.smallint_min', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.smallint_min; CREATE TABLE @SCHEMANAME.smallint_min ( id int primary key, value smallint ); INSERT INTO @SCHEMANAME.smallint_min (id, value) VALUES (1, -32768); tds_fdw-2.0.3/tests/tests/mssql/004_create_smallint_max_table.json000066400000000000000000000002621432462773100252620ustar00rootroot00000000000000{ "test_desc" : "table creation with smallint, maximum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/004_create_smallint_max_table.sql000066400000000000000000000004131432462773100251060ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.smallint_max', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.smallint_max; CREATE TABLE @SCHEMANAME.smallint_max ( id int primary key, value smallint ); INSERT INTO @SCHEMANAME.smallint_max (id, value) VALUES (1, 32767); tds_fdw-2.0.3/tests/tests/mssql/005_create_int_min_table.json000066400000000000000000000002551432462773100242320ustar00rootroot00000000000000{ "test_desc" : "table creation with int, minimum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/005_create_int_min_table.sql000066400000000000000000000003701432462773100240560ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.int_min', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.int_min; CREATE TABLE @SCHEMANAME.int_min ( id int primary key, value int ); INSERT INTO @SCHEMANAME.int_min (id, value) VALUES (1, -2147483648); tds_fdw-2.0.3/tests/tests/mssql/006_create_int_max_table.json000066400000000000000000000002551432462773100242350ustar00rootroot00000000000000{ "test_desc" : "table creation with int, maximum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/006_create_int_max_table.sql000066400000000000000000000003671432462773100240670ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.int_max', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.int_max; CREATE TABLE @SCHEMANAME.int_max ( id int primary key, value int ); INSERT INTO @SCHEMANAME.int_max (id, value) VALUES (1, 2147483647); tds_fdw-2.0.3/tests/tests/mssql/007_create_bigint_min_table.json000066400000000000000000000002601432462773100247120ustar00rootroot00000000000000{ "test_desc" : "table creation with bigint, minimum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/007_create_bigint_min_table.sql000066400000000000000000000004201432462773100245360ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.bigint_min', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.bigint_min; CREATE TABLE @SCHEMANAME.bigint_min ( id int primary key, value bigint ); INSERT INTO @SCHEMANAME.bigint_min (id, value) VALUES (1, -9223372036854775808); tds_fdw-2.0.3/tests/tests/mssql/008_create_bigint_max_table.json000066400000000000000000000002601432462773100247150ustar00rootroot00000000000000{ "test_desc" : "table creation with bigint, maximum value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/008_create_bigint_max_table.sql000066400000000000000000000004171432462773100245470ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.bigint_max', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.bigint_max; CREATE TABLE @SCHEMANAME.bigint_max ( id int primary key, value bigint ); INSERT INTO @SCHEMANAME.bigint_max (id, value) VALUES (1, 9223372036854775807); tds_fdw-2.0.3/tests/tests/mssql/009_create_decimal_table.json000066400000000000000000000002541432462773100241760ustar00rootroot00000000000000{ "test_desc" : "table creation with decimal data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/009_create_decimal_table.sql000066400000000000000000000003761432462773100240310ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.decimal', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.decimal; CREATE TABLE @SCHEMANAME.decimal ( id int primary key, value decimal(9, 2) ); INSERT INTO @SCHEMANAME.decimal (id, value) VALUES (1, 1000.01); tds_fdw-2.0.3/tests/tests/mssql/010_create_float4_table.json000066400000000000000000000002701432462773100237570ustar00rootroot00000000000000{ "test_desc" : "table creation with float data type, 4 bytes size", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/010_create_float4_table.sql000066400000000000000000000003661432462773100236130ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.float4', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.float4; CREATE TABLE @SCHEMANAME.float4 ( id int primary key, value float(24) ); INSERT INTO @SCHEMANAME.float4 (id, value) VALUES (1, 1000.01); tds_fdw-2.0.3/tests/tests/mssql/011_create_float8_table.json000066400000000000000000000002711432462773100237650ustar00rootroot00000000000000{ "test_desc" : "table creation with float data type, 8 bytes value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/011_create_float8_table.sql000066400000000000000000000003661432462773100236200ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.float8', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.float8; CREATE TABLE @SCHEMANAME.float8 ( id int primary key, value float(53) ); INSERT INTO @SCHEMANAME.float8 (id, value) VALUES (1, 1000.01); tds_fdw-2.0.3/tests/tests/mssql/012_create_date_table.json000066400000000000000000000002511432462773100235040ustar00rootroot00000000000000{ "test_desc" : "table creation with date data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/012_create_date_table.sql000066400000000000000000000003561432462773100233400ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.date', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.date; CREATE TABLE @SCHEMANAME.date ( id int primary key, value date ); INSERT INTO @SCHEMANAME.date (id, value) VALUES (1, '2015-10-22'); tds_fdw-2.0.3/tests/tests/mssql/013_create_time_table.json000066400000000000000000000002511432462773100235260ustar00rootroot00000000000000{ "test_desc" : "table creation with time data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/013_create_time_table.sql000066400000000000000000000003541432462773100233600ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.time', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.time; CREATE TABLE @SCHEMANAME.time ( id int primary key, value time ); INSERT INTO @SCHEMANAME.time (id, value) VALUES (1, '11:01:02'); tds_fdw-2.0.3/tests/tests/mssql/014_create_datetime_table.json000066400000000000000000000002551432462773100243710ustar00rootroot00000000000000{ "test_desc" : "table creation with datetime data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/014_create_datetime_table.sql000066400000000000000000000004131432462773100242130ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.datetime', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.datetime; CREATE TABLE @SCHEMANAME.datetime ( id int primary key, value datetime ); INSERT INTO @SCHEMANAME.datetime (id, value) VALUES (1, '2015-10-22 11:01:02'); tds_fdw-2.0.3/tests/tests/mssql/015_create_datetime2_table.json000066400000000000000000000002561432462773100244550ustar00rootroot00000000000000{ "test_desc" : "table creation with datetime2 data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/015_create_datetime2_table.sql000066400000000000000000000004201432462773100242740ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.datetime2', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.datetime2; CREATE TABLE @SCHEMANAME.datetime2 ( id int primary key, value datetime2 ); INSERT INTO @SCHEMANAME.datetime2 (id, value) VALUES (1, '2015-10-22 11:01:02'); tds_fdw-2.0.3/tests/tests/mssql/016_create_datetimeoffset_table.json000066400000000000000000000002631432462773100256010ustar00rootroot00000000000000{ "test_desc" : "table creation with datetimeoffset data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/016_create_datetimeoffset_table.sql000066400000000000000000000004601432462773100254260ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.datetimeoffset', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.datetimeoffset; CREATE TABLE @SCHEMANAME.datetimeoffset ( id int primary key, value datetimeoffset ); INSERT INTO @SCHEMANAME.datetimeoffset (id, value) VALUES (1, '2015-10-22 11:01:02 -07:00'); tds_fdw-2.0.3/tests/tests/mssql/017_create_char_table.json000066400000000000000000000002511432462773100235110ustar00rootroot00000000000000{ "test_desc" : "table creation with char data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/017_create_char_table.sql000066400000000000000000000003721432462773100233430ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.char', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.char; CREATE TABLE @SCHEMANAME.char ( id int primary key, value char(8000) ); INSERT INTO @SCHEMANAME.char (id, value) VALUES (1, 'this is a string'); tds_fdw-2.0.3/tests/tests/mssql/018_create_varchar_table.json000066400000000000000000000002541432462773100242260ustar00rootroot00000000000000{ "test_desc" : "table creation with varchar data type", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/018_create_varchar_table.sql000066400000000000000000000004111432462773100240470ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.varchar', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.varchar; CREATE TABLE @SCHEMANAME.varchar ( id int primary key, value varchar(8000) ); INSERT INTO @SCHEMANAME.varchar (id, value) VALUES (1, 'this is a string'); tds_fdw-2.0.3/tests/tests/mssql/019_create_varcharmax_table.json000066400000000000000000000002741432462773100247370ustar00rootroot00000000000000{ "test_desc" : "table creation with varchar data type, maximum length", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/019_create_varcharmax_table.sql000066400000000000000000000004241432462773100245620ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.varcharmax', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.varcharmax; CREATE TABLE @SCHEMANAME.varcharmax ( id int primary key, value varchar(max) ); INSERT INTO @SCHEMANAME.varcharmax (id, value) VALUES (1, 'this is a string'); tds_fdw-2.0.3/tests/tests/mssql/020_create_binary4_table.json000066400000000000000000000002731432462773100241420ustar00rootroot00000000000000{ "test_desc" : "table creation with binary data type, 4 bytes length", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/020_create_binary4_table.sql000066400000000000000000000003751432462773100237730ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.binary4', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.binary4; CREATE TABLE @SCHEMANAME.binary4 ( id int primary key, value binary(4) ); INSERT INTO @SCHEMANAME.binary4 (id, value) VALUES (1, 0x01020304); tds_fdw-2.0.3/tests/tests/mssql/021_create_varbinary4_table.json000066400000000000000000000002761432462773100246570ustar00rootroot00000000000000{ "test_desc" : "table creation with varbinary data type, 4 bytes length", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/021_create_varbinary4_table.sql000066400000000000000000000004141432462773100244770ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.varbinary4', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.varbinary4; CREATE TABLE @SCHEMANAME.varbinary4 ( id int primary key, value varbinary(4) ); INSERT INTO @SCHEMANAME.varbinary4 (id, value) VALUES (1, 0x01020304); tds_fdw-2.0.3/tests/tests/mssql/022_create_varbinarymax_table.json000066400000000000000000000002741432462773100253000ustar00rootroot00000000000000{ "test_desc" : "table creation with varbinary data type, maximum size", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/022_create_varbinarymax_table.sql000066400000000000000000000004261432462773100251250ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.varbinarymax', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.varbinarymax; CREATE TABLE @SCHEMANAME.varbinarymax ( id int primary key, value varbinary(max) ); INSERT INTO @SCHEMANAME.varbinarymax (id, value) VALUES (1, 0x01000304); tds_fdw-2.0.3/tests/tests/mssql/023_create_null_datetime_table.json000066400000000000000000000002711432462773100254210ustar00rootroot00000000000000{ "test_desc" : "table creation with datetime data type, null value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/023_create_null_datetime_table.sql000066400000000000000000000004161432462773100252500ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.null_datetime', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.null_datetime; CREATE TABLE @SCHEMANAME.null_datetime ( id int primary key, value datetime ); INSERT INTO @SCHEMANAME.null_datetime (id, value) VALUES (1, NULL); tds_fdw-2.0.3/tests/tests/mssql/024_create_null_datetime2_table.json000066400000000000000000000002721432462773100255050ustar00rootroot00000000000000{ "test_desc" : "table creation with datetime2 data type, null value", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/024_create_null_datetime2_table.sql000066400000000000000000000004231432462773100253310ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.null_datetime2', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.null_datetime2; CREATE TABLE @SCHEMANAME.null_datetime2 ( id int primary key, value datetime2 ); INSERT INTO @SCHEMANAME.null_datetime2 (id, value) VALUES (1, NULL); tds_fdw-2.0.3/tests/tests/mssql/025_create_match_column_table.json000066400000000000000000000002631432462773100252470ustar00rootroot00000000000000{ "test_desc" : "table creation for automated column matching", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/025_create_match_column_table.sql000066400000000000000000000004271432462773100250770ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.column_match', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.column_match; CREATE TABLE @SCHEMANAME.column_match ( id int primary key, value bigint ); INSERT INTO @SCHEMANAME.column_match (id, value) VALUES (1, 9223372036854775807); tds_fdw-2.0.3/tests/tests/mssql/026_create_column_name_table.json000066400000000000000000000002651432462773100250760ustar00rootroot00000000000000{ "test_desc" : "table creation for column name manual matching", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/026_create_column_name_table.sql000066400000000000000000000004241432462773100247210ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.column_name', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.column_name; CREATE TABLE @SCHEMANAME.column_name ( id int primary key, value bigint ); INSERT INTO @SCHEMANAME.column_name (id, value) VALUES (1, 9223372036854775807); tds_fdw-2.0.3/tests/tests/mssql/027_create_query_option_table.json000066400000000000000000000003021432462773100253270ustar00rootroot00000000000000{ "test_desc" : "table creation for automated column matching (query option)", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/027_create_query_option_table.sql000066400000000000000000000005771432462773100251730ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.query_option', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.query_option; CREATE TABLE @SCHEMANAME.query_option ( id int primary key, data varchar(50), owner varchar(50) ); INSERT INTO @SCHEMANAME.query_option (id, data, owner) VALUES (1, 'geoff''s data', 'geoff'), (2, 'alice''s data', 'alice'), (3, 'bob''s data', 'bob'); tds_fdw-2.0.3/tests/tests/mssql/028_create_view_simple_prerequisites.json000066400000000000000000000002421432462773100267360ustar00rootroot00000000000000{ "test_desc" : "view creation prerequisites", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/028_create_view_simple_prerequisites.sql000066400000000000000000000010141432462773100265620ustar00rootroot00000000000000IF OBJECT_ID('@SCHEMANAME.table_view_simple', 'U') IS NOT NULL DROP TABLE @SCHEMANAME.table_view_simple; CREATE TABLE @SCHEMANAME.table_view_simple ( id int primary key, data varchar(50), owner varchar(50) ); INSERT INTO @SCHEMANAME.table_view_simple (id, data, owner) VALUES (1, 'geoff''s data', 'geoff'), (2, 'alice''s data', 'alice'), (3, 'bob''s data', 'bob'); IF OBJECT_ID('@SCHEMANAME.view_simple', 'V') IS NOT NULL DROP VIEW @SCHEMANAME.view_simple; tds_fdw-2.0.3/tests/tests/mssql/029_create_view_simple.json000066400000000000000000000002351432462773100237550ustar00rootroot00000000000000{ "test_desc" : "view creation (simple)", "server" : { "version" : { "min" : "7.0.623", "max" : "" } } } tds_fdw-2.0.3/tests/tests/mssql/029_create_view_simple.sql000066400000000000000000000001561432462773100236050ustar00rootroot00000000000000CREATE VIEW @SCHEMANAME.view_simple (id, data) AS SELECT id, data FROM @SCHEMANAME.table_view_simple; tds_fdw-2.0.3/tests/tests/postgresql/000077500000000000000000000000001432462773100176665ustar00rootroot00000000000000tds_fdw-2.0.3/tests/tests/postgresql/000_create_schema.json000066400000000000000000000002241432462773100237210ustar00rootroot00000000000000{ "test_desc" : "schema creation", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/000_create_schema.sql000066400000000000000000000001111432462773100235420ustar00rootroot00000000000000DROP SCHEMA IF EXISTS @PSCHEMANAME CASCADE; CREATE SCHEMA @PSCHEMANAME; tds_fdw-2.0.3/tests/tests/postgresql/001_create_server.json000066400000000000000000000002341432462773100237710ustar00rootroot00000000000000{ "test_desc" : "foreign server creation", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/001_create_server.sql000066400000000000000000000003501432462773100236160ustar00rootroot00000000000000DROP SERVER IF EXISTS mssql_svr CASCADE; CREATE SERVER mssql_svr FOREIGN DATA WRAPPER tds_fdw OPTIONS (servername '@MSERVER', port '@MPORT', database '@MDATABASE', tds_version '@TDSVERSION', msg_handler 'blackhole'); tds_fdw-2.0.3/tests/tests/postgresql/002_create_user_mapping.json000066400000000000000000000002321432462773100251530ustar00rootroot00000000000000{ "test_desc" : "user mapping creation", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/002_create_user_mapping.sql000066400000000000000000000002561432462773100250070ustar00rootroot00000000000000DROP USER MAPPING IF EXISTS FOR @PUSER SERVER mssql_svr; CREATE USER MAPPING FOR @PUSER SERVER mssql_svr OPTIONS (username '@MUSER', password '@MPASSWORD'); tds_fdw-2.0.3/tests/tests/postgresql/003_import_schema.json000066400000000000000000000002321432462773100237720ustar00rootroot00000000000000{ "test_desc" : "foreign schema import", "server" : { "version" : { "min" : "9.5.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/003_import_schema.sql000066400000000000000000000001571432462773100236260ustar00rootroot00000000000000IMPORT FOREIGN SCHEMA @MSCHEMANAME FROM SERVER mssql_svr INTO @PSCHEMANAME OPTIONS (import_default 'true'); tds_fdw-2.0.3/tests/tests/postgresql/004_tinyintmin.json000066400000000000000000000002601432462773100233440ustar00rootroot00000000000000{ "test_desc" : "tinyint data type, minimum value (smallint)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/004_tinyintmin.sql000066400000000000000000000005451432462773100232000ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.tinyint_min; CREATE FOREIGN TABLE @PSCHEMANAME.tinyint_min ( id int, value smallint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.tinyint_min', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.tinyint_min; SELECT (value = 0) AS pass FROM @PSCHEMANAME.tinyint_min; tds_fdw-2.0.3/tests/tests/postgresql/005_tinyintmax.json000066400000000000000000000002601432462773100233470ustar00rootroot00000000000000{ "test_desc" : "tinyint data type, maximum value (smallint)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/005_tinyintmax.sql000066400000000000000000000005471432462773100232050ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.tinyint_max; CREATE FOREIGN TABLE @PSCHEMANAME.tinyint_max ( id int, value smallint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.tinyint_max', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.tinyint_max; SELECT (value = 255) AS pass FROM @PSCHEMANAME.tinyint_max; tds_fdw-2.0.3/tests/tests/postgresql/006_smallintmin.json000066400000000000000000000002611432462773100234740ustar00rootroot00000000000000{ "test_desc" : "smallint data type, minimum value (smallint)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/006_smallintmin.sql000066400000000000000000000005571432462773100233320ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.smallint_min; CREATE FOREIGN TABLE @PSCHEMANAME.smallint_min ( id int, value smallint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.smallint_min', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.smallint_min; SELECT (value = -32768) AS pass FROM @PSCHEMANAME.smallint_min; tds_fdw-2.0.3/tests/tests/postgresql/007_smallintmax.json000066400000000000000000000002611432462773100234770ustar00rootroot00000000000000{ "test_desc" : "smallint data type, maximum value (smallint)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/007_smallintmax.sql000066400000000000000000000005561432462773100233340ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.smallint_max; CREATE FOREIGN TABLE @PSCHEMANAME.smallint_max ( id int, value smallint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.smallint_max', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.smallint_max; SELECT (value = 32767) AS pass FROM @PSCHEMANAME.smallint_max; tds_fdw-2.0.3/tests/tests/postgresql/008_intmin.json000066400000000000000000000002411432462773100224430ustar00rootroot00000000000000{ "test_desc" : "int data type, minimum value", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/008_intmin.sql000066400000000000000000000005261432462773100222770ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.int_min; CREATE FOREIGN TABLE @PSCHEMANAME.int_min ( id int, value int ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.int_min', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.int_min; SELECT (value = -2147483648) AS pass FROM @PSCHEMANAME.int_min; tds_fdw-2.0.3/tests/tests/postgresql/009_intmax.json000066400000000000000000000002411432462773100224460ustar00rootroot00000000000000{ "test_desc" : "int data type, maximum value", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/009_intmax.sql000066400000000000000000000005251432462773100223010ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.int_max; CREATE FOREIGN TABLE @PSCHEMANAME.int_max ( id int, value int ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.int_max', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.int_max; SELECT (value = 2147483647) AS pass FROM @PSCHEMANAME.int_max; tds_fdw-2.0.3/tests/tests/postgresql/010_bigintmin.json000066400000000000000000000002441432462773100231210ustar00rootroot00000000000000{ "test_desc" : "bigint data type, minimum value", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/010_bigintmin.sql000066400000000000000000000005611432462773100227510ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.bigint_min; CREATE FOREIGN TABLE @PSCHEMANAME.bigint_min ( id int, value bigint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.bigint_min', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.bigint_min; SELECT (value = -9223372036854775808) AS pass FROM @PSCHEMANAME.bigint_min; tds_fdw-2.0.3/tests/tests/postgresql/011_bigintmax.json000066400000000000000000000002441432462773100231240ustar00rootroot00000000000000{ "test_desc" : "bigint data type, maximum value", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/011_bigintmax.sql000066400000000000000000000005601432462773100227530ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.bigint_max; CREATE FOREIGN TABLE @PSCHEMANAME.bigint_max ( id int, value bigint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.bigint_max', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.bigint_max; SELECT (value = 9223372036854775807) AS pass FROM @PSCHEMANAME.bigint_max; tds_fdw-2.0.3/tests/tests/postgresql/012_decimal.json000066400000000000000000000002401432462773100225350ustar00rootroot00000000000000{ "test_desc" : "decimal data type (numeric)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/012_decimal.sql000066400000000000000000000004401432462773100223650ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.decimal; CREATE FOREIGN TABLE @PSCHEMANAME.decimal ( id int, value numeric(9, 2) ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.decimal', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.decimal; tds_fdw-2.0.3/tests/tests/postgresql/013_float4.json000066400000000000000000000002511432462773100223330ustar00rootroot00000000000000{ "test_desc" : "float data type, 4 bytes size (real)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/013_float4.sql000066400000000000000000000004231432462773100221620ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.float4; CREATE FOREIGN TABLE @PSCHEMANAME.float4 ( id int, value real ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.float4', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.float4; tds_fdw-2.0.3/tests/tests/postgresql/014_float8.json000066400000000000000000000002661432462773100223460ustar00rootroot00000000000000{ "test_desc" : "float data type, 8 bits value (double precission)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/014_float8.sql000066400000000000000000000004371432462773100221740ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.float8; CREATE FOREIGN TABLE @PSCHEMANAME.float8 ( id int, value double precision ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.float8', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.float8; tds_fdw-2.0.3/tests/tests/postgresql/015_date.json000066400000000000000000000002231432462773100220600ustar00rootroot00000000000000{ "test_desc" : "date data type", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/015_date.sql000066400000000000000000000004131432462773100217070ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.date; CREATE FOREIGN TABLE @PSCHEMANAME.date ( id int, value date ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.date', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.date; tds_fdw-2.0.3/tests/tests/postgresql/016_time.json000066400000000000000000000002541432462773100221060ustar00rootroot00000000000000{ "test_desc" : "time data type (time without time zone)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/016_time.sql000066400000000000000000000004351432462773100217350ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.time; CREATE FOREIGN TABLE @PSCHEMANAME.time ( id int, value time without time zone ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.time', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.time; tds_fdw-2.0.3/tests/tests/postgresql/017_datetime.json000066400000000000000000000002651432462773100227470ustar00rootroot00000000000000{ "test_desc" : "datetime data type (timestamp without time zone)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/017_datetime.sql000066400000000000000000000004621432462773100225740ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.datetime; CREATE FOREIGN TABLE @PSCHEMANAME.datetime ( id int, value timestamp without time zone ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.datetime', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.datetime; tds_fdw-2.0.3/tests/tests/postgresql/018_datetime2.json000066400000000000000000000002661432462773100230330ustar00rootroot00000000000000{ "test_desc" : "datetime2 data type (timestamp without time zone)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/018_datetime2.sql000066400000000000000000000004661432462773100226630ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.datetime2; CREATE FOREIGN TABLE @PSCHEMANAME.datetime2 ( id int, value timestamp without time zone ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.datetime2', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.datetime2; tds_fdw-2.0.3/tests/tests/postgresql/019_datetimeoffset.json000066400000000000000000000002511432462773100241530ustar00rootroot00000000000000{ "test_desc" : "datatimeoffset data type (time zone)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/019_datetimeoffset.sql000066400000000000000000000005071432462773100240050ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.datetimeoffset; CREATE FOREIGN TABLE @PSCHEMANAME.datetimeoffset ( id int, value timestamp with time zone ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.datetimeoffset', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.datetimeoffset; tds_fdw-2.0.3/tests/tests/postgresql/020_char.json000066400000000000000000000002231432462773100220540ustar00rootroot00000000000000{ "test_desc" : "char data type", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/020_char.sql000066400000000000000000000004211432462773100217020ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.char; CREATE FOREIGN TABLE @PSCHEMANAME.char ( id int, value char(8000) ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.char', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.char; tds_fdw-2.0.3/tests/tests/postgresql/021_varchar.json000066400000000000000000000002261432462773100225710ustar00rootroot00000000000000{ "test_desc" : "varchar data type", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/021_varchar.sql000066400000000000000000000004401432462773100224150ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varchar; CREATE FOREIGN TABLE @PSCHEMANAME.varchar ( id int, value varchar(8000) ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.varchar', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.varchar; tds_fdw-2.0.3/tests/tests/postgresql/022_varcharmax.json000066400000000000000000000002441432462773100233000ustar00rootroot00000000000000{ "test_desc" : "varchar data type, maximum size", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/022_varcharmax.sql000066400000000000000000000004461432462773100231320ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varcharmax; CREATE FOREIGN TABLE @PSCHEMANAME.varcharmax ( id int, value varchar ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.varcharmax', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.varcharmax; tds_fdw-2.0.3/tests/tests/postgresql/023_binary4.json000066400000000000000000000002531432462773100225150ustar00rootroot00000000000000{ "test_desc" : "binary data type, 4 bytes size (bytea)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/023_binary4.sql000066400000000000000000000004301432462773100223400ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.binary4; CREATE FOREIGN TABLE @PSCHEMANAME.binary4 ( id int, value bytea ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.binary4', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.binary4; tds_fdw-2.0.3/tests/tests/postgresql/024_varbinary4.json000066400000000000000000000002561432462773100232320ustar00rootroot00000000000000{ "test_desc" : "varbinary data type, 4 bytes size (bytea)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/024_varbinary4.sql000066400000000000000000000004441432462773100230570ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varbinary4; CREATE FOREIGN TABLE @PSCHEMANAME.varbinary4 ( id int, value bytea ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.varbinary4', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.varbinary4; tds_fdw-2.0.3/tests/tests/postgresql/025_varbinarymax.json000066400000000000000000000002561432462773100236550ustar00rootroot00000000000000{ "test_desc" : "varbinary data type, maximum size (bytea)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/025_varbinarymax.sql000066400000000000000000000004541432462773100235030ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varbinarymax; CREATE FOREIGN TABLE @PSCHEMANAME.varbinarymax ( id int, value bytea ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.varbinarymax', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.varbinarymax; tds_fdw-2.0.3/tests/tests/postgresql/026_null_datetime.json000066400000000000000000000003011432462773100237700ustar00rootroot00000000000000{ "test_desc" : "datatime data type, null value (timestamp without time zone)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/026_null_datetime.sql000066400000000000000000000005061432462773100236250ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.null_datetime; CREATE FOREIGN TABLE @PSCHEMANAME.null_datetime ( id int, value timestamp without time zone ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.null_datetime', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.null_datetime; tds_fdw-2.0.3/tests/tests/postgresql/027_null_datetime2.json000066400000000000000000000003021432462773100240540ustar00rootroot00000000000000{ "test_desc" : "datetime2 data type, null value (timestamp without time zone)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/027_null_datetime2.sql000066400000000000000000000005121432462773100237050ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.null_datetime2; CREATE FOREIGN TABLE @PSCHEMANAME.null_datetime2 ( id int, value timestamp without time zone ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.null_datetime2', row_estimate_method 'showplan_all'); SELECT * FROM @PSCHEMANAME.null_datetime2; tds_fdw-2.0.3/tests/tests/postgresql/028_column_match_enabled.json000066400000000000000000000002461432462773100252770ustar00rootroot00000000000000{ "test_desc" : "automated column matching enabled", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/028_column_match_enabled.sql000066400000000000000000000006611432462773100251260ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.column_match_enabled; CREATE FOREIGN TABLE @PSCHEMANAME.column_match_enabled ( id int, value bigint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.column_match', row_estimate_method 'showplan_all', match_column_names '1'); SELECT * FROM @PSCHEMANAME.column_match_enabled; SELECT (value = 9223372036854775807) AS pass FROM @PSCHEMANAME.column_match_enabled;tds_fdw-2.0.3/tests/tests/postgresql/029_column_match_disabled.json000066400000000000000000000002471432462773100254560ustar00rootroot00000000000000{ "test_desc" : "automated column matching disabled", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/029_column_match_disabled.sql000066400000000000000000000006651432462773100253100ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.column_match_disabled; CREATE FOREIGN TABLE @PSCHEMANAME.column_match_disabled ( id int, value bigint ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.column_match', row_estimate_method 'showplan_all', match_column_names '0'); SELECT * FROM @PSCHEMANAME.column_match_disabled; SELECT (value = 9223372036854775807) AS pass FROM @PSCHEMANAME.column_match_disabled;tds_fdw-2.0.3/tests/tests/postgresql/030_column_name.json000066400000000000000000000002401432462773100234340ustar00rootroot00000000000000{ "test_desc" : "column name manual matching", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/030_column_name.sql000066400000000000000000000006661432462773100232760ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.column_name; CREATE FOREIGN TABLE @PSCHEMANAME.column_name ( id int, local_name bigint OPTIONS (column_name 'value') ) SERVER mssql_svr OPTIONS (table '@MSCHEMANAME.column_name', row_estimate_method 'showplan_all', match_column_names '1'); SELECT * FROM @PSCHEMANAME.column_name; SELECT (local_name = 9223372036854775807) AS pass FROM @PSCHEMANAME.column_name; tds_fdw-2.0.3/tests/tests/postgresql/031_query_option_column_match_enabled.json000066400000000000000000000002651432462773100301070ustar00rootroot00000000000000{ "test_desc" : "automated column matching enabled (query option)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/031_query_option_column_match_enabled.sql000066400000000000000000000013241432462773100277320ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.query_option_column_match_enabled; CREATE FOREIGN TABLE @PSCHEMANAME.query_option_column_match_enabled ( id int, data varchar(50), owner varchar(50) ) SERVER mssql_svr OPTIONS (query 'SELECT * FROM @MSCHEMANAME.query_option WHERE owner = ''geoff''', row_estimate_method 'showplan_all', match_column_names '1'); SELECT * FROM @PSCHEMANAME.query_option_column_match_enabled; WITH count AS ( SELECT COUNT(*) as count FROM @PSCHEMANAME.query_option_column_match_enabled ) SELECT (count = 1) FROM count; WITH owners AS ( SELECT DISTINCT owner FROM @PSCHEMANAME.query_option_column_match_enabled ) SELECT (owner = 'geoff') FROM owners; tds_fdw-2.0.3/tests/tests/postgresql/032_query_option_column_match_disabled.json000066400000000000000000000002661432462773100302660ustar00rootroot00000000000000{ "test_desc" : "automated column matching disabled (query option)", "server" : { "version" : { "min" : "9.2.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/032_query_option_column_match_disabled.sql000066400000000000000000000013311432462773100301060ustar00rootroot00000000000000DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.query_option_column_match_disabled; CREATE FOREIGN TABLE @PSCHEMANAME.query_option_column_match_disabled ( id int, data varchar(50), owner varchar(50) ) SERVER mssql_svr OPTIONS (query 'SELECT * FROM @MSCHEMANAME.query_option WHERE owner = ''geoff''', row_estimate_method 'showplan_all', match_column_names '0'); SELECT * FROM @PSCHEMANAME.query_option_column_match_disabled; WITH count AS ( SELECT COUNT(*) as count FROM @PSCHEMANAME.query_option_column_match_disabled ) SELECT (count = 1) FROM count; WITH owners AS ( SELECT DISTINCT owner FROM @PSCHEMANAME.query_option_column_match_disabled ) SELECT (owner = 'geoff') FROM owners; tds_fdw-2.0.3/tests/tests/postgresql/033_view_simple.json000066400000000000000000000002221432462773100234650ustar00rootroot00000000000000{ "test_desc" : "view (simple)", "server" : { "version" : { "min" : "9.5.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/033_view_simple.sql000066400000000000000000000000501432462773100233120ustar00rootroot00000000000000SELECT * FROM @PSCHEMANAME.view_simple; tds_fdw-2.0.3/tests/tests/postgresql/034_explain.json000066400000000000000000000002261432462773100226070ustar00rootroot00000000000000{ "test_desc" : "EXPLAIN (VERBOSE)", "server" : { "version" : { "min" : "9.6.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/034_explain.sql000066400000000000000000000000751432462773100224370ustar00rootroot00000000000000EXPLAIN (VERBOSE) SELECT data FROM @PSCHEMANAME.view_simple; tds_fdw-2.0.3/tests/tests/postgresql/035_rescan.json000066400000000000000000000002641432462773100224250ustar00rootroot00000000000000{ "test_desc" : "nested loop with foreign scan on the inner side", "server" : { "version" : { "min" : "9.6.0", "max" : "" } } } tds_fdw-2.0.3/tests/tests/postgresql/035_rescan.sql000066400000000000000000000014611432462773100222530ustar00rootroot00000000000000/* function is expensive so that the optimizer puts the foreign table on the inner side of the netsed loop */ CREATE FUNCTION @PSCHEMANAME.expensive() RETURNS TABLE (id integer) LANGUAGE sql IMMUTABLE COST 1000000000 ROWS 1 AS 'SELECT * FROM generate_series(1, 3)'; /* force a nested loop */ SET enable_hashjoin = off; SET enable_mergejoin = off; SET enable_material = off; CREATE TEMP TABLE results (id integer, data text); INSERT INTO results SELECT id, v.data FROM @PSCHEMANAME.expensive() AS f LEFT JOIN @PSCHEMANAME.view_simple AS v USING (id); /* results.data should not be NULL */ DO $$BEGIN IF EXISTS (SELECT 1 FROM results WHERE data IS NULL) THEN RAISE EXCEPTION 'bad results from query'; END IF; END;$$; RESET enable_hashjoin; RESET enable_mergejoin; RESET enable_material; tds_fdw-2.0.3/tests/validate-test-json000077500000000000000000000056551432462773100177770ustar00rootroot00000000000000#!/usr/bin/python3 from json import JSONDecodeError, loads try: from jsonschema import validate, ValidationError except ModuleNotFoundError: print_error( "jsonschema library not available, please install it before usage!") exit(1) from lib.messages import print_error, print_info, print_ok from lib.messages import print_usage_error, print_warning from optparse import OptionParser from os import listdir, path from sys import exit def parse_options(): """Parse and validate options. Returns a dict with all the options.""" usage = "%prog " description = ('Test JSON files for MSSQL or PostgreSQL tests') parser = OptionParser(usage=usage, description=description) parser.add_option('--path', action='store', help='Path to a JSON file') (options, args) = parser.parse_args() if options.path is None: print_error("Insufficient parameters, check help (-h)") exit(4) return(options) def validate_json(jsonfile): # This is the definition of the schema, according to README.md schema = { "type": "object", "properties": { "test_desc": { "type": "string" }, "server": { "type": "object", "properties": { "version": { "type": "object", "properties": { "min": { "type": "string", "pattern": "^([0-9]+\\.[0-9]+\\.[0-9]+)$" }, "max": { "type": "string", "pattern": "^([0-9]+\\.[0-9]+\\.[0-9]+)?$" } }, "required": [ "min", "max" ] } }, "required": [ "version" ] } }, "required": [ "test_desc", "server" ] } try: with open(jsonfile, 'r') as f: try: json = loads(f.read()) except JSONDecodeError as e: print_error("Invalid JSON: %s" % e) return False except FileNotFoundError as e: print_error(e) return False try: validate(instance=json, schema=schema) except ValidationError as e: print_error(e) return False print_ok("%s is valid" % jsonfile) return True def main(): try: options = parse_options() except Exception as e: print_usage_error(path.basename(__file__), e) exit(2) print_info("Validating %s" % options.path) if not validate_json(options.path): exit(3) if __name__ == "__main__": main()