Skip to content

Commit

Permalink
feat: add postgres client compatibility tests (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
NoyException authored Nov 22, 2024
1 parent 34d91fd commit c563289
Show file tree
Hide file tree
Showing 10 changed files with 1,038 additions and 0 deletions.
66 changes: 66 additions & 0 deletions .github/workflows/postgres-compatibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Compatibility Test for Postgres

on:
push:
branches:
- main
- compatibility
pull_request:
branches: [ "main" ]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '16'

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install dependencies
run: |
go get .
pip3 install "sqlglot[rs]"
pip3 install psycopg2
curl -LJO https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip
unzip duckdb_cli-linux-amd64.zip
chmod +x duckdb
sudo mv duckdb /usr/local/bin
duckdb -c 'INSTALL json from core'
duckdb -c 'SELECT extension_name, loaded, install_path FROM duckdb_extensions() where installed'
sudo apt-get update
sudo apt-get install --yes --no-install-recommends postgresql-client bats cpanminus
cd compatibility/pg
curl -L -o ./java/postgresql-42.7.4.jar https://jdbc.postgresql.org/download/postgresql-42.7.3.jar
npm install pg
sudo cpanm DBD::Pg
sudo gem install pg
- name: Build
run: go build -v

- name: Start MyDuck Server
run: |
./myduckserver &
sleep 5
- name: Run the Compatibility Test
run: |
bats ./compatibility/pg/test.bats
145 changes: 145 additions & 0 deletions compatibility/pg/c/pg_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
#include <string.h>

typedef struct {
char *query;
char **expectedResults;
int expectedRows;
int expectedCols;
} Test;

typedef struct {
PGconn *conn;
Test *tests;
int testCount;
} PGTest;

void connectDB(PGTest *pgTest, const char *ip, int port, const char *user, const char *password) {
char conninfo[256];
snprintf(conninfo, sizeof(conninfo), "host=%s port=%d dbname=main user=%s password=%s", ip, port, user, password);
pgTest->conn = PQconnectdb(conninfo);
if (PQstatus(pgTest->conn) != CONNECTION_OK) {
printf("Connection to database failed: %s", PQerrorMessage(pgTest->conn));
PQfinish(pgTest->conn);
exit(1);
}
}

void disconnectDB(PGTest *pgTest) {
PQfinish(pgTest->conn);
}

void addTest(PGTest *pgTest, const char *query, char **expectedResults, int expectedRows, int expectedCols) {
pgTest->tests = realloc(pgTest->tests, sizeof(Test) * (pgTest->testCount + 1));
pgTest->tests[pgTest->testCount].query = strdup(query);
pgTest->tests[pgTest->testCount].expectedResults = expectedResults;
pgTest->tests[pgTest->testCount].expectedRows = expectedRows;
pgTest->tests[pgTest->testCount].expectedCols = expectedCols;
pgTest->testCount++;
}

int runTests(PGTest *pgTest) {
for (int i = 0; i < pgTest->testCount; i++) {
Test *test = &pgTest->tests[i];
printf("Running test: %s\n", test->query);
PGresult *res = PQexec(pgTest->conn, test->query);
if (PQresultStatus(res) != PGRES_TUPLES_OK && PQresultStatus(res) != PGRES_COMMAND_OK) {
printf("Query failed: %s\n", PQerrorMessage(pgTest->conn));
PQclear(res);
return 0;
}
if (PQresultStatus(res) == PGRES_TUPLES_OK) {
int rows = PQntuples(res);
int cols = PQnfields(res);
if (cols != test->expectedCols) {
printf("Expected %d columns, got %d\n", test->expectedCols, cols);
PQclear(res);
return 0;
}
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
char *result = PQgetvalue(res, r, c);
if (strcmp(result, test->expectedResults[r * cols + c]) != 0) {
printf("Expected: '%s', got: '%s'\n", test->expectedResults[r * cols + c], result);
PQclear(res);
return 0;
}
}
}
if (rows != test->expectedRows) {
printf("Expected %d rows, got %d\n", test->expectedRows, rows);
PQclear(res);
return 0;
}
}
PQclear(res);
}
return 1;
}

size_t removeNewline(char *line) {
size_t len = strlen(line);
if (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
}
if (len > 0 && line[len - 1] == '\r') {
line[--len] = '\0';
}
return len;
}

void readTestsFromFile(PGTest *pgTest, const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
perror("Failed to open test data file");
exit(1);
}

char line[1024];
while (fgets(line, sizeof(line), file)) {
size_t len = removeNewline(line);
if (len == 0) continue;

char *query = strdup(line);
char **expectedResults = NULL;
int expectedRows = 0, expectedCols = 0;

while (fgets(line, sizeof(line), file)) {
len = removeNewline(line);
if (len == 0) break;

char *token = strtok(line, ",");
int col = 0;
while (token) {
expectedResults = realloc(expectedResults, sizeof(char*) * (expectedRows * expectedCols + col + 1));
expectedResults[expectedRows * expectedCols + col] = strdup(token);
token = strtok(NULL, ",");
col++;
}
expectedRows++;
if (expectedCols == 0) expectedCols = col;
}
addTest(pgTest, query, expectedResults, expectedRows, expectedCols);
}

fclose(file);
}

int main(int argc, char *argv[]) {
if (argc != 6) {
printf("Usage: %s <ip> <port> <user> <password> <testFile>\n", argv[0]);
return 1;
}

PGTest pgTest = {0};
connectDB(&pgTest, argv[1], atoi(argv[2]), argv[3], argv[4]);

readTestsFromFile(&pgTest, argv[5]);

int result = runTests(&pgTest);
disconnectDB(&pgTest);
free(pgTest.tests);
return result ? 0 : 1;
}
132 changes: 132 additions & 0 deletions compatibility/pg/java/PGTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import java.io.*;
import java.sql.*;
import java.util.List;
import java.util.LinkedList;

public class PGTest {
public static class Tests {
private Connection conn;
private Statement st;
private List<Test> tests = new LinkedList<>();

public void connect(String ip, int port, String user, String password) {
try {
String url = "jdbc:postgresql://" + ip + ":" + port + "/main";
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public void disconnect() {
try {
st.close();
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public void addTest(String query, String[][] expectedResults) {
tests.add(new Test(query, expectedResults));
}

public boolean runTests() {
for (Test test : tests) {
if (!test.run()) {
return false;
}
}
return true;
}

public void readTestsFromFile(String filename) {
try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) continue;
String query = line;
List<String[]> results = new LinkedList<>();
while ((line = br.readLine()) != null && !line.trim().isEmpty()) {
results.add(line.split(","));
}
String[][] expectedResults = results.toArray(new String[0][]);
addTest(query, expectedResults);
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}

public class Test {
private String query;
private String[][] expectedResults;

public Test(String query, String[][] expectedResults) {
this.query = query;
this.expectedResults = expectedResults;
}

public boolean run() {
try {
System.out.println("Running test: " + query);
if (!st.execute(query)) {
System.out.println("Returns 0 rows");
return expectedResults.length == 0;
}
ResultSet rs = st.getResultSet();
if (rs.getMetaData().getColumnCount() != expectedResults[0].length) {
System.err.println("Expected " + expectedResults[0].length + " columns, got " + rs.getMetaData().getColumnCount());
return false;
}
int rows = 0;
while (rs.next()) {
int cols = 0;
for (String expected : expectedResults[rows]) {
String result = rs.getString(cols + 1);
if (!expected.equals(result)) {
System.err.println("Expected:\n'" + expected + "'");
System.err.println("Result:\n'" + result + "'\nRest of the results:");
while (rs.next()) {
System.err.println(rs.getString(1));
}
return false;
}
cols++;
}
rows++;
}

System.out.println("Returns " + rows + " rows");
if (rows != expectedResults.length) {
System.err.println("Expected " + expectedResults.length + " rows");
return false;
}
return true;
} catch (SQLException e) {
System.err.println(e.getMessage());
return false;
}
}
}
}

public static void main(String[] args) {
if (args.length < 5) {
System.err.println("Usage: java PGTest <ip> <port> <user> <password> <testFile>");
System.exit(1);
}

Tests tests = new Tests();
tests.connect(args[0], Integer.parseInt(args[1]), args[2], args[3]);
tests.readTestsFromFile(args[4]);

if (!tests.runTests()) {
tests.disconnect();
System.exit(1);
}
tests.disconnect();
}
}
Loading

0 comments on commit c563289

Please sign in to comment.