chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
java/PostgresTest.class
java/postgresql-42.7.3.jar
node/node_modules/
elixir/_build
elixir/mix.lock
elixir/deps
elixir/postgrex-test
dotnet/bin
dotnet/obj
dotnet/out
c/libaprutil-test
c/libdbi-test
c/postgres-c-connector-test
ruby/Gemfile.lock
+214
View File
@@ -0,0 +1,214 @@
# syntax=docker/dockerfile:1
FROM golang:1.26.2-alpine AS golang_cgo
ENV CGO_ENABLED=1
ENV GO_LDFLAGS="-linkmode external -extldflags '-static'"
RUN apk add --no-cache build-base
# --- Build doltgres binary ---
FROM golang_cgo AS doltgres_build
RUN apk add --no-cache icu-dev icu-static
RUN apk update && apk add --no-cache bash
WORKDIR /root/building
COPY go.mod doltgresql/
WORKDIR doltgresql
RUN go mod download
COPY --exclude=testing/postgres-client-tests/ . .
WORKDIR /root/building/doltgresql/postgres/parser
RUN sh ./build.sh
WORKDIR /root/building/doltgresql/cmd/doltgres
RUN go build -ldflags "-linkmode external -extldflags '-static'" -o /build/bin/doltgres .
# --- Build Go postgres clients (pgx, lib/pq) ---
FROM golang:1.26.4 AS go_clients_build
COPY testing/postgres-client-tests/go/pgx/ /build/go/pgx/
WORKDIR /build/go/pgx
RUN go build -o /build/bin/pgx-test .
COPY testing/postgres-client-tests/go/libpq/ /build/go/libpq/
WORKDIR /build/go/libpq
RUN go build -o /build/bin/libpq-test .
# --- Build Rust sqlx client ---
FROM rust:1.96-slim-bookworm AS rust_clients_build
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
COPY testing/postgres-client-tests/rust/ /build/rust/
WORKDIR /build/rust
RUN cargo build --release
# --- Build C libpq client ---
FROM debian:bookworm-slim AS c_clients_build
RUN apt-get update && apt-get install -y \
gcc make pkg-config \
libpq-dev \
libapr1-dev libaprutil1-dev libaprutil1-dbd-pgsql \
libdbi-dev libdbd-pgsql \
&& rm -rf /var/lib/apt/lists/*
COPY testing/postgres-client-tests/c/ /build/c/
WORKDIR /build/c
RUN make
# --- Build C++ libpqxx client ---
FROM debian:bookworm-slim AS cpp_clients_build
RUN apt-get update && apt-get install -y g++ make libpqxx-dev pkg-config && rm -rf /var/lib/apt/lists/*
COPY testing/postgres-client-tests/cpp/ /build/cpp/
WORKDIR /build/cpp
RUN make
# --- Build ODBC psqlODBC client ---
FROM debian:bookworm-slim AS odbc_clients_build
RUN apt-get update && apt-get install -y gcc make unixodbc-dev && rm -rf /var/lib/apt/lists/*
COPY testing/postgres-client-tests/odbc/ /build/odbc/
WORKDIR /build/odbc
RUN make
# --- Install Node postgres client deps ---
FROM node:22-bookworm-slim AS node_clients_build
COPY testing/postgres-client-tests/node/package.json /build/node/
COPY testing/postgres-client-tests/node/package-lock.json /build/node/
WORKDIR /build/node
RUN npm install
COPY testing/postgres-client-tests/node/ /build/node/
# --- Install Ruby pg gem ---
FROM ruby:4.0.5-bookworm AS ruby_clients_build
RUN apt-get update && apt-get install -y libyaml-dev libpq-dev && rm -rf /var/lib/apt/lists/*
COPY testing/postgres-client-tests/ruby/Gemfile /build/ruby/
WORKDIR /build/ruby
RUN bundle install
COPY testing/postgres-client-tests/ruby/ /build/ruby/
# --- Install Python deps ---
FROM python:3.14-slim-bookworm AS python_clients_build
RUN pip3 install --no-cache-dir --target /build/python-deps sqlalchemy==2.0.46
COPY testing/postgres-client-tests/python/ /build/python/
WORKDIR /build/python/
# --- Build .NET Npgsql client ---
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS dotnet_clients_build
COPY testing/postgres-client-tests/dotnet/ /build/dotnet/
WORKDIR /build/dotnet
RUN dotnet publish -c Release -o /build/output/
# --- Build Java R2DBC client ---
FROM maven:3.9-eclipse-temurin-17 AS r2dbc_clients_build
COPY testing/postgres-client-tests/r2dbc/ /build/r2dbc/
WORKDIR /build/r2dbc
RUN mvn package -q
# --- Build Elixir Postgrex client ---
FROM hexpm/elixir:1.16.3-erlang-24.2-debian-bookworm-20260610-slim AS elixir_clients_build
ENV ELIXIR_ERL_OPTIONS="+fnu"
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update && apt-get install -y elixir && rm -rf /var/lib/apt/lists/*
COPY testing/postgres-client-tests/elixir/ /build/elixir/
WORKDIR /build/elixir
RUN mix local.hex --force && \
mix local.rebar --force && \
mix deps.get && \
mix escript.build
# --- Build Swift PostgresNIO client ---
FROM swift:6.0-bookworm AS swift_clients_build
COPY testing/postgres-client-tests/swift/ /build/swift/
WORKDIR /build/swift
RUN swift build -c release --static-swift-stdlib
# --- Runtime ---
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
curl \
gnupg \
lsb-release && \
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg && \
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
# java JDBC dependencies
COPY testing/postgres-client-tests/java/ /postgres-client-tests/java/
RUN apt-get install -y \
openjdk-17-jdk && \
curl -L -o /postgres-client-tests/java/postgresql-42.7.3.jar https://jdbc.postgresql.org/download/postgresql-42.7.3.jar
# perl dependencies
RUN apt-get install -y \
python3 \
python3-pip \
python3-psycopg2 \
perl \
cpanminus \
php \
php-pgsql \
r-base \
libyaml-dev \
libpq-dev \
odbc-postgresql \
g++ make libpqxx-dev pkg-config \
gcc make unixodbc-dev \
libapr1-dev libaprutil1-dev libaprutil1-dbd-pgsql \
libdbi-dev libdbd-pgsql \
ca-certificates-java \
bats \
lsof \
postgresql-client-15 \
nodejs \
elixir \
libicu-dev \
git && \
update-ca-certificates -f && \
rm -rf /var/lib/apt/lists/*
# install .NET
RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh \
&& chmod +x dotnet-install.sh \
&& ./dotnet-install.sh --channel 9.0 --runtime aspnetcore --install-dir /usr/share/dotnet \
&& ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \
&& rm dotnet-install.sh
# cpan dependencies
RUN cpanm --force DBD::Pg
# r dependencies
COPY testing/postgres-client-tests/r/ /postgres-client-tests/r/
RUN Rscript -e 'install.packages(c("RPostgres", "RPostgreSQL"), repos="https://cloud.r-project.org")'
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/
ENV GEM_HOME="/usr/local/bundle"
ENV PYTHONPATH=/usr/local/lib/python-deps
ENV ELIXIR_ERL_OPTIONS="+fnu"
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
COPY --from=ruby_clients_build /usr/local/bin/ruby /usr/local/bin/
COPY --from=ruby_clients_build /usr/local/lib/ /usr/local/lib/
COPY --from=ruby_clients_build /usr/local/bundle/ /usr/local/bundle/
RUN ldconfig
COPY --from=doltgres_build /build/bin/doltgres /usr/local/bin/doltgres
COPY --from=go_clients_build /build/bin/pgx-test /build/bin/go/pgx-test
COPY --from=go_clients_build /build/bin/libpq-test /build/bin/go/libpq-test
COPY --from=rust_clients_build /build/rust/target/release/sqlx_exists_demo /build/bin/rust/sqlx_exists_demo
COPY --from=c_clients_build /build/c/postgres-c-connector-test /build/bin/c/postgres-c-connector-test
COPY --from=dotnet_clients_build /build/output /build/bin/dotnet
COPY --from=r2dbc_clients_build /build/r2dbc/target/r2dbc-test-1.0.jar /build/bin/r2dbc/r2dbc-test.jar
COPY --from=elixir_clients_build /build/elixir/postgrex-test /build/bin/elixir/postgrex-test
COPY --from=swift_clients_build /build/swift/.build/release/postgresnio-test /build/bin/swift/postgresnio-test
COPY --from=node_clients_build /build/node/ /postgres-client-tests/node/
COPY --from=python_clients_build /build/python/ /postgres-client-tests/python/
COPY --from=python_clients_build /build/python-deps/ /usr/local/lib/python-deps/
COPY --from=ruby_clients_build /build/ruby/ /postgres-client-tests/ruby/
COPY testing/postgres-client-tests/c/ /postgres-client-tests/c/
COPY testing/postgres-client-tests/cpp/ /postgres-client-tests/cpp/
COPY testing/postgres-client-tests/odbc/ /postgres-client-tests/odbc/
COPY testing/postgres-client-tests/drizzle/ /postgres-client-tests/drizzle/
COPY testing/postgres-client-tests/helpers.bash /postgres-client-tests/
COPY testing/postgres-client-tests/php/ /postgres-client-tests/php/
COPY testing/postgres-client-tests/perl/ /postgres-client-tests/perl/
COPY testing/postgres-client-tests/postgres-client-tests.bats /postgres-client-tests/
COPY testing/postgres-client-tests/postgres-client-tests-entrypoint.sh /postgres-client-tests/entrypoint.sh
WORKDIR /postgres-client-tests
ENTRYPOINT ["/postgres-client-tests/entrypoint.sh"]
+21
View File
@@ -0,0 +1,21 @@
## PostgreSQL Client Tests
We created smoke tests for Doltgres's PostgreSQL client integrations, and we run these tests through GitHub Actions
on pull requests.
These tests can be run locally using Docker. From the doltgresql directory of the repo, run:
```bash
$ docker build -t postgres-client-tests -f testing/postgres-client-tests/Dockerfile .
$ docker run postgres-client-tests:latest
```
The `docker build` step will take a few minutes to complete as it needs to install all the dependencies in the image.
Running the built container will produce output like:
```bash
$ docker run postgres-client-tests:latest
Running postgres-client-tests:
1..2
ok 1 postgres-connector-java client
ok 2 node postgres client
```
+20
View File
@@ -0,0 +1,20 @@
LIBPQ_CFLAGS := $(shell pkg-config --cflags libpq)
LIBPQ_LDFLAGS := $(shell pkg-config --libs libpq)
APR_CFLAGS := $(shell pkg-config --cflags apr-1 apr-util-1)
APR_LDFLAGS := $(shell pkg-config --libs apr-1 apr-util-1)
CFLAGS="$(apr-1-config --includes) $(apu-1-config --includes)"
all: postgres-c-connector-test libaprutil-test libdbi-test
postgres-c-connector-test: postgres-c-connector-test.c
$(CC) $(LIBPQ_CFLAGS) -o $@ $^ $(LIBPQ_LDFLAGS)
libaprutil-test: libaprutil-test.c
$(CC) $(APR_CFLAGS) -o $@ $^ $(APR_LDFLAGS)
libdbi-test: libdbi-test.c
$(CC) -o $@ $^ -ldbi
.PHONY: clean
clean:
rm -f postgres-c-connector-test libaprutil-test libdbi-test
@@ -0,0 +1,117 @@
#include <apr.h>
#include <apr_pools.h>
#include <apr_dbd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void fail(const char *op, const char *msg) {
fprintf(stderr, "%s: %s\n", op, msg ? msg : "(no message)");
exit(1);
}
static void exec_query(const apr_dbd_driver_t *drv, apr_dbd_t *handle, const char *sql) {
int nrows = 0;
int rv = apr_dbd_query(drv, handle, &nrows, sql);
if (rv != 0)
fail(sql, apr_dbd_error(drv, handle, rv));
}
static const char *exec_scalar(const apr_dbd_driver_t *drv, apr_pool_t *pool,
apr_dbd_t *handle, const char *sql) {
apr_dbd_results_t *res = NULL;
int rv = apr_dbd_select(drv, pool, handle, &res, sql, 1);
if (rv != 0)
fail(sql, apr_dbd_error(drv, handle, rv));
apr_dbd_row_t *row = NULL;
if (apr_dbd_get_row(drv, pool, res, &row, -1) != 0)
fail(sql, "no rows returned");
return apr_dbd_get_entry(drv, row, 0);
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <user> <port>\n", argv[0]);
return 1;
}
const char *user = argv[1];
const char *port = argv[2];
apr_initialize();
apr_pool_t *pool;
apr_pool_create(&pool, NULL);
apr_status_t rv = apr_dbd_init(pool);
if (rv != APR_SUCCESS) {
char buf[256];
apr_strerror(rv, buf, sizeof(buf));
fail("apr_dbd_init", buf);
}
const apr_dbd_driver_t *driver;
rv = apr_dbd_get_driver(pool, "pgsql", &driver);
if (rv != APR_SUCCESS) {
char buf[256];
apr_strerror(rv, buf, sizeof(buf));
fail("apr_dbd_get_driver(pgsql)", buf);
}
char params[256];
snprintf(params, sizeof(params),
"host=localhost port=%s dbname=postgres user=%s password=password",
port, user);
apr_dbd_t *handle = NULL;
const char *open_err = NULL;
rv = apr_dbd_open_ex(driver, pool, params, &handle, &open_err);
if (rv != APR_SUCCESS)
fail("apr_dbd_open_ex", open_err);
const char *pk_str = exec_scalar(driver, pool, handle, "SELECT pk FROM test_table LIMIT 1");
if (!pk_str || atoi(pk_str) != 1) {
fprintf(stderr, "expected pk=1, got %s\n", pk_str ? pk_str : "NULL");
exit(1);
}
exec_query(driver, handle, "INSERT INTO test_table VALUES (2)");
const char *count_str = exec_scalar(driver, pool, handle, "SELECT COUNT(*) FROM test_table");
if (!count_str || atoi(count_str) != 2) {
fprintf(stderr, "expected count=2, got %s\n", count_str ? count_str : "NULL");
exit(1);
}
const char *dolt_queries[] = {
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
NULL
};
for (int i = 0; dolt_queries[i]; i++)
exec_query(driver, handle, dolt_queries[i]);
const char *log_count = exec_scalar(driver, pool, handle, "SELECT COUNT(*) FROM dolt_log");
if (!log_count || atoi(log_count) != 4) {
fprintf(stderr, "expected 4 dolt_log entries, got %s\n", log_count ? log_count : "NULL");
exit(1);
}
const char *test_count = exec_scalar(driver, pool, handle, "SELECT COUNT(*) FROM test");
if (!test_count || atoi(test_count) != 2) {
fprintf(stderr, "expected 2 rows in test, got %s\n", test_count ? test_count : "NULL");
exit(1);
}
apr_dbd_close(driver, handle);
apr_pool_destroy(pool);
apr_terminate();
printf("libaprutil apr_dbd test passed\n");
return 0;
}
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleIdentifier</key>
<string>com.apple.xcode.dsym.libaprutil-test</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>dSYM</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
@@ -0,0 +1,5 @@
---
triple: 'arm64-apple-darwin'
binary-path: libaprutil-test
relocations: []
...
@@ -0,0 +1,122 @@
#include <dbi/dbi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void conn_fail(dbi_conn conn, const char *op) {
const char *errstr = NULL;
dbi_conn_error(conn, &errstr);
fprintf(stderr, "%s: %s\n", op, errstr ? errstr : "(no message)");
exit(1);
}
static dbi_result exec_query(dbi_conn conn, const char *sql) {
dbi_result res = dbi_conn_query(conn, sql);
if (!res)
conn_fail(conn, sql);
return res;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <user> <port>\n", argv[0]);
return 1;
}
const char *user = argv[1];
int port = atoi(argv[2]);
dbi_inst dbi;
if (dbi_initialize_r(NULL, &dbi) < 0) {
fprintf(stderr, "dbi_initialize_r failed: no drivers found\n");
return 1;
}
dbi_conn conn = dbi_conn_new_r("pgsql", dbi);
if (!conn) {
fprintf(stderr, "dbi_conn_new_r(pgsql) failed: driver not installed?\n");
dbi_shutdown_r(dbi);
return 1;
}
dbi_conn_set_option(conn, "host", "localhost");
dbi_conn_set_option_numeric(conn, "port", (long)port);
dbi_conn_set_option(conn, "dbname", "postgres");
dbi_conn_set_option(conn, "username", user);
dbi_conn_set_option(conn, "password", "password");
if (dbi_conn_connect(conn) < 0)
conn_fail(conn, "dbi_conn_connect");
// SELECT pk from test_table (set up by bats setup())
dbi_result res = exec_query(conn, "SELECT pk FROM test_table LIMIT 1");
if (!dbi_result_next_row(res)) {
fprintf(stderr, "expected at least one row in test_table\n");
exit(1);
}
int pk = dbi_result_get_int(res, "pk");
dbi_result_free(res);
if (pk != 1) {
fprintf(stderr, "expected pk=1, got %d\n", pk);
exit(1);
}
// INSERT
res = exec_query(conn, "INSERT INTO test_table VALUES (2)");
dbi_result_free(res);
// COUNT
res = exec_query(conn, "SELECT COUNT(*) FROM test_table");
if (!dbi_result_next_row(res)) {
fprintf(stderr, "expected count row\n");
exit(1);
}
long long count = dbi_result_get_longlong(res, "count");
dbi_result_free(res);
if (count != 2) {
fprintf(stderr, "expected count=2, got %lld\n", count);
exit(1);
}
// Dolt workflow
const char *dolt_queries[] = {
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
NULL
};
for (int i = 0; dolt_queries[i]; i++) {
res = exec_query(conn, dolt_queries[i]);
dbi_result_free(res);
}
res = exec_query(conn, "SELECT COUNT(*) FROM dolt_log");
dbi_result_next_row(res);
long long log_count = dbi_result_get_longlong(res, "count");
dbi_result_free(res);
if (log_count != 4) {
fprintf(stderr, "expected 4 dolt_log entries, got %lld\n", log_count);
exit(1);
}
res = exec_query(conn, "SELECT COUNT(*) FROM test");
dbi_result_next_row(res);
long long test_count = dbi_result_get_longlong(res, "count");
dbi_result_free(res);
if (test_count != 2) {
fprintf(stderr, "expected 2 rows in test, got %lld\n", test_count);
exit(1);
}
dbi_conn_close(conn);
dbi_shutdown_r(dbi);
printf("libdbi pgsql test passed\n");
return 0;
}
@@ -0,0 +1,140 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libpq-fe.h>
#define QUERIES_SIZE 13
char *queries[QUERIES_SIZE] = {
"create table test (pk int, value int, d1 decimal(9, 3), f1 float, c1 char(10), t1 text, primary key(pk))",
"select * from test",
"insert into test (pk, value, d1, f1, c1, t1) values (0,0,0.0,0.0,'abc','a1')",
"select * from test",
"select dolt_add('-A');",
"select dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt.log",
"select dolt_checkout('-b', 'mybranch')",
"insert into test (pk, value, d1, f1, c1, t1) values (10,10, 123456.789, 420.42,'example','some text')",
"select dolt_commit('-a', '-m', 'my commit2')",
"select dolt_checkout('main')",
"select dolt_merge('mybranch')",
"select COUNT(*) FROM dolt.log",
};
int main(int argc, char *argv[]) {
char* user = argv[1];
int port = atoi(argv[2]);
// Connect to the database
// conninfo is a string of keywords and values separated by spaces.
char conninfo[100];
sprintf(conninfo, "dbname=postgres user=%s password=password host=localhost port=%d", user, port);
// Create a connection
PGconn *conn = PQconnectdb(conninfo);
// Check if the connection is successful
if (PQstatus(conn) != CONNECTION_OK) {
// If not successful, print the error message and finish the connection
printf("Error while connecting to the database server: %s\n", PQerrorMessage(conn));
// Finish the connection
PQfinish(conn);
// Exit the program
exit(1);
}
// We have successfully established a connection to the database server
printf("Connection Established\n");
printf("Port: %s\n", PQport(conn));
printf("Host: %s\n", PQhost(conn));
printf("DBName: %s\n", PQdb(conn));
for ( int i = 0; i < QUERIES_SIZE; i++ ) {
// Submit the query and retrieve the result
PGresult *res = PQexec(conn, queries[i]);
// Check the status of the query result
ExecStatusType resStatus = PQresultStatus(res);
if (resStatus == PGRES_COMMAND_OK || resStatus == PGRES_TUPLES_OK) {
// Successful completion of a command returning no data OR data (such as a SELECT or SHOW).
// Clear the result
PQclear(res);
} else {
printf("QUERY FAILED: %s\n", queries[i]);
// If not successful, print the error message and finish the connection
printf("Error while executing the query: %s\n", PQerrorMessage(conn));
// Clear the result
PQclear(res);
// Finish the connection
PQfinish(conn);
// Exit the program
exit(1);
}
}
// Submit the query and retrieve the result
PGresult *res = PQexec(conn, "SELECT * FROM test WHERE pk = 10");
// Check the status of the query result
ExecStatusType resStatus = PQresultStatus(res);
if (resStatus != PGRES_TUPLES_OK) {
printf("QUERY FAILED: %s\n", "SELECT * FROM test WHERE pk = 10");
// If not successful, print the error message and finish the connection
printf("Error while executing the query: %s\n", PQerrorMessage(conn));
// Clear the result
PQclear(res);
// Finish the connection
PQfinish(conn);
// Exit the program
exit(1);
}
// Get the number of columns in the query result
int cols = PQnfields(res);
printf("Number of cols: %d\n", cols);
assert(cols == 6);
char *expectedCols[6] = {"pk", "value", "d1", "f1", "c1", "t1"};
// Assert the column names
for (int i = 0; i < cols; i++) {
assert(strcmp(PQfname(res, i), expectedCols[i]) == 0);
}
// Get the number of rows in the query result
int rows = PQntuples(res);
printf("Number of rows: %d\n", rows);
assert(rows == 1);
char *expectedRowResults[6] = {"10", "10", "123456.789", "420.42", "example ", "some text"};
// Assert query result
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
char *actual = PQgetvalue(res, i, j);
printf("EXPECTED: '%s'\n", expectedRowResults[j]);
printf("ACTUAL: '%s'\n", actual);
assert(strcmp(actual, expectedRowResults[j]) == 0);
}
}
// Clear the result
PQclear(res);
// Close the connection and free the memory
PQfinish(conn);
return 0;
}
@@ -0,0 +1,11 @@
CXXFLAGS := -std=c++17 $(shell pkg-config --cflags libpqxx)
LDFLAGS := $(shell pkg-config --libs libpqxx)
all: libpqxx-test
libpqxx-test: libpqxx-test.cpp
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
rm -f libpqxx-test
@@ -0,0 +1,77 @@
#include <iostream>
#include <stdexcept>
#include <string>
#include <pqxx/pqxx>
int main(int argc, char *argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <user> <port>\n";
return 1;
}
std::string user = argv[1];
std::string port = argv[2];
try {
std::string connStr = "host=localhost port=" + port + " dbname=postgres user=" + user + " password=password sslmode=disable";
pqxx::connection conn(connStr);
pqxx::nontransaction ntxn(conn);
// SELECT from test_table (set up by bats setup())
pqxx::result r = ntxn.exec("SELECT pk FROM test_table LIMIT 1");
if (r.empty() || r[0][0].as<int>() != 1)
throw std::runtime_error("expected pk=1");
// INSERT
ntxn.exec("INSERT INTO test_table VALUES (2)");
// COUNT
r = ntxn.exec("SELECT COUNT(*) FROM test_table");
if (r[0][0].as<long long>() != 2)
throw std::runtime_error("expected count=2, got " + std::to_string(r[0][0].as<long long>()));
// Prepared statement
conn.prepare("select_pk", "SELECT pk FROM test_table WHERE pk = $1");
r = ntxn.exec_prepared("select_pk", 1);
if (r.empty() || r[0][0].as<int>() != 1)
throw std::runtime_error("expected pk=1 from prepared stmt");
// Dolt workflow: create table, insert, commit, branch, insert, commit, merge
for (const char *q : {
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
}) {
ntxn.exec(q);
}
conn.prepare("select_test_by_pk", "SELECT pk, value FROM test WHERE pk = $1");
r = ntxn.exec_prepared("select_test_by_pk", 0);
if (r.empty())
throw std::runtime_error("no rows for select_test_by_pk");
if (r[0]["pk"].as<int>() != 0 || r[0]["value"].as<int>() != 0)
throw std::runtime_error("expected pk=0 value=0");
conn.prepare("count_dolt_log", "SELECT COUNT(*) FROM dolt_log");
r = ntxn.exec_prepared("count_dolt_log");
if (r[0][0].as<long long>() != 4)
throw std::runtime_error("expected 4 dolt_log entries, got " + std::to_string(r[0][0].as<long long>()));
conn.prepare("count_test", "SELECT COUNT(*) FROM test");
r = ntxn.exec_prepared("count_test");
if (r[0][0].as<long long>() != 2)
throw std::runtime_error("expected 2 rows in test, got " + std::to_string(r[0][0].as<long long>()));
std::cout << "libpqxx test passed\n";
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
return 0;
}
@@ -0,0 +1,103 @@
using Npgsql;
var user = args[0];
var port = args[1];
var connStr = $"Host=localhost;Port={port};Username={user};Password=password;Database=postgres;SSL Mode=Disable";
await using var conn = new NpgsqlConnection(connStr);
await conn.OpenAsync();
// Basic SELECT
await using (var cmd = new NpgsqlCommand("SELECT pk FROM test_table LIMIT 1", conn))
{
var pk = (int)(await cmd.ExecuteScalarAsync())!;
if (pk != 1)
throw new Exception($"expected pk=1, got {pk}");
}
// INSERT
await using (var cmd = new NpgsqlCommand("INSERT INTO test_table VALUES (2)", conn))
await cmd.ExecuteNonQueryAsync();
// COUNT
await using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM test_table", conn))
{
var count = (long)(await cmd.ExecuteScalarAsync())!;
if (count != 2)
throw new Exception($"expected count=2, got {count}");
}
// Prepared SELECT
await using (var cmd = new NpgsqlCommand("SELECT pk FROM test_table WHERE pk = $1", conn))
{
cmd.Parameters.AddWithValue(1);
await cmd.PrepareAsync();
var pk = (int)(await cmd.ExecuteScalarAsync())!;
if (pk != 1)
throw new Exception($"expected pk=1 from prepared stmt, got {pk}");
}
// Dolt workflow: create table, insert, commit, branch, insert, commit, merge
foreach (var q in new[]
{
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
})
{
await using var cmd = new NpgsqlCommand(q, conn);
await cmd.ExecuteNonQueryAsync();
}
await RunPreparedQuery(
"SELECT pk, value FROM test WHERE pk = $1",
[0],
async r =>
{
if (!await r.ReadAsync()) throw new Exception("no rows");
var pk = r.GetInt32(0);
var value = r.GetInt32(1);
if (pk != 0 || value != 0)
throw new Exception($"expected pk=0 value=0, got pk={pk} value={value}");
});
await RunPreparedQuery(
"SELECT COUNT(*) FROM dolt_log",
[],
async r =>
{
if (!await r.ReadAsync()) throw new Exception("no rows");
var size = r.GetInt64(0);
if (size != 4)
throw new Exception($"expected 4 dolt_log entries, got {size}");
});
await RunPreparedQuery(
"SELECT COUNT(*) FROM test",
[],
async r =>
{
if (!await r.ReadAsync()) throw new Exception("no rows");
var size = r.GetInt64(0);
if (size != 2)
throw new Exception($"expected 2 rows in test, got {size}");
});
Console.WriteLine("Npgsql test passed");
async Task RunPreparedQuery(string query, object[] queryArgs, Func<NpgsqlDataReader, Task> check)
{
await using var cmd = new NpgsqlCommand(query, conn);
foreach (var arg in queryArgs)
cmd.Parameters.AddWithValue(arg);
await cmd.PrepareAsync();
await using var reader = await cmd.ExecuteReaderAsync();
await check(reader);
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>npgsql-test</AssemblyName>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql" Version="9.0.3" />
</ItemGroup>
</Project>
@@ -0,0 +1,11 @@
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './drizzle',
schema: './src/db/schema.ts',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
tablesFilter: ["!dolt_*"], // IMPORTANT to filter out dolt system tables
});
@@ -0,0 +1,9 @@
import { jsonb, integer, pgTable, varchar } from 'drizzle-orm/pg-core';
export const usersTable = pgTable("users", {
id: integer().primaryKey(),
name: varchar({ length: 255 }).notNull(),
age: integer().notNull(),
email: varchar({ length: 255 }).notNull().unique(),
render: jsonb('render'),
});
@@ -0,0 +1,53 @@
import 'dotenv/config';
import { Pool } from 'pg';
import { drizzle } from 'drizzle-orm/node-postgres';
import { usersTable } from "./db/schema";
import { eq } from 'drizzle-orm';
const connectionString = process.env.DATABASE_URL!
const pool = new Pool({ connectionString });
const db = drizzle(pool);
async function main() {
const user: typeof usersTable.$inferInsert = {
id: 1,
name: 'John',
age: 30,
render: null,
email: 'john@example.com',
};
await db.insert(usersTable).values(user);
console.log('New user created!')
const users = await db.select().from(usersTable);
console.log('Getting all users from the database: ', users)
/*
const users: {
id: number;
name: string;
age: number;
email: string;
render: jsonb;
}[]
*/
await db
.update(usersTable)
.set({
age: 31,
})
.where(eq(usersTable.email, user.email));
console.log('User info updated!')
// Verify the update
const result = await db
.select()
.from(usersTable)
.where(eq(usersTable.age, 31));
console.log('Retrieved updated record:', result);
// Clean up
await db.delete(usersTable).where(eq(usersTable.email, user.email));
console.log('User deleted!')
}
main();
@@ -0,0 +1,47 @@
defmodule PostgrexTest do
def main(args) do
[user, port_str] = args
port = String.to_integer(port_str)
{:ok, _} = Application.ensure_all_started(:postgrex)
{:ok, conn} = Postgrex.start_link(
hostname: "localhost",
port: port,
database: "postgres",
username: user,
password: "password",
ssl: false
)
{:ok, %{rows: [[pk]]}} = Postgrex.query(conn, "SELECT pk FROM test_table LIMIT 1", [])
if pk != 1, do: raise("expected pk=1, got #{pk}")
{:ok, _} = Postgrex.query(conn, "INSERT INTO test_table VALUES (2)", [])
{:ok, %{rows: [[count]]}} = Postgrex.query(conn, "SELECT COUNT(*) FROM test_table", [])
if count != 2, do: raise("expected count=2, got #{count}")
# Dolt workflow: create table, insert, commit, branch, insert, commit, merge
Enum.each([
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')"
], fn q -> {:ok, _} = Postgrex.query(conn, q, []) end)
{:ok, %{rows: [[log_count]]}} = Postgrex.query(conn, "SELECT COUNT(*) FROM dolt_log", [])
if log_count != 4, do: raise("expected 4 dolt_log entries, got #{log_count}")
{:ok, %{rows: [[test_count]]}} = Postgrex.query(conn, "SELECT COUNT(*) FROM test", [])
if test_count != 2, do: raise("expected 2 rows in test, got #{test_count}")
IO.puts("Postgrex test passed")
end
end
@@ -0,0 +1,21 @@
defmodule PostgrexTest.MixProject do
use Mix.Project
def project do
[
app: :postgrex_test,
version: "0.1.0",
elixir: "~> 1.14",
escript: [main_module: PostgrexTest, name: "postgrex-test"],
deps: deps()
]
end
def application do
[extra_applications: [:logger]]
end
defp deps do
[{:postgrex, "~> 0.18"}]
end
end
@@ -0,0 +1,5 @@
module libpq-test
go 1.26.3
require github.com/lib/pq v1.12.3 // indirect
@@ -0,0 +1,2 @@
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
@@ -0,0 +1,191 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"database/sql"
"fmt"
"os"
_ "github.com/lib/pq"
)
type ResFunc func(rows *sql.Rows) error
type StmtTest struct {
Query string
Args []interface{}
Res []ResFunc
}
func main() {
user := os.Args[1]
port := os.Args[2]
connStr := fmt.Sprintf("host=localhost port=%s user=%s password=password dbname=postgres sslmode=disable", port, user)
db, err := sql.Open("postgres", connStr)
if err != nil {
panic(err)
}
defer db.Close()
if err = db.Ping(); err != nil {
panic(err)
}
var pk int
err = db.QueryRow("SELECT pk FROM test_table LIMIT 1").Scan(&pk)
if err != nil {
panic(err)
}
if pk != 1 {
panic(fmt.Sprintf("expected pk=1, got %d", pk))
}
_, err = db.Exec("INSERT INTO test_table VALUES (2)")
if err != nil {
panic(err)
}
var count int
err = db.QueryRow("SELECT COUNT(*) FROM test_table").Scan(&count)
if err != nil {
panic(err)
}
if count != 2 {
panic(fmt.Sprintf("expected count=2, got %d", count))
}
stmt, err := db.Prepare("SELECT pk FROM test_table WHERE pk = $1")
if err != nil {
panic(err)
}
defer stmt.Close()
err = stmt.QueryRow(1).Scan(&pk)
if err != nil {
panic(err)
}
if pk != 1 {
panic(fmt.Sprintf("expected pk=1 from prepared stmt, got %d", pk))
}
// dolt workflow: create table, insert, commit, branch, insert, commit, merge
for _, q := range []string{
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
} {
if _, err = db.Exec(q); err != nil {
panic(fmt.Sprintf("failed to execute %q: %v", q, err))
}
}
stmtTests := []StmtTest{
{
Query: "SELECT pk, value FROM test WHERE pk = $1",
Args: []interface{}{int64(0)},
Res: []ResFunc{
func(rows *sql.Rows) error {
var pk, value int64
if err := rows.Scan(&pk, &value); err != nil {
return err
}
if pk != 0 || value != 0 {
return fmt.Errorf("expected pk=0 value=0, got pk=%d value=%d", pk, value)
}
return nil
},
},
},
{
Query: "SELECT COUNT(*) FROM dolt_log",
Res: []ResFunc{
func(rows *sql.Rows) error {
var size int64
if err := rows.Scan(&size); err != nil {
return err
}
if size != 4 {
return fmt.Errorf("expected 4 dolt_log entries, got %d", size)
}
return nil
},
},
},
{
Query: "SELECT COUNT(*) FROM test",
Res: []ResFunc{
func(rows *sql.Rows) error {
var size int64
if err := rows.Scan(&size); err != nil {
return err
}
if size != 2 {
return fmt.Errorf("expected 2 rows in test, got %d", size)
}
return nil
},
},
},
}
for _, test := range stmtTests {
func() {
stmt, err := db.Prepare(test.Query)
if err != nil {
panic(fmt.Sprintf("prepare %q: %v", test.Query, err))
}
defer func() {
if err := stmt.Close(); err != nil {
panic(fmt.Sprintf("stmt.Close() for %q: %v", test.Query, err))
}
}()
rows, err := stmt.Query(test.Args...)
if err != nil {
panic(fmt.Sprintf("query %q: %v", test.Query, err))
}
defer func() {
if err := rows.Close(); err != nil {
panic(fmt.Sprintf("rows.Close() for %q: %v", test.Query, err))
}
}()
i := 0
for rows.Next() {
if i >= len(test.Res) {
panic(fmt.Sprintf("too many rows for %q", test.Query))
}
if err := test.Res[i](rows); err != nil {
panic(fmt.Sprintf("result %d of %q: %v", i, test.Query, err))
}
i++
}
if err := rows.Err(); err != nil {
panic(fmt.Sprintf("rows.Err() for %q: %v", test.Query, err))
}
}()
}
fmt.Println("lib/pq test passed")
}
@@ -0,0 +1,10 @@
module pgx-test
go 1.26.3
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.10.0 // indirect
golang.org/x/text v0.29.0 // indirect
)
@@ -0,0 +1,15 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -0,0 +1,179 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v5"
)
type ResFunc func(rows pgx.Rows) error
type StmtTest struct {
Name string
Query string
Args []any
Res []ResFunc
}
func main() {
user := os.Args[1]
port := os.Args[2]
ctx := context.Background()
connStr := fmt.Sprintf("postgres://%s:password@localhost:%s/postgres", user, port)
conn, err := pgx.Connect(ctx, connStr)
if err != nil {
panic(err)
}
defer conn.Close(ctx)
var pk int
err = conn.QueryRow(ctx, "SELECT pk FROM test_table LIMIT 1").Scan(&pk)
if err != nil {
panic(err)
}
if pk != 1 {
panic(fmt.Sprintf("expected pk=1, got %d", pk))
}
_, err = conn.Exec(ctx, "INSERT INTO test_table VALUES (2)")
if err != nil {
panic(err)
}
var count int
err = conn.QueryRow(ctx, "SELECT COUNT(*) FROM test_table").Scan(&count)
if err != nil {
panic(err)
}
if count != 2 {
panic(fmt.Sprintf("expected count=2, got %d", count))
}
_, err = conn.Prepare(ctx, "select_pk", "SELECT pk FROM test_table WHERE pk = $1")
if err != nil {
panic(err)
}
err = conn.QueryRow(ctx, "select_pk", 1).Scan(&pk)
if err != nil {
panic(err)
}
if pk != 1 {
panic(fmt.Sprintf("expected pk=1 from prepared stmt, got %d", pk))
}
// dolt workflow: create table, insert, commit, branch, insert, commit, merge
for _, q := range []string{
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
} {
if _, err = conn.Exec(ctx, q); err != nil {
panic(fmt.Sprintf("failed to execute %q: %v", q, err))
}
}
stmtTests := []StmtTest{
{
Name: "select_test_by_pk",
Query: "SELECT pk, value FROM test WHERE pk = $1",
Args: []any{int64(0)},
Res: []ResFunc{
func(rows pgx.Rows) error {
var pk, value int64
if err := rows.Scan(&pk, &value); err != nil {
return err
}
if pk != 0 || value != 0 {
return fmt.Errorf("expected pk=0 value=0, got pk=%d value=%d", pk, value)
}
return nil
},
},
},
{
Name: "count_dolt_log",
Query: "SELECT COUNT(*) FROM dolt_log",
Res: []ResFunc{
func(rows pgx.Rows) error {
var size int64
if err := rows.Scan(&size); err != nil {
return err
}
if size != 4 {
return fmt.Errorf("expected 4 dolt_log entries, got %d", size)
}
return nil
},
},
},
{
Name: "count_test",
Query: "SELECT COUNT(*) FROM test",
Res: []ResFunc{
func(rows pgx.Rows) error {
var size int64
if err := rows.Scan(&size); err != nil {
return err
}
if size != 2 {
return fmt.Errorf("expected 2 rows in test, got %d", size)
}
return nil
},
},
},
}
for _, test := range stmtTests {
func() {
if _, err := conn.Prepare(ctx, test.Name, test.Query); err != nil {
panic(fmt.Sprintf("prepare %q: %v", test.Query, err))
}
rows, err := conn.Query(ctx, test.Name, test.Args...)
if err != nil {
panic(fmt.Sprintf("query %q: %v", test.Query, err))
}
defer rows.Close()
i := 0
for rows.Next() {
if i >= len(test.Res) {
panic(fmt.Sprintf("too many rows for %q", test.Query))
}
if err := test.Res[i](rows); err != nil {
panic(fmt.Sprintf("result %d of %q: %v", i, test.Query, err))
}
i++
}
if err := rows.Err(); err != nil {
panic(fmt.Sprintf("rows.Err() for %q: %v", test.Query, err))
}
}()
}
fmt.Println("pgx test passed")
}
@@ -0,0 +1,68 @@
start_doltgres_server() {
REPO_NAME="doltgres_repo_$$"
mkdir $REPO_NAME
cd $REPO_NAME
USER="postgres"
PORT=$( definePORT )
CONFIG=$( defineCONFIG $PORT )
echo "$CONFIG" > config.yaml
doltgres --data-dir=. -config=config.yaml &
SERVER_PID=$!
# Give the server a chance to start
sleep 2
}
setup_doltgres_repo() {
run psql --version
if [[ ! "$output" =~ "(PostgreSQL) 15" ]] && [[ ! "$output" =~ "(PostgreSQL) 16" ]] && [[ ! "$output" =~ "(PostgreSQL) 17" ]]; then
echo "PSQL must be version 15, got $output"
return 1
fi
start_doltgres_server
}
teardown_doltgres_repo() {
kill $SERVER_PID
rm -rf $REPO_NAME
}
query_server() {
PGPASSWORD="password" psql -U "${USER:-postgres}" -h localhost -p $PORT "$@" postgres
}
definePORT() {
getPORT=""
for i in {0..9}
do
let getPORT="($$ + $i) % 4096 + 2048"
portinuse=$(lsof -i -P -n | grep LISTEN | grep $attemptedPORT | wc -l)
if [ $portinuse -eq 0 ]
then
echo "$getPORT"
break
fi
done
}
defineCONFIG() {
PORT=$1
cat <<EOF
log_level: debug
behavior:
read_only: false
disable_client_multi_statements: false
dolt_transaction_commit: false
user:
name: "postgres"
password: "password"
listener:
host: localhost
port: $PORT
EOF
}
@@ -0,0 +1,134 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
public class PostgresTest {
public String query;
public Integer expectedUpdateCount; // for queries that don't return set of results
public Object fieldAccessor; // can be String (i.e column name), Integer (i.e. field position) and `null` for no result set queries.
public String[] expectedResults;
// Parameterized constructor
public PostgresTest(String query, Integer expectedUpdateCount, Object fieldAccessor, String[] expectedResults) {
this.query = query;
this.expectedUpdateCount = expectedUpdateCount;
this.fieldAccessor = fieldAccessor;
this.expectedResults = expectedResults;
}
// Getters and Setters (optional)
public String getQuery() {
return this.query;
}
public Integer getExpectedUpdateCount() {
return this.expectedUpdateCount;
}
public Object getFieldAccessor() {
return this.fieldAccessor;
}
public String[] getExpectedResults() {
return this.expectedResults;
}
// test queries to be run against Doltgres
private static final PostgresTest[] tests = {
new PostgresTest("create table test (pk int, value int, d1 decimal(4,2), c1 char(10), primary key(pk))", 0, null, null),
new PostgresTest("select pk from test", null, "pk", new String[]{}), // the table has no rows
new PostgresTest("insert into test (pk, value, d1, c1) values (0,1,2.3,'hi'), (2,3,4.56,'hello')", 2, null, null),
new PostgresTest("select * from test", null, "pk", new String[]{"0","2"}),
new PostgresTest("select * from test", null, "value", new String[]{"1","3"}),
// TODO: doltgres DECIMAL type result is returned as "2.3", should be "2.30"
// new PostgresTest("select * from test", null, "d1", new String[]{"2.30","4.56"}),
new PostgresTest("select * from test", null, "c1", new String[]{"hi ","hello "}),
new PostgresTest("select dolt_add('-A')", null, "dolt_add", new String[]{"{0}"}),
new PostgresTest("select length(dolt_commit('-m', 'my commit')::text)", null, 1, new String[]{"34"}),
new PostgresTest("select COUNT(*) FROM dolt.log", null, 1, new String[]{"3"}),
new PostgresTest("select dolt_checkout('-b', 'mybranch')", null, "dolt_checkout", new String[]{"{0,\"Switched to branch 'mybranch'\"}"}),
new PostgresTest("insert into test (pk, value, d1, c1) values (1,1,12.34,'bye')", 1, null, null),
new PostgresTest("select length(dolt_commit('-a', '-m', 'my commit2')::text)", null, 1, new String[]{"34"}),
new PostgresTest("select dolt_checkout('main')", null, "dolt_checkout", new String[]{"{0,\"Switched to branch 'main'\"}"}),
new PostgresTest("select length(dolt_merge('mybranch')::text)", null, 1, new String[]{"57"}),
new PostgresTest("select COUNT(*) FROM dolt.log", null, "COUNT", new String[]{"4"}), // returns res
};
public static void main(String[] args) {
Connection conn = null;
String user = args[0];
String port = args[1];
try {
String url = "jdbc:postgresql://127.0.0.1:" + port + "/postgres";
String password = "password";
conn = DriverManager.getConnection(url, user, password);
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
for (int i = 0; i < tests.length; i++) {
PostgresTest t = tests[i];
String query = t.getQuery();
if ( st.execute(query) ) {
String[] expectedResults = t.getExpectedResults();
Object fieldAccessor = t.getFieldAccessor();
ResultSet rs = st.getResultSet();
int rowCount = getRowCount(rs);
// Compare the row count to the length of the array
if (rowCount != expectedResults.length) {
System.out.println("Row count in ResultSet: " + rowCount + " does not match length of the expected results: " + expectedResults.length);
System.exit(1);
}
Integer j = 0;
if (rs.next()) {
String expected = expectedResults[j];
String result = "";
if (fieldAccessor instanceof String) {
result = rs.getString((String)fieldAccessor);
} else if (fieldAccessor instanceof Integer) {
result = rs.getString((Integer)fieldAccessor);
} else {
System.out.println("Unsupported field accessor value: " + fieldAccessor);
System.exit(1);
}
if (!expected.equals(result) && !(query.contains("dolt_commit")) && !(query.contains("dolt_merge"))) {
System.out.println("Query: \n" + query);
System.out.println("Expected:\n'" + expected + "'");
System.out.println("Result:\n'" + result + "'");
System.exit(1);
}
j++;
}
} else {
Integer expectedUpdateCount = t.getExpectedUpdateCount();
Integer result = st.getUpdateCount();
if ( !expectedUpdateCount.equals(result) ) {
System.out.println("Query: \n" + query);
System.out.println("Expected:\n" + expectedUpdateCount);
System.out.println("Rows Updated:\n" + result);
System.exit(1);
}
}
}
System.exit(0);
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
public static int getRowCount(ResultSet rs) throws SQLException {
int rowCount = 0;
if (rs != null) {
rs.last(); // Move to the last row
rowCount = rs.getRow(); // Get the row number (which is the row count)
rs.beforeFirst(); // Move back to the beginning of the ResultSet
}
return rowCount;
}
}
@@ -0,0 +1,33 @@
# Node Client Integration Tests
## Install
```
$ npm install
```
## Run node tests
To run the node tests, you must make sure you have Doltgres installed on your computer and
have run `npm install`. Then update your Doltgres config by running:
```shell
sh ../postgres-client-tests-entrypoint.sh
```
And then you can run the tests using the `run-tests.sh` script, which sets up the database, runs the SQL server, runs the provided `.js` test file against the running server, and then tears down the database.
For example, you can run the `workbench.js` tests by running:
```shell
sh run-tests.sh workbench.js
```
## Workbench stability tests
The tests in `workbenchTests` were written to enforce the stability of the [SQL
workbench](https://github.com/dolthub/dolt-workbench). The workbench uses many Doltgres
system tables, functions, and procedures, and any changes to these interfaces can break
the workbench. @tbantle22 will be tagged in any GitHub PR that updates those files to
ensure appropriate workbench updates are made for Doltgres changes that break these
queries.
@@ -0,0 +1,28 @@
import pkg from "pg";
const { Client } = pkg;
export class Database {
constructor(config) {
this.client = new Client(config);
this.client.connect();
}
query(sql, args) {
return new Promise((resolve, reject) => {
this.client.query(sql, args, (err, rows) => {
if (err) return reject(err);
return resolve(rows);
});
});
}
close() {
this.client.end((err) => {
if (err) {
console.error(err);
} else {
console.log("db connection closed");
}
});
}
}
@@ -0,0 +1,591 @@
export const countFields = [
{
name: "count",
tableID: 0,
columnID: 1,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
];
export const doltAddFields = [
{
name: "dolt_add",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltResetFields = [
{
name: "dolt_reset",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltBranchFields = [
{
name: "dolt_branch",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltCheckoutFields = [
{
name: "dolt_checkout",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltCleanFields = [
{
name: "dolt_clean",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltCommitFields = [
{
name: "dolt_commit",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltSchemasFields = [
{
name: "type",
tableID: 0,
columnID: 1,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: -1,
format: "text",
},
{
name: "name",
tableID: 0,
columnID: 2,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: -1,
format: "text",
},
{
name: "fragment",
tableID: 0,
columnID: 3,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "extra",
tableID: 0,
columnID: 4,
dataTypeID: 114,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "sql_mode",
tableID: 0,
columnID: 5,
dataTypeID: 1043,
dataTypeSize: 1024,
dataTypeModifier: -1,
format: "text",
},
];
export const doltDocsFields = [
{
name: "doc_name",
tableID: 0,
columnID: 1,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "doc_text",
tableID: 0,
columnID: 2,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltStatusFields = [
{
name: "table_name",
tableID: 0,
columnID: 1,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "staged",
tableID: 0,
columnID: 2,
dataTypeID: 16,
dataTypeSize: 1,
dataTypeModifier: -1,
format: "text",
},
{
name: "status",
tableID: 0,
columnID: 3,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltTagFields = [
{
name: "dolt_tag",
tableID: 0,
columnID: 1,
dataTypeID: 1009,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
];
export const doltTagsFields = [
{
name: "tag_name",
tableID: 0,
columnID: 1,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
{
name: "tag_hash",
tableID: 0,
columnID: 2,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
{
name: "tagger",
tableID: 0,
columnID: 3,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
{
name: "email",
tableID: 0,
columnID: 4,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
{
name: "date",
tableID: 0,
columnID: 5,
dataTypeID: 1114,
dataTypeSize: 26,
dataTypeModifier: -1,
format: "text",
},
{
name: "message",
tableID: 0,
columnID: 6,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
];
export const infoSchemaKeyColumnUsageFields = [
{
name: "CONSTRAINT_CATALOG",
tableID: 0,
columnID: 1,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "CONSTRAINT_SCHEMA",
tableID: 0,
columnID: 2,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "CONSTRAINT_NAME",
tableID: 0,
columnID: 3,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "TABLE_CATALOG",
tableID: 0,
columnID: 4,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "TABLE_SCHEMA",
tableID: 0,
columnID: 5,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "TABLE_NAME",
tableID: 0,
columnID: 6,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "COLUMN_NAME",
tableID: 0,
columnID: 7,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "ORDINAL_POSITION",
tableID: 0,
columnID: 8,
dataTypeID: 26,
dataTypeSize: 10,
dataTypeModifier: -1,
format: "text",
},
{
name: "POSITION_IN_UNIQUE_CONSTRAINT",
tableID: 0,
columnID: 9,
dataTypeID: 26,
dataTypeSize: 10,
dataTypeModifier: -1,
format: "text",
},
{
name: "REFERENCED_TABLE_SCHEMA",
tableID: 0,
columnID: 10,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "REFERENCED_TABLE_NAME",
tableID: 0,
columnID: 11,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
{
name: "REFERENCED_COLUMN_NAME",
tableID: 0,
columnID: 12,
dataTypeID: 1043,
dataTypeSize: 192,
dataTypeModifier: -1,
format: "text",
},
];
export const schemaNameField = {
name: "schema_name",
tableID: 0,
columnID: 1,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
};
export const pgTablesFields = [
{
name: "schemaname",
tableID: 0,
columnID: 1,
dataTypeID: 19,
dataTypeSize: 64,
dataTypeModifier: -1,
format: "text",
},
{
name: "tablename",
tableID: 0,
columnID: 2,
dataTypeID: 19,
dataTypeSize: 64,
dataTypeModifier: -1,
format: "text",
},
];
export const doltDiffStatFields = [
{
name: "table_name",
tableID: 0,
columnID: 1,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "rows_unmodified",
tableID: 0,
columnID: 2,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "rows_added",
tableID: 0,
columnID: 3,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "rows_deleted",
tableID: 0,
columnID: 4,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "rows_modified",
tableID: 0,
columnID: 5,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "cells_added",
tableID: 0,
columnID: 6,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "cells_deleted",
tableID: 0,
columnID: 7,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "cells_modified",
tableID: 0,
columnID: 8,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "old_row_count",
tableID: 0,
columnID: 9,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "new_row_count",
tableID: 0,
columnID: 10,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "old_cell_count",
tableID: 0,
columnID: 11,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
{
name: "new_cell_count",
tableID: 0,
columnID: 12,
dataTypeID: 20,
dataTypeSize: 20,
dataTypeModifier: -1,
format: "text",
},
];
export const doltDiffSummaryFields = [
{
name: "from_table_name",
tableID: 0,
columnID: 1,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "to_table_name",
tableID: 0,
columnID: 2,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "diff_type",
tableID: 0,
columnID: 3,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
{
name: "data_change",
tableID: 0,
columnID: 4,
dataTypeID: 21,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
{
name: "schema_change",
tableID: 0,
columnID: 5,
dataTypeID: 21,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
];
export const doltSchemaDiffFields = [
{
name: "from_table_name",
tableID: 0,
columnID: 1,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "to_table_name",
tableID: 0,
columnID: 2,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
{
name: "from_create_statement",
tableID: 0,
columnID: 3,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
{
name: "to_create_statement",
tableID: 0,
columnID: 4,
dataTypeID: 25,
dataTypeSize: -4,
dataTypeModifier: -1,
format: "text",
},
];
@@ -0,0 +1,64 @@
const args = process.argv.slice(2);
const user = args[0];
const port = args[1];
const version = args[2];
export const dbName = "postgres";
export function getArgs() {
return { user, port };
}
export function getDoltgresVersion() {
return version;
}
export function getConfig() {
const { user, port } = getArgs();
return {
host: "localhost",
port: port,
database: dbName,
user: user,
password: "password",
};
}
export function assertEqualRows(test, data) {
const expected = test.res;
const resultStr = JSON.stringify(data);
const result = JSON.parse(resultStr);
if (!assertQueryResult(test.q, expected, data, test.matcher)) {
console.log("Query:", test.q);
console.log("Results:", result);
console.log("Expected:", expected);
throw new Error("Query failed");
} else {
console.log("Query succeeded:", test.q);
}
}
export function assertQueryResult(q, expected, data, matcher) {
if (matcher) {
return matcher(data, expected);
}
if (q.toLowerCase().includes("dolt_commit")) {
if (data.rows.length !== 1) return false;
const hash = data.rows[0].dolt_commit[0];
if (hash.length !== 32) {
console.log("Invalid hash for dolt_commit:", hash);
return false;
}
expected.rows[0].dolt_commit = data.rows[0].dolt_commit;
}
// Does partial matching of actual and expected results.
const partialRes = {
command: data.command,
rowCount: data.rowCount,
oid: data.oid,
rows: data.rows,
fields: data.fields,
};
return JSON.stringify(expected) === JSON.stringify(partialRes);
}
+208
View File
@@ -0,0 +1,208 @@
import { Database } from "./database.js";
import { assertEqualRows, getConfig } from "./helpers.js";
import {
doltAddFields,
doltCheckoutFields,
doltCommitFields,
countFields,
} from "./fields.js";
import { mergeMatcher } from "./workbenchTests/matchers.js";
const tests = [
{
q: "create table test (pk int, value int, primary key(pk))",
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: "select * from test",
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: [
{
name: "pk",
tableID: 0, // TODO: need to be filled? Got 16859 from Postgres
columnID: 1,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
{
name: "value",
tableID: 0, // TODO: need to be filled? Got 16859 from Postgres
columnID: 2,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
q: "insert into test (pk, value) values (0,0)",
res: {
command: "INSERT",
rowCount: 1,
oid: 0,
rows: [],
fields: [],
},
},
{
q: "select * from test",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ pk: 0, value: 0 }],
fields: [
{
name: "pk",
tableID: 0,
columnID: 1,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
{
name: "value",
tableID: 0,
columnID: 2,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
q: "select dolt_add('-A');",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_add: ["0"] }],
fields: doltAddFields,
},
},
{
q: "select dolt_commit('-m', 'my commit')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: [""] }],
fields: doltCommitFields,
},
},
{
q: "select COUNT(*) FROM dolt.log",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ count: "3" }],
fields: countFields,
},
},
{
q: "select dolt_checkout('-b', 'mybranch')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_checkout: ["0", "Switched to branch 'mybranch'"] }],
fields: doltCheckoutFields,
},
},
{
q: "insert into test (pk, value) values (1,1),(2,3)",
res: {
command: "INSERT",
rowCount: 2,
oid: 0,
rows: [],
fields: [],
},
},
{
q: "select dolt_commit('-a', '-m', 'my commit2')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: [""] }],
fields: doltCommitFields,
},
},
{
q: "select dolt_checkout('main')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_checkout: ["0", "Switched to branch 'main'"] }],
fields: doltCheckoutFields,
},
},
{
q: "select dolt_merge('mybranch')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
dolt_merge: ["", "1", "0", "merge successful"],
},
],
fields: [],
},
matcher: mergeMatcher,
},
{
q: "select COUNT(*) FROM dolt.log",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ count: "4" }],
fields: countFields,
},
},
];
async function main() {
const database = new Database(getConfig());
await Promise.all(
tests.map((test) => {
return database
.query(test.q)
.then((data) => {
assertEqualRows(test, data);
})
.catch((err) => {
console.error(err);
process.exit(1);
});
})
);
database.close();
process.exit(0);
}
main();
@@ -0,0 +1,86 @@
import knex from "knex";
import wtfnode from "wtfnode";
import { Socket } from "net";
import { getConfig, getDoltgresVersion } from "./helpers.js";
const db = knex({
client: "pg",
version: getDoltgresVersion(),
connection: getConfig(),
});
async function createTable() {
const val = await db.schema.createTable("test2", (table) => {
table.integer("id").primary();
table.integer("foo");
});
return val;
}
// upsert runs INSERT ... ON CONFLICT UPDATE statement, which is not yet supported in Doltgres. (TODO)
async function upsert(table, data) {
const val = await db(table).insert(data).onConflict().merge();
return val;
}
async function insert(table, data) {
const val = await db(table).insert(data);
return val;
}
async function select() {
const val = await db.select("id", "foo").from("test2");
return val;
}
async function main() {
await createTable();
await Promise.all([
insert("test2", { id: 1, foo: 1 }),
insert("test2", { id: 2, foo: 2 }),
]);
const expectedResult = JSON.stringify([
{ id: 1, foo: 1 },
{ id: 2, foo: 2 },
]);
const result = await select();
if (JSON.stringify(result) !== expectedResult) {
console.log("Results:", result);
console.log("Expected:", expectedResult);
process.exit(1);
}
await db.destroy();
// cc: https://github.com/dolthub/dolt/issues/3752
setTimeout(async () => {
const sockets = await getOpenSockets();
if (sockets.length > 0) {
wtfnode.dump();
process.exit(1);
}
}, 3000);
}
// cc: https://github.com/myndzi/wtfnode/blob/master/index.js#L457
async function getOpenSockets() {
const sockets = [];
process._getActiveHandles().forEach(function (h) {
// handles can be null now? early exit to guard against this
if (!h) {
return;
}
if (h instanceof Socket) {
if (h.fd == null && h.localAddress && !h.destroyed) {
sockets.push(h);
}
}
});
return sockets;
}
main();
+771
View File
@@ -0,0 +1,771 @@
{
"name": "node",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "node",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"knex": "^2.5.1",
"pg": "^8.12.0",
"pg-copy-streams": "^6.0.6",
"pg-promise": "^11.6.0",
"wtfnode": "^0.9.2"
}
},
"node_modules/assert-options": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.1.tgz",
"integrity": "sha512-5lNGRB5g5i2bGIzb+J1QQE1iKU/WEMVBReFIc5pPDWjcPj23otPL0eI6PB2v7QPi0qU6Mhym5D3y0ZiSIOf3GA==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/colorette": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
"integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="
},
"node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"engines": {
"node": ">=14"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"engines": {
"node": ">=6"
}
},
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"engines": {
"node": ">=6"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/getopts": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz",
"integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA=="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/interpret": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-core-module": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/knex": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz",
"integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==",
"dependencies": {
"colorette": "2.0.19",
"commander": "^10.0.0",
"debug": "4.3.4",
"escalade": "^3.1.1",
"esm": "^3.2.25",
"get-package-type": "^0.1.0",
"getopts": "2.3.0",
"interpret": "^2.2.0",
"lodash": "^4.17.21",
"pg-connection-string": "2.6.1",
"rechoir": "^0.8.0",
"resolve-from": "^5.0.0",
"tarn": "^3.0.2",
"tildify": "2.0.0"
},
"bin": {
"knex": "bin/cli.js"
},
"engines": {
"node": ">=12"
},
"peerDependenciesMeta": {
"better-sqlite3": {
"optional": true
},
"mysql": {
"optional": true
},
"mysql2": {
"optional": true
},
"pg": {
"optional": true
},
"pg-native": {
"optional": true
},
"sqlite3": {
"optional": true
},
"tedious": {
"optional": true
}
}
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/pg": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz",
"integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==",
"dependencies": {
"pg-connection-string": "^2.6.4",
"pg-pool": "^3.6.2",
"pg-protocol": "^1.6.1",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"engines": {
"node": ">= 8.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.1.1"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg=="
},
"node_modules/pg-copy-streams": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/pg-copy-streams/-/pg-copy-streams-6.0.6.tgz",
"integrity": "sha512-Z+Dd2C2NIDTsjyFKmc6a9QLlpM8tjpERx+43RSx0WmL7j3uNChERi3xSvZUL0hWJ1oRUn4S3fhyt3apdSrTyKQ==",
"dependencies": {
"obuf": "^1.1.2"
}
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-minify": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.3.tgz",
"integrity": "sha512-NoSsPqXxbkD8RIe+peQCqiea4QzXgosdTKY8p7PsbbGsh2F8TifDj/vJxfuR8qJwNYrijdSs7uf0tAe6WOyCsQ==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz",
"integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-promise": {
"version": "11.6.0",
"resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.6.0.tgz",
"integrity": "sha512-NDRPMfkv3ia89suWlJ4iGvP6X5YFrLJ2+9AIVISeBFFZ29Eb4FNXX9JaVb1p1OrpQkE2yT7igmXPL7UYQhk+6A==",
"dependencies": {
"assert-options": "0.8.1",
"pg": "8.11.5",
"pg-minify": "1.6.3",
"spex": "3.3.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/pg-promise/node_modules/pg": {
"version": "8.11.5",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz",
"integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==",
"dependencies": {
"pg-connection-string": "^2.6.4",
"pg-pool": "^3.6.2",
"pg-protocol": "^1.6.1",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"engines": {
"node": ">= 8.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.1.1"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-promise/node_modules/pg-connection-string": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz",
"integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA=="
},
"node_modules/pg-protocol": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz",
"integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg=="
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pg/node_modules/pg-connection-string": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz",
"integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA=="
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
"integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
"dependencies": {
"resolve": "^1.20.0"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dependencies": {
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"engines": {
"node": ">=8"
}
},
"node_modules/spex": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/spex/-/spex-3.3.0.tgz",
"integrity": "sha512-VNiXjFp6R4ldPbVRYbpxlD35yRHceecVXlct1J4/X80KuuPnW2AXMq3sGwhnJOhKkUsOxAT6nRGfGE5pocVw5w==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tarn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
"integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/tildify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
"integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==",
"engines": {
"node": ">=8"
}
},
"node_modules/wtfnode": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.9.2.tgz",
"integrity": "sha512-AzGy/MOGmzufyHDKHwuZeuqaqslGIPuwxDAhdmUUJ91Ak22Ynxry8quXNR/cZZIaSs5T+C+LNS3W9AcMzJXhMw==",
"bin": {
"wtfnode": "proxy.js"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"engines": {
"node": ">=0.4"
}
}
},
"dependencies": {
"assert-options": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.1.tgz",
"integrity": "sha512-5lNGRB5g5i2bGIzb+J1QQE1iKU/WEMVBReFIc5pPDWjcPj23otPL0eI6PB2v7QPi0qU6Mhym5D3y0ZiSIOf3GA=="
},
"colorette": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
"integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="
},
"commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="
},
"getopts": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz",
"integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA=="
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"interpret": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
},
"is-core-module": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"requires": {
"has": "^1.0.3"
}
},
"knex": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz",
"integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==",
"requires": {
"colorette": "2.0.19",
"commander": "^10.0.0",
"debug": "4.3.4",
"escalade": "^3.1.1",
"esm": "^3.2.25",
"get-package-type": "^0.1.0",
"getopts": "2.3.0",
"interpret": "^2.2.0",
"lodash": "^4.17.21",
"pg-connection-string": "2.6.1",
"rechoir": "^0.8.0",
"resolve-from": "^5.0.0",
"tarn": "^3.0.2",
"tildify": "2.0.0"
}
},
"lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"pg": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz",
"integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==",
"requires": {
"pg-cloudflare": "^1.1.1",
"pg-connection-string": "^2.6.4",
"pg-pool": "^3.6.2",
"pg-protocol": "^1.6.1",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"dependencies": {
"pg-connection-string": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz",
"integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA=="
}
}
},
"pg-cloudflare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
"optional": true
},
"pg-connection-string": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg=="
},
"pg-copy-streams": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/pg-copy-streams/-/pg-copy-streams-6.0.6.tgz",
"integrity": "sha512-Z+Dd2C2NIDTsjyFKmc6a9QLlpM8tjpERx+43RSx0WmL7j3uNChERi3xSvZUL0hWJ1oRUn4S3fhyt3apdSrTyKQ==",
"requires": {
"obuf": "^1.1.2"
}
},
"pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-minify": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.3.tgz",
"integrity": "sha512-NoSsPqXxbkD8RIe+peQCqiea4QzXgosdTKY8p7PsbbGsh2F8TifDj/vJxfuR8qJwNYrijdSs7uf0tAe6WOyCsQ=="
},
"pg-pool": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz",
"integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==",
"requires": {}
},
"pg-promise": {
"version": "11.6.0",
"resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.6.0.tgz",
"integrity": "sha512-NDRPMfkv3ia89suWlJ4iGvP6X5YFrLJ2+9AIVISeBFFZ29Eb4FNXX9JaVb1p1OrpQkE2yT7igmXPL7UYQhk+6A==",
"requires": {
"assert-options": "0.8.1",
"pg": "8.11.5",
"pg-minify": "1.6.3",
"spex": "3.3.0"
},
"dependencies": {
"pg": {
"version": "8.11.5",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz",
"integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==",
"requires": {
"pg-cloudflare": "^1.1.1",
"pg-connection-string": "^2.6.4",
"pg-pool": "^3.6.2",
"pg-protocol": "^1.6.1",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
"pg-connection-string": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz",
"integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA=="
}
}
},
"pg-protocol": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz",
"integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg=="
},
"pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"requires": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
}
},
"pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"requires": {
"split2": "^4.1.0"
}
},
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
},
"postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
},
"postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
},
"postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"requires": {
"xtend": "^4.0.0"
}
},
"rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
"integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
"requires": {
"resolve": "^1.20.0"
}
},
"resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"requires": {
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
},
"spex": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/spex/-/spex-3.3.0.tgz",
"integrity": "sha512-VNiXjFp6R4ldPbVRYbpxlD35yRHceecVXlct1J4/X80KuuPnW2AXMq3sGwhnJOhKkUsOxAT6nRGfGE5pocVw5w=="
},
"split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"tarn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
"integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="
},
"tildify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
"integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw=="
},
"wtfnode": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.9.2.tgz",
"integrity": "sha512-AzGy/MOGmzufyHDKHwuZeuqaqslGIPuwxDAhdmUUJ91Ak22Ynxry8quXNR/cZZIaSs5T+C+LNS3W9AcMzJXhMw=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}
}
}
@@ -0,0 +1,20 @@
{
"name": "node",
"version": "1.0.0",
"node": ">=16.9",
"description": "A simple node command line utility to show how to connect a node application to a Doltgres database using the PostgreSQL connector.",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"knex": "^2.5.1",
"pg": "^8.12.0",
"pg-copy-streams": "^6.0.6",
"pg-promise": "^11.6.0",
"wtfnode": "^0.9.2"
}
}
@@ -0,0 +1,13 @@
#!/bin/sh
source ../helpers.bash
echo "Running $1 tests"
start_doltgres_server
query_server -c "CREATE TABLE IF NOT EXISTS test_table(pk int)" -t
query_server -c "DELETE FROM test_table" -t
query_server -c "INSERT INTO test_table VALUES (1)" -t
cd ..
DOLTGRES_VERSION=$( doltgres --version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p' )
node $1 $USER $PORT $DOLTGRES_VERSION $PWD/testdata
teardown_doltgres_repo
@@ -0,0 +1,4 @@
id,info,test_pk
4,string for 4,1
5,string for 5,0
6,string for 6,0
1 id info test_pk
2 4 string for 4 1
3 5 string for 5 0
4 6 string for 6 0
@@ -0,0 +1,4 @@
id|info|test_pk
10|string for 10|0
11|string for 11|0
12|string for 12|0
@@ -0,0 +1,14 @@
import { Database } from "./database.js";
import { getConfig } from "./helpers.js";
import runWorkbenchTests from "./workbenchTests/index.js";
async function workbench() {
const database = new Database(getConfig());
await runWorkbenchTests(database);
database.close();
process.exit(0);
}
workbench();
@@ -0,0 +1,152 @@
import {
countFields,
doltBranchFields,
doltCheckoutFields,
doltStatusFields,
doltCommitFields,
} from "../fields.js";
import { branchesMatcher } from "./matchers.js";
import { dbName } from "../helpers.js";
export const branchTests = [
{
q: `SELECT DOLT_BRANCH($1::text, $2::text)`, // TODO: Should work without casts
p: ["mybranch", "main"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_branch: ["0"] }],
fields: doltBranchFields,
},
},
{
q: `USE '${dbName}/mybranch';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `create table test (pk int, "value" int, primary key(pk));`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT * FROM dolt.status;`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
table_name: "public.test",
staged: false,
status: "new table",
},
],
fields: doltStatusFields,
},
},
{
q: `SELECT DOLT_COMMIT('-Am', $1::text, '--author', $2::text);`,
p: ["Create table test", "Dolt <dolt@dolthub.com>"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: "" }],
fields: doltCommitFields,
},
},
{
q: `SELECT * FROM dolt.branches LIMIT 200`,
res: {
rows: [
{
name: "main",
hash: "",
latest_committer: "postgres",
latest_committer_email: "postgres@127.0.0.1",
latest_commit_date: "",
latest_commit_message: "CREATE DATABASE",
remote: "",
branch: "",
dirty: 1,
},
{
name: "mybranch",
hash: "",
latest_committer: "Dolt",
latest_committer_email: "dolt@dolthub.com",
latest_commit_date: "",
latest_commit_message: "Create table test",
remote: "",
branch: "",
dirty: 0,
},
],
},
matcher: branchesMatcher,
},
{
q: `SELECT DOLT_CHECKOUT('-b', $1::text)`,
p: ["branch-to-delete"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_checkout: ["0", "Switched to branch 'branch-to-delete'"] }],
fields: doltCheckoutFields,
},
},
{
q: `SELECT COUNT(*) FROM dolt.branches LIMIT 200`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ count: "3" }],
fields: countFields,
},
},
{
q: `USE '${dbName}/main';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT DOLT_BRANCH('-D', $1::text)`,
p: ["branch-to-delete"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_branch: ["0"] }],
fields: doltBranchFields,
},
},
{
q: `SELECT COUNT(*) FROM dolt.branches LIMIT 200`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ count: "2" }],
fields: countFields,
},
},
];
@@ -0,0 +1,86 @@
import { doltCleanFields } from "../fields.js";
import { dbName } from "../helpers.js";
export const databaseTests = [
{
q: `USE '${dbName}/main';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT DOLT_CLEAN('test_table')`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_clean: ["0"] }],
fields: doltCleanFields,
},
},
{
q: `SELECT datname FROM pg_database;`,
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [{ datname: dbName }, { datname: `${dbName}/main` }],
fields: [
{
name: "datname",
tableID: 0,
columnID: 0,
dataTypeID: 19,
dataTypeSize: 64,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
q: `CREATE DATABASE "new_db";`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT datname FROM pg_database;`,
res: {
command: "SELECT",
rowCount: 3,
oid: null,
rows: [
{ datname: "new_db" },
{ datname: dbName },
{ datname: `${dbName}/main` },
],
fields: [
{
name: "datname",
tableID: 0,
columnID: 0,
dataTypeID: 19,
dataTypeSize: 64,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
q: `SELECT dolt_version();`,
res: [{ "dolt_version()": "0.0.0" }],
matcher: (data) => {
return data.rows[0].dolt_version.length > 0;
},
},
];
@@ -0,0 +1,780 @@
import {
doltCommitFields,
doltDiffStatFields,
doltDiffSummaryFields,
doltSchemaDiffFields,
doltStatusFields,
pgTablesFields,
} from "../fields.js";
import { diffRowsMatcher, patchRowsMatcher } from "./matchers.js";
export const diffTests = [
{
q: "UPDATE test SET value=1 WHERE pk=0",
res: {
command: "UPDATE",
rowCount: 1,
oid: null,
rows: [],
fields: [],
},
},
{
q: "DROP TABLE test_info",
res: {
command: "DROP",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `CREATE SCHEMA anotherschema;`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `create table anotherschema.testanother (pk int, "value" int, primary key(pk));`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: "INSERT INTO anotherschema.testanother VALUES (1, 2)",
res: {
command: "INSERT",
rowCount: 1,
oid: 0,
rows: [],
fields: [],
},
},
{
q: `SELECT * FROM dolt.status ORDER BY table_name;`,
res: {
command: "SELECT",
rowCount: 5,
oid: null,
rows: [
{ table_name: "anotherschema", staged: false, status: "new schema" },
{
table_name: "anotherschema.testanother",
staged: false,
status: "new table",
},
{
table_name: "public.dolt_schemas",
staged: false,
status: "new table",
},
{ table_name: "public.test", staged: false, status: "modified" },
{ table_name: "public.test_info", staged: false, status: "deleted" },
],
fields: doltStatusFields,
},
},
{
q: "SELECT * FROM dolt_diff_summary('HEAD', 'WORKING') ORDER BY to_table_name;", // TODO: Prepared not working
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{
from_table_name: "public.test_info",
to_table_name: "",
diff_type: "dropped",
data_change: 1,
schema_change: 1,
},
{
from_table_name: "",
to_table_name: "anotherschema.testanother",
diff_type: "added",
data_change: 1,
schema_change: 1,
},
{
from_table_name: "",
to_table_name: "public.dolt_schemas",
diff_type: "added",
data_change: 1,
schema_change: 1,
},
{
from_table_name: "public.test",
to_table_name: "public.test",
diff_type: "modified",
data_change: 1,
schema_change: 0,
},
],
fields: doltDiffSummaryFields,
},
},
{
q: "SELECT * FROM dolt_diff_summary('HEAD', 'WORKING', 'test')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
from_table_name: "public.test",
to_table_name: "public.test",
diff_type: "modified",
data_change: 1,
schema_change: 0,
},
],
fields: doltDiffSummaryFields,
},
},
{
// TODO: What if a table with same name but different schema exists in different schema?
q: "SELECT * FROM dolt_diff_summary('HEAD', 'WORKING', 'testanother')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
from_table_name: "",
to_table_name: "anotherschema.testanother",
diff_type: "added",
data_change: 1,
schema_change: 1,
},
],
fields: doltDiffSummaryFields,
},
},
{
q: "SELECT * FROM dolt_diff_stat('HEAD', 'WORKING') ORDER BY table_name",
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{
table_name: "anotherschema.testanother",
rows_unmodified: "0",
rows_added: "1",
rows_deleted: "0",
rows_modified: "0",
cells_added: "2",
cells_deleted: "0",
cells_modified: "0",
old_row_count: "0",
new_row_count: "1",
old_cell_count: "0",
new_cell_count: "2",
},
{
table_name: "public.dolt_schemas",
rows_unmodified: "0",
rows_added: "1",
rows_deleted: "0",
rows_modified: "0",
cells_added: "5",
cells_deleted: "0",
cells_modified: "0",
old_row_count: "0",
new_row_count: "1",
old_cell_count: "0",
new_cell_count: "5",
},
{
table_name: "public.test",
rows_unmodified: "2",
rows_added: "0",
rows_deleted: "0",
rows_modified: "1",
cells_added: "0",
cells_deleted: "0",
cells_modified: "1",
old_row_count: "3",
new_row_count: "3",
old_cell_count: "6",
new_cell_count: "6",
},
{
table_name: "public.test_info",
rows_unmodified: "0",
rows_added: "0",
rows_deleted: "1",
rows_modified: "0",
cells_added: "0",
cells_deleted: "3",
cells_modified: "0",
old_row_count: "1",
new_row_count: "0",
old_cell_count: "3",
new_cell_count: "0",
},
],
fields: doltDiffStatFields,
},
},
{
q: "SELECT * FROM dolt_diff_stat('HEAD', 'WORKING', 'test_info')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
table_name: "public.test_info",
rows_unmodified: "0",
rows_added: "0",
rows_deleted: "1",
rows_modified: "0",
cells_added: "0",
cells_deleted: "3",
cells_modified: "0",
old_row_count: "1",
new_row_count: "0",
old_cell_count: "3",
new_cell_count: "0",
},
],
fields: doltDiffStatFields,
},
},
{
q: "SELECT * FROM dolt_diff_stat('HEAD', 'WORKING', 'testanother')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
table_name: "anotherschema.testanother",
rows_unmodified: "0",
rows_added: "1",
rows_deleted: "0",
rows_modified: "0",
cells_added: "2",
cells_deleted: "0",
cells_modified: "0",
old_row_count: "0",
new_row_count: "1",
old_cell_count: "0",
new_cell_count: "2",
},
],
fields: doltDiffStatFields,
},
},
{
q: "SELECT * FROM DOLT_DIFF('HEAD', 'WORKING', 'test') ORDER BY to_pk ASC, from_pk ASC LIMIT 10 OFFSET 0",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
to_pk: 0,
to_value: 1,
to_commit: "WORKING",
to_commit_date: "2023-03-09T07:44:47.670Z",
from_pk: 0,
from_value: 0,
from_commit: "HEAD",
from_commit_date: "2023-03-09T07:44:47.488Z",
diff_type: "modified",
},
],
fields: [],
},
matcher: diffRowsMatcher,
},
{
q: "SELECT * FROM DOLT_DIFF('HEAD', 'WORKING', 'test_info') ORDER BY to_id ASC, from_id ASC LIMIT 10 OFFSET 0",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
to_id: null,
to_info: null,
to_test_pk: null,
to_commit: "WORKING",
to_commit_date: "2023-03-09T07:53:48.614Z",
from_id: 1,
from_info: "info about test pk 0",
from_test_pk: 0,
from_commit: "HEAD",
from_commit_date: "2023-03-09T07:53:48.284Z",
diff_type: "removed",
},
],
fields: [],
},
matcher: diffRowsMatcher,
},
{
q: "SELECT * FROM DOLT_DIFF('HEAD', 'WORKING', 'testanother') ORDER BY to_pk ASC, from_pk ASC LIMIT 10 OFFSET 0",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
to_pk: 1,
to_value: 2,
to_commit: "WORKING",
to_commit_date: "2024-10-03T04:33:43.486Z",
from_pk: null,
from_value: null,
from_commit: "HEAD",
from_commit_date: "2024-10-03T04:33:43.430Z",
diff_type: "added",
},
],
fields: [],
},
matcher: diffRowsMatcher,
},
{
q: "SELECT * FROM DOLT_DIFF('HEAD', 'WORKING', 'dolt_schemas') ORDER BY to_name ASC, from_name ASC LIMIT 10 OFFSET 0",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
to_type: "view",
to_name: "myview",
to_fragment: "CREATE VIEW myview AS SELECT * FROM test",
to_extra: { CreatedAt: 0 },
to_sql_mode:
"NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES",
to_commit: "WORKING",
to_commit_date: "2023-03-09T07:56:29.035Z",
from_type: null,
from_name: null,
from_fragment: null,
from_extra: null,
from_sql_mode: null,
from_commit: "HEAD",
from_commit_date: "2023-03-09T07:56:28.841Z",
diff_type: "added",
},
],
fields: [],
},
matcher: diffRowsMatcher,
},
{
skip: true, // TODO: Order is not consistent
q: "SELECT * FROM DOLT_PATCH('HEAD', 'WORKING') WHERE diff_type = 'schema'",
res: {
command: "SELECT",
rowCount: 3,
oid: null,
rows: [
{
statement_order: "1",
from_commit_hash: "",
to_commit_hash: "WORKING",
table_name: "test_info",
diff_type: "schema",
statement: "DROP TABLE `test_info`;",
},
// TODO: Should `CREATE SCHEMA` statement be included here?
{
statement_order: "2",
from_commit_hash: "r6g8g61k89dpgb3cuks70jfnavr6b1q0",
to_commit_hash: "WORKING",
table_name: "testanother",
diff_type: "schema",
statement:
"CREATE TABLE `testanother` (\n" +
" `pk` integer NOT NULL,\n" +
" `value` integer,\n" +
" PRIMARY KEY (`pk`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
{
statement_order: "4", // TODO: Why is this 4?
from_commit_hash: "",
to_commit_hash: "WORKING",
table_name: "dolt_schemas",
diff_type: "schema",
statement:
"CREATE TABLE `dolt_schemas` (\n" + // TODO: No backticks
" `type` varchar(64) COLLATE utf8mb4_0900_ai_ci NOT NULL,\n" +
" `name` varchar(64) COLLATE utf8mb4_0900_ai_ci NOT NULL,\n" +
" `fragment` longtext,\n" +
" `extra` json,\n" +
" `sql_mode` varchar(256) COLLATE utf8mb4_0900_ai_ci,\n" +
" PRIMARY KEY (`type`,`name`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
],
fields: [],
},
matcher: patchRowsMatcher,
},
{
q: "SELECT * FROM DOLT_PATCH('HEAD', 'WORKING', 'test_info') WHERE diff_type = 'schema'",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
statement_order: "1",
from_commit_hash: "",
to_commit_hash: "WORKING",
table_name: "public.test_info",
diff_type: "schema",
statement: "DROP TABLE `test_info`;", // TODO: No backticks
},
],
},
matcher: patchRowsMatcher,
},
{
q: "SELECT * FROM DOLT_SCHEMA_DIFF('HEAD', 'WORKING') ORDER BY to_table_name;",
res: {
command: "SELECT",
rowCount: 3,
oid: null,
rows: [
{
from_table_name: "public.test_info",
to_table_name: "",
from_create_statement:
"CREATE TABLE `test_info` (\n" + // TODO: No backticks
" `id` integer NOT NULL,\n" +
" `info` varchar(255),\n" +
" `test_pk` integer,\n" +
" PRIMARY KEY (`id`),\n" +
" KEY `test_info_test_pk_fkey` (`test_pk`),\n" +
" CONSTRAINT `test_info_test_pk_fkey` FOREIGN KEY (`test_pk`) REFERENCES `test` (`pk`) ON DELETE NO ACTION ON UPDATE NO ACTION\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
to_create_statement: "",
},
{
from_table_name: "",
to_table_name: "anotherschema.testanother",
from_create_statement: "",
to_create_statement:
"CREATE TABLE `testanother` (\n" +
" `pk` integer NOT NULL,\n" +
" `value` integer,\n" +
" PRIMARY KEY (`pk`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
{
from_table_name: "",
to_table_name: "public.dolt_schemas",
from_create_statement: "",
to_create_statement:
"CREATE TABLE `dolt_schemas` (\n" + // TODO: No backticks
" `type` varchar(64) COLLATE utf8mb4_0900_ai_ci NOT NULL,\n" +
" `name` varchar(64) COLLATE utf8mb4_0900_ai_ci NOT NULL,\n" +
" `fragment` longtext,\n" +
" `extra` json,\n" +
" `sql_mode` varchar(256) COLLATE utf8mb4_0900_ai_ci,\n" +
" PRIMARY KEY (`type`,`name`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
],
fields: doltSchemaDiffFields,
},
},
{
q: "SELECT * FROM DOLT_SCHEMA_DIFF('HEAD', 'WORKING', 'test_info')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
from_table_name: "public.test_info",
to_table_name: "",
from_create_statement:
"CREATE TABLE `test_info` (\n" + // TODO: No backticks
" `id` integer NOT NULL,\n" +
" `info` varchar(255),\n" +
" `test_pk` integer,\n" +
" PRIMARY KEY (`id`),\n" +
" KEY `test_info_test_pk_fkey` (`test_pk`),\n" +
" CONSTRAINT `test_info_test_pk_fkey` FOREIGN KEY (`test_pk`) REFERENCES `test` (`pk`) ON DELETE NO ACTION ON UPDATE NO ACTION\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
to_create_statement: "",
},
],
fields: doltSchemaDiffFields,
},
},
{
q: "SELECT * FROM DOLT_SCHEMA_DIFF('HEAD', 'WORKING', 'testanother')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
from_table_name: "",
to_table_name: "anotherschema.testanother",
from_create_statement: "",
to_create_statement:
"CREATE TABLE `testanother` (\n" +
" `pk` integer NOT NULL,\n" +
" `value` integer,\n" +
" PRIMARY KEY (`pk`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
],
fields: doltSchemaDiffFields,
},
},
{
q: `SELECT DOLT_COMMIT('-A', '-m', $1::text)`,
p: ["Make some changes on branch"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: "" }],
fields: doltCommitFields,
},
},
// Three dot
{
q: "SELECT * FROM dolt_diff_summary('main...HEAD') ORDER BY to_table_name",
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{
from_table_name: "public.test_info",
to_table_name: "",
diff_type: "dropped",
data_change: 1,
schema_change: 1,
},
{
from_table_name: "",
to_table_name: "anotherschema.testanother",
diff_type: "added",
data_change: 1,
schema_change: 1,
},
{
from_table_name: "",
to_table_name: "public.dolt_schemas",
diff_type: "added",
data_change: 1,
schema_change: 1,
},
{
from_table_name: "public.test",
to_table_name: "public.test",
diff_type: "modified",
data_change: 1,
schema_change: 0,
},
],
fields: doltDiffSummaryFields,
},
},
{
q: "SELECT * FROM dolt_diff_summary('main...HEAD', 'test')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
from_table_name: "public.test",
to_table_name: "public.test",
diff_type: "modified",
data_change: 1,
schema_change: 0,
},
],
fields: doltDiffSummaryFields,
},
},
{
q: "SELECT * FROM dolt_diff_stat('main...HEAD') ORDER BY table_name;",
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{
table_name: "anotherschema.testanother",
rows_unmodified: "0",
rows_added: "1",
rows_deleted: "0",
rows_modified: "0",
cells_added: "2",
cells_deleted: "0",
cells_modified: "0",
old_row_count: "0",
new_row_count: "1",
old_cell_count: "0",
new_cell_count: "2",
},
{
table_name: "public.dolt_schemas",
rows_unmodified: "0",
rows_added: "1",
rows_deleted: "0",
rows_modified: "0",
cells_added: "5",
cells_deleted: "0",
cells_modified: "0",
old_row_count: "0",
new_row_count: "1",
old_cell_count: "0",
new_cell_count: "5",
},
{
table_name: "public.test",
rows_unmodified: "2",
rows_added: "0",
rows_deleted: "0",
rows_modified: "1",
cells_added: "0",
cells_deleted: "0",
cells_modified: "1",
old_row_count: "3",
new_row_count: "3",
old_cell_count: "6",
new_cell_count: "6",
},
{
table_name: "public.test_info",
rows_unmodified: "0",
rows_added: "0",
rows_deleted: "1",
rows_modified: "0",
cells_added: "0",
cells_deleted: "3",
cells_modified: "0",
old_row_count: "1",
new_row_count: "0",
old_cell_count: "3",
new_cell_count: "0",
},
],
fields: doltDiffStatFields,
},
},
{
q: "SELECT * FROM dolt_diff_stat('main...HEAD', 'test_info')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
table_name: "public.test_info",
rows_unmodified: "0",
rows_added: "0",
rows_deleted: "1",
rows_modified: "0",
cells_added: "0",
cells_deleted: "3",
cells_modified: "0",
old_row_count: "1",
new_row_count: "0",
old_cell_count: "3",
new_cell_count: "0",
},
],
fields: doltDiffStatFields,
},
},
{
skip: true, // TODO: Order not consistent
q: "SELECT * FROM DOLT_PATCH('main...HEAD') WHERE diff_type = 'schema'",
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{
statement_order: "1",
from_commit_hash: "",
to_commit_hash: "",
table_name: "public.test_info",
diff_type: "schema",
statement: "DROP TABLE `test_info`;",
},
{
statement_order: "2",
from_commit_hash: "",
to_commit_hash: "",
table_name: "anotherschema.testanother",
diff_type: "schema",
statement:
"CREATE TABLE `testanother` (\n" +
" `pk` integer NOT NULL,\n" +
" `value` integer,\n" +
" PRIMARY KEY (`pk`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
// TODO: `CREATE SCHEMA` here?
{
statement_order: "4", // TODO: why
from_commit_hash: "",
to_commit_hash: "",
table_name: "public.dolt_schemas",
diff_type: "schema",
statement:
"CREATE TABLE `dolt_schemas` (\n" +
" `type` varchar(64) COLLATE utf8mb4_0900_ai_ci NOT NULL,\n" +
" `name` varchar(64) COLLATE utf8mb4_0900_ai_ci NOT NULL,\n" +
" `fragment` longtext,\n" +
" `extra` json,\n" +
" `sql_mode` varchar(256) COLLATE utf8mb4_0900_ai_ci,\n" +
" PRIMARY KEY (`type`,`name`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
},
],
fields: [],
},
matcher: patchRowsMatcher,
},
{
q: "SELECT * FROM DOLT_PATCH('main...HEAD', 'test_info') WHERE diff_type = 'schema'",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
statement_order: "1",
from_commit_hash: "",
to_commit_hash: "",
table_name: "public.test_info",
diff_type: "schema",
statement: "DROP TABLE `test_info`;",
},
],
fields: [],
},
matcher: patchRowsMatcher,
},
];
@@ -0,0 +1,157 @@
import {
doltCommitFields,
doltDocsFields,
doltStatusFields,
} from "../fields.js";
const readmeText = `# README
## My List
- Item 1
- Item 2
`;
const updatedReadmeText = `${readmeText}-Item 3`;
export const docsTests = [
{
q: "select * from dolt.docs",
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltDocsFields,
},
},
{
q: "INSERT INTO dolt.docs VALUES ($1, $2);",
p: ["README.md", readmeText],
res: {
command: "INSERT",
rowCount: 1,
oid: 0,
rows: [],
fields: [],
},
},
{
q: `select * from dolt.docs where doc_name=$1`,
p: ["README.md"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ doc_name: "README.md", doc_text: readmeText }],
fields: doltDocsFields,
},
},
{
q: "INSERT INTO dolt.docs VALUES ($1, $2) ON CONFLICT (doc_name) DO UPDATE SET doc_text = $2",
p: ["README.md", updatedReadmeText],
res: {
command: "INSERT",
rowCount: 2, // TODO: This should be 1, but there's a bug in the GMS iterators that overcounts
oid: 0,
rows: [],
fields: [],
},
},
{
q: `select * from dolt.docs where doc_name=$1`,
p: ["README.md"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ doc_name: "README.md", doc_text: updatedReadmeText }],
fields: doltDocsFields,
},
},
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ table_name: "dolt.docs", staged: false, status: "new table" }],
fields: doltStatusFields,
},
},
{
q: `SELECT DOLT_COMMIT('-A', '-m', $1::text)`,
p: ["Add dolt.docs table"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: "" }],
fields: doltCommitFields,
},
},
{
q: `select * from dolt.docs where doc_name=$1`,
p: ["README.md"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ doc_name: "README.md", doc_text: updatedReadmeText }],
fields: doltDocsFields,
},
},
{
q: "DELETE FROM dolt.docs WHERE doc_name=$1",
p: ["README.md"],
res: {
command: "DELETE",
rowCount: 1,
oid: null,
rows: [],
fields: [],
},
},
{
q: `select * from dolt.docs where doc_name=$1`,
p: ["README.md"],
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltDocsFields,
},
},
{
q: `SET SEARCH_PATH = 'public,';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `select * from dolt.docs where doc_name=$1`,
p: ["README.md"],
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltDocsFields,
},
},
{
q: `SELECT DOLT_COMMIT('-A', '-m', $1::text)`,
p: ["Remove README"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: "" }],
fields: doltCommitFields,
},
},
];
@@ -0,0 +1,75 @@
import fs from "fs";
import { pipeline } from "stream/promises";
import { from as copyFrom } from "pg-copy-streams";
import path from "path";
import { branchTests } from "./branches.js";
import { databaseTests } from "./databases.js";
import { docsTests } from "./docs.js";
import { logTests } from "./logs.js";
import { assertEqualRows } from "../helpers.js";
import { mergeTests } from "./merge.js";
import { tableTests } from "./table.js";
import { schemaTests } from "./schemas.js";
import { tagsTests } from "./tags.js";
import { viewsTests } from "./views.js";
import { diffTests } from "./diffs.js";
const args = process.argv.slice(2);
const testDataPath = args[3];
export default async function runWorkbenchTests(database) {
await runTests(database, databaseTests, "database");
await runTests(database, branchTests, "branches");
await runTests(database, schemaTests, "schemas");
await runTests(database, logTests, "logs");
await runTests(database, mergeTests, "merge");
await runTests(database, tableTests, "tables");
await runTests(database, docsTests, "docs");
await runTests(database, tagsTests, "tags");
await runTests(database, viewsTests, "views");
await runTests(database, diffTests, "diffs");
}
async function runTests(database, tests, name) {
console.log("Running tests for", name);
await Promise.all(
tests.map(async (test) => {
if (test.skip) return;
if (test.file) {
const filePath = path.resolve(testDataPath, test.file);
try {
// TODO: Is it possible to test the COPY FROM output?
const ingestStream = database.client.query(copyFrom(test.q));
const sourceStream = fs.createReadStream(filePath);
await pipeline([sourceStream, ingestStream]);
} catch (err) {
console.log("Query errored:", test.q);
console.error(err);
process.exit(1);
}
return;
}
return database
.query(test.q, test.p)
.then((data) => {
assertEqualRows(test, data);
})
.catch((err) => {
if (test.expectedErr) {
if (err.message.includes(test.expectedErr)) {
return;
} else {
console.log("Query error did not match expected:", test.q);
}
} else {
console.log("Query errored:", test.q);
}
console.error(err);
process.exit(1);
});
})
);
}
@@ -0,0 +1,105 @@
import { logsMatcher } from "./matchers.js";
import { dbName } from "../helpers.js";
export const logTests = [
{
q: `SELECT * FROM DOLT_LOG('main', '--parents') LIMIT 10 OFFSET 0;`, // TODO: Prepared not working
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
commit_hash: "",
committer: "postgres",
email: "postgres@127.0.0.1",
date: "",
message: "CREATE DATABASE",
parents: ["3orrg69ou1loj2ph21guie3r2lf8bsab"],
},
{
commit_hash: "",
committer: "Dolt System Account",
email: "doltuser@dolthub.com",
date: "",
message: "Initialize data repository",
parents: [],
},
],
fields: [],
},
matcher: logsMatcher,
},
{
q: `USE '${dbName}/mybranch';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT * FROM dolt.log;`, // TODO: If we decide to implement AS OF, use here instead of USE statement above and below
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [
{
commit_hash: "",
committer: "Dolt",
email: "dolt@dolthub.com",
date: "",
message: "Create table test",
},
{
commit_hash: "",
committer: "postgres",
email: "postgres@127.0.0.1",
date: "",
message: "CREATE DATABASE",
},
{
commit_hash: "",
committer: "Dolt System Account",
email: "doltuser@dolthub.com",
date: "",
message: "Initialize data repository",
},
],
},
matcher: logsMatcher,
},
{
q: `USE '${dbName}/main';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT * FROM DOLT_LOG('main..mybranch', '--parents')`, // TODO: Prepared not working
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
commit_hash: "",
committer: "Dolt",
email: "dolt@dolthub.com",
date: "",
message: "Create table test",
parents: [""],
},
],
fields: [],
},
matcher: logsMatcher,
},
];
@@ -0,0 +1,173 @@
function matcher(rows, exp, exceptionKeys, getExceptionIsValid) {
// Row lengths match
if (rows.length !== exp.length) {
console.log("row lengths don't match", rows.length, exp.length);
return false;
}
for (let i = 0; i < rows.length; i++) {
const rowKeys = Object.keys(rows[i]);
const expKeys = Object.keys(exp[i]);
// Row key lengths match
if (rowKeys.length !== expKeys.length) {
console.log(
"row key lengths don't match",
rowKeys.length,
expKeys.length
);
return false;
}
// Row key values match
for (let j = 0; j < rowKeys.length; j++) {
const rowKey = rowKeys[j];
// Check if key has an exception function
if (exceptionKeys.includes(rowKey)) {
const isValid = getExceptionIsValid(rows[i], rowKey, exp[i]);
if (!isValid) {
console.log("exception was not valid for key:", rowKey);
return false;
}
} else {
// Compare cell values
const cellVal = JSON.stringify(rows[i][rowKey]);
const expCellVal = JSON.stringify(exp[i][rowKey]);
if (cellVal !== expCellVal) {
console.log("values don't match", cellVal, expCellVal);
return false;
}
}
}
}
return true;
}
function commitHashIsValid(commit) {
return commit === "STAGED" || commit === "WORKING" || commit.length === 32;
}
function dateIsValid(date) {
return JSON.stringify(date).length > 0;
}
export function branchesMatcher(data, exp) {
const exceptionKeys = ["hash", "latest_commit_date"];
function getExceptionIsValid(row, key) {
const val = row[key];
switch (key) {
case "hash":
return commitHashIsValid(val);
case "latest_commit_date":
return dateIsValid(val);
default:
return false;
}
}
return matcher(data.rows, exp.rows, exceptionKeys, getExceptionIsValid);
}
export function logsMatcher(data, exp) {
const exceptionKeys = ["commit_hash", "date", "parents"];
function getExceptionIsValid(row, key, expRow) {
const val = row[key];
switch (key) {
case "commit_hash":
return commitHashIsValid(val);
case "date":
return dateIsValid(val);
case "parents":
const numParents = val.split(", ").filter((v) => !!v.length).length;
const expParents = expRow.parents.length;
return numParents === expParents;
default:
return false;
}
}
return matcher(data.rows, exp.rows, exceptionKeys, getExceptionIsValid);
}
export function mergeBaseMatcher(data) {
if (data.rows.length !== 1) {
return false;
}
return commitHashIsValid(data.rows[0].dolt_merge_base);
}
export function mergeMatcher(data, exp) {
if (data.rows.length !== 1) {
console.log("Rows length not 1", data.rows.length);
return false;
}
const row = data.rows[0].dolt_merge;
const expRow = exp.rows[0].dolt_merge;
// Check valid commit hash
if (!commitHashIsValid(row[0])) {
console.log("Invalid commit hash", row[0]);
return false;
}
// Check the rest of the fields
for (let i = 1; i < row.length; i++) {
if (row[i] !== expRow[i]) {
console.log("Values don't match", row[i], expRow[i]);
return false;
}
}
return true;
}
export function tagsMatcher(data, exp) {
const exceptionKeys = ["tag_hash", "date"];
function getExceptionIsValid(row, key) {
const val = row[key];
switch (key) {
case "tag_hash":
return commitHashIsValid(val);
case "date":
return dateIsValid(val);
default:
return false;
}
}
return matcher(data.rows, exp.rows, exceptionKeys, getExceptionIsValid);
}
export function diffRowsMatcher(data, exp) {
const exceptionKeys = ["to_commit_date", "from_commit_date"];
function getExceptionIsValid(row, key) {
const val = row[key];
switch (key) {
case "to_commit_date":
case "from_commit_date":
return dateIsValid(val);
default:
return false;
}
}
return matcher(data.rows, exp.rows, exceptionKeys, getExceptionIsValid);
}
export function patchRowsMatcher(data, exp) {
const exceptionKeys = ["to_commit_hash", "from_commit_hash"];
function getExceptionIsValid(row, key) {
const val = row[key];
switch (key) {
case "to_commit_hash":
case "from_commit_hash":
return commitHashIsValid(val);
default:
return false;
}
}
return matcher(data.rows, exp.rows, exceptionKeys, getExceptionIsValid);
}
@@ -0,0 +1,87 @@
import { doltStatusFields } from "../fields.js";
import { logsMatcher, mergeBaseMatcher, mergeMatcher } from "./matchers.js";
export const mergeTests = [
{
q: `SELECT DOLT_MERGE_BASE($1::text, $2::text);`,
p: ["mybranch", "main"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_merge_base: "" }],
fields: [],
},
matcher: mergeBaseMatcher,
},
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltStatusFields,
},
},
{
q: `SELECT DOLT_MERGE($1::text, '--no-ff', '-m', $2::text)`,
p: ["mybranch", "Merge mybranch into main"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
dolt_merge: ["", "0", "0", "merge successful"],
},
],
fields: [],
},
matcher: mergeMatcher,
},
{
q: `SELECT * FROM DOLT_LOG('main', '--parents') LIMIT 10 OFFSET 0;`, // TODO: Prepared not working
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{
commit_hash: "",
message: "Merge mybranch into main",
committer: "postgres",
email: "postgres@127.0.0.1",
date: "",
parents: ["", ""],
},
{
commit_hash: "",
committer: "Dolt",
email: "dolt@dolthub.com",
date: "",
message: "Create table test",
parents: [""],
},
{
commit_hash: "",
committer: "postgres",
email: "postgres@127.0.0.1",
date: "",
message: "CREATE DATABASE",
parents: [""],
},
{
commit_hash: "",
committer: "Dolt System Account",
email: "doltuser@dolthub.com",
date: "",
message: "Initialize data repository",
parents: [],
},
],
fields: [],
},
matcher: logsMatcher,
},
];
@@ -0,0 +1,197 @@
import {
doltBranchFields,
doltStatusFields,
doltCommitFields,
schemaNameField,
pgTablesFields,
} from "../fields.js";
import { dbName } from "../helpers.js";
export const schemaTests = [
{
q: `SELECT DOLT_BRANCH($1::text, $2::text)`, // TODO: Should work without casts
p: ["schemabranch", "main"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_branch: ["0"] }],
fields: doltBranchFields,
},
},
{
q: `USE '${dbName}/schemabranch';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `create table testpub (pk int, "value" int, primary key(pk));`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT DOLT_COMMIT('-Am', $1::text, '--author', $2::text);`,
p: ["Create table testpub", "Dolt <dolt@dolthub.com>"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: "" }],
fields: doltCommitFields,
},
},
{
q: `CREATE SCHEMA testschema;`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SET SEARCH_PATH = 'testschema';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `create table test2 (pk int, "value" int, primary key(pk));`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT * FROM dolt.status;`,
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [
{
table_name: "testschema.test2",
staged: false,
status: "new table",
},
{ table_name: "testschema", staged: false, status: "new schema" },
],
fields: doltStatusFields,
},
},
{
q: `SELECT schema_name FROM information_schema.schemata WHERE catalog_name = $1;`,
p: [`${dbName}/schemabranch`],
res: {
command: "SELECT",
rowCount: 5,
oid: null,
rows: [
{ schema_name: "dolt" },
{ schema_name: "pg_catalog" },
{ schema_name: "public" },
{ schema_name: "testschema" },
{ schema_name: "information_schema" },
],
fields: [schemaNameField],
},
},
{
q: `SELECT DOLT_COMMIT('-Am', $1::text, '--author', $2::text);`,
p: ["Create table test2", "Dolt <dolt@dolthub.com>"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: "" }],
fields: doltCommitFields,
},
},
{
q: `SELECT schemaname, tablename FROM pg_catalog.pg_tables where schemaname=$1;`,
p: ["testschema"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ schemaname: "testschema", tablename: "test2" }],
fields: pgTablesFields,
},
},
{
q: `SET SEARCH_PATH = 'public';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT schemaname, tablename FROM pg_catalog.pg_tables where schemaname=$1;`,
p: ["public"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ schemaname: "public", tablename: "testpub" }],
fields: pgTablesFields,
},
},
{
q: `USE '${dbName}/main';`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT schema_name FROM information_schema.schemata WHERE catalog_name = $1;`,
p: [`${dbName}/main`],
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{ schema_name: "dolt" },
{ schema_name: "pg_catalog" },
{ schema_name: "public" },
{ schema_name: "information_schema" },
],
fields: [schemaNameField],
},
},
{
q: `SELECT schemaname, tablename FROM pg_catalog.pg_tables where schemaname=$1;`,
p: ["public"],
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: pgTablesFields,
},
},
];
@@ -0,0 +1,455 @@
import { dbName } from "../helpers.js";
import {
doltAddFields,
doltCheckoutFields,
doltCommitFields,
doltDiffStatFields,
doltResetFields,
doltStatusFields,
infoSchemaKeyColumnUsageFields,
} from "../fields.js";
const testInfoFields = [
{
name: "id",
tableID: 0,
columnID: 0,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
{
name: "info",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 1020,
dataTypeModifier: 259,
format: "text",
},
{
name: "test_pk",
tableID: 0,
columnID: 0,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
];
export const tableTests = [
{
q: "SET search_path TO DEFAULT",
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: "INSERT INTO test VALUES (0, 0), (1, 1), (2,2)",
res: {
command: "INSERT",
rowCount: 3,
oid: 0,
rows: [],
fields: [],
},
},
{
q: `CREATE UNIQUE INDEX test_idx ON test (pk, value)`,
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: `SELECT ordinal_position, column_name, udt_name as data_type, is_nullable, column_default FROM information_schema.columns WHERE table_catalog=$1 AND table_schema = $2 AND table_name = $3;`,
p: [`${dbName}/main`, "public", "test"],
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [
{
ordinal_position: 1,
column_name: "pk",
data_type: "int4",
is_nullable: "NO",
column_default: null,
},
{
ordinal_position: 2,
column_name: "value",
data_type: "int4",
is_nullable: "YES",
column_default: null,
},
],
fields: [
{
name: "ordinal_position",
tableID: 0,
columnID: 0,
dataTypeID: 23,
dataTypeSize: 4,
dataTypeModifier: -1,
format: "text",
},
{
name: "column_name",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
{
name: "data_type",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
{
name: "is_nullable",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 12,
dataTypeModifier: 7,
format: "text",
},
{
name: "column_default",
tableID: 0,
columnID: 0,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
q: `SELECT DOLT_COMMIT('-A', '-m', $1::text)`,
p: ["Add some rows and a column index"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: [""] }],
fields: doltCommitFields,
},
},
{
skip: true, // TODO: ORDER BY is not yet supported
q: `SELECT
table_name, index_name, comment, non_unique, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS COLUMNS
FROM information_schema.statistics
WHERE table_catalog=$1 AND table_schema=$2 AND table_name=$3 AND index_name!='PRIMARY'
GROUP BY index_name;`,
p: [`${dbName}/main`, "public", "test"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
TABLE_NAME: "test",
INDEX_NAME: "test_idx",
COMMENT: "",
NON_UNIQUE: 0,
COLUMNS: "pk,value",
},
],
},
},
{
q: "CREATE TABLE test_info (id int, info varchar(255), test_pk int, primary key(id), foreign key (test_pk) references test(pk))",
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: "INSERT INTO test_info VALUES (1, 'info about test pk 0', 0)",
res: {
command: "INSERT",
rowCount: 1,
oid: 0,
rows: [],
fields: [],
},
},
{
q: `SELECT DOLT_COMMIT('-A', '-m', $1::text)`,
p: ["Add test_info with foreign key"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_commit: [""] }],
fields: doltCommitFields,
},
},
{
q: `SELECT "table_schema", "table_name" FROM "information_schema"."tables" WHERE "table_schema" = $1 AND "table_catalog" = $2;`,
p: ["public", `${dbName}/main`],
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [
{ table_schema: "public", table_name: "test" },
{ table_schema: "public", table_name: "test_info" },
],
fields: [
{
name: "table_schema",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
{
name: "table_name",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
],
},
},
{
q: `SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_name=$1 AND table_schema=$2 AND table_catalog=$3 AND referenced_table_schema IS NOT NULL`,
p: ["test_info", "public", `${dbName}/main`],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
CONSTRAINT_CATALOG: `${dbName}/main`,
CONSTRAINT_SCHEMA: "public",
CONSTRAINT_NAME: "test_info_test_pk_fkey",
TABLE_CATALOG: `${dbName}/main`,
TABLE_SCHEMA: "public",
TABLE_NAME: "test_info",
COLUMN_NAME: "test_pk",
ORDINAL_POSITION: 1,
POSITION_IN_UNIQUE_CONSTRAINT: 1,
REFERENCED_TABLE_SCHEMA: `${dbName}/main`,
REFERENCED_TABLE_NAME: "test",
REFERENCED_COLUMN_NAME: "pk",
},
],
fields: infoSchemaKeyColumnUsageFields,
},
},
{
q: `SELECT * FROM "public"."test_info" "public.test_info" ORDER BY id ASC LIMIT 10;`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ id: 1, info: "info about test pk 0", test_pk: 0 }],
fields: testInfoFields,
},
},
{
q: `USE '${dbName}/main'`,
res: {
command: "SET",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
// Copy from tests
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltStatusFields,
},
},
{
q: `COPY "test_info" FROM STDIN WITH (FORMAT csv, HEADER TRUE);`,
file: "insert_test_info.csv",
res: { command: "COPY", rowCount: 3, oid: null, rows: [], fields: [] },
},
{
q: "SELECT * FROM test_info",
res: {
command: "SELECT",
rowCount: 4,
oid: null,
rows: [
{ id: 1, info: "info about test pk 0", test_pk: 0 },
{ id: 4, info: "string for 4", test_pk: 1 },
{ id: 5, info: "string for 5", test_pk: 0 },
{ id: 6, info: "string for 6", test_pk: 0 },
],
fields: testInfoFields,
},
},
{
q: "SELECT * FROM dolt_diff_stat('HEAD', 'WORKING')", // TODO: Prepared not working
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
table_name: "public.test_info",
rows_unmodified: "1",
rows_added: "3",
rows_deleted: "0",
rows_modified: "0",
cells_added: "9",
cells_deleted: "0",
cells_modified: "0",
old_row_count: "1",
new_row_count: "4",
old_cell_count: "3",
new_cell_count: "12",
},
],
fields: doltDiffStatFields,
},
},
{
q: `COPY "test_info" FROM STDIN WITH (FORMAT csv, HEADER TRUE, DELIMITER '|');`,
file: "insert_test_info.psv",
res: { command: "COPY", rowCount: 3, oid: null, rows: [], fields: [] },
},
{
skip: true,
q: "SELECT * FROM dolt_diff_stat('HEAD', 'WORKING')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
table_name: "public.test_info",
rows_unmodified: 0,
rows_added: 3,
rows_deleted: 0,
rows_modified: 1,
cells_added: 9,
cells_deleted: 0,
cells_modified: 1,
old_row_count: 1,
new_row_count: 4,
old_cell_count: 3,
new_cell_count: 12,
},
],
fields: doltDiffStatFields,
},
},
// Add and revert load data changes
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{ table_name: "public.test_info", staged: false, status: "modified" },
],
fields: doltStatusFields,
},
},
{
q: "SELECT DOLT_ADD('.')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_add: ["0"] }],
fields: doltAddFields,
},
},
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{ table_name: "public.test_info", staged: true, status: "modified" },
],
fields: doltStatusFields,
},
},
{
q: "SELECT DOLT_RESET('test_info');",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_reset: ["0"] }],
fields: doltResetFields,
},
},
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{ table_name: "public.test_info", staged: false, status: "modified" },
],
fields: doltStatusFields,
},
},
{
q: "SELECT DOLT_CHECKOUT('test_info')",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_checkout: ["0"] }],
fields: doltCheckoutFields,
},
},
{
q: `SELECT * FROM dolt.status`,
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltStatusFields,
},
},
];
@@ -0,0 +1,121 @@
import { doltTagFields, doltTagsFields } from "../fields.js";
import { tagsMatcher } from "./matchers.js";
export const tagsTests = [
{
q: "SELECT * FROM dolt.tags ORDER BY date DESC",
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltTagsFields,
},
},
{
q: `SELECT DOLT_TAG($1::text, $2::text);`,
p: ["mytag", "main"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_tag: ["0"] }],
fields: doltTagFields,
},
},
{
q: "SELECT * FROM dolt.tags ORDER BY date DESC",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
tag_name: "mytag",
message: "",
tagger: "Dolt System Account",
email: "doltuser@dolthub.com",
tag_hash: "",
date: "",
},
],
fields: doltTagsFields,
},
matcher: tagsMatcher,
},
{
q: `SELECT DOLT_TAG('-m', $1::text, $2::text, $3::text)`,
p: ["latest release", "mytagnew", "main"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_tag: ["0"] }],
fields: doltTagFields,
},
},
{
q: "SELECT * FROM dolt.tags ORDER BY date DESC",
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [
{
tag_name: "mytagnew",
message: "latest release",
tagger: "Dolt System Account",
email: "doltuser@dolthub.com",
tag_hash: "",
date: "",
},
{
tag_name: "mytag",
message: "",
tagger: "Dolt System Account",
email: "doltuser@dolthub.com",
tag_hash: "",
date: "",
},
],
fields: doltTagsFields,
},
matcher: tagsMatcher,
},
{
q: `SELECT DOLT_TAG('-d', $1::text)`,
p: ["mytagnew"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_tag: ["0"] }],
fields: doltTagFields,
},
},
{
q: "SELECT * FROM dolt.tags ORDER BY date DESC",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
tag_name: "mytag",
message: "",
tagger: "Dolt System Account",
email: "doltuser@dolthub.com",
tag_hash: "",
date: "",
},
],
fields: doltTagsFields,
},
matcher: tagsMatcher,
},
{
q: `SELECT DOLT_COMMIT('-A', '-m', $1::text)`,
p: ["Add a tag"],
expectedErr: "nothing to commit",
},
];
@@ -0,0 +1,196 @@
import {
doltCheckoutFields,
doltSchemasFields,
pgTablesFields,
} from "../fields.js";
export const viewsTests = [
{
q: "SELECT DOLT_CHECKOUT('-b', $1::text);",
p: ["more-updates"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ dolt_checkout: ["0", "Switched to branch 'more-updates'"] }],
fields: doltCheckoutFields,
},
},
{
q: "SELECT * FROM dolt_schemas LIMIT 10 OFFSET 0;",
res: {
command: "SELECT",
rowCount: 0,
oid: null,
rows: [],
fields: doltSchemasFields,
},
},
{
q: "CREATE VIEW myview AS SELECT * FROM test;",
res: {
command: "CREATE",
rowCount: null,
oid: null,
rows: [],
fields: [],
},
},
{
q: "SELECT * FROM dolt_schemas LIMIT 10 OFFSET 0",
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [
{
type: "view",
name: "myview",
fragment: "CREATE VIEW myview AS SELECT * FROM test",
extra: { CreatedAt: 0 },
sql_mode:
"NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES",
},
],
fields: doltSchemasFields,
},
},
{
// Excludes views
q: "SELECT schemaname, tablename FROM pg_catalog.pg_tables where schemaname=$1;",
p: ["public"],
res: {
command: "SELECT",
rowCount: 2,
oid: null,
rows: [
{
schemaname: "public",
tablename: "test",
},
{
schemaname: "public",
tablename: "test_info",
},
],
fields: pgTablesFields,
},
},
{
// Includes views
q: "SELECT table_name FROM INFORMATION_SCHEMA.views WHERE table_schema = $1;",
p: ["public"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ table_name: "myview" }],
fields: [
{
name: "table_name",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
],
},
},
{
// Includes views
q: "SELECT viewname FROM pg_catalog.pg_views WHERE schemaname=$1;",
p: ["public"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ viewname: "myview" }],
fields: [
{
name: "viewname",
tableID: 0,
columnID: 0,
dataTypeID: 19,
dataTypeSize: 64,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
// Includes tables and views
q: "SELECT table_schema, table_name, table_type FROM INFORMATION_SCHEMA.tables WHERE table_schema = $1;",
p: ["public"],
res: {
command: "SELECT",
rowCount: 3,
oid: null,
rows: [
{ table_schema: "public", table_name: "myview", table_type: "VIEW" },
{
table_schema: "public",
table_name: "test",
table_type: "BASE TABLE",
},
{
table_schema: "public",
table_name: "test_info",
table_type: "BASE TABLE",
},
],
fields: [
{
name: "table_schema",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
{
name: "table_name",
tableID: 0,
columnID: 0,
dataTypeID: 1043,
dataTypeSize: 256,
dataTypeModifier: 68,
format: "text",
},
{
name: "table_type",
tableID: 0,
columnID: 0,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
],
},
},
{
q: `SELECT pg_get_viewdef($1::regclass, true)`,
p: ["myview"],
res: {
command: "SELECT",
rowCount: 1,
oid: null,
rows: [{ pg_get_viewdef: "SELECT * FROM test" }],
fields: [
{
name: "pg_get_viewdef",
tableID: 0,
columnID: 0,
dataTypeID: 25,
dataTypeSize: -1,
dataTypeModifier: -1,
format: "text",
},
],
},
},
];
@@ -0,0 +1,10 @@
LDFLAGS := -lodbc
all: psqlodbc-test
psqlodbc-test: psqlodbc-test.c
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
rm -f psqlodbc-test
@@ -0,0 +1,127 @@
#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void die(SQLSMALLINT type, SQLHANDLE handle, const char *op) {
SQLCHAR state[6], msg[256];
SQLINTEGER native;
SQLSMALLINT len;
SQLGetDiagRec(type, handle, 1, state, &native, msg, sizeof(msg), &len);
fprintf(stderr, "%s failed: %s (state=%s)\n", op, msg, state);
exit(1);
}
#define CHECK_DBC(ret, h, op) do { if ((ret) != SQL_SUCCESS && (ret) != SQL_SUCCESS_WITH_INFO) die(SQL_HANDLE_DBC, (h), (op)); } while (0)
#define CHECK_STMT(ret, h, op) do { if ((ret) != SQL_SUCCESS && (ret) != SQL_SUCCESS_WITH_INFO) die(SQL_HANDLE_STMT, (h), (op)); } while (0)
static void exec_query(SQLHDBC dbc, const char *sql) {
SQLHSTMT stmt;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLRETURN ret = SQLExecDirect(stmt, (SQLCHAR *)sql, SQL_NTS);
CHECK_STMT(ret, stmt, sql);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
static long long fetch_count(SQLHDBC dbc, const char *sql) {
SQLHSTMT stmt;
SQLCHAR buf[32];
SQLLEN indicator;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLRETURN ret = SQLExecDirect(stmt, (SQLCHAR *)sql, SQL_NTS);
CHECK_STMT(ret, stmt, sql);
SQLFetch(stmt);
SQLGetData(stmt, 1, SQL_C_CHAR, buf, sizeof(buf), &indicator);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return atoll((char *)buf);
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <user> <port>\n", argv[0]);
return 1;
}
const char *user = argv[1];
const char *port = argv[2];
SQLHENV env;
SQLHDBC dbc;
SQLRETURN ret;
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
char connStr[512];
snprintf(connStr, sizeof(connStr),
"Driver={PostgreSQL Unicode};Server=localhost;Port=%s;Database=postgres;UID=%s;PWD=password;",
port, user);
SQLCHAR outStr[1024];
SQLSMALLINT outLen;
ret = SQLDriverConnect(dbc, NULL, (SQLCHAR *)connStr, SQL_NTS,
outStr, sizeof(outStr), &outLen, SQL_DRIVER_NOPROMPT);
CHECK_DBC(ret, dbc, "SQLDriverConnect");
// SELECT from test_table (set up by bats setup())
{
SQLHSTMT stmt;
SQLINTEGER pk;
SQLLEN indicator;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
ret = SQLExecDirect(stmt, (SQLCHAR *)"SELECT pk FROM test_table LIMIT 1", SQL_NTS);
CHECK_STMT(ret, stmt, "SELECT pk FROM test_table LIMIT 1");
SQLFetch(stmt);
SQLGetData(stmt, 1, SQL_C_SLONG, &pk, sizeof(pk), &indicator);
if (pk != 1) {
fprintf(stderr, "expected pk=1, got %d\n", pk);
return 1;
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
exec_query(dbc, "INSERT INTO test_table VALUES (2)");
long long count = fetch_count(dbc, "SELECT COUNT(*) FROM test_table");
if (count != 2) {
fprintf(stderr, "expected count=2, got %lld\n", count);
return 1;
}
// Dolt workflow: create table, insert, commit, branch, insert, commit, merge
const char *dolt_queries[] = {
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
NULL
};
for (int i = 0; dolt_queries[i]; i++)
exec_query(dbc, dolt_queries[i]);
count = fetch_count(dbc, "SELECT COUNT(*) FROM dolt_log");
if (count != 4) {
fprintf(stderr, "expected 4 dolt_log entries, got %lld\n", count);
return 1;
}
count = fetch_count(dbc, "SELECT COUNT(*) FROM test");
if (count != 2) {
fprintf(stderr, "expected 2 rows in test, got %lld\n", count);
return 1;
}
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
printf("psqlODBC test passed\n");
return 0;
}
@@ -0,0 +1,80 @@
use strict;
use DBI;
my $QUERY_RESPONSE = [
{ "create table test (pk int, value int, d1 decimal(9, 3), f1 float, c1 char(10), t1 text, primary key(pk))" => '0E0' },
{ "insert into test (pk, value, d1, f1, c1, t1) values (0,0,0.0,0.0,'abc','a1')" => 1 },
{ "select * from test" => 1 },
{"select dolt_add('-A');" => 1 },
{"select dolt_commit('-m', 'my commit')" => 1 },
{"select dolt_checkout('-b', 'mybranch')" => 1 },
{"insert into test (pk, value, d1, f1, c1, t1) values (10,10, 123456.789, 420.42,'example','some text')" => 1 },
{"select dolt_commit('-a', '-m', 'my commit2')" => 1 },
{"select dolt_checkout('main')" => 1 },
{"select dolt_merge('mybranch')" => 1 },
{"select COUNT(*) FROM dolt.log" => 1 },
];
my $user = $ARGV[0];
my $port = $ARGV[1];
my $db = "postgres";
my $dsn = "DBI:Pg:database=$db;host=127.0.0.1;port=$port";
# Connect to the database
my $dbh = DBI->connect($dsn, $user, "password", { PrintError => 0, RaiseError => 1 });
die "failed to connect to database:DBI->errstr()" unless($dbh);
foreach my $query_response ( @{$QUERY_RESPONSE} ) {
my @query_keys = keys %{$query_response};
my $query = $query_keys[0];
my $exp_result = $query_response->{$query};
my $result = $dbh->do($query);
if ( $result != $exp_result ) {
print "QUERY: $query\n";
print "EXPECTED: $exp_result\n";
print "RESULT: $result\n";
exit 1
}
}
# Define the SQL query
my $query = "SELECT * FROM test WHERE pk = 10";
# Prepare the query
my $sth = $dbh->prepare($query);
# Execute the query
$sth->execute();
my @cols = ("pk", "value", "d1", "f1", "c1", "t1");
my @expectedRowResults = ("10", "10", "123456.789", "420.42", "example ", "some text");
# Fetch and process the results
while (my $row = $sth->fetchrow_hashref()) {
if ($row) {
# Process the row if it's defined
# Access individual column values using hash dereferencing
for my $i (0 .. $#cols) {
my $val = $row->{$cols[$i]};
# Comparing the strings using eq operator
my $c = $val eq $expectedRowResults[$i];
if ( $c == 0 ) {
print "Expected: '$expectedRowResults[$i]'\n";
print "Actual: '$val'\n";
# Disconnect from the database
$dbh->disconnect();
exit 1;
}
}
} else {
print("no rows?");
last; # Exit the loop when there are no more rows
}
}
# Disconnect from the database
$dbh->disconnect();
exit 0;
@@ -0,0 +1,46 @@
<?php
$user = $argv[1];
$port = $argv[2];
$db = 'postgres';
$conn = new PDO("pgsql:host=localhost;port={$port};dbname={$db}", $user, 'password');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$queries = [
"create table test (pk int, value int, d1 decimal(9, 3), f1 float, c1 char(10), t1 text, primary key(pk))" => 0,
"insert into test (pk, value, d1, f1, c1, t1) values (0,0,0.0,0.0,'abc','a1')" => 1,
"select * from test" => 1,
"select dolt_add('-A');" => 1,
"select dolt_commit('-m', 'my commit')" => 1,
"select dolt_checkout('-b', 'mybranch')" => 1,
"insert into test (pk, value, d1, f1, c1, t1) values (1,1, 123456.789, 420.42,'example','some text')" => 1,
"select dolt_commit('-a', '-m', 'my commit2')" => 1,
"select dolt_checkout('main')" => 1,
"select dolt_merge('mybranch')" => 1,
"select COUNT(*) FROM dolt.log" => 1
];
foreach ($queries as $query => $expected) {
$result = $conn->query($query);
if ($result->rowCount() != $expected) {
echo "LENGTH: {$result->rowCount()}\n";
echo "QUERY: {$query}\n";
echo "EXPECTED: {$expected}\n";
echo "RESULT: {$result}";
exit(1);
}
}
$result = $conn->query("SELECT * FROM test WHERE pk = 1");
assert(1 == $result->rowCount());
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
assert(1 == $row['pk']);
assert(1 == $row['value']);
assert(123456.789 == $row['d1']);
assert(420.42 == $row['f1']);
assert("example " == $row['c1']);
assert("some text" == $row['t1']);
}
exit(0)
?>
@@ -0,0 +1,56 @@
<?php
$user = $argv[1];
$port = $argv[2];
$db = 'postgres';
$conn = pg_connect("host = localhost port = $port dbname = $db user = $user password = 'password'")
or die('Could not connect: ' . pg_result_error());
$queries = [
"create table test (pk int, value int, d1 decimal(9, 3), f1 float, c1 char(10), t1 text, primary key(pk))" => 0,
"insert into test (pk, value, d1, f1, c1, t1) values (0,0,0.0,0.0,'abc','a1')" => 0,
"select * from test" => 1,
"select dolt_add('-A');" => 1,
"select dolt_commit('-m', 'my commit')" => 1,
"select dolt_checkout('-b', 'mybranch')" => 1,
"insert into test (pk, value, d1, f1, c1, t1) values (1,1, 123456.789, 420.42,'example','some text')" => 0,
"select dolt_commit('-a', '-m', 'my commit2')" => 1,
"select dolt_checkout('main')" => 1,
"select dolt_merge('mybranch')" => 1,
"select COUNT(*) FROM dolt.log" => 1
];
foreach ($queries as $query => $expected) {
$result = pg_query($conn, $query);
if (is_bool($result)) {
if (!$result) {
echo "LENGTH: {pg_num_rows($result)}\n";
echo "QUERY: {$query}\n";
echo "EXPECTED: {$expected}\n";
echo "RESULT: {$result}";
exit(1);
}
} else if (pg_num_rows($result) != $expected) {
echo "LENGTH: {pg_num_rows($result)}\n";
echo "QUERY: {$query}\n";
echo "EXPECTED: {$expected}\n";
echo "RESULT: {$result}";
exit(1);
}
}
$result = pg_query($conn, "SELECT * FROM test WHERE pk = 1");
assert(1 == pg_num_rows($result));
while($row = pg_fetch_assoc($result)) {
assert(1 == $row['pk']);
assert(1 == $row['value']);
assert(123456.789 == $row['d1']);
assert(420.42 == $row['f1']);
assert("example " == $row['c1']);
assert("some text" == $row['t1']);
}
pg_close($conn);
exit(0)
?>
@@ -0,0 +1,4 @@
#!/bin/sh
echo "Running postgres-client-tests:"
bats /postgres-client-tests/postgres-client-tests.bats
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helpers.bash
# PostgreSQL client tests are set up to test Doltgres as a PostgreSQL server and
# standard PostgreSQL Clients in a wide array of languages.
setup() {
setup_doltgres_repo
query_server -c "CREATE TABLE IF NOT EXISTS test_table(pk int)" -t
query_server -c "DELETE FROM test_table" -t
query_server -c "INSERT INTO test_table VALUES (1)" -t
}
teardown() {
cd ..
teardown_doltgres_repo
}
@test "postgres-connector-java client" {
javac $BATS_TEST_DIRNAME/java/PostgresTest.java
java -cp $BATS_TEST_DIRNAME/java:$BATS_TEST_DIRNAME/java/postgresql-42.7.3.jar PostgresTest $USER $PORT
}
@test "r2dbc-postgresql client" {
java -jar /build/bin/r2dbc/r2dbc-test.jar $USER $PORT
}
@test "node postgres client" {
node $BATS_TEST_DIRNAME/node/index.js $USER $PORT
}
@test "knex node postgres client" {
DOLTGRES_VERSION=$( doltgres --version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p' )
echo $DOLTGRES_VERSION
node $BATS_TEST_DIRNAME/node/knex.js $USER $PORT $DOLTGRES_VERSION
}
@test "node postgres client, workbench stability" {
skip "Passes locally, fails on CI, investigating"
DOLTGRES_VERSION=$( doltgres --version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p' )
echo $DOLTGRES_VERSION
node $BATS_TEST_DIRNAME/node/workbench.js $USER $PORT $DOLTGRES_VERSION $BATS_TEST_DIRNAME/node/testdata
}
@test "perl DBI:Pg client" {
perl $BATS_TEST_DIRNAME/perl/postgres-test.pl $USER $PORT
}
@test "ruby pg test" {
ruby $BATS_TEST_DIRNAME/ruby/pg-test.rb $USER $PORT
}
@test "ruby Sequel client" {
ruby $BATS_TEST_DIRNAME/ruby/sequel-test.rb $USER $PORT
}
@test "ruby ActiveRecord client" {
ruby $BATS_TEST_DIRNAME/ruby/activerecord-test.rb $USER $PORT
}
@test "php pg_connect client" {
cd $BATS_TEST_DIRNAME/php
php pg_connect_test.php $USER $PORT
}
@test "php pdo pgsql client" {
cd $BATS_TEST_DIRNAME/php
php pdo_connector_test.php $USER $PORT
}
@test "c postgres: libpq connector" {
(cd $BATS_TEST_DIRNAME/c; make clean; make)
$BATS_TEST_DIRNAME/c/postgres-c-connector-test $USER $PORT
}
@test "c++ libpqxx client" {
cd $BATS_TEST_DIRNAME/cpp
make
./libpqxx-test $USER $PORT
}
@test "psqlODBC client" {
cd $BATS_TEST_DIRNAME/odbc
make
./psqlodbc-test $USER $PORT
}
@test "python postgres: psycopg2 client" {
cd $BATS_TEST_DIRNAME/python
python3 psycopg2_test.py $USER $PORT
}
@test "python postgres: sqlalcchemy client" {
cd $BATS_TEST_DIRNAME/python
python3 sqlalchemy-test.py $USER $PORT
}
@test "Drizzle ORM smoke test" {
# the schema should be empty
query_server -c "DROP TABLE IF EXISTS test_table" -t
cd $BATS_TEST_DIRNAME/drizzle
# Construct the string and append it to the .env file
touch .env
echo "DATABASE_URL=postgres://$USER:password@localhost:$PORT/postgres" >> .env
npm i drizzle-orm pg dotenv
npm i -D drizzle-kit tsx @types/pg
npx drizzle-kit push
# we can check if 'components table was created'
query_server -c "SELECT * FROM users" -t
run query_server -c "SELECT * FROM users" -t
[ "$status" -eq 0 ]
# this inserts and updates row and check its updated result
npx tsx src/index.ts
}
@test "R RPostgres client" {
Rscript $BATS_TEST_DIRNAME/r/rpostgres-test.r $USER $PORT
}
@test "R RPostgreSQL client" {
Rscript $BATS_TEST_DIRNAME/r/rpostgresql-test.r $USER $PORT
}
@test "rust sqlx" {
/build/bin/rust/sqlx_exists_demo $USER $PORT
}
@test "go pgx client" {
/build/bin/go/pgx-test $USER $PORT
}
@test "go lib/pq client" {
/build/bin/go/libpq-test $USER $PORT
}
@test "dotnet Npgsql client" {
/build/bin/dotnet/npgsql-test $USER $PORT
}
@test "elixir postgrex client" {
skip "fails from https://github.com/dolthub/doltgresql/issues/2859"
/build/bin/elixir/postgrex-test $USER $PORT
}
@test "swift postgresnio client" {
/build/bin/swift/postgresnio-test $USER $PORT
}
@test "libaprutil apr_dbd client" {
(cd $BATS_TEST_DIRNAME/c; make)
$BATS_TEST_DIRNAME/c/libaprutil-test $USER $PORT
}
@test "libdbi pgsql client" {
(cd $BATS_TEST_DIRNAME/c; make)
$BATS_TEST_DIRNAME/c/libdbi-test $USER $PORT
}
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
import os
import sys
import traceback
import psycopg2
# ---------------------------------------------------------------------------
# Query list (kept at top for consistency with other tests)
# ---------------------------------------------------------------------------
TEST_QUERIES = [
"DROP TABLE IF EXISTS test",
"create table test (pk int, value int, d1 decimal(9, 3), f1 float, c1 char(10), t1 text, primary key(pk))",
"select * from test",
"insert into test (pk, value, d1, f1, c1, t1) values (0,0,0.0,0.0,'abc','a1')",
"select * from test",
"select dolt_add('-A');",
"select dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt.log",
"select dolt_checkout('-b', 'mybranch')",
"insert into test (pk, value, d1, f1, c1, t1) values (10,10, 123456.789, 420.42,'example','some text')",
"select dolt_commit('-a', '-m', 'my commit2')",
"select dolt_checkout('main')",
"select dolt_merge('mybranch')",
"select COUNT(*) FROM dolt.log",
]
# ---------------------------------------------------------------------------
def env(name, default=None):
return os.getenv(name, default)
def connect(user: str, port: int):
conn = psycopg2.connect(
host=env("PGHOST", "localhost"),
port=port,
dbname="postgres",
user=user,
password=env("PGPASSWORD", "password"),
connect_timeout=int(env("PGCONNECT_TIMEOUT", "10")),
sslmode=env("PGSSLMODE"),
)
conn.autocommit = True
return conn
def run(cur, q):
print(f"SQL> {q}", flush=True)
cur.execute(q)
if cur.description is not None:
cur.fetchall() # drain result set
# load_test creates a table with |n_rows| and asserts that all rows are correctly returned.
def load_test(cur, n_rows=1000):
print("\n=== Part 1: Load test ===", flush=True)
rows = max(1000, int(n_rows))
run(cur, "DROP TABLE IF EXISTS load_test")
run(cur, "CREATE TABLE load_test (id INT PRIMARY KEY, val INT NOT NULL)")
data = [(i, i * 10) for i in range(rows)]
cur.executemany(
"INSERT INTO load_test (id, val) VALUES (%s, %s)",
data,
)
cur.execute("SELECT COUNT(*) FROM load_test")
cnt = cur.fetchone()[0]
if cnt != rows:
raise AssertionError(f"COUNT(*) mismatch: expected {rows}, got {cnt}")
cur.execute("SELECT id FROM load_test ORDER BY id")
got = cur.fetchall()
if len(got) != rows:
raise AssertionError(f"fetchall mismatch: expected {rows}, got {len(got)}")
print(f"Inserted and selected {rows} rows OK.", flush=True)
def compliance_test(cur):
print("\n=== Part 2: Test Queries ===", flush=True)
for q in TEST_QUERIES:
run(cur, q)
print("Compliance queries executed OK.", flush=True)
def main():
if len(sys.argv) != 3:
print("Usage: python3 psycopg2_test.py <user> <port>")
return 2
user = sys.argv[1]
port = int(sys.argv[2])
load_rows = int(env("LOAD_ROWS", "1000"))
try:
with connect(user, port) as conn:
with conn.cursor() as cur:
load_test(cur, load_rows)
compliance_test(cur)
print("\n✅ All tests passed.", flush=True)
return 0
except Exception as e:
print("\n❌ Test failed.", flush=True)
print(f"Error: {e}", flush=True)
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,67 @@
import sqlalchemy
import psycopg2
from sqlalchemy.engine import Engine
from sqlalchemy import create_engine
from sqlalchemy import text
import sys
QUERY_RESPONSE = [
{"create table test (pk int, \"value\" int, primary key(pk))": []},
{"describe test": [
('pk', 'integer', 'NO', 'PRI', None, ''),
('value', 'integer', 'YES', '', None, '')
]},
{"insert into test (pk, value) values (0,0)": ()},
{"select * from test": [(0, 0)]},
{"select dolt_add('-A');": [(['0'],)]},
{"select dolt_commit('-m', 'my commit')": [('',)]},
{"select COUNT(*) FROM dolt_log": [(3,)]},
{"select dolt_checkout('-b', 'mybranch')": [(['0', "Switched to branch 'mybranch'"],)]},
{"insert into test (pk, value) values (1,1)": []},
{"select dolt_commit('-a', '-m', 'my commit2')": [('',)]},
{"select dolt_checkout('main')": [(['0', "Switched to branch 'main'"],)]},
{"select dolt_merge('mybranch')": [('',1,0,)]},
{"select COUNT(*) FROM dolt_log": [(4,)]},
]
def main():
user = sys.argv[1]
port = int(sys.argv[2])
conn_string_base = "postgresql+psycopg2://"
engine = create_engine(conn_string_base +
"{user}:password@127.0.0.1:{port}/postgres".format(user=user,
port=port)
)
with engine.connect() as con:
for query_response in QUERY_RESPONSE:
query = list(query_response.keys())[0]
exp_results = query_response[query]
result_proxy = con.execute(text(query))
try:
results = result_proxy.fetchall()
if (results != exp_results) and ("dolt_commit" not in query) and ("dolt_merge" not in query):
print("Query:")
print(query)
print("Expected:")
print(exp_results)
print("Received:")
print(results)
sys.exit(1)
# You can't call fetchall on an insert
# so we'll just ignore the exception
except sqlalchemy.exc.ResourceClosedError:
pass
con.close()
sys.exit(0)
main()
@@ -0,0 +1,79 @@
library(RPostgres)
library(DBI)
args = commandArgs(trailingOnly=TRUE)
user = args[1]
port = strtoi(args[2])
conn = dbConnect(RPostgres::Postgres(),
host="localhost",
port=port,
user=user,
password="password",
dbname="postgres")
# check standard queries
queries = list(
"DROP TABLE IF EXISTS test",
"create table test (pk int, value int, primary key(pk))",
"select * from test",
"insert into test (pk, value) values (0,0)",
"select * from test")
responses = list(
NULL,
NULL,
data.frame(pk = integer(0), value = integer(0), stringsAsFactors = FALSE),
NULL,
data.frame(pk = c(as.integer(0)), value = c(as.integer(0)), stringsAsFactors = FALSE))
for (i in 1:length(queries)) {
q = queries[[i]]
want = responses[[i]]
if (!is.null(want)) {
got <- dbGetQuery(conn, q)
if (length(want) == length(got)) {
for (j in 1:length(want)) {
if (!identical(want[[j]], got[[j]])) {
print(q)
print(c("want:", want[[j]], "type: ", typeof(want[[j]])))
print(c("got:", got[[j]], "type: ", typeof(got[[j]])))
quit("no", 1)
}
}
}
} else {
invisible(dbExecute(conn, q))
}
}
dolt_queries = list(
"select dolt_add('-A')",
"select dolt_commit('-m', 'my commit')",
"select dolt_checkout('-b', 'mybranch')",
"insert into test (pk, value) values (1,1)",
"select dolt_commit('-a', '-m', 'my commit2')",
"select dolt_checkout('main')",
"select dolt_merge('mybranch')")
for (i in 1:length(dolt_queries)) {
q = dolt_queries[[i]]
if (startsWith(trimws(tolower(q)), "select")) {
dbGetQuery(conn, q)
} else {
invisible(dbExecute(conn, q))
}
}
count <- dbGetQuery(conn, "select COUNT(*) as c from dolt.log")
want <- data.frame(c = c(4))
ret <- all.equal(count, want)
if (!isTRUE(ret)) {
print("Number of commits is incorrect")
print(count)
quit("no", 1)
}
dbDisconnect(conn)
print("RPostgres test passed")
@@ -0,0 +1,80 @@
library(RPostgreSQL)
library(DBI)
args = commandArgs(trailingOnly=TRUE)
user = args[1]
port = strtoi(args[2])
drv = dbDriver("PostgreSQL")
conn = dbConnect(drv,
host="localhost",
port=port,
user=user,
password="password",
dbname="postgres")
# check standard queries
queries = list(
"DROP TABLE IF EXISTS test",
"create table test (pk int, value int, primary key(pk))",
"select * from test",
"insert into test (pk, value) values (0,0)",
"select * from test")
responses = list(
NULL,
NULL,
data.frame(pk = integer(0), value = integer(0), stringsAsFactors = FALSE),
NULL,
data.frame(pk = c(as.integer(0)), value = c(as.integer(0)), stringsAsFactors = FALSE))
for (i in 1:length(queries)) {
q = queries[[i]]
want = responses[[i]]
if (!is.null(want)) {
got <- dbGetQuery(conn, q)
if (length(want) == length(got)) {
for (j in 1:length(want)) {
if (!identical(want[[j]], got[[j]])) {
print(q)
print(c("want:", want[[j]], "type: ", typeof(want[[j]])))
print(c("got:", got[[j]], "type: ", typeof(got[[j]])))
quit("no", 1)
}
}
}
} else {
invisible(dbExecute(conn, q))
}
}
dolt_queries = list(
"select dolt_add('-A')",
"select dolt_commit('-m', 'my commit')",
"select dolt_checkout('-b', 'mybranch')",
"insert into test (pk, value) values (1,1)",
"select dolt_commit('-a', '-m', 'my commit2')",
"select dolt_checkout('main')",
"select dolt_merge('mybranch')")
for (i in 1:length(dolt_queries)) {
q = dolt_queries[[i]]
if (startsWith(trimws(tolower(q)), "select")) {
dbGetQuery(conn, q)
} else {
invisible(dbExecute(conn, q))
}
}
count <- dbGetQuery(conn, "select COUNT(*) as c from dolt.log")
want <- data.frame(c = c(4L))
ret <- all.equal(count, want)
if (!isTRUE(ret)) {
print("Number of commits is incorrect")
print(count)
quit("no", 1)
}
dbDisconnect(conn)
print("RPostgreSQL test passed")
@@ -0,0 +1,53 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dolthub</groupId>
<artifactId>r2dbc-test</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<version>1.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>2.0.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>R2dbcTest</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,85 @@
import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
import io.r2dbc.postgresql.client.SSLMode;
import io.r2dbc.spi.Connection;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class R2dbcTest {
public static void main(String[] args) {
String user = args[0];
int port = Integer.parseInt(args[1]);
PostgresqlConnectionFactory factory = new PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host("localhost")
.port(port)
.database("postgres")
.username(user)
.password("password")
.sslMode(SSLMode.DISABLE)
.build()
);
try {
Mono.usingWhen(
factory.create(),
R2dbcTest::runTests,
Connection::close
).block();
System.out.println("r2dbc test passed");
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
System.exit(1);
}
}
// Execute a statement and discard all results (works for both DML and SELECT).
static Mono<Void> exec(Connection conn, String sql) {
return Flux.from(conn.createStatement(sql).execute())
.flatMap(r -> Flux.from(r.map((row, meta) -> 0)))
.then();
}
static <T> Mono<T> queryOne(Connection conn, String sql, Class<T> type) {
return Mono.from(conn.createStatement(sql).execute())
.flatMap(r -> Mono.from(r.map((row, meta) -> row.get(0, type))));
}
static Mono<Void> runTests(Connection conn) {
return queryOne(conn, "SELECT pk FROM test_table LIMIT 1", Integer.class)
.doOnNext(pk -> {
if (pk != 1) throw new RuntimeException("expected pk=1, got " + pk);
})
.then(exec(conn, "INSERT INTO test_table VALUES (2)"))
.then(queryOne(conn, "SELECT COUNT(*) FROM test_table", Long.class))
.doOnNext(n -> {
if (n.longValue() != 2L)
throw new RuntimeException("expected count=2, got " + n);
})
.thenMany(Flux.fromArray(new String[]{
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
}).concatMap(sql -> exec(conn, sql)))
.then(queryOne(conn, "SELECT COUNT(*) FROM dolt_log", Long.class))
.doOnNext(n -> {
if (n.longValue() != 4L)
throw new RuntimeException("expected 4 dolt_log entries, got " + n);
})
.then(queryOne(conn, "SELECT COUNT(*) FROM test", Long.class))
.doOnNext(n -> {
if (n.longValue() != 2L)
throw new RuntimeException("expected 2 rows in test, got " + n);
})
.then();
}
}
@@ -0,0 +1,5 @@
source "https://rubygems.org"
gem 'pg'
gem 'test'
gem 'sequel'
gem 'activerecord'
@@ -0,0 +1,52 @@
#!/usr/bin/ruby
require 'active_record'
user = ARGV[0]
port = ARGV[1].to_i
ActiveRecord::Base.establish_connection(
adapter: 'postgresql',
host: 'localhost',
port: port,
database: 'postgres',
username: user,
password: 'password'
)
ActiveRecord::Base.logger = nil
conn = ActiveRecord::Base.connection
# SELECT pk from test_table (set up by bats setup())
pk = conn.execute("SELECT pk FROM test_table LIMIT 1").first["pk"].to_i
raise "expected pk=1, got #{pk}" unless pk == 1
# INSERT
conn.execute("INSERT INTO test_table VALUES (2)")
# COUNT
count = conn.execute("SELECT COUNT(*) FROM test_table").first["count"].to_i
raise "expected count=2, got #{count}" unless count == 2
# Dolt workflow
[
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
].each { |sql| conn.execute(sql) }
log_count = conn.execute("SELECT COUNT(*) FROM dolt_log").first["count"].to_i
raise "expected 4 dolt_log entries, got #{log_count}" unless log_count == 4
test_count = conn.execute("SELECT COUNT(*) FROM test").first["count"].to_i
raise "expected 2 rows in test, got #{test_count}" unless test_count == 2
ActiveRecord::Base.connection_pool.disconnect!
puts "ActiveRecord test passed"
@@ -0,0 +1,49 @@
#!/usr/bin/ruby
require 'pg'
require 'test/unit'
extend Test::Unit::Assertions
user = ARGV[0]
port = ARGV[1]
db = "postgres"
queries = [
"create table test (pk int, value int, d1 decimal(9, 3), f1 float, c1 char(10), t1 text, primary key(pk))",
"select * from test",
"insert into test (pk, value, d1, f1, c1, t1) values (0,0,0.0,0.0,'abc','a1')",
"select * from test",
"select dolt_add('-A');",
"select dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt.log",
"select dolt_checkout('-b', 'mybranch')",
"insert into test (pk, value, d1, f1, c1, t1) values (1,1, 123456.789, 420.42,'example','some text')",
"select dolt_commit('-a', '-m', 'my commit2')",
"select dolt_checkout('main')",
"select dolt_merge('mybranch')",
"select COUNT(*) FROM dolt.log",
]
# Smoke test the queries to make sure nothing blows up
conn = PG::Connection.new(:host => "localhost", :user => user, :dbname => db, :port => port, :password => "password")
queries.each do |query|
res = conn.query(query)
end
# Then make sure we can read some data back
res = conn.query("SELECT * from test where pk = 1;")
rowCount = 0
res.each do |row|
rowCount += 1
assert_equal 1, row["pk"].to_i
assert_equal 1, row["value"].to_i
assert_equal 123456.789, row["d1"].to_f
assert_equal 420.42, row["f1"].to_f
assert_equal "example ", row["c1"]
assert_equal "some text", row["t1"]
end
assert_equal 1, rowCount
conn.close()
exit(0)
@@ -0,0 +1,49 @@
#!/usr/bin/ruby
require 'sequel'
user = ARGV[0]
port = ARGV[1].to_i
DB = Sequel.connect(
adapter: 'postgres',
host: 'localhost',
port: port,
database: 'postgres',
user: user,
password: 'password'
)
# SELECT pk from test_table (set up by bats setup())
pk = DB["SELECT pk FROM test_table LIMIT 1"].first[:pk]
raise "expected pk=1, got #{pk}" unless pk == 1
# INSERT
DB.run("INSERT INTO test_table VALUES (2)")
# COUNT
count = DB["SELECT COUNT(*) FROM test_table"].first[:count]
raise "expected count=2, got #{count}" unless count == 2
# Dolt workflow
[
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
].each { |sql| DB.run(sql) }
log_count = DB["SELECT COUNT(*) FROM dolt_log"].first[:count]
raise "expected 4 dolt_log entries, got #{log_count}" unless log_count == 4
test_count = DB["SELECT COUNT(*) FROM test"].first[:count]
raise "expected 2 rows in test, got #{test_count}" unless test_count == 2
DB.disconnect
puts "Sequel test passed"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
[package]
name = "sqlx_exists_demo"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "tls-native-tls", "uuid", "chrono"] }
@@ -0,0 +1,75 @@
use sqlx::postgres::PgPoolOptions;
use sqlx::types::Uuid;
use sqlx::types::chrono::Utc;
use sqlx::types::chrono::DateTime;
use sqlx::types::chrono::NaiveDate;
#[derive(sqlx::FromRow)]
struct Event {
id: sqlx::types::Uuid,
created_at: DateTime<Utc>,
event_date: Option<NaiveDate>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args();
let program = args.next().unwrap_or_else(|| "app".to_string());
let user = args.next().ok_or_else(|| {
format!("Usage: {program} <USER> <PORT>")
})?;
let port: u16 = args.next().ok_or_else(|| {
format!("Usage: {program} <USER> <PORT>")
})?.parse()?;
let database_url = format!("postgresql://{user}:password@localhost:{port}/postgres");
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await?;
let exists: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM pg_catalog.pg_tables WHERE tablename = $1);")
.bind("test_table")
.fetch_one(&pool)
.await?;
println!("exists={exists}");
sqlx::query("DROP TABLE IF EXISTS users, events;")
.execute(&pool)
.await?;
sqlx::query("CREATE TABLE users (id uuid default gen_random_uuid(), name text, email text);")
.execute(&pool)
.await?;
sqlx::query("INSERT INTO users (name, email) VALUES ($1, $2)")
.bind("Alice")
.bind("alice@example.com")
.execute(&pool)
.await?;
let some_uuid: Uuid = sqlx::query_scalar("SELECT id FROM users WHERE email = $1 LIMIT 1")
.bind("alice@example.com")
.fetch_one(&pool)
.await?;
sqlx::query("UPDATE users SET name = $1 WHERE id = $2")
.bind("Bob")
.bind(some_uuid)
.execute(&pool)
.await?;
sqlx::query("CREATE TABLE events (id UUID PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), event_date DATE);")
.execute(&pool)
.await?;
let some_id: Uuid = sqlx::query_scalar("INSERT INTO events (id, event_date) VALUES (gen_random_uuid(), '2026-02-12') RETURNING id;")
.fetch_one(&pool)
.await?;
let __event = sqlx::query_as::<_, Event>("SELECT * FROM events WHERE id = $1")
.bind(some_id)
.fetch_one(&pool)
.await?;
Ok(())
}
@@ -0,0 +1,18 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "postgresnio-test",
dependencies: [
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.19.0"),
],
targets: [
.executableTarget(
name: "postgresnio-test",
dependencies: [
.product(name: "PostgresNIO", package: "postgres-nio"),
],
path: "Sources"
),
]
)
@@ -0,0 +1,102 @@
import Logging
import NIOPosix
import PostgresNIO
struct TestError: Error, CustomStringConvertible {
let description: String
init(_ msg: String) { description = msg }
}
@main
struct PostgresNIOTest {
static func main() async {
let args = CommandLine.arguments
guard args.count >= 3 else {
fputs("Usage: postgresnio-test <user> <port>\n", stderr)
exit(1)
}
let user = args[1]
let port = Int(args[2])!
do {
try await runTests(user: user, port: port)
print("PostgresNIO test passed")
} catch {
fputs("Error: \(error)\n", stderr)
exit(1)
}
}
// Execute a statement and drain any result rows.
static func exec(_ conn: PostgresConnection, _ sql: String, logger: Logger) async throws {
for try await _ in try await conn.query(PostgresQuery(unsafeSQL: sql), logger: logger) {}
}
// Execute a single-row query and return the first row.
static func queryRow(_ conn: PostgresConnection, _ sql: String, logger: Logger) async throws -> PostgresRow {
for try await row in try await conn.query(PostgresQuery(unsafeSQL: sql), logger: logger) {
return row
}
throw TestError("expected at least one row for: \(sql)")
}
static func runTests(user: String, port: Int) async throws {
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try! eventLoopGroup.syncShutdownGracefully() }
var logger = Logger(label: "postgres-test")
logger.logLevel = .critical
let config = PostgresConnection.Configuration(
host: "localhost",
port: port,
username: user,
password: "password",
database: "postgres",
tls: .disable
)
let conn = try await PostgresConnection.connect(
on: eventLoopGroup.next(),
configuration: config,
id: 1,
logger: logger
)
defer { try! conn.close().wait() }
// SELECT pk from test_table (set up by bats setup())
let pkRow = try await queryRow(conn, "SELECT pk FROM test_table LIMIT 1", logger: logger)
let pk = try pkRow.decode(Int32.self, context: .default)
guard pk == 1 else { throw TestError("expected pk=1, got \(pk)") }
// INSERT
try await exec(conn, "INSERT INTO test_table VALUES (2)", logger: logger)
// COUNT
let countRow = try await queryRow(conn, "SELECT COUNT(*) FROM test_table", logger: logger)
let count = try countRow.decode(Int64.self, context: .default)
guard count == 2 else { throw TestError("expected count=2, got \(count)") }
// Dolt workflow: create table, insert, commit, branch, insert, commit, merge
for q in [
"DROP TABLE IF EXISTS test",
"CREATE TABLE test (pk int, value int, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT dolt_add('-A')",
"SELECT dolt_commit('-m', 'added table test')",
"SELECT dolt_checkout('-b', 'mybranch')",
"INSERT INTO test VALUES (1, 1)",
"SELECT dolt_commit('-a', '-m', 'updated test')",
"SELECT dolt_checkout('main')",
"SELECT dolt_merge('mybranch')",
] {
try await exec(conn, q, logger: logger)
}
let logRow = try await queryRow(conn, "SELECT COUNT(*) FROM dolt_log", logger: logger)
let logCount = try logRow.decode(Int64.self, context: .default)
guard logCount == 4 else { throw TestError("expected 4 dolt_log entries, got \(logCount)") }
let testRow = try await queryRow(conn, "SELECT COUNT(*) FROM test", logger: logger)
let testCount = try testRow.decode(Int64.self, context: .default)
guard testCount == 2 else { throw TestError("expected 2 rows in test, got \(testCount)") }
}
}