Contents
oai_fdw 1.15
2026-10-01
Enhancements
Regression tests without network access:
make installchecknow only runs the tests that need nothing but a PostgreSQL server. Tests against live repositories and through the Squid proxies are opt-in, withINCLUDE_EXTERNAL_TESTS=1,INCLUDE_LOCAL_TESTS=1orINCLUDE_ALL_TESTS=1.Reproducible builds: the build date shown by
oai_fdw_settings()is taken fromSOURCE_DATE_EPOCH, if set.HTTP 429 flow control:
429 Too Many Requestsis handled like503, honouringRetry-After. WithoutRetry-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
fromanduntil: the table options must be a validYYYY-MM-DDorYYYY-MM-DDThh:mm:ssZvalue.Bug fixes
Fixed backend crashes: a NULL element in a
setspecarray filter, a prefix operator inWHERE, support functions called on a server of another FDW, andListSets/ListMetadataFormatsresponses 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()andOAI_ListMetadataFormats(), nor libxml2’s copies of the values in queries.Support functions require
USAGE:OAI_Identify(),OAI_ListSets()andOAI_ListMetadataFormats()now check theUSAGEprivilege on the foreign server.ListSetsfollowsresumptionToken:OAI_ListSets()andIMPORT FOREIGN SCHEMA oai_setsno longer stop after the first page of sets.from/untilgranularity: both are sent in a granularity the repository supports, as announced byIdentify, so repositories with day granularity no longer answerbadArgument.GetRecord, which takes neither, needs noIdentifyrequest.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:
dateconstants, 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.
$1in a generic plan of a prepared statement or PL/pgSQL) or an expression such asnow() - interval '1 day'were not pushed down, so they harvested the whole repository. They are now evaluated when the scan starts, andEXPLAINlists them asRuntime arguments.varcharcolumns:varchar(n)limits are applied, and array operators work onvarchar[]setspeccolumns.<metadata>content: the root element is returned, even if a comment precedes it.IMPORT FOREIGN SCHEMA: the user mapping is used, andLIMIT TOonly imports sets that exist. Sets whosesetSpecis 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 aContent-Typeheader 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 defaultend_dateis 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. Apage_sizethat 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 withclient_min_messages; the per-page messages ofexec_verboseremain INFOs.Server options:
request_redirect,request_max_redirectand the URL scheme are validated, andconnect_retry '0'disables retries. Server options are read by one function for queries, the support functions andIMPORT 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_timeoutof0. Timeouts are reported with libcurl’s reason.No signals from libcurl: libcurl builds without an asynchronous resolver used
SIGALRMfor DNS timeouts, which PostgreSQL uses itself (e.g. forstatement_timeout).CURLOPT_NOSIGNALis now set.
oai_fdw 1.14
2026-09-09
Enhancements
Improved EXPLAIN diagnostics:
EXPLAINoutput 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 newoai_fdw_settings()function provides extended dependency information including optional components like SSL, zlib, libSSH, and nghttp2. Theoai_fdw_settingsview 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).
0disables the limit (default). Unlikeconnect_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 duringCreateOAITuple(), 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_messagesis set toDEBUG3, libcurl’s verbose output is now routed through PostgreSQL’selog(DEBUG3)via a customCURLOPT_DEBUGFUNCTIONcallback (CurlDebugCallback) instead of being written directly tostderr. Crucially, any outgoing or incoming HTTP header whose name matchesAuthorization:orProxy-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-logelog()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: TheCREATE FOREIGN TABLEstatements generated byIMPORT FOREIGN SCHEMAinterpolated the server name, themetadataPrefixand thesetSpecdirectly into the command text without quoting. Since themetadataPrefixandsetSpecvalues are taken from the remote OAI repository’sListMetadataFormatsandListSetsresponses, 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 throughquote_identifier(), and option values throughquote_literal_cstr().metadataPrefixis now URL-encoded inGetRecordrequests: Unlike theListRecordsandListIdentifierscode paths, theGetRecordrequest builder appended themetadataPrefixoption to the request URL without passing it throughcurl_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,setSpecanddatestampvalues of a record header were extracted withxmlNodeDump(), 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 asoai:example.org/a&bcame back asoai:example.org/a&b. Besides being wrong on its own, this silently broke pushdown: a query filtering on the true identifier pushed a correctGetRecordrequest, 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 withxmlNodeGetContent(), which resolves XML escapes. Thecontentnode 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 answer503 Service Unavailablewith aRetry-Afterheader to ask a harvester to slow down. Such a response was treated as an ordinary failure and retried immediately, up toconnect_retrytimes 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 throughRetry-Afteris waited out before the next attempt. Both forms allowed by RFC 9110 are accepted (delta-seconds and HTTP-date), a repository that sends no usableRetry-Afterfalls 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 withWaitLatch()rather thanpg_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 byRaiseOAIException(), such asbadArgumentorcannotDisseminateFormat- 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 throughPG_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()andHeaderCallbackFunction()couldereport(ERROR)- and usedpalloc(), which throws on out-of-memory - while running insidecurl_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 withmalloc(), 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 noverb, nometadataPrefixand no other argument - relevant for anySERVERcreated withrequest_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_userandproxy_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 -
userandpassword, so that the user can be authenticated.
- This feature defines a mapping of a PostgreSQL user to an user in the target
triplestore -
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_redirectandrequest_max_redirectoptions (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.
- Adds
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