├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── ForeignSchemaImporting.md ├── ForeignServerCreation.md ├── ForeignTableCreation.md ├── InstallAlpine.md ├── InstallDebian.md ├── InstallOSX.md ├── InstallRHELandClones.md ├── InstallUbuntu.md ├── InstallopenSUSE.md ├── LICENSE ├── META.json ├── Makefile ├── README.md ├── UserMappingCreation.md ├── Variables.md ├── include ├── deparse.h ├── options.h ├── tds_fdw.h └── visibility.h ├── logo └── tds_fdw.svg ├── sql └── tds_fdw.sql ├── src ├── deparse.c ├── options.c └── tds_fdw.c ├── tds_fdw--2.0.1--2.0.2.sql ├── tds_fdw--2.0.2--2.0.3.sql ├── tds_fdw--2.0.3--2.0.4.sql ├── tds_fdw.control └── tests ├── .gitignore ├── README.md ├── lib ├── __init__.py ├── messages.py └── tests.py ├── mssql-tests.py ├── postgresql-tests.py ├── tests ├── mssql │ ├── 000_create_schema.json │ ├── 000_create_schema.sql │ ├── 001_create_tinyint_min_table.json │ ├── 001_create_tinyint_min_table.sql │ ├── 002_create_tinyint_max_table.json │ ├── 002_create_tinyint_max_table.sql │ ├── 003_create_smallint_min_table.json │ ├── 003_create_smallint_min_table.sql │ ├── 004_create_smallint_max_table.json │ ├── 004_create_smallint_max_table.sql │ ├── 005_create_int_min_table.json │ ├── 005_create_int_min_table.sql │ ├── 006_create_int_max_table.json │ ├── 006_create_int_max_table.sql │ ├── 007_create_bigint_min_table.json │ ├── 007_create_bigint_min_table.sql │ ├── 008_create_bigint_max_table.json │ ├── 008_create_bigint_max_table.sql │ ├── 009_create_decimal_table.json │ ├── 009_create_decimal_table.sql │ ├── 010_create_float4_table.json │ ├── 010_create_float4_table.sql │ ├── 011_create_float8_table.json │ ├── 011_create_float8_table.sql │ ├── 012_create_date_table.json │ ├── 012_create_date_table.sql │ ├── 013_create_time_table.json │ ├── 013_create_time_table.sql │ ├── 014_create_datetime_table.json │ ├── 014_create_datetime_table.sql │ ├── 015_create_datetime2_table.json │ ├── 015_create_datetime2_table.sql │ ├── 016_create_datetimeoffset_table.json │ ├── 016_create_datetimeoffset_table.sql │ ├── 017_create_char_table.json │ ├── 017_create_char_table.sql │ ├── 018_create_varchar_table.json │ ├── 018_create_varchar_table.sql │ ├── 019_create_varcharmax_table.json │ ├── 019_create_varcharmax_table.sql │ ├── 020_create_binary4_table.json │ ├── 020_create_binary4_table.sql │ ├── 021_create_varbinary4_table.json │ ├── 021_create_varbinary4_table.sql │ ├── 022_create_varbinarymax_table.json │ ├── 022_create_varbinarymax_table.sql │ ├── 023_create_null_datetime_table.json │ ├── 023_create_null_datetime_table.sql │ ├── 024_create_null_datetime2_table.json │ ├── 024_create_null_datetime2_table.sql │ ├── 025_create_match_column_table.json │ ├── 025_create_match_column_table.sql │ ├── 026_create_column_name_table.json │ ├── 026_create_column_name_table.sql │ ├── 027_create_query_option_table.json │ ├── 027_create_query_option_table.sql │ ├── 028_create_view_simple_prerequisites.json │ ├── 028_create_view_simple_prerequisites.sql │ ├── 029_create_view_simple.json │ ├── 029_create_view_simple.sql │ ├── 030_create_money_min_table.json │ ├── 030_create_money_min_table.sql │ ├── 031_create_money_max_table.json │ ├── 031_create_money_max_table.sql │ ├── 032_create_smallmoney_min_table.json │ ├── 032_create_smallmoney_min_table.sql │ ├── 033_create_smallmoney_max_table.json │ └── 033_create_smallmoney_max_table.sql └── postgresql │ ├── 000_create_schema.json │ ├── 000_create_schema.sql │ ├── 001_create_server.json │ ├── 001_create_server.sql │ ├── 002_create_user_mapping.json │ ├── 002_create_user_mapping.sql │ ├── 003_import_schema.json │ ├── 003_import_schema.sql │ ├── 004_tinyintmin.json │ ├── 004_tinyintmin.sql │ ├── 005_tinyintmax.json │ ├── 005_tinyintmax.sql │ ├── 006_smallintmin.json │ ├── 006_smallintmin.sql │ ├── 007_smallintmax.json │ ├── 007_smallintmax.sql │ ├── 008_intmin.json │ ├── 008_intmin.sql │ ├── 009_intmax.json │ ├── 009_intmax.sql │ ├── 010_bigintmin.json │ ├── 010_bigintmin.sql │ ├── 011_bigintmax.json │ ├── 011_bigintmax.sql │ ├── 012_decimal.json │ ├── 012_decimal.sql │ ├── 013_float4.json │ ├── 013_float4.sql │ ├── 014_float8.json │ ├── 014_float8.sql │ ├── 015_date.json │ ├── 015_date.sql │ ├── 016_time.json │ ├── 016_time.sql │ ├── 017_datetime.json │ ├── 017_datetime.sql │ ├── 018_datetime2.json │ ├── 018_datetime2.sql │ ├── 019_datetimeoffset.json │ ├── 019_datetimeoffset.sql │ ├── 020_char.json │ ├── 020_char.sql │ ├── 021_varchar.json │ ├── 021_varchar.sql │ ├── 022_varcharmax.json │ ├── 022_varcharmax.sql │ ├── 023_binary4.json │ ├── 023_binary4.sql │ ├── 024_varbinary4.json │ ├── 024_varbinary4.sql │ ├── 025_varbinarymax.json │ ├── 025_varbinarymax.sql │ ├── 026_null_datetime.json │ ├── 026_null_datetime.sql │ ├── 027_null_datetime2.json │ ├── 027_null_datetime2.sql │ ├── 028_column_match_enabled.json │ ├── 028_column_match_enabled.sql │ ├── 029_column_match_disabled.json │ ├── 029_column_match_disabled.sql │ ├── 030_column_name.json │ ├── 030_column_name.sql │ ├── 031_query_option_column_match_enabled.json │ ├── 031_query_option_column_match_enabled.sql │ ├── 032_query_option_column_match_disabled.json │ ├── 032_query_option_column_match_disabled.sql │ ├── 033_view_simple.json │ ├── 033_view_simple.sql │ ├── 034_explain.json │ ├── 034_explain.sql │ ├── 035_rescan.json │ ├── 035_rescan.sql │ ├── 036_moneymin.json │ ├── 036_moneymin.sql │ ├── 037_moneymax.json │ ├── 037_moneymax.sql │ ├── 038_smallmoneymin.json │ ├── 038_smallmoneymin.sql │ ├── 039_smallmoneymax.json │ └── 039_smallmoneymax.sql └── validate-test-json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.h text 8 | 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue report 2 | 3 | _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._ 4 | 5 | ## Operating system 6 | 7 | _On recent GNU/Linux distributions, you can provide the content of the file `/etc/os-release`_ 8 | 9 | ``` 10 | Replace this with the content 11 | ``` 12 | 13 | 14 | ## Version of tds\_fdw 15 | 16 | _From a `psql` session, paste the outputs of running `\dx`_ 17 | 18 | _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_ 19 | 20 | ``` 21 | Replace this with the output 22 | ``` 23 | 24 | 25 | ## Version of PostgreSQL 26 | 27 | _From a `psql` session, paste the output of running `SELECT version();`_ 28 | 29 | ``` 30 | Replace this with the output 31 | ``` 32 | 33 | 34 | ## Version of FreeTDS 35 | 36 | _How to get it will depend on your Operating System and how you installes FreeTDS_ 37 | 38 | _From a console:_ 39 | * _On RPM based systems: `rpm -qa|grep freetds`_ 40 | * _On Deb based systems: `dpkg -l|grep freetds`_ 41 | * _If you built your own binaries from source code, then go to the sources, and run: `grep 'AC_INIT' configure.ac`_ 42 | 43 | ``` 44 | Replace this with the output 45 | ``` 46 | 47 | 48 | ## Logs 49 | 50 | _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_ 51 | 52 | _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_ 53 | 54 | _For MSSQL you will need to use the SQL Server Audit Log_ 55 | 56 | ``` 57 | Replace this with the commands and outputs 58 | ``` 59 | 60 | 61 | ## Sentences, data structures, data 62 | 63 | _This will depend on the exact problem you are having and data privacy restrictions_ 64 | 65 | _However the more data you provide, the more likely we will be able to help_ 66 | 67 | _As a bare minimum, you should provide_ 68 | 69 | * _The SQL sentence that is failing_ 70 | * _The data structure on the PostgreSQL side and on the MSSQL side_ 71 | 72 | ``` 73 | Replace this with the SQL sentences, data structures, etc 74 | ``` 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | README.tds_fdw.md 3 | sql/*--*.sql 4 | *.o 5 | *.bc 6 | *.so 7 | tests/lib/__pycache__ 8 | -------------------------------------------------------------------------------- /ForeignSchemaImporting.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Name:** tds_fdw 4 | * **File:** tds_fdw/ForeignSchemaImporting.md 5 | 6 | ## Importing a Foreign Schema 7 | 8 | ### Options 9 | 10 | #### Foreign schema parameters accepted: 11 | 12 | * *import_default* 13 | 14 | Required: No 15 | 16 | Default: false 17 | 18 | Controls whether column DEFAULT expressions are included in the definitions of foreign tables. 19 | 20 | * *import_not_null* 21 | 22 | Required: No 23 | 24 | Default: true 25 | 26 | Controls whether column NOT NULL constraints are included in the definitions of foreign tables. 27 | 28 | ### Example 29 | 30 | ```SQL 31 | IMPORT FOREIGN SCHEMA dbo 32 | EXCEPT (mssql_table) 33 | FROM SERVER mssql_svr 34 | INTO public 35 | OPTIONS (import_default 'true'); 36 | ``` 37 | -------------------------------------------------------------------------------- /ForeignServerCreation.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/ForeignServerCreation.md 6 | 7 | ## Creating a Foreign Server 8 | 9 | ### Options 10 | 11 | #### Foreign server parameters accepted: 12 | 13 | * *servername* 14 | 15 | Required: Yes 16 | 17 | Default: 127.0.0.1 18 | 19 | The servername, address or hostname of the foreign server server. 20 | 21 | This can be a DSN, as specified in *freetds.conf*. See [FreeTDS name lookup](https://www.freetds.org/userguide/name.lookup.html). 22 | 23 | You can set this option to a comma separated list of server names, then each 24 | server is tried until the first connection succeeds. 25 | This is useful for automatic fail-over to a secondary server. 26 | 27 | * *port* 28 | 29 | Required: No 30 | 31 | The port of the foreign server. This is optional. Instead of providing a port 32 | here, it can be specified in *freetds.conf* (if *servername* is a DSN). 33 | 34 | * *database* 35 | 36 | Required: No 37 | 38 | The database to connect to for this server. 39 | 40 | * *dbuse* 41 | 42 | Required: No 43 | 44 | Default: 0 45 | 46 | This option tells tds_fdw to connect directly to *database* if *dbuse* is 0. 47 | If *dbuse* is not 0, tds_fdw will connect to the server's default database, and 48 | then select *database* by calling DB-Library's dbuse() function. 49 | 50 | For Azure, *dbuse* currently needs to be set to 0. 51 | 52 | * *language* 53 | 54 | Required: No 55 | 56 | The language to use for messages and the locale to use for date formats. 57 | FreeTDS may default to *us_english* on most systems. You can probably also change 58 | this in *freetds.conf*. 59 | 60 | 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). 61 | 62 | 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) 63 | and [SET LANGUAGE in Sybase ASE](http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc36272.1572/html/commands/X64136.htm). 64 | 65 | * *character_set* 66 | 67 | Required: No 68 | 69 | The client character set to use for the connection, if you need to set this 70 | for some reason. 71 | 72 | For TDS protocol versions 7.0+, the connection always uses UCS-2, so 73 | this parameter does nothing in those cases. See [Localization and TDS 7.0](https://www.freetds.org/userguide/Localization.html). 74 | 75 | * *tds_version* 76 | 77 | Required: No 78 | 79 | 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). 80 | 81 | * *msg_handler* 82 | 83 | Required: No 84 | 85 | Default: blackhole 86 | 87 | 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. 88 | 89 | * *fdw_startup_cost* 90 | 91 | Required: No 92 | 93 | A cost that is used to represent the overhead of using this FDW used in query planning. 94 | 95 | * *fdw_tuple_cost* 96 | 97 | Required: No 98 | 99 | A cost that is used to represent the overhead of fetching rows from this server used in query planning. 100 | 101 | * *sqlserver_ansi_mode* 102 | 103 | Required: No 104 | 105 | This option is supported for SQL Server only. The default is "false". Setting this to "true" will enable the following server-side settings after a successful connection to the foreign server: 106 | 107 | * CONCAT_NULLS_YIELDS_NULL ON 108 | * ANSI_NULLS ON 109 | * ANSI_WARNINGS ON 110 | * QUOTED_IDENTIFIER ON 111 | * ANSI_PADDING ON 112 | * ANSI_NULL_DFLT_ON ON 113 | 114 | Those parameters in summary are comparable to the SQL Server option *ANSI_DEFAULTS*. In contrast, *sqlserver_ansi_mode* currently does not activate the following options: 115 | 116 | * CURSOR_CLOSE_ON_COMMIT 117 | * IMPLICIT_TRANSACTIONS 118 | 119 | This follows the behavior of the native ODBC and OLEDB driver for SQL Servers, which explicitly turn them `OFF` if not configured otherwise. 120 | 121 | #### Foreign table parameters accepted in server definition: 122 | 123 | Some foreign table options can also be set at the server level. Those include: 124 | 125 | * *use_remote_estimate* 126 | * *row_estimate_method* 127 | 128 | ### Example 129 | 130 | ```SQL 131 | CREATE SERVER mssql_svr 132 | FOREIGN DATA WRAPPER tds_fdw 133 | OPTIONS (servername '127.0.0.1', port '1433', database 'tds_fdw_test', tds_version '7.1'); 134 | ``` 135 | -------------------------------------------------------------------------------- /ForeignTableCreation.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/ForeignTableCreation.md 6 | 7 | ## Creating a Foreign Table 8 | 9 | ### Options 10 | 11 | #### Foreign table parameters accepted: 12 | 13 | * *query* 14 | 15 | Required: Yes (mutually exclusive with *table*) 16 | 17 | The query string to use to query the foreign table. 18 | 19 | * *schema_name* 20 | 21 | Required: No 22 | 23 | The schema that the table is in. The schema name can also be included in *table_name*, so this is not required. 24 | 25 | * *table_name* 26 | 27 | Aliases: *table* 28 | 29 | Required: Yes (mutually exclusive with *query*) 30 | 31 | The table on the foreign server to query. 32 | 33 | * *match_column_names* 34 | 35 | Required: No 36 | 37 | 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. 38 | 39 | * *use_remote_estimate* 40 | 41 | Required: No 42 | 43 | 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*. 44 | 45 | * *local_tuple_estimate* 46 | 47 | Required: No 48 | 49 | A locally set estimate of the number of tuples that is used when *use_remote_estimate* is disabled. 50 | 51 | * *row_estimate_method* 52 | 53 | Required: No 54 | 55 | Default: `execute` 56 | 57 | This can be one of the following values: 58 | 59 | * `execute`: Execute the query on the remote server, and get the actual number of rows in the query. 60 | * `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). 61 | 62 | #### Foreign table column parameters accepted: 63 | 64 | * *column_name* 65 | 66 | Required: No 67 | 68 | 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. 69 | 70 | ### Example 71 | 72 | Using a *table_name* definition: 73 | 74 | ```SQL 75 | CREATE FOREIGN TABLE mssql_table ( 76 | id integer, 77 | data varchar) 78 | SERVER mssql_svr 79 | OPTIONS (table_name 'dbo.mytable', row_estimate_method 'showplan_all'); 80 | ``` 81 | 82 | Or using a *schema_name* and *table_name* definition: 83 | 84 | ```SQL 85 | CREATE FOREIGN TABLE mssql_table ( 86 | id integer, 87 | data varchar) 88 | SERVER mssql_svr 89 | OPTIONS (schema_name 'dbo', table_name 'mytable', row_estimate_method 'showplan_all'); 90 | ``` 91 | 92 | Or using a *query* definition: 93 | 94 | ```SQL 95 | CREATE FOREIGN TABLE mssql_table ( 96 | id integer, 97 | data varchar) 98 | SERVER mssql_svr 99 | OPTIONS (query 'SELECT * FROM dbo.mytable', row_estimate_method 'showplan_all'); 100 | ``` 101 | 102 | Or setting a remote column name: 103 | 104 | ```SQL 105 | CREATE FOREIGN TABLE mssql_table ( 106 | id integer, 107 | col2 varchar OPTIONS (column_name 'data')) 108 | SERVER mssql_svr 109 | OPTIONS (schema_name 'dbo', table_name 'mytable', row_estimate_method 'showplan_all'); 110 | ``` 111 | -------------------------------------------------------------------------------- /InstallAlpine.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** TheRevenantStar 4 | * **EditedBy:** Guriy Samarin 5 | * **Name:** tds_fdw 6 | * **File:** tds_fdw/InstallAlpine.md 7 | 8 | ## Installing on Alpine Linux 9 | 10 | This document will show how to install tds_fdw on Alpine Linux 3.10.3. Other Alpine Linux distributions should be similar. 11 | 12 | ### Install FreeTDS and build dependencies 13 | 14 | The TDS foreign data wrapper requires a library that implements the DB-Library interface, 15 | such as [FreeTDS](http://www.freetds.org). 16 | 17 | ```bash 18 | apk add --update freetds-dev 19 | ``` 20 | 21 | Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: 22 | 23 | ```bash 24 | apk add gcc libc-dev make 25 | ``` 26 | 27 | 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): 28 | 29 | ```bash 30 | apk add musl-dev 31 | ``` 32 | 33 | ### Install PostgreSQL 34 | 35 | If you need to install PostgreSQL, do so by installing from APK. For example, to install PostgreSQL 11.6 on Alpine Linux: 36 | 37 | ```bash 38 | apk add postgresql=11.6-r0 postgresql-client=11.6-r0 postgresql-dev=11.6-r0 39 | ``` 40 | 41 | In postgres-alpine docker image you will need only 42 | 43 | ```bash 44 | apk add postgresql-dev 45 | ``` 46 | 47 | ### Install tds_fdw 48 | 49 | #### Build from release package 50 | 51 | If you'd like to use one of the release packages, you can download and install them via something like the following: 52 | 53 | ```bash 54 | export TDS_FDW_VERSION="2.0.4" 55 | apk add wget 56 | wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz 57 | tar -xvzf v${TDS_FDW_VERSION}.tar.gz 58 | cd tds_fdw-${TDS_FDW_VERSION}/ 59 | make USE_PGXS=1 60 | sudo make USE_PGXS=1 install 61 | ``` 62 | 63 | **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. 64 | 65 | #### Build from repository 66 | 67 | If you would rather use the current development version, you can clone and build the git repository via something like the following: 68 | 69 | ```bash 70 | apk add git 71 | git clone https://github.com/tds-fdw/tds_fdw.git 72 | cd tds_fdw 73 | make USE_PGXS=1 74 | make USE_PGXS=1 install 75 | ``` 76 | 77 | **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. 78 | 79 | #### Start server 80 | 81 | If this is a fresh installation, then create the initial cluster and start the server: 82 | 83 | ```bash 84 | mkdir /var/lib/postgresql/data 85 | chmod 0700 /var/lib/postgresql/data 86 | chown postgres. /var/lib/postgresql/data 87 | su postgres -c 'initdb /var/lib/postgresql/data' 88 | mkdir /run/postgresql/ 89 | chown postgres. /run/postgresql/ 90 | su postgres -c 'pg_ctl start -D /var/lib/postgresql/data "-o -c listen_addresses=\"\""' 91 | ``` 92 | 93 | #### Install extension 94 | 95 | ```bash 96 | psql -U postgres 97 | postgres=# CREATE EXTENSION tds_fdw; 98 | ``` 99 | 100 | #### Dockerfile Example 101 | 102 | This Dockerfile will build PostgreSQL 11 in Alpine Linux with tds_fdw from master branch 103 | 104 | ``` 105 | FROM library/postgres:11-alpine 106 | RUN apk add --update freetds-dev && \ 107 | apk add git gcc libc-dev make && \ 108 | apk add postgresql-dev postgresql-contrib && \ 109 | git clone https://github.com/tds-fdw/tds_fdw.git && \ 110 | cd tds_fdw && \ 111 | make USE_PGXS=1 && \ 112 | make USE_PGXS=1 install && \ 113 | apk del git gcc libc-dev make && \ 114 | cd .. && \ 115 | rm -rf tds_fdw 116 | ``` 117 | 118 | You can easily adapt the Dockerfile if you want to use a release package. 119 | 120 | 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. 121 | -------------------------------------------------------------------------------- /InstallDebian.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/InstallDebian.md 6 | 7 | ## Installing on Debian 8 | 9 | This document will show how to install tds_fdw on Debian 10. Other Debian distributions should be similar. 10 | 11 | ### Install FreeTDS and build dependencies 12 | 13 | The TDS foreign data wrapper requires a library that implements the DB-Library interface, 14 | such as [FreeTDS](http://www.freetds.org). 15 | 16 | ```bash 17 | sudo apt-get update 18 | sudo apt-get install libsybdb5 freetds-dev freetds-common 19 | ``` 20 | 21 | Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: 22 | 23 | ```bash 24 | sudo apt-get install gnupg gcc make 25 | ``` 26 | 27 | ### Install PostgreSQL 28 | 29 | 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: 30 | 31 | ```bash 32 | 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' 33 | sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-keys 0xACCC4CF8 34 | sudo apt-get update 35 | sudo apt-get upgrade 36 | sudo apt-get install postgresql-11 postgresql-client-11 postgresql-server-dev-11 37 | ``` 38 | 39 | **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). 40 | 41 | ### Install tds_fdw 42 | 43 | #### Build from release package 44 | 45 | If you'd like to use one of the release packages, you can download and install them via something like the following: 46 | 47 | ```bash 48 | export TDS_FDW_VERSION="2.0.4" 49 | sudo apt-get install wget 50 | wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz 51 | tar -xvzf v${TDS_FDW_VERSION}.tar.gz 52 | cd tds_fdw-${TDS_FDW_VERSION}/ 53 | make USE_PGXS=1 54 | sudo make USE_PGXS=1 install 55 | ``` 56 | 57 | **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. 58 | 59 | #### Build from repository 60 | 61 | If you would rather use the current development version, you can clone and build the git repository via something like the following: 62 | 63 | ```bash 64 | sudo apt-get install git 65 | git clone https://github.com/tds-fdw/tds_fdw.git 66 | cd tds_fdw 67 | make USE_PGXS=1 68 | sudo make USE_PGXS=1 install 69 | ``` 70 | 71 | **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. 72 | 73 | #### Start server 74 | 75 | If this is a fresh installation, then start the server: 76 | 77 | ```bash 78 | sudo service postgresql start 79 | ``` 80 | 81 | #### Install extension 82 | 83 | ```bash 84 | psql -U postgres 85 | postgres=# CREATE EXTENSION tds_fdw; 86 | ``` 87 | -------------------------------------------------------------------------------- /InstallOSX.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/InstallOSX.md 6 | 7 | ## Installing on OSX 8 | 9 | This document will show how to install tds_fdw on OSX using the [Homebrew](https://brew.sh/) package manager for the required packages. 10 | 11 | ### Install FreeTDS 12 | 13 | The TDS foreign data wrapper requires a library that implements the DB-Library interface, 14 | such as [FreeTDS](http://www.freetds.org). 15 | 16 | ```bash 17 | brew install freetds 18 | ``` 19 | 20 | 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). 21 | 22 | ### Install PostgreSQL 23 | 24 | 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: 25 | 26 | ```bash 27 | brew install postgres 28 | ``` 29 | 30 | Or use Postgres.app: 31 | 32 | ### Install tds_fdw 33 | 34 | #### Build from release package 35 | 36 | If you'd like to use one of the release packages, you can download and install them via something like the following: 37 | 38 | ```bash 39 | export TDS_FDW_VERSION="2.0.4" 40 | wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz 41 | tar -xvzf v${TDS_FDW_VERSION}.tar.gz 42 | cd tds_fdw-${TDS_FDW_VERSION} 43 | make USE_PGXS=1 TDS_INCLUDE=-I/usr/local/include/ 44 | sudo make USE_PGXS=1 install 45 | ``` 46 | 47 | **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. 48 | 49 | #### Build from repository 50 | 51 | If you would rather use the current development version, you can clone and build the git repository via something like the following: 52 | 53 | ```bash 54 | git clone https://github.com/tds-fdw/tds_fdw.git 55 | cd tds_fdw 56 | make USE_PGXS=1 TDS_INCLUDE=-I/usr/local/include/ 57 | sudo make USE_PGXS=1 install 58 | ``` 59 | 60 | **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. 61 | 62 | #### Start server 63 | 64 | If this is a fresh installation, then start the server: 65 | 66 | ```bash 67 | brew services start postgresql 68 | ``` 69 | 70 | Or the equivalent command if you are not using Homebrew. 71 | 72 | #### Install extension 73 | 74 | ```bash 75 | psql -U postgres 76 | postgres=# CREATE EXTENSION tds_fdw; 77 | ``` 78 | -------------------------------------------------------------------------------- /InstallRHELandClones.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/InstallRHELandClones.md 6 | 7 | 8 | ## Installing on RHEL and Clones such as CentOS, Rocky Linux, AlmaLinux or Oracle 9 | 10 | This document will show how to install tds_fdw on Rocky Linux 8.9. RHEL distributions should be similar. 11 | 12 | NOTE: For the sake of simplicity, we will use `yum` as it works as an alias for `dnf` on newer distributions. 13 | 14 | ### Option A: yum/dnf (released versions) 15 | 16 | #### PostgreSQL 17 | 18 | If you need to install PostgreSQL, do so by following the [RHEL installation instructions](https://www.postgresql.org/download/linux/redhat/). 19 | 20 | #### tds_fdw 21 | 22 | The PostgreSQL development team packages `tds_fdw`, but they do not provide FreeTDS. 23 | 24 | First, install the EPEL repository: 25 | 26 | ```bash 27 | sudo yum install epel-release 28 | ``` 29 | 30 | And then install `tds_fdw`: 31 | 32 | ```bash 33 | sudo yum install tds_fdw11.x86_64 34 | ``` 35 | 36 | ### Option B: Compile tds_fdw 37 | 38 | #### PostgreSQL 39 | 40 | If you need to install PostgreSQL, do so by following the [RHEL installation instructions](https://www.postgresql.org/download/linux/redhat/). 41 | 42 | Make sure that, besides `postgresqlXX-server`, `postgresqlXX-devel` is installed as well 43 | 44 | You need to enable the PowerTools repository for RHEL8 and clones, or the CBR repository for RHEL9 and clones. 45 | 46 | #### Install FreeTDS devel and build dependencies 47 | 48 | The TDS foreign data wrapper requires a library that implements the DB-Library interface, 49 | such as [FreeTDS](http://www.freetds.org). 50 | 51 | **NOTE:** You need the [EPEL repository installed](https://fedoraproject.org/wiki/EPEL) to install FreeTDS 52 | 53 | ```bash 54 | sudo yum install epel-release 55 | sudo yum install freetds-devel 56 | ``` 57 | 58 | Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: 59 | 60 | ```bash 61 | sudo yum install clang llvm make redhat-rpm-config wget 62 | ``` 63 | 64 | #### IMPORTANT: CentOS7/Oracle7 and PostgreSQL >= 11 65 | 66 | 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. 67 | 68 | You have LLVM5 at the EPEL CentOS7 repository, but not LLVM7, so you will need install the CentOS Software collections. 69 | 70 | You can easily do it with the following commands: 71 | 72 | ```bash 73 | sudo yum install centos-release-scl 74 | ``` 75 | 76 | ##### Build from release package 77 | 78 | If you'd like to use one of the release packages, you can download and install them via something like the following: 79 | 80 | ```bash 81 | export TDS_FDW_VERSION="2.0.4" 82 | wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz 83 | tar -xvzf v${TDS_FDW_VERSION}.tar.gz 84 | cd tds_fdw-${TDS_FDW_VERSION} 85 | make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config 86 | sudo make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config install 87 | ``` 88 | 89 | **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. 90 | 91 | ##### Build from repository 92 | 93 | If you would rather use the current development version, you can clone and build the git repository via something like the following: 94 | 95 | ```bash 96 | yum install git 97 | git clone https://github.com/tds-fdw/tds_fdw.git 98 | cd tds_fdw 99 | make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config 100 | sudo make USE_PGXS=1 PG_CONFIG=/usr/pgsql-11/bin/pg_config install 101 | ``` 102 | 103 | **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. 104 | 105 | ### Final steps 106 | 107 | #### Start server 108 | 109 | If this is a fresh installation, then initialize the data directory and start the server: 110 | 111 | ```bash 112 | sudo /usr/pgsql-11/bin/postgresql11-setup initdb 113 | sudo systemctl enable postgresql-11.service 114 | sudo systemctl start postgresql-11.service 115 | ``` 116 | 117 | #### Install extension 118 | 119 | ```bash 120 | /usr/pgsql-11/bin/psql -U postgres 121 | postgres=# CREATE EXTENSION tds_fdw; 122 | ``` 123 | -------------------------------------------------------------------------------- /InstallUbuntu.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/InstallUbuntu.md 6 | 7 | ## Installing on Ubuntu 8 | 9 | This document will show how to install tds_fdw on Ubuntu 18.04. Other Ubuntu distributions should be similar. 10 | 11 | ### Install FreeTDS and build dependencies 12 | 13 | The TDS foreign data wrapper requires a library that implements the DB-Library interface, 14 | such as [FreeTDS](http://www.freetds.org). 15 | 16 | ```bash 17 | sudo apt-get update 18 | sudo apt-get install libsybdb5 freetds-dev freetds-common 19 | ``` 20 | 21 | Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: 22 | 23 | ```bash 24 | sudo apt-get install gnupg gcc make 25 | ``` 26 | 27 | ### Install PostgreSQL 28 | 29 | 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: 30 | 31 | ```bash 32 | 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' 33 | sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-keys 0xACCC4CF8 34 | sudo apt-get update 35 | sudo apt-get upgrade 36 | sudo apt-get install postgresql-11 postgresql-client-11 postgresql-server-dev-11 37 | ``` 38 | 39 | **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). 40 | 41 | ### Install tds_fdw 42 | 43 | #### Build from release package 44 | 45 | If you'd like to use one of the release packages, you can download and install them via something like the following: 46 | 47 | ```bash 48 | export TDS_FDW_VERSION="2.0.4" 49 | sudo apt-get install wget 50 | wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz 51 | tar -xvzf v${TDS_FDW_VERSION}.tar.gz 52 | cd tds_fdw-${TDS_FDW_VERSION}/ 53 | make USE_PGXS=1 54 | sudo make USE_PGXS=1 install 55 | ``` 56 | 57 | **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. 58 | 59 | #### Build from repository 60 | 61 | If you would rather use the current development version, you can clone and build the git repository via something like the following: 62 | 63 | ```bash 64 | sudo apt-get install git 65 | git clone https://github.com/tds-fdw/tds_fdw.git 66 | cd tds_fdw 67 | make USE_PGXS=1 68 | sudo make USE_PGXS=1 install 69 | ``` 70 | 71 | **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. 72 | 73 | #### Start server 74 | 75 | If this is a fresh installation, then start the server: 76 | 77 | ```bash 78 | sudo service postgresql start 79 | ``` 80 | 81 | #### Install extension 82 | 83 | ```bash 84 | psql -U postgres 85 | postgres=# CREATE EXTENSION tds_fdw; 86 | ``` 87 | -------------------------------------------------------------------------------- /InstallopenSUSE.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/InstallopenSUSE.md 6 | 7 | ## Installing on openSUSE 8 | 9 | This document will show how to install tds_fdw on openSUSE Leap 15.1. Other openSUSE and SUSE distributions should be similar. 10 | 11 | ### Install FreeTDS and build dependencies 12 | 13 | The TDS foreign data wrapper requires a library that implements the DB-Library interface, 14 | such as [FreeTDS](http://www.freetds.org). 15 | 16 | ```bash 17 | sudo zypper install freetds-devel 18 | ``` 19 | 20 | Some other dependencies are also needed to install PostgreSQL and then compile tds_fdw: 21 | 22 | ```bash 23 | sudo zypper install gcc make 24 | ``` 25 | 26 | ### Install PostgreSQL 27 | 28 | If you need to install PostgreSQL, for example, 10.X: 29 | 30 | ```bash 31 | sudo zypper install postgresql10 postgresql10-server postgresql10-devel 32 | ``` 33 | 34 | **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). 35 | 36 | ### Install tds_fdw 37 | 38 | #### Build from release package 39 | 40 | If you'd like to use one of the release packages, you can download and install them via something like the following: 41 | 42 | ```bash 43 | export TDS_FDW_VERSION="2.0.4" 44 | wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz 45 | tar -xvzf v${TDS_FDW_VERSION}.tar.gz 46 | cd tds_fdw-${TDS_FDW_VERSION}/ 47 | make USE_PGXS=1 48 | sudo make USE_PGXS=1 install 49 | ``` 50 | 51 | **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. 52 | 53 | #### Build from repository 54 | 55 | If you would rather use the current development version, you can clone and build the git repository via something like the following: 56 | 57 | ```bash 58 | zypper in git 59 | git clone https://github.com/tds-fdw/tds_fdw.git 60 | cd tds_fdw 61 | make USE_PGXS=1 62 | sudo make USE_PGXS=1 install 63 | ``` 64 | 65 | **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. 66 | 67 | #### Start server 68 | 69 | If this is a fresh installation, then start the server: 70 | 71 | ```bash 72 | sudo service postgresql start 73 | ``` 74 | 75 | #### Install extension 76 | 77 | ```bash 78 | psql -U postgres 79 | postgres=# CREATE EXTENSION tds_fdw; 80 | ``` 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TDS Foreign Data Wrapper for PostgreSQL 2 | 3 | Copyright (c) 2011 - 2016, Geoff Montee 4 | 5 | Portions Copyright (c) 1996-2016, The PostgreSQL Global Development Group 6 | 7 | Portions Copyright (c) 1994, The Regents of the University of California 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | 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. 14 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tds_fdw", 3 | "abstract": "TDS Foreign data wrapper", 4 | "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.", 5 | "version": "2.0.4", 6 | "maintainer": [ 7 | "Geoff Montee " 8 | ], 9 | "license": "postgresql", 10 | "prereqs": { 11 | "runtime": { 12 | "requires": { 13 | "PostgreSQL": "9.2.0" 14 | } 15 | } 16 | }, 17 | "provides": { 18 | "tds_fdw": { 19 | "abstract": "TDS Foreign data wrapper", 20 | "file": "sql/tds_fdw.sql", 21 | "docfile": "README.md", 22 | "version": "2.0.4" 23 | } 24 | }, 25 | "resources": { 26 | "bugtracker": { 27 | "web": "https://github.com/tds-fdw/tds_fdw/issues" 28 | }, 29 | "repository": { 30 | "url": "https://github.com/tds-fdw/tds_fdw.git", 31 | "web": "https://github.com/tds-fdw/tds_fdw", 32 | "type": "git" 33 | } 34 | }, 35 | "generated_by": "Geoff Montee", 36 | "release_status": "stable", 37 | "meta-spec": { 38 | "version": "1.0.0", 39 | "url": "http://pgxn.org/meta/spec.txt" 40 | }, 41 | "tags": [ 42 | "tds_fdw", 43 | "foreign data wrapper", 44 | "fdw", 45 | "tds", 46 | "tabular data stream", 47 | "sybase", 48 | "microsoft sql server", 49 | "sql server" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #*------------------------------------------------------------------ 2 | # 3 | # Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) 4 | # 5 | # Author: Geoff Montee 6 | # Name: tds_fdw 7 | # File: tds_fdw/Makefile 8 | # 9 | # Description: 10 | # This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, 11 | # such as Sybase databases and Microsoft SQL server. 12 | # 13 | # This foreign data wrapper requires a library that uses the DB-Library interface, 14 | # such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not 15 | # the proprietary implementations of DB-Library. 16 | #---------------------------------------------------------------------------- 17 | 18 | EXTENSION = tds_fdw 19 | 20 | MODULE_big = $(EXTENSION) 21 | 22 | OBJS = src/tds_fdw.o src/options.o src/deparse.o 23 | 24 | EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\\([^']*\\)'/\\1/") 25 | 26 | # no tests yet 27 | # TESTS = $(wildcard test/sql/*.sql) 28 | # REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) 29 | # REGRESS_OPTS = --inputdir=test 30 | 31 | DOCS = README.${EXTENSION}.md 32 | 33 | DATA = tds_fdw--2.0.1--2.0.2.sql tds_fdw--2.0.2--2.0.3.sql tds_fdw--2.0.3--2.0.4.sql sql/$(EXTENSION)--$(EXTVERSION).sql 34 | 35 | PG_CONFIG = pg_config 36 | 37 | # modify these variables to point to FreeTDS, if needed 38 | SHLIB_LINK := -lsybdb 39 | TDS_INCLUDE := 40 | PG_CPPFLAGS := -I./include/ -fvisibility=hidden ${TDS_INCLUDE} 41 | # PG_LIBS := 42 | 43 | all: sql/$(EXTENSION)--$(EXTVERSION).sql README.${EXTENSION}.md 44 | 45 | sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql 46 | cp $< $@ 47 | 48 | README.${EXTENSION}.md: README.md 49 | cp $< $@ 50 | 51 | EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql README.${EXTENSION}.md 52 | 53 | PGXS := $(shell $(PG_CONFIG) --pgxs) 54 | include $(PGXS) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # TDS Foreign data wrapper 3 | 4 | * **Author:** Geoff Montee 5 | * **Name:** tds_fdw 6 | * **File:** tds_fdw/README.md 7 | 8 | ## Logo 9 | 10 | ![Logo SVG](./logo/tds_fdw.svg) 11 | 12 | ## About 13 | 14 | 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), 15 | such as Sybase databases and Microsoft SQL server. 16 | 17 | This foreign data wrapper requires a library that implements the DB-Library interface, 18 | such as [FreeTDS](https://www.freetds.org). This has been tested with FreeTDS, but not 19 | the proprietary implementations of DB-Library. 20 | 21 | This should support PostgreSQL 9.2+. 22 | 23 | The current version does not yet support JOIN push-down, or write operations. 24 | 25 | It does support WHERE and column pushdowns when *match_column_names* is enabled. 26 | 27 | ## Build Status 28 | 29 | | | Rocky Linux 8 | Ubuntu 20.04 | Ubuntu 24.04 | openSUSE Leap 15.6 | 30 | | --------------:|:------------------:|:-----------------:|:-----------------:|:------------------:| 31 | | **PostgreSQL 13** |[![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)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=13,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=13,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=13,label=docker)| 32 | | **PostgreSQL 14** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=14,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=14,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=14,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=14,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=14,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=14,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=14,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=14,label=docker)| 33 | | **PostgreSQL 15** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=15,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=15,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=15,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=15,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=15,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=15,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=15,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=15,label=docker)| 34 | | **PostgreSQL 16** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=16,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=16,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=16,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=16,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=16,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=16,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=16,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=16,label=docker)| 35 | | **PostgreSQL 17** |[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=17,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=rockylinux8,PG_VER=17,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=17,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu20.04,PG_VER=17,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=17,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=ubuntu24.04,PG_VER=17,label=docker)|[![Build Status](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=17,label=docker/badge/icon)](https://jenkins.juliogonzalez.es/job/tds_fdw-build/DISTRO=opensuseleap15.6,PG_VER=17,label=docker)| 36 | 37 | ## Installing on RHEL and clones (CentOS, Rocky Linux, AlmaLinux, Oracle...) 38 | 39 | See [installing tds_fdw on CentOS](InstallRHELandClones.md). 40 | 41 | ## Installing on Ubuntu 42 | 43 | See [installing tds_fdw on Ubuntu](InstallUbuntu.md). 44 | 45 | ## Installing on Debian 46 | 47 | See [installing tds_fdw on Debian](InstallDebian.md). 48 | 49 | ## Installing on openSUSE 50 | 51 | See [installing tds_fdw on openSUSE](InstallopenSUSE.md). 52 | 53 | ## Installing on OSX 54 | 55 | See [installing tds_fdw on OSX](InstallOSX.md). 56 | 57 | ## Installing on Alpine (and Docker) 58 | 59 | See [installing tds_fdw on Alpine](InstallAlpine.md). 60 | 61 | ## Usage 62 | 63 | ### Foreign server 64 | 65 | See [creating a foreign server](ForeignServerCreation.md). 66 | 67 | ### Foreign table 68 | 69 | See [creating a foreign table](ForeignTableCreation.md). 70 | 71 | ### User mapping 72 | 73 | See [creating a user mapping](UserMappingCreation.md). 74 | 75 | ### Foreign schema 76 | 77 | See [importing a foreign schema](ForeignSchemaImporting.md). 78 | 79 | ### Variables 80 | 81 | See [variables](Variables.md). 82 | 83 | ### `EXPLAIN` 84 | 85 | `EXPLAIN (VERBOSE)` will show the query issued on the remote system. 86 | 87 | ## Notes about character sets/encoding 88 | 89 | 1. If you get an error like this with MS SQL Server when working with Unicode data: 90 | 91 | > NOTICE: DB-Library notice: Msg #: 4004, Msg state: 1, Msg: Unicode data in a Unicode-only 92 | > collation or ntext data cannot be sent to clients using DB-Library (such as ISQL) or ODBC 93 | > version 3.7 or earlier., Server: PILLIUM\SQLEXPRESS, Process: , Line: 1, Level: 16 94 | > ERROR: DB-Library error: DB #: 4004, DB Msg: General SQL Server error: Check messages from 95 | > the SQL Server, OS #: -1, OS Msg: (null), Level: 16 96 | 97 | 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). 98 | and [Choosing a TDS protocol version](https://www.freetds.org/userguide/ChoosingTdsProtocol.html). 99 | 100 | 2. Although many newer versions of the TDS protocol will only use USC-2 to communicate 101 | with the server, FreeTDS converts the UCS-2 to the client character set of your choice. 102 | To set the client character set, you can set *client charset* in *freetds.conf*. See 103 | [The *freetds.conf* File](https://www.freetds.org/userguide/freetdsconf.html) and [Localization and TDS 7.0](https://www.freetds.org/userguide/Localization.html). 104 | 105 | ## Encrypted connections to MSSQL 106 | 107 | 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`. 108 | 109 | ## Support 110 | 111 | 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). 112 | 113 | 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. 114 | 115 | ## Debugging 116 | 117 | See [Debugging](tests/README.md) 118 | 119 | -------------------------------------------------------------------------------- /UserMappingCreation.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/UserMappingCreation.md 6 | 7 | ## Creating a User Mapping 8 | 9 | ### Options 10 | 11 | User mapping parameters accepted: 12 | 13 | * *username* 14 | 15 | Required: Yes 16 | 17 | The username of the account on the foreign server. 18 | 19 | **IMPORTANT:** If you are using Azure SQL, then your username for the foreign server will be need to be in the format `username@servername`. If you only use the username, the authentication will fail. 20 | 21 | * *password* 22 | 23 | Required: Yes 24 | 25 | The password of the account on the foreign server. 26 | 27 | ### Example 28 | 29 | ```SQL 30 | CREATE USER MAPPING FOR postgres 31 | SERVER mssql_svr 32 | OPTIONS (username 'sa', password ''); 33 | ``` 34 | -------------------------------------------------------------------------------- /Variables.md: -------------------------------------------------------------------------------- 1 | # TDS Foreign data wrapper 2 | 3 | * **Author:** Geoff Montee 4 | * **Name:** tds_fdw 5 | * **File:** tds_fdw/Variables.md 6 | 7 | ## Variables 8 | 9 | ### Available Variables 10 | 11 | * *tds_fdw.show_before_row_memory_stats* - print memory context stats to the PostgreSQL log before each row is fetched. 12 | 13 | * *tds_fdw.show_after_row_memory_stats* - print memory context stats to the PostgreSQL log after each row is fetched. 14 | 15 | * *tds_fdw.show_finished_memory_stats* - print memory context stats to the PostgreSQL log when a query is finished. 16 | 17 | ### Setting Variables 18 | 19 | To set a variable, use the [SET command](https://www.postgresql.org/docs/12/sql-set.html). i.e.: 20 | 21 | ``` 22 | postgres=# SET tds_fdw.show_finished_memory_stats=1; 23 | SET 24 | ``` -------------------------------------------------------------------------------- /include/deparse.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------ 2 | * 3 | * Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) 4 | * 5 | * Author: Geoff Montee 6 | * Name: tds_fdw 7 | * File: tds_fdw/include/planner.h 8 | * 9 | * Description: 10 | * This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, 11 | * such as Sybase databases and Microsoft SQL server. 12 | * 13 | * This foreign data wrapper requires requires a library that uses the DB-Library interface, 14 | * such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not 15 | * the proprietary implementations of DB-Library. 16 | *---------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | #ifndef DEPARSE_H 21 | #define DEPARSE_H 22 | 23 | /* 24 | * Examine each qual clause in input_conds, and classify them into two groups, 25 | * which are returned as two lists: 26 | * - remote_conds contains expressions that can be evaluated remotely 27 | * - local_conds contains expressions that can't be evaluated remotely 28 | */ 29 | void 30 | classifyConditions(PlannerInfo *root, 31 | RelOptInfo *baserel, 32 | List *input_conds, 33 | List **remote_conds, 34 | List **local_conds); 35 | 36 | /* 37 | * Returns true if given expr is safe to evaluate on the foreign server. 38 | */ 39 | bool 40 | is_foreign_expr(PlannerInfo *root, 41 | RelOptInfo *baserel, 42 | Expr *expr); 43 | 44 | /* 45 | * Construct a simple SELECT statement that retrieves desired columns 46 | * of the specified foreign table, and append it to "buf". The output 47 | * contains just "SELECT ... FROM tablename". 48 | * 49 | * We also create an integer List of the columns being retrieved, which is 50 | * returned to *retrieved_attrs. 51 | */ 52 | void 53 | deparseSelectSql(StringInfo buf, 54 | PlannerInfo *root, 55 | RelOptInfo *baserel, 56 | Bitmapset *attrs_used, 57 | List **retrieved_attrs, 58 | TdsFdwOptionSet* option_set); 59 | 60 | /* 61 | * deparse remote INSERT statement 62 | * 63 | * The statement text is appended to buf, and we also create an integer List 64 | * of the columns being retrieved by RETURNING (if any), which is returned 65 | * to *retrieved_attrs. 66 | */ 67 | void 68 | deparseInsertSql(StringInfo buf, PlannerInfo *root, 69 | Index rtindex, Relation rel, 70 | List *targetAttrs, bool doNothing, 71 | List *returningList, List **retrieved_attrs, TdsFdwOptionSet* option_set); 72 | 73 | /* 74 | * deparse remote UPDATE statement 75 | * 76 | * The statement text is appended to buf, and we also create an integer List 77 | * of the columns being retrieved by RETURNING (if any), which is returned 78 | * to *retrieved_attrs. 79 | */ 80 | void 81 | deparseUpdateSql(StringInfo buf, PlannerInfo *root, 82 | Index rtindex, Relation rel, 83 | List *targetAttrs, List *returningList, 84 | List **retrieved_attrs, 85 | TdsFdwOptionSet* option_set); 86 | 87 | /* 88 | * deparse remote DELETE statement 89 | * 90 | * The statement text is appended to buf, and we also create an integer List 91 | * of the columns being retrieved by RETURNING (if any), which is returned 92 | * to *retrieved_attrs. 93 | */ 94 | void 95 | deparseDeleteSql(StringInfo buf, PlannerInfo *root, 96 | Index rtindex, Relation rel, 97 | List *returningList, 98 | List **retrieved_attrs, 99 | TdsFdwOptionSet* option_set); 100 | 101 | /* 102 | * Construct SELECT statement to acquire size in blocks of given relation. 103 | * 104 | * Note: we use local definition of block size, not remote definition. 105 | * This is perhaps debatable. 106 | * 107 | * Note: pg_relation_size() exists in 8.1 and later. 108 | */ 109 | void 110 | deparseAnalyzeSizeSql(StringInfo buf, Relation rel); 111 | 112 | /* 113 | * Append a SQL string literal representing "val" to buf. 114 | */ 115 | void 116 | deparseStringLiteral(StringInfo buf, const char *val); 117 | 118 | /* 119 | * Construct SELECT statement to acquire sample rows of given relation. 120 | * 121 | * SELECT command is appended to buf, and list of columns retrieved 122 | * is returned to *retrieved_attrs. 123 | */ 124 | void 125 | deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs); 126 | 127 | /* 128 | * Deparse WHERE clauses in given list of RestrictInfos and append them to buf. 129 | * 130 | * baserel is the foreign table we're planning for. 131 | * 132 | * If no WHERE clause already exists in the buffer, is_first should be true. 133 | * 134 | * If params is not NULL, it receives a list of Params and other-relation Vars 135 | * used in the clauses; these values must be transmitted to the remote server 136 | * as parameter values. 137 | * 138 | * If params is NULL, we're generating the query for EXPLAIN purposes, 139 | * so Params and other-relation Vars should be replaced by dummy values. 140 | */ 141 | void 142 | appendWhereClause(StringInfo buf, 143 | PlannerInfo *root, 144 | RelOptInfo *baserel, 145 | List *exprs, 146 | bool is_first, 147 | List **params); 148 | 149 | /* 150 | * Deparse ORDER BY clause according to the given pathkeys for given base 151 | * relation. From given pathkeys expressions belonging entirely to the given 152 | * base relation are obtained and deparsed. 153 | */ 154 | void 155 | appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, 156 | List *pathkeys); 157 | 158 | #endif -------------------------------------------------------------------------------- /include/options.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef OPTIONS_H 3 | #define OPTIONS_H 4 | 5 | #include "postgres.h" 6 | 7 | /* valid options follow this format */ 8 | 9 | typedef struct TdsFdwOption 10 | { 11 | const char *optname; 12 | Oid optcontext; 13 | } TdsFdwOption; 14 | 15 | /* option values will be put here */ 16 | 17 | typedef struct TdsFdwOptionSet 18 | { 19 | char *servername; 20 | char *language; 21 | char *character_set; 22 | int port; 23 | char *database; 24 | int dbuse; 25 | char* tds_version; 26 | char* msg_handler; 27 | char *username; 28 | char *password; 29 | char *query; 30 | char *schema_name; 31 | char *table_name; 32 | char* row_estimate_method; 33 | bool sqlserver_ansi_mode; 34 | int match_column_names; 35 | int use_remote_estimate; 36 | int fdw_startup_cost; 37 | int fdw_tuple_cost; 38 | int local_tuple_estimate; 39 | } TdsFdwOptionSet; 40 | 41 | void tdsValidateOptions(List *options_list, Oid context, TdsFdwOptionSet* option_set); 42 | void tdsGetForeignServerOptionsFromCatalog(Oid foreignserverid, TdsFdwOptionSet* option_set); 43 | void tdsGetForeignTableOptionsFromCatalog(Oid foreigntableid, TdsFdwOptionSet* option_set); 44 | void tdsValidateOptionSet(TdsFdwOptionSet* option_set); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/tds_fdw.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------ 2 | * 3 | * Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) 4 | * 5 | * Author: Geoff Montee 6 | * Name: tds_fdw 7 | * File: tds_fdw/include/tds_fdw.h 8 | * 9 | * Description: 10 | * This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, 11 | * such as Sybase databases and Microsoft SQL server. 12 | * 13 | * This foreign data wrapper requires requires a library that uses the DB-Library interface, 14 | * such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not 15 | * the proprietary implementations of DB-Library. 16 | *---------------------------------------------------------------------------- 17 | */ 18 | 19 | 20 | #ifndef TDS_FDW_H 21 | #define TDS_FDW_H 22 | 23 | /* postgres headers */ 24 | 25 | #include "postgres.h" 26 | #include "funcapi.h" 27 | #include "commands/explain.h" 28 | #include "foreign/fdwapi.h" 29 | #include "foreign/foreign.h" 30 | 31 | #if (PG_VERSION_NUM >= 90200) 32 | #include "optimizer/pathnode.h" 33 | #include "optimizer/restrictinfo.h" 34 | #include "optimizer/planmain.h" 35 | #endif 36 | 37 | /* DB-Library headers (e.g. FreeTDS) */ 38 | #include 39 | #include 40 | 41 | #include "options.h" 42 | 43 | #if PG_VERSION_NUM >= 90500 44 | #define IMPORT_API 45 | #else 46 | #undef IMPORT_API 47 | #endif /* PG_VERSION_NUM */ 48 | 49 | /* a column */ 50 | 51 | typedef union COL_VALUE 52 | { 53 | DBSMALLINT dbsmallint; 54 | DBINT dbint; 55 | DBBIGINT dbbigint; 56 | DBREAL dbreal; 57 | DBFLT8 dbflt8; 58 | } COL_VALUE; 59 | 60 | typedef struct COL 61 | { 62 | char *name; 63 | int srctype; 64 | bool useraw; 65 | COL_VALUE value; 66 | int local_index; 67 | Oid attr_oid; 68 | } COL; 69 | 70 | /* This struct is similar to PgFdwRelationInfo from postgres_fdw */ 71 | typedef struct TdsFdwRelationInfo 72 | { 73 | /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */ 74 | List *remote_conds; 75 | List *local_conds; 76 | 77 | /* Bitmap of attr numbers we need to fetch from the remote server. */ 78 | Bitmapset *attrs_used; 79 | 80 | /* Cost and selectivity of local_conds. */ 81 | QualCost local_conds_cost; 82 | Selectivity local_conds_sel; 83 | 84 | /* Estimated size and cost for a scan with baserestrictinfo quals. */ 85 | double rows; 86 | int width; 87 | Cost startup_cost; 88 | Cost total_cost; 89 | 90 | /* Options extracted from catalogs. */ 91 | bool use_remote_estimate; 92 | Cost fdw_startup_cost; 93 | Cost fdw_tuple_cost; 94 | /* tds_fdw won't ship any PostgreSQL extensions. remove this later. */ 95 | //List *shippable_extensions; /* OIDs of whitelisted extensions */ 96 | 97 | /* Cached catalog information. */ 98 | ForeignTable *table; 99 | ForeignServer *server; 100 | UserMapping *user; /* only set in use_remote_estimate mode */ 101 | } TdsFdwRelationInfo; 102 | 103 | /* this maintains state */ 104 | 105 | typedef struct TdsFdwExecutionState 106 | { 107 | LOGINREC *login; 108 | DBPROCESS *dbproc; 109 | AttInMetadata *attinmeta; 110 | char *query; 111 | List *retrieved_attrs; 112 | int first; 113 | COL *columns; 114 | Datum *datums; 115 | bool *isnull; 116 | int ncols; 117 | int row; 118 | MemoryContext mem_cxt; 119 | } TdsFdwExecutionState; 120 | 121 | /* Callback argument for ec_member_matches_foreign */ 122 | typedef struct 123 | { 124 | Expr *current; /* current expr, or NULL if not yet found */ 125 | List *already_used; /* expressions already dealt with */ 126 | } ec_member_foreign_arg; 127 | 128 | /* functions called via SQL */ 129 | 130 | extern Datum tds_fdw_handler(PG_FUNCTION_ARGS); 131 | extern Datum tds_fdw_validator(PG_FUNCTION_ARGS); 132 | 133 | /* FDW callback routines */ 134 | 135 | void tdsExplainForeignScan(ForeignScanState *node, ExplainState *es); 136 | void tdsBeginForeignScan(ForeignScanState *node, int eflags); 137 | TupleTableSlot* tdsIterateForeignScan(ForeignScanState *node); 138 | void tdsReScanForeignScan(ForeignScanState *node); 139 | void tdsEndForeignScan(ForeignScanState *node); 140 | 141 | /* routines for 9.2.0+ */ 142 | #if (PG_VERSION_NUM >= 90200) 143 | void tdsGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); 144 | void tdsEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid); 145 | void tdsGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); 146 | bool tdsAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); 147 | #if (PG_VERSION_NUM >= 90500) 148 | ForeignScan* tdsGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan); 149 | #else 150 | ForeignScan* tdsGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses); 151 | #endif 152 | /* routines for versions older than 9.2.0 */ 153 | #else 154 | FdwPlan* tdsPlanForeignScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel); 155 | #endif 156 | 157 | #ifdef IMPORT_API 158 | List *tdsImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid); 159 | #endif /* IMPORT_API */ 160 | 161 | /* compatibility with PostgreSQL 9.6+ */ 162 | #ifndef ALLOCSET_DEFAULT_SIZES 163 | #define ALLOCSET_DEFAULT_SIZES \ 164 | ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE 165 | #endif 166 | 167 | /* compatibility with PostgreSQL v11+ */ 168 | #if PG_VERSION_NUM < 110000 169 | /* new in v11 */ 170 | #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) 171 | #else 172 | /* removed in v11 */ 173 | #define get_relid_attribute_name(relid, varattno) get_attname((relid), (varattno), false) 174 | #endif 175 | 176 | /* Helper functions */ 177 | 178 | bool is_builtin(Oid objectId); 179 | Expr * find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel); 180 | bool is_shippable(Oid objectId, Oid classId, TdsFdwRelationInfo *fpinfo); 181 | void tdsBuildForeignQuery(PlannerInfo *root, RelOptInfo *baserel, TdsFdwOptionSet* option_set, 182 | Bitmapset* attrs_used, List** retrieved_attrs, 183 | List* remote_conds, List* remote_join_conds, List* pathkeys); 184 | int tdsSetupConnection(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS **dbproc); 185 | double tdsGetRowCount(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc); 186 | double tdsGetRowCountShowPlanAll(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc); 187 | double tdsGetRowCountExecute(TdsFdwOptionSet* option_set, LOGINREC *login, DBPROCESS *dbproc); 188 | double tdsGetStartupCost(TdsFdwOptionSet* option_set); 189 | void tdsGetColumnMetadata(ForeignScanState *node, TdsFdwOptionSet *option_set); 190 | char* tdsConvertToCString(DBPROCESS* dbproc, int srctype, const BYTE* src, DBINT srclen); 191 | #if (PG_VERSION_NUM >= 90400) 192 | int tdsDatetimeToDatum(DBPROCESS *dbproc, DBDATETIME *src, Datum *datetime_out); 193 | #endif 194 | 195 | /* Helper functions for DB-Library API */ 196 | 197 | int tds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); 198 | int tds_notice_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *svr_name, char *proc_name, int line); 199 | int tds_blackhole_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *svr_name, char *proc_name, int line); 200 | 201 | #endif 202 | -------------------------------------------------------------------------------- /include/visibility.h: -------------------------------------------------------------------------------- 1 | #ifndef VISIBILITY_H 2 | #define VISIBILITY_H 3 | 4 | 5 | #if __GNUC__ >= 4 6 | #define PGDLLEXPORT __attribute__((visibility("default"))) 7 | #endif 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /logo/tds_fdw.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 39 | 44 | 45 | 46 | 67 | 73 | 76 | 83 | 91 | 97 | 98 | 99 | 105 | 107 | 114 | 122 | 128 | 129 | 130 | 136 | 138 | 141 | 149 | 157 | 158 | 161 | 169 | 177 | 178 | 182 | 190 | 198 | 199 | 207 | 208 | 209 | 214 | 217 | 221 | 225 | 229 | 235 | 241 | 247 | 253 | 259 | 265 | 271 | 277 | 283 | 289 | 295 | 301 | 307 | 313 | 319 | 325 | 331 | 337 | 343 | 349 | 355 | 361 | 367 | 373 | 379 | 385 | 391 | 397 | 403 | 409 | 415 | 421 | 427 | 429 | 431 | 433 | 435 | 437 | 439 | 441 | 443 | 445 | 447 | 449 | 451 | 453 | 455 | 457 | 458 | 459 | 465 | 468 | 472 | 476 | 480 | 486 | 492 | 498 | 504 | 510 | 516 | 522 | 528 | 534 | 540 | 546 | 552 | 558 | 564 | 570 | 576 | 582 | 588 | 594 | 600 | 606 | 612 | 618 | 624 | 630 | 636 | 642 | 648 | 654 | 660 | 666 | 672 | 678 | 680 | 682 | 684 | 686 | 688 | 690 | 692 | 694 | 696 | 698 | 700 | 702 | 704 | 706 | 708 | 709 | 710 | 717 | 726 | TDS 737 | 738 | 745 | 754 | TDS 765 | 766 | 773 | 776 | 779 | 782 | 786 | 790 | 793 | 796 | 799 | 803 | 806 | 809 | 813 | 817 | 820 | 824 | 825 | 826 | 827 | 828 | -------------------------------------------------------------------------------- /sql/tds_fdw.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------ 2 | # 3 | # Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) 4 | # 5 | # Author: Geoff Montee 6 | # Name: tds_fdw 7 | # File: tds_fdw/sql/tds_fdw--1.0.0-beta.sql 8 | # 9 | # Description: 10 | # This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, 11 | # such as Sybase databases and Microsoft SQL server. 12 | # 13 | # This foreign data wrapper requires requires a library that uses the DB-Library interface, 14 | # such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not 15 | # the proprietary implementations of DB-Library. 16 | #----------------------------------------------------------------------------*/ 17 | 18 | CREATE FUNCTION tds_fdw_handler() 19 | RETURNS fdw_handler 20 | AS 'MODULE_PATHNAME' 21 | LANGUAGE C STRICT; 22 | 23 | CREATE FUNCTION tds_fdw_validator(text[], oid) 24 | RETURNS void 25 | AS 'MODULE_PATHNAME' 26 | LANGUAGE C STRICT; 27 | 28 | CREATE FOREIGN DATA WRAPPER tds_fdw 29 | HANDLER tds_fdw_handler 30 | VALIDATOR tds_fdw_validator; 31 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | 2 | /* postgres headers */ 3 | 4 | #include "postgres.h" 5 | #include "funcapi.h" 6 | #include "access/reloptions.h" 7 | #include "catalog/pg_foreign_server.h" 8 | #include "catalog/pg_foreign_table.h" 9 | #include "catalog/pg_user_mapping.h" 10 | #include "catalog/pg_type.h" 11 | #include "commands/defrem.h" 12 | #include "commands/explain.h" 13 | #include "foreign/fdwapi.h" 14 | #include "foreign/foreign.h" 15 | #include "miscadmin.h" 16 | #include "mb/pg_wchar.h" 17 | #include "optimizer/cost.h" 18 | #include "storage/fd.h" 19 | #include "utils/array.h" 20 | #include "utils/builtins.h" 21 | #include "utils/rel.h" 22 | #include "utils/memutils.h" 23 | 24 | #if (PG_VERSION_NUM >= 90200) 25 | #include "optimizer/pathnode.h" 26 | #include "optimizer/restrictinfo.h" 27 | #include "optimizer/planmain.h" 28 | #endif 29 | 30 | /* DB-Library headers (e.g. FreeTDS */ 31 | #include 32 | #include 33 | 34 | #include "tds_fdw.h" 35 | #include "options.h" 36 | 37 | void tdsGetForeignServerOptions(List *options_list, TdsFdwOptionSet *option_set); 38 | void tdsGetForeignServerTableOptions(List *options_list, TdsFdwOptionSet *option_set); 39 | void tdsGetForeignTableOptions(List *options_list, TdsFdwOptionSet *option_set); 40 | void tdsGetUserMappingOptions(List *options_list, TdsFdwOptionSet *option_set); 41 | 42 | void tdsValidateForeignTableOptionSet(TdsFdwOptionSet *option_set); 43 | 44 | void tdsSetDefaultOptions(TdsFdwOptionSet *option_set); 45 | 46 | bool tdsIsValidOption(const char *option, Oid context); 47 | void tdsOptionSetInit(TdsFdwOptionSet* option_set); 48 | 49 | /* these are valid options */ 50 | 51 | static struct TdsFdwOption valid_options[] = 52 | { 53 | { "servername", ForeignServerRelationId }, 54 | { "language", ForeignServerRelationId }, 55 | { "character_set", ForeignServerRelationId }, 56 | { "port", ForeignServerRelationId }, 57 | { "database", ForeignServerRelationId }, 58 | { "dbuse", ForeignServerRelationId }, 59 | { "sqlserver_ansi_mode", ForeignServerRelationId }, 60 | { "tds_version", ForeignServerRelationId }, 61 | { "msg_handler", ForeignServerRelationId }, 62 | { "row_estimate_method", ForeignServerRelationId }, 63 | { "use_remote_estimate", ForeignServerRelationId }, 64 | { "fdw_startup_cost", ForeignServerRelationId }, 65 | { "fdw_tuple_cost", ForeignServerRelationId }, 66 | { "username", UserMappingRelationId }, 67 | { "password", UserMappingRelationId }, 68 | { "query", ForeignTableRelationId }, 69 | { "table", ForeignTableRelationId }, 70 | { "schema_name", ForeignTableRelationId }, 71 | { "table_name", ForeignTableRelationId }, 72 | { "row_estimate_method", ForeignTableRelationId }, 73 | { "match_column_names", ForeignTableRelationId }, 74 | { "use_remote_estimate", ForeignTableRelationId }, 75 | { "local_tuple_estimate", ForeignTableRelationId }, 76 | { "column_name", AttributeRelationId }, 77 | { NULL, InvalidOid } 78 | }; 79 | 80 | /* default IP address */ 81 | 82 | static const char *DEFAULT_SERVERNAME = "127.0.0.1"; 83 | 84 | /* default method to use to estimate rows in results */ 85 | 86 | static const char *DEFAULT_ROW_ESTIMATE_METHOD = "execute"; 87 | 88 | /* default function used to handle TDS messages */ 89 | 90 | static const char *DEFAULT_MSG_HANDLER = "blackhole"; 91 | 92 | /* whether to match on column names by default. if not, we use column order. */ 93 | 94 | static const int DEFAULT_MATCH_COLUMN_NAMES = 1; 95 | 96 | /* by default we use remote estimates */ 97 | 98 | static const int DEFAULT_USE_REMOTE_ESTIMATE = 1; 99 | 100 | /* by default we use remote estimates */ 101 | 102 | static const int DEFAULT_FDW_STARTUP_COST = 100; 103 | 104 | /* by default we use remote estimates */ 105 | 106 | static const int DEFAULT_FDW_TUPLE_COST = 100; 107 | 108 | /* conservative default tuple count */ 109 | 110 | static const int DEFAULT_LOCAL_TUPLE_ESTIMATE = 1000; 111 | 112 | void tdsValidateOptions(List *options_list, Oid context, TdsFdwOptionSet* option_set) 113 | { 114 | #ifdef DEBUG 115 | ereport(NOTICE, 116 | (errmsg("----> starting tdsValidateOptions") 117 | )); 118 | #endif 119 | 120 | tdsOptionSetInit(option_set); 121 | 122 | if (context == ForeignServerRelationId) 123 | { 124 | tdsGetForeignServerOptions(options_list, option_set); 125 | tdsGetForeignServerTableOptions(options_list, option_set); 126 | } 127 | 128 | else if (context == ForeignTableRelationId) 129 | { 130 | tdsGetForeignTableOptions(options_list, option_set); 131 | tdsSetDefaultOptions(option_set); 132 | tdsValidateForeignTableOptionSet(option_set); 133 | } 134 | 135 | else if (context == UserMappingRelationId) 136 | { 137 | tdsGetUserMappingOptions(options_list, option_set); 138 | } 139 | 140 | #ifdef DEBUG 141 | ereport(NOTICE, 142 | (errmsg("----> finishing tdsValidateOptions") 143 | )); 144 | #endif 145 | } 146 | 147 | /* get options for FOREIGN SERVER objects using this module */ 148 | 149 | void tdsGetForeignServerOptionsFromCatalog(Oid foreignserverid, TdsFdwOptionSet* option_set) 150 | { 151 | ForeignServer *f_server; 152 | UserMapping *f_mapping; 153 | 154 | #ifdef DEBUG 155 | ereport(NOTICE, 156 | (errmsg("----> starting tdsGetForeignServerOptionsFromCatalog") 157 | )); 158 | #endif 159 | 160 | tdsOptionSetInit(option_set); 161 | 162 | f_server = GetForeignServer(foreignserverid); 163 | f_mapping = GetUserMapping(GetUserId(), foreignserverid); 164 | 165 | tdsGetForeignServerOptions(f_server->options, option_set); 166 | tdsGetForeignServerTableOptions(f_server->options, option_set); 167 | 168 | tdsGetUserMappingOptions(f_mapping->options, option_set); 169 | 170 | tdsSetDefaultOptions(option_set); 171 | 172 | #ifdef DEBUG 173 | ereport(NOTICE, 174 | (errmsg("----> finishing tdsGetForeignTableOptionsFromCatalog") 175 | )); 176 | #endif 177 | } 178 | 179 | /* get options for FOREIGN TABLE and FOREIGN SERVER objects using this module */ 180 | 181 | void tdsGetForeignTableOptionsFromCatalog(Oid foreigntableid, TdsFdwOptionSet* option_set) 182 | { 183 | ForeignTable *f_table; 184 | ForeignServer *f_server; 185 | UserMapping *f_mapping; 186 | 187 | #ifdef DEBUG 188 | ereport(NOTICE, 189 | (errmsg("----> starting tdsGetForeignTableOptionsFromCatalog") 190 | )); 191 | #endif 192 | 193 | tdsOptionSetInit(option_set); 194 | 195 | f_table = GetForeignTable(foreigntableid); 196 | f_server = GetForeignServer(f_table->serverid); 197 | f_mapping = GetUserMapping(GetUserId(), f_table->serverid); 198 | 199 | tdsGetForeignServerOptions(f_server->options, option_set); 200 | tdsGetForeignServerTableOptions(f_server->options, option_set); 201 | 202 | tdsGetForeignTableOptions(f_table->options, option_set); 203 | 204 | tdsGetUserMappingOptions(f_mapping->options, option_set); 205 | 206 | tdsSetDefaultOptions(option_set); 207 | tdsValidateOptionSet(option_set); 208 | 209 | #ifdef DEBUG 210 | ereport(NOTICE, 211 | (errmsg("----> finishing tdsGetForeignTableOptionsFromCatalog") 212 | )); 213 | #endif 214 | } 215 | 216 | void tdsGetForeignServerOptions(List *options_list, TdsFdwOptionSet *option_set) 217 | { 218 | ListCell *cell; 219 | 220 | #ifdef DEBUG 221 | ereport(NOTICE, 222 | (errmsg("----> starting tdsGetForeignServerOptions") 223 | )); 224 | #endif 225 | 226 | foreach (cell, options_list) 227 | { 228 | DefElem *def = (DefElem *) lfirst(cell); 229 | 230 | #ifdef DEBUG 231 | ereport(NOTICE, 232 | (errmsg("Working on option %s", def->defname) 233 | )); 234 | #endif 235 | 236 | if (!tdsIsValidOption(def->defname, ForeignServerRelationId)) 237 | { 238 | TdsFdwOption *opt; 239 | StringInfoData buf; 240 | 241 | initStringInfo(&buf); 242 | for (opt = valid_options; opt->optname; opt++) 243 | { 244 | if (ForeignServerRelationId == opt->optcontext) 245 | appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); 246 | } 247 | 248 | ereport(ERROR, 249 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 250 | errmsg("Invalid option \"%s\"", def->defname), 251 | errhint("Valid options in this context are: %s", buf.len ? buf.data : "") 252 | )); 253 | } 254 | 255 | if (strcmp(def->defname, "servername") == 0) 256 | { 257 | if (option_set->servername) 258 | ereport(ERROR, 259 | (errcode(ERRCODE_SYNTAX_ERROR), 260 | errmsg("Redundant option: servername (%s)", defGetString(def)) 261 | )); 262 | 263 | option_set->servername = defGetString(def); 264 | } 265 | 266 | else if (strcmp(def->defname, "language") == 0) 267 | { 268 | if (option_set->language) 269 | ereport(ERROR, 270 | (errcode(ERRCODE_SYNTAX_ERROR), 271 | errmsg("Redundant option: language (%s)", defGetString(def)) 272 | )); 273 | 274 | option_set->language = defGetString(def); 275 | } 276 | 277 | else if (strcmp(def->defname, "character_set") == 0) 278 | { 279 | if (option_set->character_set) 280 | ereport(ERROR, 281 | (errcode(ERRCODE_SYNTAX_ERROR), 282 | errmsg("Redundant option: character_set (%s)", defGetString(def)) 283 | )); 284 | 285 | option_set->character_set = defGetString(def); 286 | } 287 | 288 | else if (strcmp(def->defname, "port") == 0) 289 | { 290 | if (option_set->port) 291 | ereport(ERROR, 292 | (errcode(ERRCODE_SYNTAX_ERROR), 293 | errmsg("Redundant option: port (%s)", defGetString(def)) 294 | )); 295 | 296 | option_set->port = atoi(defGetString(def)); 297 | } 298 | 299 | else if (strcmp(def->defname, "database") == 0) 300 | { 301 | if (option_set->database) 302 | ereport(ERROR, 303 | (errcode(ERRCODE_SYNTAX_ERROR), 304 | errmsg("Redundant option: database (%s)", defGetString(def)) 305 | )); 306 | 307 | option_set->database = defGetString(def); 308 | } 309 | 310 | else if (strcmp(def->defname, "dbuse") == 0) 311 | { 312 | if (option_set->dbuse) 313 | ereport(ERROR, 314 | (errcode(ERRCODE_SYNTAX_ERROR), 315 | errmsg("Redundant option: dbuse (%s)", defGetString(def)) 316 | )); 317 | 318 | option_set->dbuse = atoi(defGetString(def)); 319 | } 320 | 321 | else if(strcmp(def->defname, "sqlserver_ansi_mode") == 0) 322 | { 323 | if (option_set->sqlserver_ansi_mode) 324 | ereport(ERROR, 325 | (errcode(ERRCODE_SYNTAX_ERROR), 326 | errmsg("Redundant option: sqlserver_ansi_mode (%s)", defGetBoolean(def)) 327 | )); 328 | 329 | option_set->sqlserver_ansi_mode = defGetBoolean(def); 330 | } 331 | 332 | else if (strcmp(def->defname, "tds_version") == 0) 333 | { 334 | int tds_version_test = 0; 335 | 336 | if (option_set->tds_version) 337 | ereport(ERROR, 338 | (errcode(ERRCODE_SYNTAX_ERROR), 339 | errmsg("Redundant option: database (%s)", defGetString(def)) 340 | )); 341 | 342 | option_set->tds_version = defGetString(def); 343 | 344 | if (strcmp(option_set->tds_version, "4.2") == 0) 345 | { 346 | tds_version_test = 1; 347 | } 348 | 349 | else if (strcmp(option_set->tds_version, "5.0") == 0) 350 | { 351 | tds_version_test = 1; 352 | } 353 | 354 | else if (strcmp(option_set->tds_version, "7.0") == 0) 355 | { 356 | tds_version_test = 1; 357 | } 358 | 359 | #ifdef DBVERSION_71 360 | else if (strcmp(option_set->tds_version, "7.1") == 0) 361 | { 362 | tds_version_test = 1; 363 | } 364 | #endif 365 | 366 | #ifdef DBVERSION_72 367 | else if (strcmp(option_set->tds_version, "7.2") == 0) 368 | { 369 | tds_version_test = 1; 370 | } 371 | #endif 372 | 373 | #ifdef DBVERSION_73 374 | else if (strcmp(option_set->tds_version, "7.3") == 0) 375 | { 376 | tds_version_test = 1; 377 | } 378 | #endif 379 | 380 | #ifdef DBVERSION_74 381 | else if (strcmp(option_set->tds_version, "7.4") == 0) 382 | { 383 | tds_version_test = 1; 384 | } 385 | #endif 386 | 387 | if (!tds_version_test) 388 | { 389 | ereport(ERROR, 390 | (errcode(ERRCODE_SYNTAX_ERROR), 391 | errmsg("Unknown tds version: %s.", option_set->tds_version) 392 | )); 393 | } 394 | } 395 | 396 | else if (strcmp(def->defname, "msg_handler") == 0) 397 | { 398 | int msg_handler_test = 0; 399 | 400 | if (option_set->msg_handler) 401 | ereport(ERROR, 402 | (errcode(ERRCODE_SYNTAX_ERROR), 403 | errmsg("Redundant option: msg_handler (%s)", defGetString(def)) 404 | )); 405 | 406 | option_set->msg_handler = defGetString(def); 407 | 408 | if (strcmp(option_set->msg_handler, "notice") == 0) 409 | { 410 | msg_handler_test = 1; 411 | } 412 | 413 | else if (strcmp(option_set->msg_handler, "blackhole") == 0) 414 | { 415 | msg_handler_test = 1; 416 | } 417 | 418 | if (!msg_handler_test) 419 | { 420 | ereport(ERROR, 421 | (errcode(ERRCODE_SYNTAX_ERROR), 422 | errmsg("Unknown msg handler: %s.", option_set->msg_handler) 423 | )); 424 | } 425 | } 426 | 427 | else if (strcmp(def->defname, "fdw_startup_cost") == 0) 428 | { 429 | if (option_set->fdw_startup_cost) 430 | ereport(ERROR, 431 | (errcode(ERRCODE_SYNTAX_ERROR), 432 | errmsg("Redundant option: fdw_startup_cost (%s)", defGetString(def)) 433 | )); 434 | 435 | option_set->fdw_startup_cost = atoi(defGetString(def)); 436 | } 437 | 438 | else if (strcmp(def->defname, "fdw_tuple_cost") == 0) 439 | { 440 | if (option_set->fdw_tuple_cost) 441 | ereport(ERROR, 442 | (errcode(ERRCODE_SYNTAX_ERROR), 443 | errmsg("Redundant option: fdw_tuple_cost (%s)", defGetString(def)) 444 | )); 445 | 446 | option_set->fdw_tuple_cost = atoi(defGetString(def)); 447 | } 448 | } 449 | 450 | #ifdef DEBUG 451 | ereport(NOTICE, 452 | (errmsg("----> finishing tdsGetForeignServerOptions") 453 | )); 454 | #endif 455 | } 456 | 457 | void tdsGetForeignServerTableOptions(List *options_list, TdsFdwOptionSet *option_set) 458 | { 459 | ListCell *cell; 460 | 461 | #ifdef DEBUG 462 | ereport(NOTICE, 463 | (errmsg("----> starting tdsGetForeignServerTableOptions") 464 | )); 465 | #endif 466 | 467 | foreach (cell, options_list) 468 | { 469 | DefElem *def = (DefElem *) lfirst(cell); 470 | 471 | #ifdef DEBUG 472 | ereport(NOTICE, 473 | (errmsg("Working on option %s", def->defname) 474 | )); 475 | #endif 476 | 477 | if (!tdsIsValidOption(def->defname, ForeignServerRelationId)) 478 | { 479 | TdsFdwOption *opt; 480 | StringInfoData buf; 481 | 482 | initStringInfo(&buf); 483 | for (opt = valid_options; opt->optname; opt++) 484 | { 485 | if (ForeignServerRelationId == opt->optcontext) 486 | appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); 487 | } 488 | 489 | ereport(ERROR, 490 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 491 | errmsg("Invalid option \"%s\"", def->defname), 492 | errhint("Valid options in this context are: %s", buf.len ? buf.data : "") 493 | )); 494 | } 495 | 496 | if (strcmp(def->defname, "row_estimate_method") == 0) 497 | { 498 | if (option_set->row_estimate_method) 499 | ereport(ERROR, 500 | (errcode(ERRCODE_SYNTAX_ERROR), 501 | errmsg("Redundant option: row_estimate_method (%s)", defGetString(def)) 502 | )); 503 | 504 | option_set->row_estimate_method = defGetString(def); 505 | 506 | if ((strcmp(option_set->row_estimate_method, "execute") != 0) 507 | && (strcmp(option_set->row_estimate_method, "showplan_all") != 0)) 508 | { 509 | ereport(ERROR, 510 | (errcode(ERRCODE_SYNTAX_ERROR), 511 | errmsg("row_estimate_method should be set to \"execute\" or \"showplan_all\". Currently set to %s", option_set->row_estimate_method) 512 | )); 513 | } 514 | } 515 | 516 | else if (strcmp(def->defname, "use_remote_estimate") == 0) 517 | { 518 | if (option_set->use_remote_estimate != -1) 519 | ereport(ERROR, 520 | (errcode(ERRCODE_SYNTAX_ERROR), 521 | errmsg("Redundant option: use_remote_estimate (%s)", defGetString(def)) 522 | )); 523 | 524 | option_set->use_remote_estimate = atoi(defGetString(def)); 525 | } 526 | } 527 | 528 | #ifdef DEBUG 529 | ereport(NOTICE, 530 | (errmsg("----> finishing tdsGetForeignServerTableOptions") 531 | )); 532 | #endif 533 | } 534 | 535 | void tdsGetForeignTableOptions(List *options_list, TdsFdwOptionSet *option_set) 536 | { 537 | ListCell *cell; 538 | 539 | #ifdef DEBUG 540 | ereport(NOTICE, 541 | (errmsg("----> starting tdsGetForeignTableOptions") 542 | )); 543 | #endif 544 | 545 | foreach (cell, options_list) 546 | { 547 | DefElem *def = (DefElem *) lfirst(cell); 548 | 549 | #ifdef DEBUG 550 | ereport(NOTICE, 551 | (errmsg("Working on option %s", def->defname) 552 | )); 553 | #endif 554 | 555 | if (!tdsIsValidOption(def->defname, ForeignTableRelationId)) 556 | { 557 | TdsFdwOption *opt; 558 | StringInfoData buf; 559 | 560 | initStringInfo(&buf); 561 | for (opt = valid_options; opt->optname; opt++) 562 | { 563 | if (ForeignTableRelationId == opt->optcontext) 564 | appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); 565 | } 566 | 567 | ereport(ERROR, 568 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 569 | errmsg("Invalid option \"%s\"", def->defname), 570 | errhint("Valid options in this context are: %s", buf.len ? buf.data : "") 571 | )); 572 | } 573 | 574 | if (strcmp(def->defname, "query") == 0) 575 | { 576 | if (option_set->query) 577 | ereport(ERROR, 578 | (errcode(ERRCODE_SYNTAX_ERROR), 579 | errmsg("Redundant option: query (%s)", defGetString(def)) 580 | )); 581 | 582 | option_set->query = defGetString(def); 583 | } 584 | 585 | else if (strcmp(def->defname, "schema_name") == 0) 586 | { 587 | if (option_set->schema_name) 588 | ereport(ERROR, 589 | (errcode(ERRCODE_SYNTAX_ERROR), 590 | errmsg("Redundant option: schema_name (%s)", defGetString(def)) 591 | )); 592 | 593 | option_set->schema_name = defGetString(def); 594 | } 595 | 596 | else if (strcmp(def->defname, "table") == 0 597 | || strcmp(def->defname, "table_name") == 0) 598 | { 599 | if (option_set->table_name) 600 | ereport(ERROR, 601 | (errcode(ERRCODE_SYNTAX_ERROR), 602 | errmsg("Redundant option: %s (%s)", def->defname, defGetString(def)) 603 | )); 604 | 605 | option_set->table_name = defGetString(def); 606 | } 607 | 608 | else if (strcmp(def->defname, "row_estimate_method") == 0) 609 | { 610 | if (option_set->row_estimate_method) 611 | ereport(ERROR, 612 | (errcode(ERRCODE_SYNTAX_ERROR), 613 | errmsg("Redundant option: row_estimate_method (%s)", defGetString(def)) 614 | )); 615 | 616 | option_set->row_estimate_method = defGetString(def); 617 | 618 | if ((strcmp(option_set->row_estimate_method, "execute") != 0) 619 | && (strcmp(option_set->row_estimate_method, "showplan_all") != 0)) 620 | { 621 | ereport(ERROR, 622 | (errcode(ERRCODE_SYNTAX_ERROR), 623 | errmsg("row_estimate_method should be set to \"execute\" or \"showplan_all\". Currently set to %s", option_set->row_estimate_method) 624 | )); 625 | } 626 | } 627 | 628 | else if (strcmp(def->defname, "match_column_names") == 0) 629 | { 630 | option_set->match_column_names = atoi(defGetString(def)); 631 | } 632 | 633 | else if (strcmp(def->defname, "use_remote_estimate") == 0) 634 | { 635 | 636 | option_set->use_remote_estimate = atoi(defGetString(def)); 637 | } 638 | 639 | else if (strcmp(def->defname, "local_tuple_estimate") == 0) 640 | { 641 | if (option_set->local_tuple_estimate) 642 | ereport(ERROR, 643 | (errcode(ERRCODE_SYNTAX_ERROR), 644 | errmsg("Redundant option: local_tuple_estimate (%s)", defGetString(def)) 645 | )); 646 | 647 | option_set->local_tuple_estimate = atoi(defGetString(def)); 648 | } 649 | } 650 | 651 | #ifdef DEBUG 652 | ereport(NOTICE, 653 | (errmsg("----> finishing tdsGetForeignTableOptions") 654 | )); 655 | #endif 656 | } 657 | 658 | void tdsGetUserMappingOptions(List *options_list, TdsFdwOptionSet *option_set) 659 | { 660 | ListCell *cell; 661 | 662 | #ifdef DEBUG 663 | ereport(NOTICE, 664 | (errmsg("----> starting tdsGetUserMappingOptions") 665 | )); 666 | #endif 667 | 668 | foreach (cell, options_list) 669 | { 670 | DefElem *def = (DefElem *) lfirst(cell); 671 | 672 | #ifdef DEBUG 673 | ereport(NOTICE, 674 | (errmsg("Working on option %s", def->defname) 675 | )); 676 | #endif 677 | 678 | if (!tdsIsValidOption(def->defname, UserMappingRelationId)) 679 | { 680 | TdsFdwOption *opt; 681 | StringInfoData buf; 682 | 683 | initStringInfo(&buf); 684 | for (opt = valid_options; opt->optname; opt++) 685 | { 686 | if (UserMappingRelationId == opt->optcontext) 687 | appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); 688 | } 689 | 690 | ereport(ERROR, 691 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 692 | errmsg("Invalid option \"%s\"", def->defname), 693 | errhint("Valid options in this context are: %s", buf.len ? buf.data : "") 694 | )); 695 | } 696 | 697 | if (strcmp(def->defname, "username") == 0) 698 | { 699 | if (option_set->username) 700 | ereport(ERROR, 701 | (errcode(ERRCODE_SYNTAX_ERROR), 702 | errmsg("Redundant option: username (%s)", defGetString(def)) 703 | )); 704 | 705 | option_set->username = defGetString(def); 706 | } 707 | 708 | else if (strcmp(def->defname, "password") == 0) 709 | { 710 | if (option_set->password) 711 | ereport(ERROR, 712 | (errcode(ERRCODE_SYNTAX_ERROR), 713 | errmsg("Redundant option: password (%s)", defGetString(def)) 714 | )); 715 | 716 | option_set->password = defGetString(def); 717 | } 718 | } 719 | 720 | #ifdef DEBUG 721 | ereport(NOTICE, 722 | (errmsg("----> finishing tdsGetUserMappingOptions") 723 | )); 724 | #endif 725 | } 726 | 727 | void tdsSetDefaultOptions(TdsFdwOptionSet *option_set) 728 | { 729 | #ifdef DEBUG 730 | ereport(NOTICE, 731 | (errmsg("----> starting tdsSetDefaultOptions") 732 | )); 733 | #endif 734 | 735 | if (!option_set->servername) 736 | { 737 | if ((option_set->servername = palloc((strlen(DEFAULT_SERVERNAME) + 1) * sizeof(char))) == NULL) 738 | { 739 | ereport(ERROR, 740 | (errcode(ERRCODE_FDW_OUT_OF_MEMORY), 741 | errmsg("Failed to allocate memory for server name") 742 | )); 743 | } 744 | 745 | sprintf(option_set->servername, "%s", DEFAULT_SERVERNAME); 746 | 747 | #ifdef DEBUG 748 | ereport(NOTICE, 749 | (errmsg("Set servername to default: %s", option_set->servername) 750 | )); 751 | #endif 752 | } 753 | 754 | if (!option_set->row_estimate_method) 755 | { 756 | if ((option_set->row_estimate_method = palloc((strlen(DEFAULT_ROW_ESTIMATE_METHOD) + 1) * sizeof(char))) == NULL) 757 | { 758 | ereport(ERROR, 759 | (errcode(ERRCODE_FDW_OUT_OF_MEMORY), 760 | errmsg("Failed to allocate memory for row estimate method") 761 | )); 762 | } 763 | 764 | sprintf(option_set->row_estimate_method, "%s", DEFAULT_ROW_ESTIMATE_METHOD); 765 | 766 | #ifdef DEBUG 767 | ereport(NOTICE, 768 | (errmsg("Set row_estimate_method to default: %s", option_set->row_estimate_method) 769 | )); 770 | #endif 771 | } 772 | 773 | if (!option_set->msg_handler) 774 | { 775 | if ((option_set->msg_handler= palloc((strlen(DEFAULT_MSG_HANDLER) + 1) * sizeof(char))) == NULL) 776 | { 777 | ereport(ERROR, 778 | (errcode(ERRCODE_FDW_OUT_OF_MEMORY), 779 | errmsg("Failed to allocate memory for msg handler") 780 | )); 781 | } 782 | 783 | sprintf(option_set->msg_handler, "%s", DEFAULT_MSG_HANDLER); 784 | 785 | #ifdef DEBUG 786 | ereport(NOTICE, 787 | (errmsg("Set msg_handler to default: %s", option_set->msg_handler) 788 | )); 789 | #endif 790 | } 791 | 792 | if (option_set->use_remote_estimate == -1) 793 | { 794 | option_set->use_remote_estimate = DEFAULT_USE_REMOTE_ESTIMATE; 795 | 796 | #ifdef DEBUG 797 | ereport(NOTICE, 798 | (errmsg("Set use_remote_estimate to default: %d", option_set->use_remote_estimate) 799 | )); 800 | #endif 801 | } 802 | 803 | if (!option_set->local_tuple_estimate) 804 | { 805 | option_set->local_tuple_estimate = DEFAULT_LOCAL_TUPLE_ESTIMATE; 806 | 807 | #ifdef DEBUG 808 | ereport(NOTICE, 809 | (errmsg("Set local_tuple_estimate to default: %d", option_set->local_tuple_estimate) 810 | )); 811 | #endif 812 | } 813 | 814 | if (!option_set->fdw_startup_cost) 815 | { 816 | option_set->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST; 817 | 818 | #ifdef DEBUG 819 | ereport(NOTICE, 820 | (errmsg("Set fdw_startup_cost to default: %d", option_set->fdw_startup_cost) 821 | )); 822 | #endif 823 | } 824 | 825 | if (!option_set->fdw_tuple_cost) 826 | { 827 | option_set->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST; 828 | 829 | #ifdef DEBUG 830 | ereport(NOTICE, 831 | (errmsg("Set fdw_tuple_cost to default: %d", option_set->fdw_tuple_cost) 832 | )); 833 | #endif 834 | } 835 | 836 | #ifdef DEBUG 837 | ereport(NOTICE, 838 | (errmsg("----> finishing tdsSetDefaultOptions") 839 | )); 840 | #endif 841 | } 842 | 843 | void tdsValidateOptionSet(TdsFdwOptionSet *option_set) 844 | { 845 | #ifdef DEBUG 846 | ereport(NOTICE, 847 | (errmsg("----> starting tdsValidateOptionSet") 848 | )); 849 | #endif 850 | 851 | tdsValidateForeignTableOptionSet(option_set); 852 | 853 | #ifdef DEBUG 854 | ereport(NOTICE, 855 | (errmsg("----> finishing tdsValidateOptionSet") 856 | )); 857 | #endif 858 | } 859 | 860 | void tdsValidateForeignTableOptionSet(TdsFdwOptionSet *option_set) 861 | { 862 | #ifdef DEBUG 863 | ereport(NOTICE, 864 | (errmsg("----> starting tdsValidateForeignTableOptionSet") 865 | )); 866 | #endif 867 | 868 | /* Check conflicting options */ 869 | 870 | if (option_set->table_name && option_set->query) 871 | { 872 | ereport(ERROR, 873 | (errcode(ERRCODE_SYNTAX_ERROR), 874 | errmsg("Conflicting options: table and query options can't be used together.") 875 | )); 876 | } 877 | 878 | /* Check required options */ 879 | 880 | if (!option_set->table_name && !option_set->query) 881 | { 882 | ereport(ERROR, 883 | (errcode(ERRCODE_SYNTAX_ERROR), 884 | errmsg("Required options: either a table or a query must be specified") 885 | )); 886 | } 887 | 888 | /* Check option ranges */ 889 | if (option_set->use_remote_estimate < 0 || option_set->use_remote_estimate > 1) 890 | { 891 | ereport(ERROR, 892 | (errcode(ERRCODE_SYNTAX_ERROR), 893 | errmsg("Invalid value for use_remote_estimate: %d", option_set->use_remote_estimate) 894 | )); 895 | } 896 | #ifdef DEBUG 897 | ereport(NOTICE, 898 | (errmsg("----> finishing tdsValidateForeignTableOptionSet") 899 | )); 900 | #endif 901 | } 902 | 903 | /* validate options for FOREIGN TABLE and FOREIGN SERVER objects using this module */ 904 | 905 | bool tdsIsValidOption(const char *option, Oid context) 906 | { 907 | TdsFdwOption *opt; 908 | 909 | #ifdef DEBUG 910 | ereport(NOTICE, 911 | (errmsg("----> starting tdsIdValidOption") 912 | )); 913 | #endif 914 | 915 | for (opt = valid_options; opt->optname; opt++) 916 | { 917 | if (context == opt->optcontext && strcmp(opt->optname, option) == 0) 918 | return true; 919 | } 920 | 921 | #ifdef DEBUG 922 | ereport(NOTICE, 923 | (errmsg("----> finishing tdsIdValidOption") 924 | )); 925 | #endif 926 | 927 | return false; 928 | } 929 | 930 | /* initialize the option set */ 931 | 932 | void tdsOptionSetInit(TdsFdwOptionSet* option_set) 933 | { 934 | #ifdef DEBUG 935 | ereport(NOTICE, 936 | (errmsg("----> starting tdsOptionSetInit") 937 | )); 938 | #endif 939 | 940 | option_set->servername = NULL; 941 | option_set->language = NULL; 942 | option_set->character_set = NULL; 943 | option_set->port = 0; 944 | option_set->database = NULL; 945 | option_set->dbuse = 0; 946 | option_set->sqlserver_ansi_mode = false; 947 | option_set->tds_version = NULL; 948 | option_set->msg_handler = NULL; 949 | option_set->username = NULL; 950 | option_set->password = NULL; 951 | option_set->query = NULL; 952 | option_set->schema_name = NULL; 953 | option_set->table_name = NULL; 954 | option_set->row_estimate_method = NULL; 955 | option_set->match_column_names = DEFAULT_MATCH_COLUMN_NAMES; 956 | option_set->use_remote_estimate = -1; 957 | option_set->fdw_startup_cost = 0; 958 | option_set->fdw_tuple_cost = 0; 959 | option_set->local_tuple_estimate = 0; 960 | 961 | #ifdef DEBUG 962 | ereport(NOTICE, 963 | (errmsg("----> finishing tdsOptionSetInit") 964 | )); 965 | #endif 966 | } 967 | 968 | -------------------------------------------------------------------------------- /tds_fdw--2.0.1--2.0.2.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via ALTER EXTENSION 2 | \echo Use "ALTER EXTENSION tds_fdw UPDATE TO '2.0.2'" to load this file. \quit 3 | -------------------------------------------------------------------------------- /tds_fdw--2.0.2--2.0.3.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via ALTER EXTENSION 2 | \echo Use "ALTER EXTENSION tds_fdw UPDATE TO '2.0.3'" to load this file. \quit 3 | -------------------------------------------------------------------------------- /tds_fdw--2.0.3--2.0.4.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via ALTER EXTENSION 2 | \echo Use "ALTER EXTENSION tds_fdw UPDATE TO '2.0.4'" to load this file. \quit 3 | -------------------------------------------------------------------------------- /tds_fdw.control: -------------------------------------------------------------------------------- 1 | #*------------------------------------------------------------------ 2 | # 3 | # Foreign data wrapper for TDS (Sybase and Microsoft SQL Server) 4 | # 5 | # Author: Geoff Montee 6 | # Name: tds_fdw 7 | # File: tds_fdw/tds_fdw.control 8 | # 9 | # Description: 10 | # This is a PostgreSQL foreign data wrapper for use to connect to databases that use TDS, 11 | # such as Sybase databases and Microsoft SQL server. 12 | # 13 | # This foreign data wrapper requires requires a library that uses the DB-Library interface, 14 | # such as FreeTDS (http://www.freetds.org/). This has been tested with FreeTDS, but not 15 | # the proprietary implementations of DB-Library. 16 | #---------------------------------------------------------------------------- 17 | 18 | comment = 'Foreign data wrapper for querying a TDS database (Sybase or Microsoft SQL Server)' 19 | default_version = '2.0.4' 20 | module_pathname = '$libdir/tds_fdw' 21 | relocatable = true 22 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | lib/*.pyc 2 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Testing scripts 2 | 3 | Testing should follow that workflow : 4 | 5 | * First build a MSSQL Server 6 | 1. Create the server (local, container, VM or azure) 7 | 2. Create a testing database with a proper user and access privileges 8 | 3. Run `mssql-tests.py` against that server. 9 | * Next, build a PostgreSQL Server 10 | 1. Create the server (on your machine, with docker o rVM) 11 | 2. Compile and install `tds_fdw` extension 12 | 3. Create a testing database and schema with proper user and access privilege 13 | 4. On that database you'll have first to install `tds_fdw`: `CREATE EXTENSION tds_fdw;` 14 | 5. You can run `postgresql_test.py` 15 | 16 | # Debugging 17 | 18 | It may be interesting to build a full setup for debugging purpose and use tests to check if anythong regresses. 19 | For this, you can use `--debugging` parameter at `postgresql-tests.py` launch time. 20 | 21 | 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. 22 | 23 | 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. 24 | 25 | # Adding or modifying tests 26 | 27 | There are two folders where tests are added: 28 | 29 | * `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. 30 | * `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. 31 | 32 | Each test is made up of two files, with the same name and different extension in this format: 33 | 34 | ``` 35 | XXX_description 36 | ``` 37 | 38 | For example: `000_my_test.json` and `000_my_test.sql`. 39 | 40 | **Rule1:** `XXX` is used to provide an order to the scripts. 41 | 42 | **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) 43 | 44 | ## The JSON file 45 | 46 | Always has the following format: 47 | 48 | ``` 49 | { 50 | "test_desc" : "", 51 | "server" : { 52 | "version" : { 53 | "min" : "", 54 | "max" : "" 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | * `test_desc` can be any arbitrary string describing the test. 61 | * `min` and `max` are version the for mat `X.W[.Y[.Z]]` for MSSQL and PostgreSQL respectively. 62 | * `min` is mandatory, as minimum `7.0.623` for MSSQL (MSSQL 7.0 RTM) and `9.2.0` for PostgreSQL. 63 | * `max` is also mandatory, but it can be an empty string if the test supports up to the most recent MSSQL or PostgreSQL version. 64 | 65 | 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. 66 | 67 | To validate the JSON file, you can use the script `validate-test-json`. 68 | 69 | ## The SQL file 70 | 71 | It is a regular SQL file for one or more queries for either MSSQL or PostgreSQL. 72 | 73 | There are several variables that can be used and will be placed by the testing scripts. 74 | 75 | For the MSSQL scripts, the values come from the `mssql-tests.py` parameters: 76 | 77 | * `@SCHEMANAME`: The MSSQL schema name. 78 | 79 | For the PostgreSQL scripts the values come from the `postgresql-tests.py` parameters: 80 | 81 | * `@PSCHEMANAME`: The PostgreSQL schema name 82 | * `@PUSER`: The PostgreSQL user 83 | * `@MSERVER`: The MSSQL Server 84 | * `@MPORT`: The MSSQL port 85 | * `@MUSER`: The MSSQL user 86 | * `@MPASSWORD`: The MSSQL password 87 | * `@MDATABASE`: The MSSQL database 88 | * `@MSCHEMANAME`: The MSSQL schema name 89 | * `@TDSVERSION`: The TDS version 90 | -------------------------------------------------------------------------------- /tests/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tds-fdw/tds_fdw/8ac2e0b6910aed785469b0ca54b046b7a4130c8e/tests/lib/__init__.py -------------------------------------------------------------------------------- /tests/lib/messages.py: -------------------------------------------------------------------------------- 1 | class bcolors: 2 | BOLDRED = '\033[1;31m' 3 | BOLDGREEN = '\033[1;32m' 4 | BOLDYELLOW = '\033[1;33m' 5 | BOLDCYAN = '\033[1;36m' 6 | BOLDPURPLE = '\033[1;35m' 7 | RESET = '\033[0m' 8 | 9 | 10 | def print_error(msg): 11 | """Print an error message in red""" 12 | print(bcolors.BOLDRED + "[ERROR] %s" % msg + bcolors.RESET) 13 | 14 | 15 | def print_warning(msg): 16 | """Print a warning message in yellow""" 17 | print(bcolors.BOLDYELLOW + "[WARNING] %s" % msg + bcolors.RESET) 18 | 19 | 20 | def print_ok(msg): 21 | """Print an ok message in green""" 22 | print(bcolors.BOLDGREEN + "[OK] %s" % msg + bcolors.RESET) 23 | 24 | 25 | def print_info(msg): 26 | """Print an info message in cyan""" 27 | print(bcolors.BOLDCYAN + "[INFO] %s" % msg + bcolors.RESET) 28 | 29 | 30 | def print_report(total, ok, error): 31 | """Print a test report""" 32 | if error != 0: 33 | print_error("=========== TEST REPORT ==============") 34 | print_error(" OK : %s" % ok) 35 | print_error(" ERROR: %s" % error) 36 | print_error(" Total: %s" % total) 37 | else: 38 | print_ok("=========== TEST REPORT ==============") 39 | print_ok(" OK : %s" % ok) 40 | print_ok(" ERROR: %s" % error) 41 | print_ok(" Total: %s" % total) 42 | 43 | 44 | def print_usage_error(script, error): 45 | """Print script's usage and an error 46 | 47 | Keyword arguments: 48 | script -- A string with the script filename (without path) 49 | error -- A string with an error 50 | """ 51 | print('Usage: %s ' % script) 52 | print('') 53 | print('%s: error: %s' % (script, error)) 54 | -------------------------------------------------------------------------------- /tests/lib/tests.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from json import load 3 | from lib.messages import print_error, print_info 4 | from os import listdir 5 | from os.path import basename, isfile, realpath 6 | from re import match 7 | from psycopg2.extensions import Diagnostics 8 | 9 | 10 | def version_to_array(version, dbtype): 11 | """ Convert a version string to a version array, or return an empty string if 12 | the original string was empty 13 | 14 | Keyword arguments: 15 | version -- A string with a dot separated version 16 | dbtype -- A string with the database type (postgresql|mssql) 17 | """ 18 | if version != '': 19 | try: 20 | version = version.decode('utf-8') 21 | except (UnicodeDecodeError, AttributeError): 22 | pass 23 | # Cleanup version, since Ubuntu decided to add their own 24 | # versioning starting with PostgreSQL 10.2: 25 | # '10.2 (Ubuntu 10.2-1.pgdg14.04+1' instead of '10.2' 26 | version = match('(\d[\.\d]+).*', version).group(1) 27 | version = version.split('.') 28 | for i in range(0, len(version)): 29 | version[i] = int(version[i]) 30 | # To be able to work with MSSQL 2000 and 7.0, 31 | # see https://sqlserverbuilds.blogspot.ru/ 32 | if dbtype == 'mssql': 33 | if len(version) == 3: 34 | version.append(0) 35 | else: 36 | version = [] 37 | return(version) 38 | 39 | 40 | def check_ver(conn, min_ver, max_ver, dbtype): 41 | """ Check SQL server version against test required max and min versions. 42 | 43 | Keyword arguments: 44 | conn -- A db connection (according to Python DB API v2.0) 45 | min_ver -- A string with the minimum version required 46 | max_ver -- A string with the maximum version required 47 | dbtype -- A string with the database type (postgresql|mssql) 48 | """ 49 | cursor = conn.cursor() 50 | if dbtype == 'postgresql': 51 | sentence = 'SELECT setting FROM pg_settings WHERE name = \'server_version\';' 52 | elif dbtype == 'mssql': 53 | sentence = 'SELECT serverproperty(\'ProductVersion\');' 54 | cursor.execute(sentence) 55 | server_ver = cursor.fetchone()[0] 56 | cursor.close() 57 | min_ver = version_to_array(min_ver, dbtype) 58 | max_ver = version_to_array(max_ver, dbtype) 59 | server_ver = version_to_array(server_ver, dbtype) 60 | if server_ver >= min_ver and (server_ver <= max_ver or len(max_ver) == 0): 61 | return(True) 62 | else: 63 | return(False) 64 | 65 | def get_logs_path(conn, dbtype): 66 | """ Get PostgreSQL logs 67 | 68 | Keyword arguments: 69 | conn -- A db connection (according to Python DB API v2.0) 70 | dbtye -- A string with the database type (postgresql|mssql). mssql will return an empty a array. 71 | """ 72 | logs = [] 73 | if dbtype == 'mssql': 74 | return(logs) 75 | cursor = conn.cursor() 76 | try: 77 | cursor.execute("SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_directory';") 78 | data_dir = cursor.fetchone()[0] 79 | except TypeError: 80 | print_error("The user does not have SUPERUSER access to PostgreSQL.") 81 | print_error("Cannot access pg_catalog.pg_settings required values, so logs cannot be found") 82 | return(logs) 83 | cursor.execute("SELECT setting FROM pg_catalog.pg_settings WHERE name = 'log_directory';") 84 | log_dir = cursor.fetchone()[0] 85 | if log_dir[0] != '/': 86 | log_dir = "%s/%s" % (data_dir, log_dir) 87 | cursor.execute("SELECT setting FROM pg_catalog.pg_settings WHERE name = 'logging_collector';") 88 | # No logging collector, add stdout from postmaster (assume stderr is redirected to stdout) 89 | if cursor.fetchone()[0] == 'off': 90 | with open("%s/postmaster.pid" % data_dir, "r") as f: 91 | postmaster_pid = f.readline().rstrip('\n') 92 | postmaster_log = "/proc/%s/fd/1" % postmaster_pid 93 | if isfile(postmaster_log): 94 | logs.append(realpath(postmaster_log)) 95 | # Logging collector enabled 96 | else: 97 | # Add stdout from logger (assume stderr is redirected to stdout) 98 | pids = [pid for pid in listdir('/proc') if pid.isdigit()] 99 | for pid in pids: 100 | try: 101 | cmdline = open('/proc/' + pid + '/cmdline', 'r').read() 102 | if 'postgres: logger' in cmdline: 103 | logger_log = "/proc/%s/fd/2" % pid 104 | if isfile(logger_log): 105 | logs.append(realpath(logger_log)) 106 | except IOError: # proc has already terminated 107 | continue 108 | # Add all files from log_dir 109 | for f in listdir(log_dir): 110 | logs.append(realpath(log_dir + '/' + f)) 111 | return(logs) 112 | 113 | 114 | def run_tests(path, conn, replaces, dbtype, debugging=False, unattended_debugging=False): 115 | """Run SQL tests over a connection, returns a dict with results. 116 | 117 | Keyword arguments: 118 | path -- String with the path having the SQL files for tests 119 | conn -- A db connection (according to Python DB API v2.0) 120 | replaces -- A dict with replaces to perform at testing code 121 | dbtype -- A string with the database type (postgresql|mssql) 122 | """ 123 | files = sorted(glob(path)) 124 | tests = {'total': 0, 'ok': 0, 'errors': 0} 125 | for fname in files: 126 | test_file = open('%s.json' % fname.rsplit('.', 1)[0], 'r') 127 | test_properties = load(test_file) 128 | test_desc = test_properties['test_desc'] 129 | test_number = basename(fname).split('_')[0] 130 | req_ver = test_properties['server']['version'] 131 | if check_ver(conn, req_ver['min'], req_ver['max'], dbtype): 132 | tests['total'] += 1 133 | f = open(fname, 'r') 134 | sentence = f.read() 135 | for key, elem in replaces.items(): 136 | sentence = sentence.replace(key, elem) 137 | print_info("%s: Testing %s" % (test_number, test_desc)) 138 | if debugging or unattended_debugging: 139 | print_info("Query:") 140 | print(sentence) 141 | try: 142 | cursor = conn.cursor() 143 | cursor.execute(sentence) 144 | conn.commit() 145 | cursor.close() 146 | tests['ok'] += 1 147 | except Exception as e: 148 | print_error("Error running %s (%s)" % (test_desc, fname)) 149 | print_error("Query:") 150 | print(sentence) 151 | try: 152 | print_error(e.pgcode) 153 | print_error(e.pgerror) 154 | for att in [member for member in dir(Diagnostics) if not member.startswith("__")]: 155 | print_error("%s: %s"%(att, getattr(e.diag,att))) 156 | except: 157 | print_error(e) 158 | conn.rollback() 159 | tests['errors'] += 1 160 | f.close() 161 | return(tests) 162 | -------------------------------------------------------------------------------- /tests/mssql-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from lib.messages import print_error, print_info, print_ok, print_report 3 | from lib.messages import print_usage_error, print_warning 4 | from lib.tests import run_tests 5 | from optparse import OptionParser 6 | from os import path 7 | 8 | DEFAULT_TDS_VERSION="7.1" 9 | 10 | try: 11 | from pymssql import connect 12 | except: 13 | print_error( 14 | "pymssql library not available, please install it before usage!") 15 | exit(1) 16 | 17 | 18 | def parse_options(): 19 | """Parse and validate options. Returns a dict with all the options.""" 20 | usage = "%prog " 21 | description = ('Run MSSQL tests from sql files') 22 | parser = OptionParser(usage=usage, description=description) 23 | parser.add_option('--server', action='store', 24 | help='MSSQL/Azure server') 25 | parser.add_option('--port', action='store', 26 | help='MSSQL/Azure TCP port') 27 | parser.add_option('--database', action='store', 28 | help='Database name') 29 | parser.add_option('--schema', action='store', 30 | help='Schema to use (and create if needed)') 31 | parser.add_option('--username', action='store', 32 | help='Username to connect') 33 | parser.add_option('--password', action='store', 34 | help='Passord to connect') 35 | parser.add_option('--azure', action='store_true', default=False, 36 | help='If present, will connect as Azure, otherwise as ' 37 | 'standard MSSQL') 38 | parser.add_option('--tds_version', action="store", default=DEFAULT_TDS_VERSION, 39 | help='Specifies th TDS protocol version, default="%s"'%DEFAULT_TDS_VERSION) 40 | (options, args) = parser.parse_args() 41 | # Check for test parameters 42 | if (options.server is None or 43 | options.port is None or 44 | options.database is None or 45 | options.schema is None or 46 | options.username is None or 47 | options.password is None): 48 | print_error("Insufficient parameters, check help (-h)") 49 | exit(4) 50 | else: 51 | if options.azure is True: 52 | options.username = '%s@%s' % ( 53 | options.username, options.server.split('.')[0]) 54 | return(options) 55 | 56 | 57 | def main(): 58 | try: 59 | args = parse_options() 60 | except Exception as e: 61 | print_usage_error(path.basename(__file__), e) 62 | exit(2) 63 | try: 64 | conn = connect(server=args.server, user=args.username, 65 | password=args.password, database=args.database, 66 | port=args.port, tds_version=args.tds_version) 67 | replaces = {'@SCHEMANAME': args.schema} 68 | tests = run_tests('tests/mssql/*.sql', conn, replaces, 'mssql') 69 | print_report(tests['total'], tests['ok'], tests['errors']) 70 | if tests['errors'] != 0: 71 | exit(5) 72 | else: 73 | exit(0) 74 | except Exception as e: 75 | print_error(e) 76 | exit(3) 77 | 78 | if __name__ == "__main__": 79 | main() 80 | -------------------------------------------------------------------------------- /tests/postgresql-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from lib.messages import print_error, print_info, print_ok, print_report 3 | from lib.messages import print_usage_error, print_warning 4 | from lib.tests import get_logs_path, run_tests 5 | from optparse import OptionParser 6 | from os import path 7 | try: 8 | from psycopg2 import connect 9 | except: 10 | print_error( 11 | "psycopg2 library not available, please install it before usage!") 12 | exit(1) 13 | 14 | DEFAULT_TDS_VERSION="7.1" 15 | 16 | 17 | def parse_options(): 18 | """Parse and validate options. Returns a dict with all the options.""" 19 | usage = "%prog " 20 | description = ('Run PostgreSQL tests from sql files') 21 | parser = OptionParser(usage=usage, description=description) 22 | parser.add_option('--postgres_server', action='store', 23 | help='PostgreSQL server') 24 | parser.add_option('--postgres_port', action='store', 25 | help='PostgreSQL TCP port') 26 | parser.add_option('--postgres_database', action='store', 27 | help='Database name') 28 | parser.add_option('--postgres_schema', action='store', 29 | help='Schema to use (and create if needed)') 30 | parser.add_option('--postgres_username', action='store', 31 | help='Username to connect') 32 | parser.add_option('--postgres_password', action='store', 33 | help='Passord to connect') 34 | parser.add_option('--mssql_server', action='store', 35 | help='MSSQL/Azure server') 36 | parser.add_option('--mssql_port', action='store', 37 | help='MSSQL/Azure TCP port') 38 | parser.add_option('--mssql_database', action='store', 39 | help='Database name') 40 | parser.add_option('--mssql_schema', action='store', 41 | help='Schema to use (and create if needed)') 42 | parser.add_option('--mssql_username', action='store', 43 | help='Username to connect') 44 | parser.add_option('--mssql_password', action='store', 45 | help='Passord to connect') 46 | parser.add_option('--azure', action="store_true", default=False, 47 | help='If present, will connect as Azure, otherwise as ' 48 | 'standard MSSQL') 49 | parser.add_option('--debugging', action="store_true", default=False, 50 | help='If present, will pause after backend PID and before ' 51 | 'launching tests (so gdb can be attached. It will also ' 52 | 'display contextual SQL queries') 53 | parser.add_option('--unattended_debugging', action="store_true", default=False, 54 | help='Same as --debugging, but without pausing and printing ' 55 | 'PostgreSQL logs at the end (useful for CI)') 56 | parser.add_option('--tds_version', action="store", default=DEFAULT_TDS_VERSION, 57 | help='Specifies th TDS protocol version, default="%s"'%DEFAULT_TDS_VERSION) 58 | 59 | (options, args) = parser.parse_args() 60 | # Check for test parameters 61 | if (options.postgres_server is None or 62 | options.postgres_port is None or 63 | options.postgres_database is None or 64 | options.postgres_schema is None or 65 | options.postgres_username is None or 66 | options.postgres_password is None or 67 | options.mssql_server is None or 68 | options.mssql_port is None or 69 | options.mssql_database is None or 70 | options.mssql_schema is None or 71 | options.mssql_username is None or 72 | options.mssql_password is None): 73 | print_error("Insufficient parameters, check help (-h)") 74 | exit(4) 75 | else: 76 | if options.azure is True: 77 | options.mssql_username = '%s@%s' % (options.mssql_username, 78 | options.mssql_server.split('.')[0]) 79 | return(options) 80 | 81 | 82 | def main(): 83 | try: 84 | args = parse_options() 85 | except Exception as e: 86 | print_usage_error(path.basename(__file__), e) 87 | exit(2) 88 | try: 89 | conn = connect(host=args.postgres_server, user=args.postgres_username, 90 | password=args.postgres_password, 91 | database=args.postgres_database, 92 | port=args.postgres_port) 93 | if args.debugging or args.unattended_debugging: 94 | curs = conn.cursor() 95 | curs.execute("SELECT pg_backend_pid()") 96 | print("Backend PID = %d"%curs.fetchone()[0]) 97 | if not args.unattended_debugging: 98 | print("Press any key to launch tests.") 99 | raw_input() 100 | replaces = {'@PSCHEMANAME': args.postgres_schema, 101 | '@PUSER': args.postgres_username, 102 | '@MSERVER': args.mssql_server, 103 | '@MPORT': args.mssql_port, 104 | '@MUSER': args.mssql_username, 105 | '@MPASSWORD': args.mssql_password, 106 | '@MDATABASE': args.mssql_database, 107 | '@MSCHEMANAME': args.mssql_schema, 108 | '@TDSVERSION' : args.tds_version} 109 | tests = run_tests('tests/postgresql/*.sql', conn, replaces, 'postgresql', 110 | args.debugging, args.unattended_debugging) 111 | print_report(tests['total'], tests['ok'], tests['errors']) 112 | logs = get_logs_path(conn, 'postgresql') 113 | if (tests['errors'] != 0 or args.unattended_debugging) and not args.debugging: 114 | for fpath in logs: 115 | print_info("=========== Content of %s ===========" % fpath) 116 | with open(fpath, "r") as f: 117 | print(f.read()) 118 | if tests['errors'] != 0: 119 | exit(5) 120 | else: 121 | exit(0) 122 | except Exception as e: 123 | print_error(e) 124 | exit(3) 125 | 126 | if __name__ == "__main__": 127 | main() 128 | -------------------------------------------------------------------------------- /tests/tests/mssql/000_create_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "schema creation", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/000_create_schema.sql: -------------------------------------------------------------------------------- 1 | IF NOT EXISTS ( 2 | SELECT schema_name 3 | FROM INFORMATION_SCHEMA.SCHEMATA 4 | WHERE schema_name = '@SCHEMANAME') 5 | BEGIN 6 | EXEC sp_executesql N'CREATE SCHEMA @SCHEMANAME' 7 | END 8 | -------------------------------------------------------------------------------- /tests/tests/mssql/001_create_tinyint_min_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with tinyint, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/001_create_tinyint_min_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.tinyint_min', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.tinyint_min; 3 | 4 | CREATE TABLE @SCHEMANAME.tinyint_min ( 5 | id int primary key, 6 | value tinyint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.tinyint_min (id, value) VALUES (1, 0); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/002_create_tinyint_max_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with tinyint, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/002_create_tinyint_max_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.tinyint_max', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.tinyint_max; 3 | 4 | CREATE TABLE @SCHEMANAME.tinyint_max ( 5 | id int primary key, 6 | value tinyint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.tinyint_max (id, value) VALUES (1, 255); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/003_create_smallint_min_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with smallint, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/003_create_smallint_min_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.smallint_min', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.smallint_min; 3 | 4 | CREATE TABLE @SCHEMANAME.smallint_min ( 5 | id int primary key, 6 | value smallint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.smallint_min (id, value) VALUES (1, -32768); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/004_create_smallint_max_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with smallint, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/004_create_smallint_max_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.smallint_max', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.smallint_max; 3 | 4 | CREATE TABLE @SCHEMANAME.smallint_max ( 5 | id int primary key, 6 | value smallint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.smallint_max (id, value) VALUES (1, 32767); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/005_create_int_min_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with int, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/005_create_int_min_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.int_min', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.int_min; 3 | 4 | CREATE TABLE @SCHEMANAME.int_min ( 5 | id int primary key, 6 | value int 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.int_min (id, value) VALUES (1, -2147483648); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/006_create_int_max_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with int, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/006_create_int_max_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.int_max', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.int_max; 3 | 4 | CREATE TABLE @SCHEMANAME.int_max ( 5 | id int primary key, 6 | value int 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.int_max (id, value) VALUES (1, 2147483647); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/007_create_bigint_min_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with bigint, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/007_create_bigint_min_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.bigint_min', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.bigint_min; 3 | 4 | CREATE TABLE @SCHEMANAME.bigint_min ( 5 | id int primary key, 6 | value bigint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.bigint_min (id, value) VALUES (1, -9223372036854775808); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/008_create_bigint_max_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with bigint, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/008_create_bigint_max_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.bigint_max', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.bigint_max; 3 | 4 | CREATE TABLE @SCHEMANAME.bigint_max ( 5 | id int primary key, 6 | value bigint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.bigint_max (id, value) VALUES (1, 9223372036854775807); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/009_create_decimal_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with decimal data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/009_create_decimal_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.decimal', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.decimal; 3 | 4 | CREATE TABLE @SCHEMANAME.decimal ( 5 | id int primary key, 6 | value decimal(9, 2) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.decimal (id, value) VALUES (1, 1000.01); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/010_create_float4_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with float data type, 4 bytes size", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/010_create_float4_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.float4', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.float4; 3 | 4 | CREATE TABLE @SCHEMANAME.float4 ( 5 | id int primary key, 6 | value float(24) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.float4 (id, value) VALUES (1, 1000.01); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/011_create_float8_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with float data type, 8 bytes value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/011_create_float8_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.float8', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.float8; 3 | 4 | CREATE TABLE @SCHEMANAME.float8 ( 5 | id int primary key, 6 | value float(53) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.float8 (id, value) VALUES (1, 1000.01); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/012_create_date_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with date data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/012_create_date_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.date', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.date; 3 | 4 | CREATE TABLE @SCHEMANAME.date ( 5 | id int primary key, 6 | value date 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.date (id, value) VALUES (1, '2015-10-22'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/013_create_time_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with time data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/013_create_time_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.time', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.time; 3 | 4 | CREATE TABLE @SCHEMANAME.time ( 5 | id int primary key, 6 | value time 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.time (id, value) VALUES (1, '11:01:02'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/014_create_datetime_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with datetime data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/014_create_datetime_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.datetime', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.datetime; 3 | 4 | CREATE TABLE @SCHEMANAME.datetime ( 5 | id int primary key, 6 | value datetime 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.datetime (id, value) VALUES (1, '2015-10-22 11:01:02'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/015_create_datetime2_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with datetime2 data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/015_create_datetime2_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.datetime2', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.datetime2; 3 | 4 | CREATE TABLE @SCHEMANAME.datetime2 ( 5 | id int primary key, 6 | value datetime2 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.datetime2 (id, value) VALUES (1, '2015-10-22 11:01:02'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/016_create_datetimeoffset_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with datetimeoffset data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/016_create_datetimeoffset_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.datetimeoffset', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.datetimeoffset; 3 | 4 | CREATE TABLE @SCHEMANAME.datetimeoffset ( 5 | id int primary key, 6 | value datetimeoffset 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.datetimeoffset (id, value) VALUES (1, '2015-10-22 11:01:02 -07:00'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/017_create_char_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with char data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/017_create_char_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.char', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.char; 3 | 4 | CREATE TABLE @SCHEMANAME.char ( 5 | id int primary key, 6 | value char(8000) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.char (id, value) VALUES (1, 'this is a string'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/018_create_varchar_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with varchar data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/018_create_varchar_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.varchar', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.varchar; 3 | 4 | CREATE TABLE @SCHEMANAME.varchar ( 5 | id int primary key, 6 | value varchar(8000) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.varchar (id, value) VALUES (1, 'this is a string'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/019_create_varcharmax_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with varchar data type, maximum length", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/019_create_varcharmax_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.varcharmax', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.varcharmax; 3 | 4 | CREATE TABLE @SCHEMANAME.varcharmax ( 5 | id int primary key, 6 | value varchar(max) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.varcharmax (id, value) VALUES (1, 'this is a string'); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/020_create_binary4_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with binary data type, 4 bytes length", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/020_create_binary4_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.binary4', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.binary4; 3 | 4 | CREATE TABLE @SCHEMANAME.binary4 ( 5 | id int primary key, 6 | value binary(4) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.binary4 (id, value) VALUES (1, 0x01020304); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/021_create_varbinary4_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with varbinary data type, 4 bytes length", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/021_create_varbinary4_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.varbinary4', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.varbinary4; 3 | 4 | CREATE TABLE @SCHEMANAME.varbinary4 ( 5 | id int primary key, 6 | value varbinary(4) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.varbinary4 (id, value) VALUES (1, 0x01020304); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/022_create_varbinarymax_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with varbinary data type, maximum size", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/022_create_varbinarymax_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.varbinarymax', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.varbinarymax; 3 | 4 | CREATE TABLE @SCHEMANAME.varbinarymax ( 5 | id int primary key, 6 | value varbinary(max) 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.varbinarymax (id, value) VALUES (1, 0x01000304); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/023_create_null_datetime_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with datetime data type, null value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/023_create_null_datetime_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.null_datetime', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.null_datetime; 3 | 4 | CREATE TABLE @SCHEMANAME.null_datetime ( 5 | id int primary key, 6 | value datetime 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.null_datetime (id, value) VALUES (1, NULL); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/024_create_null_datetime2_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with datetime2 data type, null value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/024_create_null_datetime2_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.null_datetime2', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.null_datetime2; 3 | 4 | CREATE TABLE @SCHEMANAME.null_datetime2 ( 5 | id int primary key, 6 | value datetime2 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.null_datetime2 (id, value) VALUES (1, NULL); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/025_create_match_column_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation for automated column matching", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/025_create_match_column_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.column_match', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.column_match; 3 | 4 | CREATE TABLE @SCHEMANAME.column_match ( 5 | id int primary key, 6 | value bigint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.column_match (id, value) VALUES (1, 9223372036854775807); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/026_create_column_name_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation for column name manual matching", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/026_create_column_name_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.column_name', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.column_name; 3 | 4 | CREATE TABLE @SCHEMANAME.column_name ( 5 | id int primary key, 6 | value bigint 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.column_name (id, value) VALUES (1, 9223372036854775807); 10 | 11 | -------------------------------------------------------------------------------- /tests/tests/mssql/027_create_query_option_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation for automated column matching (query option)", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/027_create_query_option_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.query_option', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.query_option; 3 | 4 | CREATE TABLE @SCHEMANAME.query_option ( 5 | id int primary key, 6 | data varchar(50), 7 | owner varchar(50) 8 | ); 9 | 10 | INSERT INTO @SCHEMANAME.query_option (id, data, owner) VALUES 11 | (1, 'geoff''s data', 'geoff'), 12 | (2, 'alice''s data', 'alice'), 13 | (3, 'bob''s data', 'bob'); 14 | -------------------------------------------------------------------------------- /tests/tests/mssql/028_create_view_simple_prerequisites.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "view creation prerequisites", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/028_create_view_simple_prerequisites.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.table_view_simple', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.table_view_simple; 3 | 4 | CREATE TABLE @SCHEMANAME.table_view_simple ( 5 | id int primary key, 6 | data varchar(50), 7 | owner varchar(50) 8 | ); 9 | 10 | INSERT INTO @SCHEMANAME.table_view_simple (id, data, owner) VALUES 11 | (1, 'geoff''s data', 'geoff'), 12 | (2, 'alice''s data', 'alice'), 13 | (3, 'bob''s data', 'bob'); 14 | 15 | IF OBJECT_ID('@SCHEMANAME.view_simple', 'V') IS NOT NULL 16 | DROP VIEW @SCHEMANAME.view_simple; 17 | -------------------------------------------------------------------------------- /tests/tests/mssql/029_create_view_simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "view creation (simple)", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/029_create_view_simple.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW @SCHEMANAME.view_simple (id, data) 2 | AS SELECT id, data FROM @SCHEMANAME.table_view_simple; 3 | -------------------------------------------------------------------------------- /tests/tests/mssql/030_create_money_min_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with money data type, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/030_create_money_min_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.money_min', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.money_min; 3 | 4 | CREATE TABLE @SCHEMANAME.money_min ( 5 | id int primary key, 6 | value money 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.money_min (id, value) VALUES (1, -922337203685477.5808); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/031_create_money_max_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with money data type, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/031_create_money_max_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.money_max', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.money_max; 3 | 4 | CREATE TABLE @SCHEMANAME.money_max ( 5 | id int primary key, 6 | value money 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.money_max (id, value) VALUES (1, 922337203685477.5807); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/032_create_smallmoney_min_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with smallmoney data type, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/032_create_smallmoney_min_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.smallmoney_min', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.smallmoney_min; 3 | 4 | CREATE TABLE @SCHEMANAME.smallmoney_min ( 5 | id int primary key, 6 | value smallmoney 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.smallmoney_min (id, value) VALUES (1, -214748.3648); 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/033_create_smallmoney_max_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "table creation with smallmoney data type, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "7.0.623", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/mssql/033_create_smallmoney_max_table.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('@SCHEMANAME.smallmoney_max', 'U') IS NOT NULL 2 | DROP TABLE @SCHEMANAME.smallmoney_max; 3 | 4 | CREATE TABLE @SCHEMANAME.smallmoney_max ( 5 | id int primary key, 6 | value smallmoney 7 | ); 8 | 9 | INSERT INTO @SCHEMANAME.smallmoney_max (id, value) VALUES (1, 214748.3647); 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/000_create_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "schema creation", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/000_create_schema.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS @PSCHEMANAME CASCADE; 2 | 3 | CREATE SCHEMA @PSCHEMANAME; 4 | -------------------------------------------------------------------------------- /tests/tests/postgresql/001_create_server.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "foreign server creation", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/001_create_server.sql: -------------------------------------------------------------------------------- 1 | DROP SERVER IF EXISTS mssql_svr CASCADE; 2 | 3 | CREATE SERVER mssql_svr 4 | FOREIGN DATA WRAPPER tds_fdw 5 | OPTIONS (servername '@MSERVER', port '@MPORT', database '@MDATABASE', tds_version '@TDSVERSION', msg_handler 'blackhole'); 6 | -------------------------------------------------------------------------------- /tests/tests/postgresql/002_create_user_mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "user mapping creation", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/002_create_user_mapping.sql: -------------------------------------------------------------------------------- 1 | DROP USER MAPPING IF EXISTS FOR @PUSER 2 | SERVER mssql_svr; 3 | 4 | CREATE USER MAPPING FOR @PUSER 5 | SERVER mssql_svr 6 | OPTIONS (username '@MUSER', password '@MPASSWORD'); 7 | -------------------------------------------------------------------------------- /tests/tests/postgresql/003_import_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "foreign schema import", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.5.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/003_import_schema.sql: -------------------------------------------------------------------------------- 1 | IMPORT FOREIGN SCHEMA @MSCHEMANAME 2 | FROM SERVER mssql_svr 3 | INTO @PSCHEMANAME 4 | OPTIONS (import_default 'true'); 5 | -------------------------------------------------------------------------------- /tests/tests/postgresql/004_tinyintmin.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "tinyint data type, minimum value (smallint)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/004_tinyintmin.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.tinyint_min; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.tinyint_min ( 4 | id int, 5 | value smallint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.tinyint_min', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.tinyint_min; 11 | SELECT (value = 0) AS pass FROM @PSCHEMANAME.tinyint_min; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/005_tinyintmax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "tinyint data type, maximum value (smallint)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/005_tinyintmax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.tinyint_max; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.tinyint_max ( 4 | id int, 5 | value smallint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.tinyint_max', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.tinyint_max; 11 | SELECT (value = 255) AS pass FROM @PSCHEMANAME.tinyint_max; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/006_smallintmin.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "smallint data type, minimum value (smallint)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/006_smallintmin.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.smallint_min; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.smallint_min ( 4 | id int, 5 | value smallint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.smallint_min', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.smallint_min; 11 | SELECT (value = -32768) AS pass FROM @PSCHEMANAME.smallint_min; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/007_smallintmax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "smallint data type, maximum value (smallint)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/007_smallintmax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.smallint_max; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.smallint_max ( 4 | id int, 5 | value smallint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.smallint_max', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.smallint_max; 11 | SELECT (value = 32767) AS pass FROM @PSCHEMANAME.smallint_max; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/008_intmin.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "int data type, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/008_intmin.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.int_min; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.int_min ( 4 | id int, 5 | value int 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.int_min', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.int_min; 11 | SELECT (value = -2147483648) AS pass FROM @PSCHEMANAME.int_min; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/009_intmax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "int data type, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/009_intmax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.int_max; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.int_max ( 4 | id int, 5 | value int 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.int_max', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.int_max; 11 | SELECT (value = 2147483647) AS pass FROM @PSCHEMANAME.int_max; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/010_bigintmin.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "bigint data type, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/010_bigintmin.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.bigint_min; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.bigint_min ( 4 | id int, 5 | value bigint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.bigint_min', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.bigint_min; 11 | SELECT (value = -9223372036854775808) AS pass FROM @PSCHEMANAME.bigint_min; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/011_bigintmax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "bigint data type, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/011_bigintmax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.bigint_max; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.bigint_max ( 4 | id int, 5 | value bigint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.bigint_max', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.bigint_max; 11 | SELECT (value = 9223372036854775807) AS pass FROM @PSCHEMANAME.bigint_max; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/012_decimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "decimal data type (numeric)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/012_decimal.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.decimal; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.decimal ( 4 | id int, 5 | value numeric(9, 2) 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.decimal', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.decimal; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/013_float4.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "float data type, 4 bytes size (real)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/013_float4.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.float4; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.float4 ( 4 | id int, 5 | value real 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.float4', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.float4; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/014_float8.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "float data type, 8 bits value (double precission)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/014_float8.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.float8; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.float8 ( 4 | id int, 5 | value double precision 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.float8', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.float8; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/015_date.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "date data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/015_date.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.date; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.date ( 4 | id int, 5 | value date 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.date', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.date; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/016_time.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "time data type (time without time zone)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/016_time.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.time; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.time ( 4 | id int, 5 | value time without time zone 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.time', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.time; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/017_datetime.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "datetime data type (timestamp without time zone)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/017_datetime.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.datetime; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.datetime ( 4 | id int, 5 | value timestamp without time zone 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.datetime', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.datetime; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/018_datetime2.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "datetime2 data type (timestamp without time zone)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/018_datetime2.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.datetime2; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.datetime2 ( 4 | id int, 5 | value timestamp without time zone 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.datetime2', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.datetime2; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/019_datetimeoffset.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "datatimeoffset data type (time zone)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/019_datetimeoffset.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.datetimeoffset; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.datetimeoffset ( 4 | id int, 5 | value timestamp with time zone 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.datetimeoffset', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.datetimeoffset; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/020_char.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "char data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/020_char.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.char; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.char ( 4 | id int, 5 | value char(8000) 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.char', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.char; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/021_varchar.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "varchar data type", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/021_varchar.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varchar; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.varchar ( 4 | id int, 5 | value varchar(8000) 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.varchar', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.varchar; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/022_varcharmax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "varchar data type, maximum size", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/022_varcharmax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varcharmax; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.varcharmax ( 4 | id int, 5 | value varchar 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.varcharmax', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.varcharmax; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/023_binary4.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "binary data type, 4 bytes size (bytea)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/023_binary4.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.binary4; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.binary4 ( 4 | id int, 5 | value bytea 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.binary4', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.binary4; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/024_varbinary4.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "varbinary data type, 4 bytes size (bytea)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/024_varbinary4.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varbinary4; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.varbinary4 ( 4 | id int, 5 | value bytea 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.varbinary4', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.varbinary4; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/025_varbinarymax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "varbinary data type, maximum size (bytea)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/025_varbinarymax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.varbinarymax; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.varbinarymax ( 4 | id int, 5 | value bytea 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.varbinarymax', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.varbinarymax; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/026_null_datetime.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "datatime data type, null value (timestamp without time zone)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/026_null_datetime.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.null_datetime; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.null_datetime ( 4 | id int, 5 | value timestamp without time zone 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.null_datetime', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.null_datetime; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/027_null_datetime2.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "datetime2 data type, null value (timestamp without time zone)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/027_null_datetime2.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.null_datetime2; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.null_datetime2 ( 4 | id int, 5 | value timestamp without time zone 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.null_datetime2', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.null_datetime2; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/028_column_match_enabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "automated column matching enabled", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/028_column_match_enabled.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.column_match_enabled; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.column_match_enabled ( 4 | id int, 5 | value bigint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.column_match', row_estimate_method 'showplan_all', match_column_names '1'); 9 | 10 | SELECT * FROM @PSCHEMANAME.column_match_enabled; 11 | SELECT (value = 9223372036854775807) AS pass FROM @PSCHEMANAME.column_match_enabled; -------------------------------------------------------------------------------- /tests/tests/postgresql/029_column_match_disabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "automated column matching disabled", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/029_column_match_disabled.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.column_match_disabled; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.column_match_disabled ( 4 | id int, 5 | value bigint 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.column_match', row_estimate_method 'showplan_all', match_column_names '0'); 9 | 10 | SELECT * FROM @PSCHEMANAME.column_match_disabled; 11 | SELECT (value = 9223372036854775807) AS pass FROM @PSCHEMANAME.column_match_disabled; -------------------------------------------------------------------------------- /tests/tests/postgresql/030_column_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "column name manual matching", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/030_column_name.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.column_name; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.column_name ( 4 | id int, 5 | local_name bigint OPTIONS (column_name 'value') 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.column_name', row_estimate_method 'showplan_all', match_column_names '1'); 9 | 10 | SELECT * FROM @PSCHEMANAME.column_name; 11 | SELECT (local_name = 9223372036854775807) AS pass FROM @PSCHEMANAME.column_name; 12 | 13 | -------------------------------------------------------------------------------- /tests/tests/postgresql/031_query_option_column_match_enabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "automated column matching enabled (query option)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/031_query_option_column_match_enabled.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.query_option_column_match_enabled; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.query_option_column_match_enabled ( 4 | id int, 5 | data varchar(50), 6 | owner varchar(50) 7 | ) 8 | SERVER mssql_svr 9 | OPTIONS (query 'SELECT * FROM @MSCHEMANAME.query_option WHERE owner = ''geoff''', row_estimate_method 'showplan_all', match_column_names '1'); 10 | 11 | SELECT * FROM @PSCHEMANAME.query_option_column_match_enabled; 12 | WITH count AS ( 13 | SELECT COUNT(*) as count FROM @PSCHEMANAME.query_option_column_match_enabled 14 | ) 15 | SELECT (count = 1) FROM count; 16 | WITH owners AS ( 17 | SELECT DISTINCT owner FROM @PSCHEMANAME.query_option_column_match_enabled 18 | ) 19 | SELECT (owner = 'geoff') FROM owners; 20 | 21 | -------------------------------------------------------------------------------- /tests/tests/postgresql/032_query_option_column_match_disabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "automated column matching disabled (query option)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/032_query_option_column_match_disabled.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.query_option_column_match_disabled; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.query_option_column_match_disabled ( 4 | id int, 5 | data varchar(50), 6 | owner varchar(50) 7 | ) 8 | SERVER mssql_svr 9 | OPTIONS (query 'SELECT * FROM @MSCHEMANAME.query_option WHERE owner = ''geoff''', row_estimate_method 'showplan_all', match_column_names '0'); 10 | 11 | SELECT * FROM @PSCHEMANAME.query_option_column_match_disabled; 12 | WITH count AS ( 13 | SELECT COUNT(*) as count FROM @PSCHEMANAME.query_option_column_match_disabled 14 | ) 15 | SELECT (count = 1) FROM count; 16 | WITH owners AS ( 17 | SELECT DISTINCT owner FROM @PSCHEMANAME.query_option_column_match_disabled 18 | ) 19 | SELECT (owner = 'geoff') FROM owners; 20 | 21 | -------------------------------------------------------------------------------- /tests/tests/postgresql/033_view_simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "view (simple)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.5.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/033_view_simple.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM @PSCHEMANAME.view_simple; 2 | -------------------------------------------------------------------------------- /tests/tests/postgresql/034_explain.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "EXPLAIN (VERBOSE)", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.6.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/034_explain.sql: -------------------------------------------------------------------------------- 1 | EXPLAIN (VERBOSE) SELECT data FROM @PSCHEMANAME.view_simple; 2 | -------------------------------------------------------------------------------- /tests/tests/postgresql/035_rescan.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "nested loop with foreign scan on the inner side", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.6.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/035_rescan.sql: -------------------------------------------------------------------------------- 1 | /* function is expensive so that the optimizer puts the foreign table on the inner side of the netsed loop */ 2 | CREATE FUNCTION @PSCHEMANAME.expensive() RETURNS TABLE (id integer) 3 | LANGUAGE sql IMMUTABLE COST 1000000000 ROWS 1 AS 4 | 'SELECT * FROM generate_series(1, 3)'; 5 | 6 | /* force a nested loop */ 7 | SET enable_hashjoin = off; 8 | SET enable_mergejoin = off; 9 | SET enable_material = off; 10 | 11 | CREATE TEMP TABLE results (id integer, data text); 12 | 13 | INSERT INTO results 14 | SELECT id, v.data FROM @PSCHEMANAME.expensive() AS f 15 | LEFT JOIN @PSCHEMANAME.view_simple AS v 16 | USING (id); 17 | 18 | /* results.data should not be NULL */ 19 | DO $$BEGIN 20 | IF EXISTS (SELECT 1 FROM results WHERE data IS NULL) 21 | THEN 22 | RAISE EXCEPTION 'bad results from query'; 23 | END IF; 24 | END;$$; 25 | 26 | RESET enable_hashjoin; 27 | RESET enable_mergejoin; 28 | RESET enable_material; 29 | -------------------------------------------------------------------------------- /tests/tests/postgresql/036_moneymin.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "money data type, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/036_moneymin.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.money_min; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.money_min ( 4 | id int, 5 | value money 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.money_min', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.money_min; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/037_moneymax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "money data type, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/037_moneymax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.money_max; 2 | 3 | CREATE FOREIGN TABLE @PSCHEMANAME.money_max ( 4 | id int, 5 | value money 6 | ) 7 | SERVER mssql_svr 8 | OPTIONS (table '@MSCHEMANAME.money_max', row_estimate_method 'showplan_all'); 9 | 10 | SELECT * FROM @PSCHEMANAME.money_max; 11 | -------------------------------------------------------------------------------- /tests/tests/postgresql/038_smallmoneymin.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "smallmoney data type, minimum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/038_smallmoneymin.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.smallmoney_min; 2 | 3 | -- There is no smallmoney in PostgreSQL so we map it as the 8bit money type 4 | CREATE FOREIGN TABLE @PSCHEMANAME.smallmoney_min ( 5 | id int, 6 | value money 7 | ) 8 | SERVER mssql_svr 9 | OPTIONS (table '@MSCHEMANAME.smallmoney_min', row_estimate_method 'showplan_all'); 10 | 11 | SELECT * FROM @PSCHEMANAME.smallmoney_min; 12 | -------------------------------------------------------------------------------- /tests/tests/postgresql/039_smallmoneymax.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_desc" : "smallmoney data type, maximum value", 3 | "server" : { 4 | "version" : { 5 | "min" : "9.2.0", 6 | "max" : "" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tests/postgresql/039_smallmoneymax.sql: -------------------------------------------------------------------------------- 1 | DROP FOREIGN TABLE IF EXISTS @PSCHEMANAME.smallmoney_max; 2 | 3 | -- There is no smallmoney in PostgreSQL so we map it as the 8bit money type 4 | CREATE FOREIGN TABLE @PSCHEMANAME.smallmoney_max ( 5 | id int, 6 | value money 7 | ) 8 | SERVER mssql_svr 9 | OPTIONS (table '@MSCHEMANAME.smallmoney_max', row_estimate_method 'showplan_all'); 10 | 11 | SELECT * FROM @PSCHEMANAME.smallmoney_max; 12 | -------------------------------------------------------------------------------- /tests/validate-test-json: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from json import JSONDecodeError, loads 4 | try: 5 | from jsonschema import validate, ValidationError 6 | except ModuleNotFoundError: 7 | print_error( 8 | "jsonschema library not available, please install it before usage!") 9 | exit(1) 10 | from lib.messages import print_error, print_info, print_ok 11 | from lib.messages import print_usage_error, print_warning 12 | from optparse import OptionParser 13 | from os import listdir, path 14 | from sys import exit 15 | 16 | 17 | def parse_options(): 18 | """Parse and validate options. Returns a dict with all the options.""" 19 | usage = "%prog " 20 | description = ('Test JSON files for MSSQL or PostgreSQL tests') 21 | parser = OptionParser(usage=usage, description=description) 22 | parser.add_option('--path', action='store', 23 | help='Path to a JSON file') 24 | (options, args) = parser.parse_args() 25 | if options.path is None: 26 | print_error("Insufficient parameters, check help (-h)") 27 | exit(4) 28 | return(options) 29 | 30 | 31 | def validate_json(jsonfile): 32 | # This is the definition of the schema, according to README.md 33 | schema = { 34 | "type": "object", 35 | "properties": { 36 | "test_desc": { 37 | "type": "string" 38 | }, 39 | "server": { 40 | "type": "object", 41 | "properties": { 42 | "version": { 43 | "type": "object", 44 | "properties": { 45 | "min": { 46 | "type": "string", 47 | "pattern": "^([0-9]+\\.[0-9]+\\.[0-9]+)$" 48 | }, 49 | "max": { 50 | "type": "string", 51 | "pattern": "^([0-9]+\\.[0-9]+\\.[0-9]+)?$" 52 | } 53 | }, 54 | "required": [ 55 | "min", 56 | "max" 57 | ] 58 | } 59 | }, 60 | "required": [ 61 | "version" 62 | ] 63 | } 64 | }, 65 | "required": [ 66 | "test_desc", 67 | "server" 68 | ] 69 | } 70 | try: 71 | with open(jsonfile, 'r') as f: 72 | try: 73 | json = loads(f.read()) 74 | except JSONDecodeError as e: 75 | print_error("Invalid JSON: %s" % e) 76 | return False 77 | except FileNotFoundError as e: 78 | print_error(e) 79 | return False 80 | try: 81 | validate(instance=json, schema=schema) 82 | except ValidationError as e: 83 | print_error(e) 84 | return False 85 | print_ok("%s is valid" % jsonfile) 86 | return True 87 | 88 | 89 | def main(): 90 | try: 91 | options = parse_options() 92 | except Exception as e: 93 | print_usage_error(path.basename(__file__), e) 94 | exit(2) 95 | print_info("Validating %s" % options.path) 96 | if not validate_json(options.path): 97 | exit(3) 98 | 99 | 100 | if __name__ == "__main__": 101 | main() 102 | --------------------------------------------------------------------------------