### oai_fdw 1.15 **2026-10-01** * Enhancements **Regression tests without network access**: `make installcheck` now only runs the tests that need nothing but a PostgreSQL server. Tests against live repositories and through the Squid proxies are opt-in, with `INCLUDE_EXTERNAL_TESTS=1`, `INCLUDE_LOCAL_TESTS=1` or `INCLUDE_ALL_TESTS=1`. **Reproducible builds**: the build date shown by `oai_fdw_settings()` is taken from `SOURCE_DATE_EPOCH`, if set. **HTTP 429 flow control**: `429 Too Many Requests` is handled like `503`, honouring `Retry-After`. Without `Retry-After`, the wait before each retry doubles, starting at 5 seconds, and so does the wait after network errors, which used to be retried immediately. Other client errors (4xx), also from a proxy, are no longer retried. libcurl's reason for a failed attempt is written to the server log. **Validation of `from` and `until`**: the table options must be a valid `YYYY-MM-DD` or `YYYY-MM-DDThh:mm:ssZ` value. * Bug fixes **Fixed backend crashes**: a NULL element in a `setspec` array filter, a prefix operator in `WHERE`, support functions called on a server of another FDW, and `ListSets`/`ListMetadataFormats` responses with missing elements crashed the backend. **Fixed leaks on query cancel**: cancelling a query during a request no longer leaks the curl handle, its socket and the response buffer. **Fixed leaks on errors while parsing**: an error while reading a response, e.g. a value that cannot be converted to the database encoding, no longer leaks the response document in `OAI_Identify()`, `OAI_ListSets()` and `OAI_ListMetadataFormats()`, nor libxml2's copies of the values in queries. **Support functions require `USAGE`**: `OAI_Identify()`, `OAI_ListSets()` and `OAI_ListMetadataFormats()` now check the `USAGE` privilege on the foreign server. **`ListSets` follows `resumptionToken`**: `OAI_ListSets()` and `IMPORT FOREIGN SCHEMA oai_sets` no longer stop after the first page of sets. **`from`/`until` granularity**: both are sent in a granularity the repository supports, as announced by `Identify`, so repositories with day granularity no longer answer `badArgument`. `GetRecord`, which takes neither, needs no `Identify` request. **Character encoding**: values are converted between UTF-8 and the database encoding, in both directions. **Responses are checked**: a response that is not an OAI-PMH document (e.g. an HTML maintenance page) raises an error instead of returning no rows, and OAI errors are raised for all requests. libxml2 parser errors are no longer written to the server's stderr. **Pushdown**: `date` constants, infinite and BC timestamps, and constants of other types (e.g. `name`) are no longer pushed down with a wrong value. **Pushdown of values known at execution time**: conditions comparing with a parameter (e.g. `$1` in a generic plan of a prepared statement or PL/pgSQL) or an expression such as `now() - interval '1 day'` were not pushed down, so they harvested the whole repository. They are now evaluated when the scan starts, and `EXPLAIN` lists them as `Runtime arguments`. **`varchar` columns**: `varchar(n)` limits are applied, and array operators work on `varchar[]` `setspec` columns. **`` content**: the root element is returned, even if a comment precedes it. **`IMPORT FOREIGN SCHEMA`**: the user mapping is used, and `LIMIT TO` only imports sets that exist. Sets whose `setSpec` is longer than 63 characters get unique table names instead of making the import fail. **User mapping of views**: a foreign table queried through a view now uses the user mapping of the view owner, as permissions are checked as that role. **Redirects**: a redirect that is not followed now raises an error naming its target and suggesting `request_redirect`, instead of reporting an invalid response. A followed redirect no longer warns about the content-type of the redirect response. Neither does a `Content-Type` header without a space after the colon. **`OAI_HarvestTable`**: the last time window is no longer skipped, quoted table names work, a current schema whose name needs quoting works, the default `end_date` is the current time in UTC instead of the session's local time (which left out the latest records in sessions west of UTC), and same-named tables in other schemas are no longer mixed up. A `page_size` that is not positive, or a NULL argument, is rejected instead of reporting a completed harvest of nothing. Its "target table created" and "harvester complete" messages are now NOTICEs instead of INFOs, so that they can be silenced with `client_min_messages`; the per-page messages of `exec_verbose` remain INFOs. **Server options**: `request_redirect`, `request_max_redirect` and the URL scheme are validated, and `connect_retry '0'` disables retries. Server options are read by one function for queries, the support functions and `IMPORT FOREIGN SCHEMA`, so they are interpreted the same way everywhere. **Linking**: the library now links against libxml2. **Build with older libcurl**: the build no longer fails with libcurl older than 7.66, e.g. 7.61.1 on RHEL / Rocky Linux 8. `oai_fdw_settings()` then does not report nghttp2. **Stalled transfers**: a transfer that receives less than one byte per second for 300 seconds is now aborted and retried, instead of hanging forever with the default `request_timeout` of `0`. Timeouts are reported with libcurl's reason. **No signals from libcurl**: libcurl builds without an asynchronous resolver used `SIGALRM` for DNS timeouts, which PostgreSQL uses itself (e.g. for `statement_timeout`). `CURLOPT_NOSIGNAL` is now set. ### oai_fdw 1.14 2026-09-09 * Enhancements **Improved EXPLAIN diagnostics**: `EXPLAIN` output now include oai_fdw-specific details for each Foreign Scan node, showing which parameters are pushed down to the remote OAI Server. **Enhanced version information**: The `oai_fdw_version()` function now returns a comprehensive version string that includes PostgreSQL version, compiler information, and all dependency versions (libxml, librdf, libcurl) in a single formatted output. A new `oai_fdw_settings()` function provides extended dependency information including optional components like SSL, zlib, libSSH, and nghttp2. The `oai_fdw_settings` view parses this extended information into a table format for convenient programmatic access to individual component versions. **Add 'request_timeout' to FOREIGN SERVERS**: This option sets the maximum time in seconds allowed for a complete HTTP request (connect + transfer). `0` disables the limit (default). Unlike `connect_timeout`, this applies to the entire duration of the request, including data transfer. * Bug fixes **Fixed invalid libcurl lifecycle**: `curl_global_init()`/`curl_global_cleanup()` were being called on every OAI request instead of once per backend process. This could interfere with other libcurl users loaded in the same backend (e.g. other FDWs). Global initialization now happens once in `_PG_init()`; cleanup is left to the OS at process exit. **Fixed catalog lookup overhead**: Foreign table column metadata (name, OAI node mapping, PostgreSQL type, type modifier, and attribute number) is now loaded once during session initialization and cached in the scan state, then passed to the executor via the serialized plan. Previously this information was looked up from the system catalogs (`GetForeignColumnOptions`) for every column of every row during `CreateOAITuple()`, causing significant syscache overhead on large result sets. **Add missing user mapping in helper functions**: GetIdentity, ListSets, and ListMetadataFormats were being executed without loading the `USER MAPPINGS`, which could fail requests if the server needed any sort of authentication. This has now been solved. **Encoded credentials are no longer written to server logs via libcurl verbose output**: When `client_min_messages` is set to `DEBUG3`, libcurl's verbose output is now routed through PostgreSQL's `elog(DEBUG3)` via a custom `CURLOPT_DEBUGFUNCTION` callback (`CurlDebugCallback`) instead of being written directly to `stderr`. Crucially, any outgoing or incoming HTTP header whose name matches `Authorization:` or `Proxy-Authorization:` has its value replaced with `[REDACTED]` before being logged, so Bearer tokens, Basic auth credentials (base64-encoded), and proxy passwords never appear in plaintext in the PostgreSQL server log. **HTTP error response bodies are now truncated in log and error messages**: Error bodies included in `errdetail()` and server-log `elog()` calls were previously unbounded. A misconfigured proxy returning a large HTML error page would be written verbatim into the PostgreSQL server log. Error bodies are now truncated to 512 bytes (`OAI_FDW_MAX_ERROR_BODY`) before being included in any message. **SQL identifiers and literals are now properly quoted in `IMPORT FOREIGN SCHEMA`**: The `CREATE FOREIGN TABLE` statements generated by `IMPORT FOREIGN SCHEMA` interpolated the server name, the `metadataPrefix` and the `setSpec` directly into the command text without quoting. Since the `metadataPrefix` and `setSpec` values are taken from the remote OAI repository's `ListMetadataFormats` and `ListSets` responses, a malicious or compromised repository could inject arbitrary SQL that would then be executed with the privileges of the user running the import. Server names and generated table names are now passed through `quote_identifier()`, and option values through `quote_literal_cstr()`. **`metadataPrefix` is now URL-encoded in `GetRecord` requests**: Unlike the `ListRecords` and `ListIdentifiers` code paths, the `GetRecord` request builder appended the `metadataPrefix` option to the request URL without passing it through `curl_easy_escape()`. A value containing `&` or `#` could therefore inject additional parameters into the OAI request. **OAI header values are no longer returned XML-escaped**: The `identifier`, `setSpec` and `datestamp` values of a record header were extracted with `xmlNodeDump()`, which *serialises* a node back to XML instead of decoding it. Any header value containing a character that has to be escaped in XML was therefore returned to SQL in its escaped form - an identifier such as `oai:example.org/a&b` came back as `oai:example.org/a&b`. Besides being wrong on its own, this silently broke pushdown: a query filtering on the true identifier pushed a correct `GetRecord` request, but the escaped value returned by the scan then failed the local re-check, so a record that exists yielded no rows at all. These values are now read with `xmlNodeGetContent()`, which resolves XML escapes. The `content` node is unaffected, as it legitimately holds serialised XML. **OAI-PMH flow control (HTTP 503 / `Retry-After`) is now honoured**: Section 3.4 of the OAI-PMH specification lets a repository answer `503 Service Unavailable` with a `Retry-After` header to ask a harvester to slow down. Such a response was treated as an ordinary failure and retried immediately, up to `connect_retry` times with no delay whatsoever, which hammers a repository that has just reported itself overloaded and then aborts the harvest. A 503 is now recognised, and the delay requested through `Retry-After` is waited out before the next attempt. Both forms allowed by RFC 9110 are accepted (delta-seconds and HTTP-date), a repository that sends no usable `Retry-After` falls back to 5 seconds, and delays are capped at 300 seconds so that a repository cannot pin a backend down indefinitely. The wait is implemented with `WaitLatch()` rather than `pg_usleep()`, so queries remain cancellable while it is in progress. **Harvests no longer accumulate every page in memory**: The scan context holding the records of a page was only reset when the scan was restarted or finished, so `LoadOAIRecords()` dropped its reference to the previous page without releasing it. Memory use therefore grew with the size of the whole harvest rather than with the size of a single page. The context is now reset before each new page is requested, with the resumption token carried across the reset in a context of its own. **A page that is empty but carries a resumption token no longer truncates the harvest**: `OAIFdwIterateForeignScan()` loaded at most one page per call, so an empty page arriving with a resumption token ended the scan silently and every record behind that token was lost. Pages are now requested until a record is produced or the repository stops handing out tokens. **XML response documents are no longer leaked when a request fails**: `LoadOAIRecords()` freed the parsed response only after walking it, so any error raised in between - including every OAI error condition reported by `RaiseOAIException()`, such as `badArgument` or `cannotDisseminateFormat` - skipped the call. As libxml2 allocates these documents outside PostgreSQL's memory contexts, they were not reclaimed at the end of the query and stayed lost for the life of the backend. The document is now released through `PG_TRY()`/`PG_CATCH()` and the pointer cleared, which also removes a stale pointer that could be freed a second time. **libcurl callbacks no longer raise PostgreSQL errors**: `WriteMemoryCallback()` and `HeaderCallbackFunction()` could `ereport(ERROR)` - and used `palloc()`, which throws on out-of-memory - while running inside `curl_easy_perform()`. Either would longjmp straight out of libcurl, leaving its handle and connection state inconsistent and skipping the cleanup that follows the transfer. The callbacks now allocate with `malloc()`, record the condition and return a short count, so the transfer fails cleanly and the caller reports it. **OAI requests are sent as GET instead of POST**: The request arguments were passed through `CURLOPT_POSTFIELDS`, which made every request a POST even though the log line claimed otherwise. On a 301 or 302 libcurl turns a POST into a GET and drops the body while doing so, so a redirected request reached the repository with no `verb`, no `metadataPrefix` and no other argument - relevant for any `SERVER` created with `request_redirect`. The arguments are now part of the request URL and survive redirects. ### oai_fdw 1.13 2026-02-20 * Bug Fixes Moved proxy authentication options (`proxy_user` and `proxy_password`) from SERVER to USER MAPPING. These credentials are now correctly specified in CREATE USER MAPPING instead of CREATE SERVER. ### oai_fdw 1.12 2026-01-09 * Bug Fixes - Added URL encoding for resumption tokens in ListRecords and ListIdentifiers requests using curl_easy_escape to properly handle special characters like '&' and '='. - Ensured xmldoc is only freed if successfully initialized to prevent crashes from freeing uninitialized memory. - Set User-Agent and Accept headers in requests for better server compatibility and to mimic standard HTTP client behavior. ### oai_fdw 1.11 2025-09-26 * New Features PostgreSQL 18 support. * Bug fixes A safeguard has been introduced to handle cases where xmlDocGetRootElement fails to parse the root node of an XML document. Instead of proceeding with an empty set, an error message is now displayed to inform the user of the issue. ### oai_fdw 1.10 2024-10-14 * New Features PostgreSQL 17 support. ### oai_fdw 1.9 2024-03-21 * New Features * - This feature defines a mapping of a PostgreSQL user to an user in the target triplestore - `user` and `password`, so that the user can be authenticated. * Bug Fixes * - This fix a fixed string length limit in the support functions (512 bytes). I mistakenly assumed that it was defined like that in the OAI-PMH Standard. ### oai_fdw 1.8 2023-11-15 * Performance improvements - This intoduces a new pagination logic to go through the result sets from OAI requests. It no longer iterates over the xml document to retrieve the OAI records for building the tuples. Instead, it now parses all records from a result set into a list right after loading the xml response, so that they can accessed later on for building the tuples. ### oai_fdw 1.7 2023-09-22 * New Features * - Adds PostgreSQL 16 support ### oai_fdw 1.6 2023-04-11 * New Features * - Adds `request_redirect` and `request_max_redirect` options (CREATE SERVER). This allows users to enable or disable URL redirects issued by the server and also how many times a redirection may occur in a single http request. * Bug Fixes * - This bug fix implements a http response validation to avoid a server crash when a support function receives an invalid OAI document from the repository. ### oai_fdw 1.5.1 2023-04-11 * Bug Fixes * - This bug fix implements a http response validation to avoid a server crash when a support function receives an invalid OAI document from the repository. ### oai_fdw 1.5.0 2022-11-15 * Enhancements * - add number of inserted and updated records in the harvester log messages * Bug Fixes * - clean up parser in support function calls (libxml2) - fix wrong initial page size in the logs for debug1 sessions - add missing memory contexts for oai loader and parser. ### oai_fdw 1.4.0 2022-10-27 * New Features * - add update path script for oai_fdw upgrades: 1.1 -> 1.4, 1.2 -> 1.4 and 1.3 -> 1.4 * Bug Fixes * - fix memory leak in xmldoc for requests with resumption tokens ### oai_fdw 1.3.0 2022-10-21 * New Features * - Connection retry option for HTTP requests (CREATE SERVER). ### oai_fdw 1.2.0 2022-10-19 * New Features * - OAI_HarvestTable: new procedure to harvest data from OAI foreign tables and store them into a local table. - Connection retry option for HTTP requests (CREATE SERVER). * Bug Fixes * - Fix memory leaks in the libxml2 parser for ListRecords and ListIdentifiers requests - Fix missing timeout parameter for non-proxy OAI requests * Enhancements * - Add regression tests for PostgreSQL 15. ### oai_fdw 1.1.0 2022-10-01 * New Features * - Proxy support for HTTP requests (CREATE SERVER). - Connection timeout option for HTTP requests (CREATE SERVER). * Bug Fixes * - cURL exception handling: Adds missing cURL error message and response code in case of failed http requests. In previous versions these values were never fetched. * Enhancements * - URL validator no longer checks if the URL is reachable. It now only checks the URL's syntax. - URls with protocols other than HTTP and HTTPS are now rejected before execution, as other protocols are not supported in the OAI-PMH Standard. ### oai_fdw 1.0.0 2022-09-05 * Features * This release fully enables usage of the following OAI-PMH 2.0 Requests via SQL queries (see README): - ListRecords - ListIdentifiers - GetRecord - Identify - ListMetadataFormats - ListSets