Skip to content

Commit 6bb9526

Browse files
committed
feat: add postgres client compatibility test (apecloud#111)
1 parent 9d087d6 commit 6bb9526

File tree

10 files changed

+1038
-0
lines changed

10 files changed

+1038
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Compatibility Test for Postgres
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- compatibility
8+
pull_request:
9+
branches: [ "main" ]
10+
11+
jobs:
12+
13+
build:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Set up Go
19+
uses: actions/setup-go@v5
20+
with:
21+
go-version: '1.23'
22+
23+
- name: Set up Node.js
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: '16'
27+
28+
- name: Set up Python
29+
uses: actions/setup-python@v5
30+
with:
31+
python-version: '3.10'
32+
33+
- name: Install dependencies
34+
run: |
35+
go get .
36+
37+
pip3 install "sqlglot[rs]"
38+
pip3 install psycopg2
39+
40+
curl -LJO https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip
41+
unzip duckdb_cli-linux-amd64.zip
42+
chmod +x duckdb
43+
sudo mv duckdb /usr/local/bin
44+
duckdb -c 'INSTALL json from core'
45+
duckdb -c 'SELECT extension_name, loaded, install_path FROM duckdb_extensions() where installed'
46+
47+
sudo apt-get update
48+
sudo apt-get install --yes --no-install-recommends postgresql-client bats cpanminus
49+
50+
cd compatibility/pg
51+
curl -L -o ./java/postgresql-42.7.4.jar https://jdbc.postgresql.org/download/postgresql-42.7.3.jar
52+
npm install pg
53+
sudo cpanm DBD::Pg
54+
sudo gem install pg
55+
56+
- name: Build
57+
run: go build -v
58+
59+
- name: Start MyDuck Server
60+
run: |
61+
./myduckserver &
62+
sleep 5
63+
64+
- name: Run the Compatibility Test
65+
run: |
66+
bats ./compatibility/pg/test.bats

compatibility/pg/c/pg_test.c

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <libpq-fe.h>
4+
#include <string.h>
5+
6+
typedef struct {
7+
char *query;
8+
char **expectedResults;
9+
int expectedRows;
10+
int expectedCols;
11+
} Test;
12+
13+
typedef struct {
14+
PGconn *conn;
15+
Test *tests;
16+
int testCount;
17+
} PGTest;
18+
19+
void connectDB(PGTest *pgTest, const char *ip, int port, const char *user, const char *password) {
20+
char conninfo[256];
21+
snprintf(conninfo, sizeof(conninfo), "host=%s port=%d dbname=main user=%s password=%s", ip, port, user, password);
22+
pgTest->conn = PQconnectdb(conninfo);
23+
if (PQstatus(pgTest->conn) != CONNECTION_OK) {
24+
printf("Connection to database failed: %s", PQerrorMessage(pgTest->conn));
25+
PQfinish(pgTest->conn);
26+
exit(1);
27+
}
28+
}
29+
30+
void disconnectDB(PGTest *pgTest) {
31+
PQfinish(pgTest->conn);
32+
}
33+
34+
void addTest(PGTest *pgTest, const char *query, char **expectedResults, int expectedRows, int expectedCols) {
35+
pgTest->tests = realloc(pgTest->tests, sizeof(Test) * (pgTest->testCount + 1));
36+
pgTest->tests[pgTest->testCount].query = strdup(query);
37+
pgTest->tests[pgTest->testCount].expectedResults = expectedResults;
38+
pgTest->tests[pgTest->testCount].expectedRows = expectedRows;
39+
pgTest->tests[pgTest->testCount].expectedCols = expectedCols;
40+
pgTest->testCount++;
41+
}
42+
43+
int runTests(PGTest *pgTest) {
44+
for (int i = 0; i < pgTest->testCount; i++) {
45+
Test *test = &pgTest->tests[i];
46+
printf("Running test: %s\n", test->query);
47+
PGresult *res = PQexec(pgTest->conn, test->query);
48+
if (PQresultStatus(res) != PGRES_TUPLES_OK && PQresultStatus(res) != PGRES_COMMAND_OK) {
49+
printf("Query failed: %s\n", PQerrorMessage(pgTest->conn));
50+
PQclear(res);
51+
return 0;
52+
}
53+
if (PQresultStatus(res) == PGRES_TUPLES_OK) {
54+
int rows = PQntuples(res);
55+
int cols = PQnfields(res);
56+
if (cols != test->expectedCols) {
57+
printf("Expected %d columns, got %d\n", test->expectedCols, cols);
58+
PQclear(res);
59+
return 0;
60+
}
61+
for (int r = 0; r < rows; r++) {
62+
for (int c = 0; c < cols; c++) {
63+
char *result = PQgetvalue(res, r, c);
64+
if (strcmp(result, test->expectedResults[r * cols + c]) != 0) {
65+
printf("Expected: '%s', got: '%s'\n", test->expectedResults[r * cols + c], result);
66+
PQclear(res);
67+
return 0;
68+
}
69+
}
70+
}
71+
if (rows != test->expectedRows) {
72+
printf("Expected %d rows, got %d\n", test->expectedRows, rows);
73+
PQclear(res);
74+
return 0;
75+
}
76+
}
77+
PQclear(res);
78+
}
79+
return 1;
80+
}
81+
82+
size_t removeNewline(char *line) {
83+
size_t len = strlen(line);
84+
if (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
85+
line[--len] = '\0';
86+
}
87+
if (len > 0 && line[len - 1] == '\r') {
88+
line[--len] = '\0';
89+
}
90+
return len;
91+
}
92+
93+
void readTestsFromFile(PGTest *pgTest, const char *filename) {
94+
FILE *file = fopen(filename, "r");
95+
if (!file) {
96+
perror("Failed to open test data file");
97+
exit(1);
98+
}
99+
100+
char line[1024];
101+
while (fgets(line, sizeof(line), file)) {
102+
size_t len = removeNewline(line);
103+
if (len == 0) continue;
104+
105+
char *query = strdup(line);
106+
char **expectedResults = NULL;
107+
int expectedRows = 0, expectedCols = 0;
108+
109+
while (fgets(line, sizeof(line), file)) {
110+
len = removeNewline(line);
111+
if (len == 0) break;
112+
113+
char *token = strtok(line, ",");
114+
int col = 0;
115+
while (token) {
116+
expectedResults = realloc(expectedResults, sizeof(char*) * (expectedRows * expectedCols + col + 1));
117+
expectedResults[expectedRows * expectedCols + col] = strdup(token);
118+
token = strtok(NULL, ",");
119+
col++;
120+
}
121+
expectedRows++;
122+
if (expectedCols == 0) expectedCols = col;
123+
}
124+
addTest(pgTest, query, expectedResults, expectedRows, expectedCols);
125+
}
126+
127+
fclose(file);
128+
}
129+
130+
int main(int argc, char *argv[]) {
131+
if (argc != 6) {
132+
printf("Usage: %s <ip> <port> <user> <password> <testFile>\n", argv[0]);
133+
return 1;
134+
}
135+
136+
PGTest pgTest = {0};
137+
connectDB(&pgTest, argv[1], atoi(argv[2]), argv[3], argv[4]);
138+
139+
readTestsFromFile(&pgTest, argv[5]);
140+
141+
int result = runTests(&pgTest);
142+
disconnectDB(&pgTest);
143+
free(pgTest.tests);
144+
return result ? 0 : 1;
145+
}

compatibility/pg/java/PGTest.java

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import java.io.*;
2+
import java.sql.*;
3+
import java.util.List;
4+
import java.util.LinkedList;
5+
6+
public class PGTest {
7+
public static class Tests {
8+
private Connection conn;
9+
private Statement st;
10+
private List<Test> tests = new LinkedList<>();
11+
12+
public void connect(String ip, int port, String user, String password) {
13+
try {
14+
String url = "jdbc:postgresql://" + ip + ":" + port + "/main";
15+
conn = DriverManager.getConnection(url, user, password);
16+
st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
17+
} catch (SQLException e) {
18+
throw new RuntimeException(e);
19+
}
20+
}
21+
22+
public void disconnect() {
23+
try {
24+
st.close();
25+
conn.close();
26+
} catch (SQLException e) {
27+
throw new RuntimeException(e);
28+
}
29+
}
30+
31+
public void addTest(String query, String[][] expectedResults) {
32+
tests.add(new Test(query, expectedResults));
33+
}
34+
35+
public boolean runTests() {
36+
for (Test test : tests) {
37+
if (!test.run()) {
38+
return false;
39+
}
40+
}
41+
return true;
42+
}
43+
44+
public void readTestsFromFile(String filename) {
45+
try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
46+
String line;
47+
while ((line = br.readLine()) != null) {
48+
if (line.trim().isEmpty()) continue;
49+
String query = line;
50+
List<String[]> results = new LinkedList<>();
51+
while ((line = br.readLine()) != null && !line.trim().isEmpty()) {
52+
results.add(line.split(","));
53+
}
54+
String[][] expectedResults = results.toArray(new String[0][]);
55+
addTest(query, expectedResults);
56+
}
57+
} catch (IOException e) {
58+
e.printStackTrace();
59+
System.exit(1);
60+
}
61+
}
62+
63+
public class Test {
64+
private String query;
65+
private String[][] expectedResults;
66+
67+
public Test(String query, String[][] expectedResults) {
68+
this.query = query;
69+
this.expectedResults = expectedResults;
70+
}
71+
72+
public boolean run() {
73+
try {
74+
System.out.println("Running test: " + query);
75+
if (!st.execute(query)) {
76+
System.out.println("Returns 0 rows");
77+
return expectedResults.length == 0;
78+
}
79+
ResultSet rs = st.getResultSet();
80+
if (rs.getMetaData().getColumnCount() != expectedResults[0].length) {
81+
System.err.println("Expected " + expectedResults[0].length + " columns, got " + rs.getMetaData().getColumnCount());
82+
return false;
83+
}
84+
int rows = 0;
85+
while (rs.next()) {
86+
int cols = 0;
87+
for (String expected : expectedResults[rows]) {
88+
String result = rs.getString(cols + 1);
89+
if (!expected.equals(result)) {
90+
System.err.println("Expected:\n'" + expected + "'");
91+
System.err.println("Result:\n'" + result + "'\nRest of the results:");
92+
while (rs.next()) {
93+
System.err.println(rs.getString(1));
94+
}
95+
return false;
96+
}
97+
cols++;
98+
}
99+
rows++;
100+
}
101+
102+
System.out.println("Returns " + rows + " rows");
103+
if (rows != expectedResults.length) {
104+
System.err.println("Expected " + expectedResults.length + " rows");
105+
return false;
106+
}
107+
return true;
108+
} catch (SQLException e) {
109+
System.err.println(e.getMessage());
110+
return false;
111+
}
112+
}
113+
}
114+
}
115+
116+
public static void main(String[] args) {
117+
if (args.length < 5) {
118+
System.err.println("Usage: java PGTest <ip> <port> <user> <password> <testFile>");
119+
System.exit(1);
120+
}
121+
122+
Tests tests = new Tests();
123+
tests.connect(args[0], Integer.parseInt(args[1]), args[2], args[3]);
124+
tests.readTestsFromFile(args[4]);
125+
126+
if (!tests.runTests()) {
127+
tests.disconnect();
128+
System.exit(1);
129+
}
130+
tests.disconnect();
131+
}
132+
}

0 commit comments

Comments
 (0)