From 4d98aa50a0624c0ca6e334daa91cf4d2e1bcf410 Mon Sep 17 00:00:00 2001 From: Noy Date: Fri, 22 Nov 2024 16:30:40 +0800 Subject: [PATCH] feat: add postgres client compatibility test (#111) --- .github/workflows/postgres-compatibility.yml | 66 +++++++++ compatibility/pg/c/pg_test.c | 145 +++++++++++++++++++ compatibility/pg/java/PGTest.java | 132 +++++++++++++++++ compatibility/pg/node/pg_test.js | 120 +++++++++++++++ compatibility/pg/perl/pg_test.pl | 128 ++++++++++++++++ compatibility/pg/php/pg_test.php | 119 +++++++++++++++ compatibility/pg/python/pg_test.py | 107 ++++++++++++++ compatibility/pg/ruby/pg_test.rb | 125 ++++++++++++++++ compatibility/pg/test.bats | 69 +++++++++ compatibility/pg/test.data | 27 ++++ 10 files changed, 1038 insertions(+) create mode 100644 .github/workflows/postgres-compatibility.yml create mode 100644 compatibility/pg/c/pg_test.c create mode 100644 compatibility/pg/java/PGTest.java create mode 100644 compatibility/pg/node/pg_test.js create mode 100644 compatibility/pg/perl/pg_test.pl create mode 100644 compatibility/pg/php/pg_test.php create mode 100644 compatibility/pg/python/pg_test.py create mode 100644 compatibility/pg/ruby/pg_test.rb create mode 100644 compatibility/pg/test.bats create mode 100644 compatibility/pg/test.data diff --git a/.github/workflows/postgres-compatibility.yml b/.github/workflows/postgres-compatibility.yml new file mode 100644 index 00000000..2d604c11 --- /dev/null +++ b/.github/workflows/postgres-compatibility.yml @@ -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 diff --git a/compatibility/pg/c/pg_test.c b/compatibility/pg/c/pg_test.c new file mode 100644 index 00000000..ec2e8b6b --- /dev/null +++ b/compatibility/pg/c/pg_test.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include + +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 \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; +} \ No newline at end of file diff --git a/compatibility/pg/java/PGTest.java b/compatibility/pg/java/PGTest.java new file mode 100644 index 00000000..9297dab7 --- /dev/null +++ b/compatibility/pg/java/PGTest.java @@ -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 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 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 "); + 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(); + } +} \ No newline at end of file diff --git a/compatibility/pg/node/pg_test.js b/compatibility/pg/node/pg_test.js new file mode 100644 index 00000000..87df7270 --- /dev/null +++ b/compatibility/pg/node/pg_test.js @@ -0,0 +1,120 @@ +const { Client } = require('pg'); +const fs = require('fs'); + +class Test { + constructor(query, expectedResults) { + this.query = query; + this.expectedResults = expectedResults; + } + + async run(client) { + try { + console.log(`Running test: ${this.query}`); + const res = await client.query(this.query); + if (res.rows.length === 0) { + console.log("Returns 0 rows"); + return this.expectedResults.length === 0; + } + if (res.fields.length !== this.expectedResults[0].length) { + console.error(`Expected ${this.expectedResults[0].length} columns, got ${res.fields.length}`); + return false; + } + for (let i = 0; i < res.rows.length; i++) { + const row = res.rows[i]; + const expectedRow = this.expectedResults[i]; + for (let j = 0; j < expectedRow.length; j++) { + const columnName = res.fields[j].name; + if (String(row[columnName]) !== String(expectedRow[j])) { + console.error(`Expected: ${String(expectedRow[j])}, got: ${String(row[columnName])}`); + return false; + } + } + } + console.log(`Returns ${res.rows.length} rows`); + if (res.rows.length !== this.expectedResults.length) { + console.error(`Expected ${this.expectedResults.length} rows`); + return false; + } + return true; + } catch (err) { + console.error(err.message); + return false; + } + } +} + +class Tests { + constructor() { + this.client = null; + this.tests = []; + } + + async connect(ip, port, user, password) { + this.client = new Client({ + host: ip, + port: port, + user: user, + password: password, + database: 'main' + }); + await this.client.connect(); + } + + async disconnect() { + await this.client.end(); + } + + addTest(query, expectedResults) { + this.tests.push(new Test(query, expectedResults)); + } + + async runTests() { + for (const test of this.tests) { + if (!(await test.run(this.client))) { + return false; + } + } + return true; + } + + readTestsFromFile(filename) { + const data = fs.readFileSync(filename, 'utf8'); + const lines = data.split('\n'); + let query = null; + let expectedResults = []; + for (const line of lines) { + const trimmedLine = line.trim(); + if (!trimmedLine) { + if (query) { + this.addTest(query, expectedResults); + query = null; + expectedResults = []; + } + } else if (query === null) { + query = trimmedLine; + } else { + expectedResults.push(trimmedLine.split(',')); + } + } + if (query) { + this.addTest(query, expectedResults); + } + } +} + +(async () => { + if (process.argv.length < 6) { + console.error("Usage: node pg_test.js "); + process.exit(1); + } + + const tests = new Tests(); + await tests.connect(process.argv[2], parseInt(process.argv[3]), process.argv[4], process.argv[5]); + tests.readTestsFromFile(process.argv[6]); + + if (!(await tests.runTests())) { + await tests.disconnect(); + process.exit(1); + } + await tests.disconnect(); +})(); \ No newline at end of file diff --git a/compatibility/pg/perl/pg_test.pl b/compatibility/pg/perl/pg_test.pl new file mode 100644 index 00000000..7d22e96c --- /dev/null +++ b/compatibility/pg/perl/pg_test.pl @@ -0,0 +1,128 @@ +use strict; +use warnings; +use DBI; + +package Tests; + +sub new { + my $class = shift; + my $self = { + conn => undef, + tests => [], + }; + bless $self, $class; + return $self; +} + +sub connect { + my ($self, $ip, $port, $user, $password) = @_; + my $dsn = "dbi:Pg:dbname=main;host=$ip;port=$port"; + $self->{conn} = DBI->connect($dsn, $user, $password, { RaiseError => 1, AutoCommit => 1 }); +} + +sub disconnect { + my $self = shift; + $self->{conn}->disconnect(); +} + +sub add_test { + my ($self, $query, $expected_results) = @_; + push @{$self->{tests}}, { query => $query, expected_results => $expected_results }; +} + +sub run_tests { + my $self = shift; + foreach my $test (@{$self->{tests}}) { + return 0 unless $self->run_test($test); + } + return 1; +} + +sub trim { + my $s = shift; + $s =~ s/^\s+|\s+$//g; + return $s; +} + +sub read_tests_from_file { + my ($self, $filename) = @_; + open my $fh, '<', $filename or die "Could not open file '$filename': $!"; + my $query; + my @results; + while (my $line = <$fh>) { + $line = trim($line); + if ($line eq '') { + if ($query) { + $self->add_test($query, [@results]); + $query = undef; + @results = (); + } + } elsif (!$query) { + $query = $line; + } else { + push @results, [split /,/, $line]; + } + } + $self->add_test($query, [@results]) if $query; + close $fh; +} + +sub run_test { + my ($self, $test) = @_; + my $query = $test->{query}; + my $expected_results = $test->{expected_results}; + print "Running test: $query\n"; + + my $st = $self->{conn}->prepare($query); + $st->execute(); + my $rows = $st->rows; + if ($rows == 0 || $rows == -1) { + print "No rows returned\n"; + return defined $expected_results && @$expected_results == 0; + } + my $cols = $st->{NUM_OF_FIELDS}; + if (!defined $cols) { + print "No columns returned\n"; + return defined $expected_results && @$expected_results == 0; + } + if ($cols != @{$expected_results->[0]}) { + print "Expected " . @{$expected_results->[0]} . " columns\n"; + return 0; + } + my $row_num = 0; + while (my @row = $st->fetchrow_array) { + for my $col_num (0..$#row) { + if ($row[$col_num] ne $expected_results->[$row_num][$col_num]) { + print "Expected:\n'$expected_results->[$row_num][$col_num]'\n"; + print "Result:\n'$row[$col_num]'\nRest of the results:\n"; + while (my @rest = $st->fetchrow_array) { + print join(',', @rest) . "\n"; + } + return 0; + } + } + $row_num++; + } + print "Returns $row_num rows\n"; + if ($row_num != @$expected_results) { + print "Expected " . @$expected_results . " rows\n"; + return 0; + } + return 1; +} + +package main; + +if (@ARGV < 5) { + die "Usage: perl PGTest.pl \n"; +} + +my $tests = Tests->new(); +$tests->connect($ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3]); +$tests->read_tests_from_file($ARGV[4]); + +if (!$tests->run_tests()) { + $tests->disconnect(); + exit 1; +} +$tests->disconnect(); \ No newline at end of file diff --git a/compatibility/pg/php/pg_test.php b/compatibility/pg/php/pg_test.php new file mode 100644 index 00000000..e103c09c --- /dev/null +++ b/compatibility/pg/php/pg_test.php @@ -0,0 +1,119 @@ + \n"; + exit(1); + } + + $tests = new Tests(); + $tests->connect($args[0], intval($args[1]), $args[2], $args[3]); + $tests->readTestsFromFile($args[4]); + + if (!$tests->runTests()) { + $tests->disconnect(); + exit(1); + } + $tests->disconnect(); + } +} + +class Tests { + private $conn; + private $tests = []; + + public function connect($ip, $port, $user, $password) { + try { + $url = "pgsql:host=$ip;port=$port;dbname=main"; + $this->conn = new PDO($url, $user, $password); + } catch (PDOException $e) { + throw new RuntimeException($e->getMessage()); + } + } + + public function disconnect() { + $this->conn = null; + } + + public function addTest($query, $expectedResults) { + $this->tests[] = new Test($query, $expectedResults); + } + + public function runTests() { + foreach ($this->tests as $test) { + if (!$test->run($this->conn)) { + return false; + } + } + return true; + } + + public function readTestsFromFile($filename) { + $file = fopen($filename, "r"); + if (!$file) { + throw new RuntimeException("Failed to open test data file"); + } + + while (($line = fgets($file)) !== false) { + $line = trim($line); + if (empty($line)) continue; // Skip empty lines + $query = $line; + $expectedResults = []; + while (($line = fgets($file)) !== false) { + $line = trim($line); + if (empty($line)) break; // End of expected results for this query + $expectedResults[] = explode(",", $line); + } + $this->addTest($query, $expectedResults); + } + + fclose($file); + } +} + +class Test { + private $query; + private $expectedResults; + + public function __construct($query, $expectedResults) { + $this->query = $query; + $this->expectedResults = $expectedResults; + } + + public function run($conn) { + try { + echo "Running test: " . $this->query . "\n"; + $st = $conn->prepare($this->query); + $st->execute(); + if ($st->columnCount() == 0) { + echo "Returns 0 rows\n"; + return count($this->expectedResults) == 0; + } + $rows = $st->fetchAll(PDO::FETCH_NUM); + if (count($rows[0]) != count($this->expectedResults[0])) { + echo "Expected " . count($this->expectedResults[0]) . " columns, got " . count($rows[0]) . "\n"; + return false; + } + foreach ($rows as $i => $row) { + if ($row != $this->expectedResults[$i]) { + echo "Expected: " . implode(", ", $this->expectedResults[$i]) . ", got: " . implode(", ", $row) . "\n"; + return false; + } + } + echo "Returns " . count($rows) . " rows\n"; + if (count($rows) != count($this->expectedResults)) { + echo "Expected " . count($this->expectedResults) . " rows\n"; + return false; + } + return true; + } catch (PDOException $e) { + echo $e->getMessage() . "\n"; + return false; + } + } +} + +PGTest::main(array_slice($argv, 1)); + +?> \ No newline at end of file diff --git a/compatibility/pg/python/pg_test.py b/compatibility/pg/python/pg_test.py new file mode 100644 index 00000000..0f47e2ce --- /dev/null +++ b/compatibility/pg/python/pg_test.py @@ -0,0 +1,107 @@ +import psycopg2 + +class PGTest: + class Test: + def __init__(self, query, expected_results): + self.query = query + self.expected_results = expected_results + + def run(self, cursor): + print(f"Running test: {self.query}") + cursor.execute(self.query) + if cursor.description is None: + print("Returns 0 rows") + return len(self.expected_results) == 0 + + rows = cursor.fetchall() + if len(rows[0]) != len(self.expected_results[0]): + print(f"Expected {len(self.expected_results[0])} columns, got {len(rows[0])}") + return False + + for row, expected_row in zip(rows, self.expected_results): + if list(map(str, row)) != list(map(str, expected_row)): + print(f"Expected: {list(map(str, expected_row))}, got: {list(map(str, row))}") + return False + + print(f"Returns {len(rows)} rows") + if len(rows) != len(self.expected_results): + print(f"Expected {len(self.expected_results)} rows") + return False + + return True + + def __init__(self): + self.conn = None + self.cursor = None + self.tests = [] + + def connect(self, ip, port, user, password): + try: + self.conn = psycopg2.connect( + host=ip, + port=port, + dbname="main", + user=user, + password=password + ) + self.cursor = self.conn.cursor() + except Exception as e: + raise RuntimeError(e) + + def disconnect(self): + self.cursor.close() + self.conn.close() + + def add_test(self, query, expected_results): + self.tests.append(self.Test(query, expected_results)) + + def run_tests(self): + for test in self.tests: + try: + self.conn.autocommit = False + if not test.run(self.cursor): + self.conn.rollback() + return False + self.conn.commit() + except Exception as e: + print(f"Error running test: {e}") + self.conn.rollback() + return False + return True + + def read_tests_from_file(self, filename): + with open(filename, "r") as file: + lines = file.readlines() + + query = None + expected_results = [] + for line in lines: + line = line.strip() + if not line: + if query: + self.add_test(query, expected_results) + query = None + expected_results = [] + elif query is None: + query = line + else: + expected_results.append(line.split(",")) + + if query: + self.add_test(query, expected_results) + +if __name__ == "__main__": + import sys + if len(sys.argv) != 6: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + pg_test = PGTest() + pg_test.connect(sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4]) + pg_test.read_tests_from_file(sys.argv[5]) + + if not pg_test.run_tests(): + pg_test.disconnect() + sys.exit(1) + + pg_test.disconnect() \ No newline at end of file diff --git a/compatibility/pg/ruby/pg_test.rb b/compatibility/pg/ruby/pg_test.rb new file mode 100644 index 00000000..5b58c8a8 --- /dev/null +++ b/compatibility/pg/ruby/pg_test.rb @@ -0,0 +1,125 @@ +require 'pg' + +class PGTest + class Tests + def initialize + @conn = nil + @tests = [] + end + + def connect(ip, port, user, password) + begin + @conn = PG.connect(host: ip, port: port, user: user, password: password, dbname: 'main') + rescue PG::Error => e + raise "Connection failed: #{e.message}" + end + end + + def disconnect + @conn.close if @conn + end + + def add_test(query, expected_results) + @tests << Test.new(query, expected_results) + end + + def run_tests + @tests.each do |test| + return false unless test.run(@conn) + end + true + end + + def read_tests_from_file(filename) + begin + File.open(filename, 'r') do |file| + query = nil + results = [] + file.each_line do |line| + line = line.strip + if line.empty? + if query + add_test(query, results) + query = nil + results = [] + end + else + if query.nil? + query = line + else + results << line.split(',') + end + end + end + add_test(query, results) if query + end + rescue IOError => e + puts "File read error: #{e.message}" + exit 1 + end + end + + class Test + def initialize(query, expected_results) + @query = query + @expected_results = expected_results + end + + def run(conn) + begin + puts "Running test: #{@query}" + result = conn.exec(@query) + if result.ntuples == 0 + puts "Returns 0 rows" + return @expected_results.empty? + end + if result.nfields != @expected_results[0].length + puts "Expected #{@expected_results[0].length} columns, got #{result.nfields}" + return false + end + result.each_with_index do |row, row_num| + row.each_with_index do |(col_name, value), col_num| + expected = @expected_results[row_num][col_num] + if value != expected + puts "Expected:\n'#{expected}'" + puts "Result:\n'#{value}'\nRest of the results:" + result.each_row { |r| puts r.join(',') } + return false + end + end + end + puts "Returns #{result.ntuples} rows" + if result.ntuples != @expected_results.length + puts "Expected #{@expected_results.length} rows" + return false + end + true + rescue PG::Error => e + puts e.message + false + end + end + end + end + + def self.main(args) + if args.length < 5 + puts "Usage: ruby PGTest.rb " + exit 1 + end + + tests = Tests.new + tests.connect(args[0], args[1].to_i, args[2], args[3]) + tests.read_tests_from_file(args[4]) + + unless tests.run_tests + tests.disconnect + exit 1 + end + tests.disconnect + end +end + +if __FILE__ == $0 + PGTest.main(ARGV) +end \ No newline at end of file diff --git a/compatibility/pg/test.bats b/compatibility/pg/test.bats new file mode 100644 index 00000000..1b4c201d --- /dev/null +++ b/compatibility/pg/test.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats + +setup() { + psql -h 127.0.0.1 -p 5432 -U main -c "DROP SCHEMA IF EXISTS test CASCADE;" +} + +@test "pg-c" { + gcc -o $BATS_TEST_DIRNAME/c/pg_test $BATS_TEST_DIRNAME/c/pg_test.c -I/usr/include/postgresql -lpq + run $BATS_TEST_DIRNAME/c/pg_test 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + fi + [ "$status" -eq 0 ] +} + +@test "pg-java" { + javac $BATS_TEST_DIRNAME/java/PGTest.java + run java -cp $BATS_TEST_DIRNAME/java:$BATS_TEST_DIRNAME/java/postgresql-42.7.4.jar PGTest 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + echo "$stderr" + fi + [ "$status" -eq 0 ] +} + +@test "pg-node" { + run node $BATS_TEST_DIRNAME/node/pg_test.js 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + echo "$stderr" + fi + [ "$status" -eq 0 ] +} + +@test "pg-perl" { + run perl $BATS_TEST_DIRNAME/perl/pg_test.pl 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + echo "$stderr" + fi + [ "$status" -eq 0 ] +} + +@test "pg-php" { + run php $BATS_TEST_DIRNAME/php/pg_test.php 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + echo "$stderr" + fi + [ "$status" -eq 0 ] +} + +@test "pg-python" { + run python3 $BATS_TEST_DIRNAME/python/pg_test.py 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + echo "$stderr" + fi + [ "$status" -eq 0 ] +} + +@test "pg-ruby" { + run ruby $BATS_TEST_DIRNAME/ruby/pg_test.rb 127.0.0.1 5432 root "" $BATS_TEST_DIRNAME/test.data + if [ "$status" -ne 0 ]; then + echo "$output" + echo "$stderr" + fi + [ "$status" -eq 0 ] +} \ No newline at end of file diff --git a/compatibility/pg/test.data b/compatibility/pg/test.data new file mode 100644 index 00000000..122b2010 --- /dev/null +++ b/compatibility/pg/test.data @@ -0,0 +1,27 @@ +CREATE SCHEMA test + +CREATE TABLE test.tb1 (id int, value float, c1 char(10), primary key(id)) + +INSERT INTO test.tb1 VALUES (1, 1.1, 'a'), (2, 2.2, 'b') + +SELECT * FROM test.tb1 +1,1.1,a +2,2.2,b + +SELECT * FROM test.tb1 WHERE id=1 +1,1.1,a + +UPDATE test.tb1 SET value=3.3 WHERE id=1 + +SELECT * FROM test.tb1 WHERE id=1 +1,3.3,a + +SELECT COUNT(*) FROM test.tb1 +2 + +DELETE FROM test.tb1 WHERE id=1 + +SELECT * FROM test.tb1 +2,2.2,b + +DROP SCHEMA test CASCADE \ No newline at end of file