Skip to content

Commit 28aa1df

Browse files
authored
Merge pull request #68 from Open-J-Proxy/copilot/run-integration-tests-twice
Run ojp-jdbc integration tests in both XA and JDBC modes via CSV parameterization (PostgreSQL only)
2 parents 124b438 + 90ddcd7 commit 28aa1df

14 files changed

+971
-121
lines changed

ADDING_DATABASE_XA_SUPPORT.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Adding XA Support for New Databases
2+
3+
This guide explains how to add XA (distributed transaction) support for additional databases to the OJP integration test suite. The current implementation includes full PostgreSQL XA support, which serves as the reference implementation.
4+
5+
## Current Status
6+
7+
**XA Support Implemented:**
8+
- ✅ PostgreSQL - Full XA support with test coverage
9+
10+
**Infrastructure Ready (Driver Detection Only):**
11+
- Oracle, SQL Server, DB2, CockroachDB - XADataSource factory methods exist but XA testing is disabled
12+
13+
**Not Supported:**
14+
- H2, MySQL, MariaDB - Limited XA support in these databases
15+
16+
## Prerequisites
17+
18+
Before adding XA support for a new database, ensure:
19+
20+
1. **Database XA Capability**: The database supports XA transactions with a proper XADataSource implementation
21+
2. **JDBC Driver**: The JDBC driver with XA support is available (not required at compile-time)
22+
3. **Privileges**: Database user has required privileges for XA operations
23+
4. **Test Environment**: Database instance is available for integration testing
24+
25+
## Step-by-Step Guide
26+
27+
### 1. Verify XADataSource Factory Method
28+
29+
Check `ojp-server/src/main/java/org/openjproxy/grpc/server/xa/XADataSourceFactory.java`:
30+
31+
```java
32+
public static DataSource createXADataSource(String url, String user, String password) throws SQLException {
33+
// ...
34+
if (url.contains("_postgresql:") || url.contains("_postgres:")) {
35+
return createPostgreSQLXADataSource(url, user, password);
36+
}
37+
// Add your database check here
38+
// ...
39+
}
40+
```
41+
42+
If your database doesn't have a factory method yet, add one following the PostgreSQL pattern:
43+
44+
```java
45+
private static DataSource createYourDatabaseXADataSource(String url, String user, String password)
46+
throws SQLException {
47+
// Check if driver is available
48+
try {
49+
Class.forName("com.yourdb.jdbc.xa.YourDBXADataSource");
50+
} catch (ClassNotFoundException e) {
51+
throw new SQLException("YourDB XA driver not found. Add yourdb-jdbc jar to classpath.");
52+
}
53+
54+
// Parse URL to extract connection parameters
55+
String cleanUrl = url.replaceFirst(".*?_", ""); // Remove ojp prefix
56+
// Extract host, port, database from cleanUrl
57+
58+
// Create XA DataSource using reflection (no compile-time dependency)
59+
try {
60+
Object xaDS = Class.forName("com.yourdb.jdbc.xa.YourDBXADataSource").newInstance();
61+
62+
// Set connection properties via reflection
63+
xaDS.getClass().getMethod("setServerName", String.class).invoke(xaDS, host);
64+
xaDS.getClass().getMethod("setPortNumber", int.class).invoke(xaDS, port);
65+
xaDS.getClass().getMethod("setDatabaseName", String.class).invoke(xaDS, database);
66+
xaDS.getClass().getMethod("setUser", String.class).invoke(xaDS, user);
67+
xaDS.getClass().getMethod("setPassword", String.class).invoke(xaDS, password);
68+
69+
return (DataSource) xaDS;
70+
} catch (Exception e) {
71+
throw new SQLException("Failed to create YourDB XA DataSource: " + e.getMessage(), e);
72+
}
73+
}
74+
```
75+
76+
### 2. Update CSV Test Files
77+
78+
Add XA test entries to your database's CSV file(s):
79+
80+
**Example: `ojp-jdbc-driver/src/test/resources/yourdb_connection.csv`**
81+
82+
```csv
83+
org.openjproxy.jdbc.Driver,jdbc:ojp[localhost:1059]_yourdb://localhost:5000/testdb,user,pass,false
84+
org.openjproxy.jdbc.Driver,jdbc:ojp[localhost:1059]_yourdb://localhost:5000/testdb,user,pass,true
85+
```
86+
87+
- First line: `isXA=false` - Standard JDBC mode
88+
- Second line: `isXA=true` - XA mode
89+
90+
Update all relevant CSV files that include your database.
91+
92+
### 3. Ensure Tests Use TestDBUtils Helper
93+
94+
Check that test files are using the `TestDBUtils.createConnection()` helper:
95+
96+
**✅ Correct Pattern:**
97+
```java
98+
@ParameterizedTest
99+
@CsvFileSource(resources = "/yourdb_connection.csv")
100+
void testOperation(String driver, String url, String user, String pwd, boolean isXA) throws Exception {
101+
TestDBUtils.ConnectionResult connResult = TestDBUtils.createConnection(url, user, pwd, isXA);
102+
Connection conn = connResult.getConnection();
103+
104+
try {
105+
// Test operations
106+
conn.createStatement().execute("CREATE TABLE test (id INT)");
107+
connResult.commit(); // Handles both XA and standard JDBC
108+
109+
// More operations...
110+
connResult.startXATransactionIfNeeded(); // For XA only
111+
conn.createStatement().execute("INSERT INTO test VALUES (1)");
112+
connResult.commit();
113+
} finally {
114+
connResult.close(); // Properly closes XA and JDBC connections
115+
}
116+
}
117+
```
118+
119+
**❌ Incorrect Pattern (ignores isXA parameter):**
120+
```java
121+
Connection conn = DriverManager.getConnection(url, user, pwd); // Always JDBC mode
122+
```
123+
124+
### 4. Handle Database-Specific XA Requirements
125+
126+
Some databases have specific requirements for XA transactions:
127+
128+
**Privileges:**
129+
- Oracle requires specific GRANT statements for XA system tables
130+
- Other databases may have similar requirements
131+
- Document these in comments or separate setup files
132+
133+
**Configuration:**
134+
- Some databases require specific connection properties for XA
135+
- Add these to the XADataSource creation method
136+
137+
**Transaction Isolation:**
138+
- Verify that the database supports XA transaction isolation levels
139+
- Test with different isolation levels if needed
140+
141+
### 5. Test XA Functionality
142+
143+
Run tests to verify XA support:
144+
145+
```bash
146+
# Run all tests for your database (both XA and JDBC modes)
147+
mvn test -pl ojp-jdbc-driver -Dtest="YourDatabase*Test"
148+
149+
# Check that XA tests actually use XA transactions
150+
# Look for log messages from XAResource.start(), commit(), end()
151+
```
152+
153+
**Validation Checklist:**
154+
- ✅ Tests run with `isXA=false` (standard JDBC)
155+
- ✅ Tests run with `isXA=true` (XA mode)
156+
- ✅ XA transactions are properly started (check server logs for XAResource.start())
157+
- ✅ XA transactions are properly committed (check server logs for XAResource.commit())
158+
- ✅ No connection leaks (check server logs for HikariCP warnings)
159+
- ✅ Transactions are properly cleaned up on test failures
160+
- ✅ No hanging tests
161+
162+
### 6. Document Database-Specific Setup
163+
164+
If your database requires special setup (privileges, configuration), create a setup guide:
165+
166+
```markdown
167+
# YourDatabase XA Setup Guide
168+
169+
## Required Privileges
170+
```sql
171+
GRANT xa_privileges TO test_user;
172+
```
173+
174+
## Common Issues
175+
- Error XYZ: Missing privilege ABC
176+
- Error 123: Configuration DEF required
177+
```
178+
179+
## Reference: PostgreSQL Implementation
180+
181+
PostgreSQL serves as the reference implementation. Key files to review:
182+
183+
### Server-Side
184+
**`ojp-server/src/main/java/org/openjproxy/grpc/server/xa/XADataSourceFactory.java`:**
185+
```java
186+
private static DataSource createPostgreSQLXADataSource(String url, String user, String password) {
187+
PGXADataSource xaDataSource = new PGXADataSource();
188+
// Parse URL and set properties
189+
xaDataSource.setServerNames(new String[]{host});
190+
xaDataSource.setPortNumbers(new int[]{port});
191+
xaDataSource.setDatabaseName(database);
192+
xaDataSource.setUser(user);
193+
xaDataSource.setPassword(password);
194+
return xaDataSource;
195+
}
196+
```
197+
198+
### Test-Side
199+
**`ojp-jdbc-driver/src/test/resources/postgres_connection.csv`:**
200+
```csv
201+
org.openjproxy.jdbc.Driver,jdbc:ojp[localhost:1059]_postgresql://localhost:5432/defaultdb,testuser,testpass,false
202+
org.openjproxy.jdbc.Driver,jdbc:ojp[localhost:1059]_postgresql://localhost:5432/defaultdb,testuser,testpass,true
203+
```
204+
205+
**Test Helper Usage:**
206+
```java
207+
TestDBUtils.ConnectionResult connResult = TestDBUtils.createConnection(url, user, pwd, isXA);
208+
Connection conn = connResult.getConnection();
209+
// Operations...
210+
connResult.commit(); // XA-aware commit
211+
connResult.close(); // Proper cleanup
212+
```
213+
214+
## Common Pitfalls
215+
216+
### 1. Not Using TestDBUtils Helper
217+
**Problem:** Tests create connections with `DriverManager.getConnection()`, ignoring `isXA` parameter.
218+
**Solution:** Always use `TestDBUtils.createConnection()` which handles XA vs JDBC branching.
219+
220+
### 2. Missing Privileges
221+
**Problem:** XA operations fail with privilege errors.
222+
**Solution:** Grant required XA privileges to database user. Document these in your setup guide.
223+
224+
### 3. Connection Leaks
225+
**Problem:** XA connections not properly closed, causing resource leaks.
226+
**Solution:** Always call `connResult.close()` in `finally` block. The helper properly ends XA transactions.
227+
228+
### 4. Transaction Not Started
229+
**Problem:** After `commit()`, next operation fails because no transaction is active.
230+
**Solution:** Call `connResult.startXATransactionIfNeeded()` after `commit()` if you need to do more work.
231+
232+
### 5. URL Parsing Issues
233+
**Problem:** XADataSource factory can't parse database URL correctly.
234+
**Solution:** Test URL parsing thoroughly. Handle different URL formats (with/without options, different separators).
235+
236+
## Testing Checklist
237+
238+
Before marking a database as "XA supported":
239+
240+
- [ ] XADataSource factory method created and tested
241+
- [ ] CSV files updated with both `isXA=false` and `isXA=true` entries
242+
- [ ] All tests using the database refactored to use `TestDBUtils.createConnection()`
243+
- [ ] Tests pass in standard JDBC mode (`isXA=false`)
244+
- [ ] Tests pass in XA mode (`isXA=true`)
245+
- [ ] Server logs show XA transactions being used (XAResource.start/commit/end)
246+
- [ ] No connection leaks in server logs
247+
- [ ] No hanging tests
248+
- [ ] Setup requirements documented (if any)
249+
- [ ] Privilege requirements documented (if any)
250+
- [ ] Common errors documented with solutions
251+
252+
## Getting Help
253+
254+
- Review PostgreSQL implementation as reference
255+
- Check `TestDBUtils.java` for XA transaction lifecycle management
256+
- Review `XADataSourceFactory.java` for DataSource creation patterns
257+
- Test incrementally - get JDBC mode working first, then add XA

ojp-jdbc-driver/src/test/java/openjproxy/jdbc/BasicCrudIntegrationTest.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package openjproxy.jdbc;
22

33
import lombok.extern.slf4j.Slf4j;
4+
import openjproxy.jdbc.testutil.TestDBUtils;
5+
import openjproxy.jdbc.testutil.TestDBUtils.ConnectionResult;
46
import org.junit.Assert;
57
import org.junit.jupiter.api.Assumptions;
68
import org.junit.jupiter.api.BeforeAll;
79
import org.junit.jupiter.params.ParameterizedTest;
810
import org.junit.jupiter.params.provider.CsvFileSource;
911

1012
import java.sql.Connection;
11-
import java.sql.DriverManager;
1213
import java.sql.ResultSet;
1314
import java.sql.SQLException;
1415

@@ -39,7 +40,7 @@ public static void setup() {
3940

4041
@ParameterizedTest
4142
@CsvFileSource(resources = "/h2_postgres_mysql_mariadb_oracle_sqlserver_connections.csv")
42-
public void crudTestSuccessful(String driverClass, String url, String user, String pwd) throws SQLException, ClassNotFoundException {
43+
public void crudTestSuccessful(String driverClass, String url, String user, String pwd, boolean isXA) throws SQLException, ClassNotFoundException {
4344
// Skip PostgreSQL tests if disabled
4445
if (url.toLowerCase().contains("postgresql") && isPostgresTestDisabled) {
4546
Assumptions.assumeFalse(true, "Skipping Postgres tests");
@@ -82,7 +83,13 @@ public void crudTestSuccessful(String driverClass, String url, String user, Stri
8283
tablePrefix = "cockroachdb_";
8384
}
8485

85-
Connection conn = DriverManager.getConnection(url, user, pwd);
86+
ConnectionResult connResult = TestDBUtils.createConnection(url, user, pwd, isXA);
87+
Connection conn = connResult.getConnection();
88+
89+
// For non-XA connections, set autocommit to false for explicit transaction control
90+
if (!isXA) {
91+
conn.setAutoCommit(false);
92+
}
8693

8794
// Set schema for DB2 connections to avoid "object not found" errors
8895
if (url.toLowerCase().contains("db2")) {
@@ -101,16 +108,33 @@ public void crudTestSuccessful(String driverClass, String url, String user, Stri
101108

102109
try {
103110
executeUpdate(conn, "drop table " + tableName);
111+
connResult.commit();
104112
} catch (Exception e) {
105-
//Does not matter
113+
//Does not matter - table might not exist
114+
try {
115+
connResult.rollback();
116+
} catch (Exception ex) {
117+
// Ignore rollback errors
118+
}
106119
}
120+
121+
// Start new transaction for next operation
122+
connResult.startXATransactionIfNeeded();
107123

108124
executeUpdate(conn, "create table " + tableName + "(" +
109125
"id INT NOT NULL," +
110126
"title VARCHAR(50) NOT NULL" +
111127
")");
128+
connResult.commit();
129+
130+
// Start new transaction for next operation
131+
connResult.startXATransactionIfNeeded();
112132

113133
executeUpdate(conn, " insert into " + tableName + " (id, title) values (1, 'TITLE_1')");
134+
connResult.commit();
135+
136+
// Start new transaction for next operation
137+
connResult.startXATransactionIfNeeded();
114138

115139
java.sql.PreparedStatement psSelect = conn.prepareStatement("select * from " + tableName + " where id = ?");
116140
psSelect.setInt(1, 1);
@@ -122,6 +146,10 @@ public void crudTestSuccessful(String driverClass, String url, String user, Stri
122146
Assert.assertEquals("TITLE_1", title);
123147

124148
executeUpdate(conn, "update " + tableName + " set title='TITLE_1_UPDATED'");
149+
connResult.commit();
150+
151+
// Start new transaction for next operation
152+
connResult.startXATransactionIfNeeded();
125153

126154
ResultSet resultSetUpdated = psSelect.executeQuery();
127155
resultSetUpdated.next();
@@ -130,14 +158,27 @@ public void crudTestSuccessful(String driverClass, String url, String user, Stri
130158
Assert.assertEquals(1, idUpdated);
131159
Assert.assertEquals("TITLE_1_UPDATED", titleUpdated);
132160

133-
executeUpdate(conn, " delete from " + tablePrefix + "basic_crud_test where id=1 and title='TITLE_1_UPDATED'");
161+
executeUpdate(conn, " delete from " + tableName + " where id=1 and title='TITLE_1_UPDATED'");
162+
connResult.commit();
163+
164+
// Start new transaction for next operation
165+
connResult.startXATransactionIfNeeded();
134166

135167
ResultSet resultSetAfterDeletion = psSelect.executeQuery();
136168
Assert.assertFalse(resultSetAfterDeletion.next());
137169

138170
resultSet.close();
139171
psSelect.close();
140-
conn.close();
172+
173+
// Clean up - drop the test table
174+
try {
175+
executeUpdate(conn, "drop table " + tableName);
176+
connResult.commit();
177+
} catch (Exception e) {
178+
// Ignore cleanup errors
179+
}
180+
181+
connResult.close();
141182
}
142183

143184
}

0 commit comments

Comments
 (0)