Introduction
PostgreSQL is widely used for applications where database logic goes beyond simple tables and CRUD operations. Modern applications often work with JSON documents, foreign tables, reporting databases, and data that is spread across multiple PostgreSQL servers.
That flexibility also means that a PostgreSQL upgrade should not be tested only with basic SELECT, INSERT, UPDATE, and DELETE statements.
PostgreSQL 19 Beta 3 is a good example of why targeted regression testing matters. The beta release includes fixes for a postgres_fdw issue involving array comparisons with implicit type coercion and a separate fix for JSON deparsing involving JSON_ARRAY(query).
These changes are small in description but important from a testing perspective. A query can execute successfully while still returning an unexpected result, or generated SQL can be structurally different from what the application expects.
This article looks at how developers can build practical regression tests around JSON and postgres_fdw when evaluating PostgreSQL 19 Beta 3.
What Is postgres_fdw?
postgres_fdw is PostgreSQL's foreign-data wrapper for accessing data stored on another PostgreSQL server. It allows a local PostgreSQL database to work with tables that physically reside in a remote PostgreSQL database.
A simplified architecture looks like this:
Application
|
v
Local PostgreSQL
|
| postgres_fdw
v
Remote PostgreSQL
|
v
Remote TableFrom the application's perspective, a foreign table can look similar to an ordinary table.
For example:
SELECT *
FROM remote_customers;The actual data, however, may be stored on another PostgreSQL server.
Why Foreign Data Wrappers Need Regression Testing
A local query can involve several layers:
Local SQL
|
v
Query Planner
|
v
FDW Pushdown
|
v
Remote SQL
|
v
Remote PostgreSQL
|
v
ResultA problem in any part of this process can produce unexpected behavior.
This is particularly important when the query contains:
Arrays
Type conversions
Functions
Joins
Filters
Aggregations
JSON expressions
PostgreSQL's documentation explains that postgres_fdw attempts to push suitable query conditions to the remote server to reduce the amount of data transferred.
That optimization is useful, but it also makes query-planning and type-handling behavior worth testing.
PostgreSQL 19 Beta 3 Changes
The PostgreSQL 19 Beta 3 release notes identify several changes relevant to this article.
Among them are:
postgres_fdw:
Fix wrong query results when pushing down
an array comparison such as field = ANY($1)
involving implicit type coercion.
JSON:
Fix missing FORMAT clause when deparsing
JSON_ARRAY(query).These fixes are explicitly listed in the PostgreSQL 19 Beta 3 announcement.
This gives developers two concrete regression-testing areas:
1. postgres_fdw array comparisons
2. JSON_ARRAY(query) deparsingThe goal should not be to reproduce an issue artificially just because it appears in release notes.
Instead, test whether the application's real workload exercises the affected areas.
Important Note About Beta Releases
PostgreSQL Beta releases are intended for testing, not production use. The PostgreSQL project specifically encourages developers to run their normal workloads against beta releases to identify compatibility problems before the final release.
That makes PostgreSQL 19 Beta 3 appropriate for a controlled regression environment.
A simple test environment could be:
Developer Machine
|
+---- PostgreSQL 19 Beta 3
|
+---- Test Application
|
+---- Remote PostgreSQLDo not use a beta database as a production database merely because a particular test passes.
Setting Up a JSON Test Table
Start with a simple table containing JSON data.
CREATE TABLE orders
(
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id BIGINT NOT NULL,
details JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);Insert some representative data:
INSERT INTO orders
(
customer_id,
details
)
VALUES
(
101,
'{
"status": "completed",
"items": 3,
"total": 250.50
}'
),
(
102,
'{
"status": "pending",
"items": 1,
"total": 75.00
}'
);Now the test database contains both relational and JSON data.
Testing JSON Extraction
Start with a simple query:
SELECT
id,
details->>'status' AS status
FROM orders;Then test JSON values in filtering:
SELECT *
FROM orders
WHERE details->>'status' = 'completed';These tests establish a baseline before moving into more complex JSON expressions.
Testing JSON Aggregation
Applications often need to construct JSON results from relational data.
For example:
SELECT JSON_AGG(
JSON_BUILD_OBJECT(
'id', id,
'customerId', customer_id,
'status', details->>'status'
)
)
FROM orders;The important thing is to validate both:
JSON Structure
+
JSON ValuesA query that returns valid JSON is not necessarily returning the correct JSON.
Testing JSON_ARRAY(query)
PostgreSQL 19 includes a fix related to deparsing JSON_ARRAY(query).
This makes it useful to include query-based JSON construction in a regression suite.
For example:
SELECT JSON_ARRAY(
SELECT details
FROM orders
);The exact SQL shape should be adapted to the syntax supported by the PostgreSQL version and the application's requirements.
The test should verify:
Valid SQL
Valid JSON
Correct number of elements
Correct element values
Expected formatting semanticsWhat Does Deparsing Mean?
Deparsing is essentially the process of reconstructing SQL from an internal representation.
This matters in PostgreSQL because several database components work with parsed or planned representations of queries.
A simplified flow is:
SQL
|
v
Parser
|
v
Internal Representation
|
v
Planner / Other Component
|
v
Deparsed SQLIf the deparser produces incorrect SQL, the resulting query can behave differently from the original intent.
That is why a seemingly small deparser fix can deserve a regression test.
Building a JSON Regression Test
A useful test should define expected output explicitly.
For example:
Input:
2 orders
Expected:
JSON array containing exactly 2 elementsThen execute the query and validate:
Element count = 2
First ID = expected ID
Second ID = expected IDAvoid relying only on:
Query executed successfullyThe test should verify the actual result.
Testing JSON NULL Behavior
JSON and SQL NULL values can behave differently.
Consider:
SELECT
details->'discount'
FROM orders;If the property does not exist, the result may not behave like an ordinary SQL column containing NULL.
Therefore, include test data such as:
{
"status": "completed"
}and:
{
"status": "completed",
"discount": null
}Then test both cases.
This can reveal application assumptions that would otherwise remain hidden.
Testing Nested JSON
Real applications rarely use only flat JSON objects.
For example:
{
"customer": {
"name": "Alice"
},
"payment": {
"method": "card",
"status": "paid"
}
}Test nested access:
SELECT
details #>> '{customer,name}' AS customer_name,
details #>> '{payment,status}' AS payment_status
FROM orders;Then verify the returned values.
Testing JSON With .NET
A .NET application may map JSON data through EF Core or access it through raw SQL.
For example:
var orders = await db.Orders
.Where(x => x.Details.RootElement
.GetProperty("status")
.GetString() == "completed")
.ToListAsync();The exact expression depends on the PostgreSQL provider and application model.
When evaluating PostgreSQL 19 Beta 3, test the complete path:
.NET Application
|
v
EF Core / Provider
|
v
Generated SQL
|
v
PostgreSQL 19
|
v
JSON ResultThis is more valuable than testing the database in isolation.
What Is an FDW Array Comparison?
One of the PostgreSQL 19 Beta 3 fixes concerns postgres_fdw when pushing down an array comparison such as:
field = ANY($1)when implicit type coercion is involved. PostgreSQL's release announcement identifies this as a case that could produce wrong query results and was fixed in Beta 3.
This is an excellent example of a bug that may be difficult to detect through ordinary syntax validation.
Understanding ANY
Consider a query like:
SELECT *
FROM remote_customers
WHERE customer_id = ANY($1);Conceptually, this asks PostgreSQL to find rows where:
customer_idmatches one of the values supplied in the array parameter.
For example:
[101, 205, 310]means:
customer_id = 101
OR
customer_id = 205
OR
customer_id = 310The important detail is the data type of both sides.
Why Type Coercion Matters
Suppose:
customer_id
BIGINTbut the supplied array is:
INTEGER[]PostgreSQL may need to perform an implicit conversion.
The query now involves:
BIGINT
|
v
Comparison
^
|
INTEGER[]The PostgreSQL 19 Beta 3 fix specifically addresses a postgres_fdw case where array comparison pushdown combined with implicit type coercion could produce wrong results.
This is exactly the kind of regression that should be tested using real parameter types.
Building an FDW Test Environment
Create a remote table:
CREATE TABLE remote_customers
(
id BIGINT PRIMARY KEY,
name TEXT NOT NULL
);Add test data:
INSERT INTO remote_customers (id, name)
VALUES
(101, 'Alice'),
(205, 'Bob'),
(310, 'Charlie'),
(450, 'David');Then expose the table through postgres_fdw.
The local database can define a foreign table corresponding to the remote table.
The exact connection configuration depends on your test environment.
Test Array-Based Filtering
Once the foreign table is configured, test:
SELECT *
FROM remote_customers
WHERE id = ANY(ARRAY[101, 205]);The expected result is:
101 | Alice
205 | BobNow introduce different integer types.
For example:
SELECT *
FROM remote_customers
WHERE id = ANY(ARRAY[101, 205]::INTEGER[]);Then compare the result with an explicitly compatible type:
SELECT *
FROM remote_customers
WHERE id = ANY(ARRAY[101, 205]::BIGINT[]);The purpose is to verify that implicit and explicit conversions produce the expected logical result.
Compare Local and FDW Queries
One of the strongest regression techniques is to compare a local query with an equivalent foreign query.
For example:
Local PostgreSQL
|
v
Local Table
|
v
Expected Resultand:
Local PostgreSQL
|
v
postgres_fdw
|
v
Remote Table
|
v
Actual ResultThe result sets should match.
This creates a practical oracle:
Local Query Result
=
FDW Query Resultassuming the underlying datasets are equivalent.
Testing With Different Array Sizes
Do not test only two IDs.
Use:
Empty array
One value
Two values
Many values
Duplicate values
NULL values where applicableFor example:
ARRAY[101]then:
ARRAY[101, 205, 310]
Join the conversation! Your thoughts help the community grow.