Merge pull request #9952 from dolthub/elian/mariadb

dolthub/dolt#9904: Add MariaDB connectors integration tests
This commit is contained in:
Elian
2025-10-14 13:24:44 -07:00
committed by GitHub
47 changed files with 2265 additions and 868 deletions

View File

@@ -1,5 +0,0 @@
name: 'Dolt MySQL client integration tests'
description: 'Smoke tests for mysql client integrations'
runs:
using: 'docker'
image: '../../../integration-tests/MySQLDockerfile'

View File

@@ -11,14 +11,50 @@ concurrency:
cancel-in-progress: true
jobs:
mysql_client_integrations_job:
mysql_client_integrations:
runs-on: ubuntu-22.04
timeout-minutes: 45
name: Run tests
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Copy go package
run: cp -r ./go ./integration-tests/go
- name: Test mysql client integrations
uses: ./.github/actions/mysql-client-tests
with:
path: dolt
- name: Free disk space
run: |
NAME="DISK-CLEANUP"
echo "[${NAME}] Starting background cleanup..."
[ -d "$AGENT_TOOLSDIRECTORY" ] && sudo rm -rf "$AGENT_TOOLSDIRECTORY" &
[ -d /usr/share/dotnet ] && sudo rm -rf /usr/share/dotnet &
[ -d /usr/local/lib/android ] && sudo rm -rf /usr/local/lib/android &
[ -d /opt/ghc ] && sudo rm -rf /opt/ghc &
[ -d /usr/local/share/boost ] && sudo rm -rf /usr/local/share/boost &
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Ensure cache directory exists
run: |
sudo mkdir -p /mnt/.buildx-cache
sudo chown $USER:$USER /mnt/.buildx-cache
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /mnt/.buildx-cache
key: ${{ runner.os }}-docker-mysql-client-integrations
restore-keys: |
${{ runner.os }}-docker
- name: Build MySQL test image
uses: docker/build-push-action@v6
with:
context: .
file: dolt/integration-tests/mysql-client-tests/Dockerfile
tags: mysql-client-tests:latest
load: true
cache-from: type=local,src=/mnt/.buildx-cache
cache-to: type=local,dest=/mnt/.buildx-cache
- name: Test MySQL client integrations
run: docker run --rm mysql-client-tests:latest

View File

@@ -1,149 +0,0 @@
FROM --platform=linux/amd64 ubuntu:20.04
# install python, libmysqlclient-dev, java, bats, git ruby, perl, cpan
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update -y && \
apt install -y \
curl \
gnupg \
libwxbase3.0-0v5 \
libwxgtk3.0-gtk3-0v5 \
libncurses5 \
libsctp1 \
software-properties-common && \
curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
add-apt-repository ppa:deadsnakes/ppa -y && \
curl -OL https://binaries2.erlang-solutions.com/ubuntu/pool/contrib/e/esl-erlang/esl-erlang_25.0-1~ubuntu~focal_amd64.deb && \
dpkg -i esl-erlang_25.0-1~ubuntu~focal_amd64.deb && \
curl -LO https://binaries2.erlang-solutions.com/ubuntu/pool/contrib/e/elixir/elixir_1.14.3_1_otp_25.3~ubuntu~focal_all.deb && \
dpkg -i elixir_1.14.3_1_otp_25.3~ubuntu~focal_all.deb
RUN apt update -y && \
apt install -y \
python3.9 \
python3-pip \
curl \
wget \
pkg-config \
mysql-client \
libmysqlclient-dev \
openjdk-17-jdk \
ant \
ca-certificates-java \
bats \
perl \
php \
php-mysqli \
cpanminus \
cmake \
g++ \
libmysqlcppconn-dev \
git \
ruby \
ruby-dev \
gem \
libc6 \
libgcc1 \
libgssapi-krb5-2 \
libicu66 \
libssl1.1 \
libstdc++6 \
zlib1g \
r-base \
postgresql \
postgresql-contrib \
libpq-dev \
nodejs \
lsof \
postgresql-server-dev-all && \
update-ca-certificates -f
# install go
WORKDIR /root
ENV GO_VERSION=1.25.0
ENV GOPATH=$HOME/go
ENV PATH=$PATH:$GOPATH/bin
ENV PATH=$PATH:$GOPATH/bin:/usr/local/go/bin
RUN curl -O "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" && \
sha256sum "go${GO_VERSION}.linux-amd64.tar.gz" && \
tar -xvf "go${GO_VERSION}.linux-amd64.tar.gz" -C /usr/local && \
chown -R root:root /usr/local/go && \
mkdir -p $HOME/go/{bin,src} && \
go version
# install MySQL dependency from source
RUN git clone https://github.com/go-sql-driver/mysql.git
WORKDIR mysql
RUN git checkout tags/v1.6.0 -b v1.6.0
RUN go install .
WORKDIR /
# install dotnet
RUN curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version 8.0.412 --install-dir /usr/local/bin --no-path && \
dotnet --version
# install pip for python3.9
RUN curl -LO https://bootstrap.pypa.io/get-pip.py && \
python3.9 get-pip.py && \
pip --version
# install mysql connector and pymsql
RUN pip install mysql-connector-python==8.0.32
RUN pip install PyMySQL==1.0.2
RUN pip install sqlalchemy==1.4.46
# Setup JAVA_HOME -- useful for docker commandline
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/
# install mysql connector java
RUN mkdir -p /mysql-client-tests/java
RUN curl -L -o /mysql-client-tests/java/mysql-connector-java-8.0.21.jar \
https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.21/mysql-connector-java-8.0.21.jar
# install node deps
COPY mysql-client-tests/node/package.json /mysql-client-tests/node/
COPY mysql-client-tests/node/package-lock.json /mysql-client-tests/node/
WORKDIR /mysql-client-tests/node
RUN npm install
# install cpan dependencies
RUN cpanm --force DBD::mysql
# install ruby dependencies
COPY mysql-client-tests/ruby/Gemfile /mysql-client-tests/ruby/
COPY mysql-client-tests/ruby/Gemfile.lock /mysql-client-tests/ruby/
WORKDIR /mysql-client-tests/ruby
RUN gem install bundler -v 2.1.4 && bundle install
# install R packages
RUN Rscript -e 'install.packages(c("DBI", "RMySQL", "RMariaDB"), \
repos = c(RSPM="https://packagemanager.rstudio.com/cran/__linux__/focal/latest"))'
# install rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y
ENV PATH="/root/.cargo/bin:${PATH}"
# install postgres and psql
RUN service postgresql start
# install mysql_fdw
WORKDIR /mysql-client-tests/mysql_fdw
RUN git clone https://github.com/EnterpriseDB/mysql_fdw --branch REL-2_9_0
WORKDIR /mysql-client-tests/mysql_fdw/mysql_fdw
RUN make USE_PGXS=1 && \
make USE_PGXS=1 install
# install dolt from source
WORKDIR /root/building
COPY ./go .
ENV GOFLAGS="-mod=readonly"
RUN go build -o /usr/local/bin/dolt ./cmd/dolt
COPY mysql-client-tests /mysql-client-tests
COPY mysql-client-tests/mysql-client-tests-entrypoint.sh /mysql-client-tests/entrypoint.sh
# install rust dependencies
WORKDIR /mysql-client-tests/rust
RUN cargo build --config net.git-fetch-with-cli=true
WORKDIR /mysql-client-tests
ENTRYPOINT ["/mysql-client-tests/entrypoint.sh"]

View File

@@ -1,5 +0,0 @@
mysql-client-tests/c/mysql-client-tests
mysql-client-tests/java/MySQLConnectorTest.class
mysql-client-tests/cpp/_build/**/*
mysql-client-tests/cpp/_build
bats

View File

@@ -0,0 +1,186 @@
# syntax=docker/dockerfile:1
FROM golang:1.25-alpine AS golang_cgo125
ENV CGO_ENABLED=1
ENV GO_LDFLAGS="-linkmode external -extldflags '-static'"
RUN apk add --no-cache build-base
FROM golang_cgo125 AS dolt_build
RUN apk add --no-cache icu-dev icu-static
COPY dolt/go/go.mod /build/dolt/go/
WORKDIR /build/dolt/go/
RUN go mod download
COPY dolt/go/ /build/dolt/go/
RUN go build -tags icu_static -ldflags "$GO_LDFLAGS" -o /build/bin/dolt ./cmd/dolt
FROM golang_cgo125 AS go_clients_build
COPY dolt/integration-tests/mysql-client-tests/go /build/go/
WORKDIR /build/go/
RUN go build -ldflags "$GO_LDFLAGS" -o /build/bin/mysql-client-test
COPY dolt/integration-tests/mysql-client-tests/go-mysql/ /build/go-mysql/
WORKDIR /build/go-mysql/
RUN go build -ldflags "$GO_LDFLAGS" -o /build/bin/sql-driver-mysql-test
FROM rust:1.90-alpine3.22 AS rust_clients_build
RUN apk add --no-cache musl-dev
COPY dolt/integration-tests/mysql-client-tests/rust/ /build/rust/
WORKDIR /build/rust/
RUN cargo build --release --target-dir /build/bin/ && cargo clean # exe is in release/
FROM debian:bookworm-slim AS dotnet_clients_build
RUN apt-get update && apt-get install -y wget gnupg ca-certificates && rm -rf /var/lib/apt/lists/*
RUN wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& rm packages-microsoft-prod.deb
RUN apt-get update && apt-get install -y dotnet-sdk-9.0 && rm -rf /var/lib/apt/lists/*
COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlClient/*.csproj /build/dotnet/MySqlClient/
WORKDIR /build/dotnet/MySqlClient/
RUN dotnet restore
COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlClient/ /build/dotnet/MySqlClient/
RUN dotnet publish -c Release -o /build/bin --no-restore
COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlConnector/*.csproj /build/dotnet/MySqlConnector/
WORKDIR /build/dotnet/MySqlConnector/
RUN dotnet restore
COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlConnector/ /build/dotnet/MySqlConnector/
RUN dotnet publish -c Release -o /build/bin --no-restore
# devart dotconnect reqs a license so we've skipped it here
FROM debian:bookworm-slim AS c_clients_build
# default-libmysqlclient-dev uses libmariadb under the hood but here we test both header interfaces
RUN apt-get update && apt-get install -y default-libmysqlclient-dev libmariadb-dev unixodbc-dev odbcinst wget gcc make && rm -rf /var/lib/apt/lists/*
RUN wget --progress=dot:giga https://dlm.mariadb.com/4465891/Connectors/odbc/connector-odbc-3.2.7/mariadb-connector-odbc_3.2.7-1+maria~bookworm_amd64.deb \
&& dpkg -i mariadb-connector-odbc_3.2.7-1+maria~bookworm_amd64.deb || apt-get install -y -f \
&& rm mariadb-connector-odbc_3.2.7-1+maria~bookworm_amd64.deb \
&& odbcinst -i -d -f /dev/stdin <<EOF
[MariaDB ODBC 3.2 Driver]
Description=MariaDB Connector/ODBC v.3.2
Driver=/usr/lib/x86_64-linux-gnu/libmaodbc.so
EOF
COPY dolt/integration-tests/mysql-client-tests/c/ /build/c/
WORKDIR /build/c/
RUN make
FROM debian:bookworm-slim AS cpp_clients_build
RUN apt-get update && apt-get install -y libmysqlcppconn-dev wget g++ make && rm -rf /var/lib/apt/lists/*
RUN wget --progress=dot:giga https://dlm.mariadb.com/4464866/Connectors/cpp/connector-cpp-1.1.7/mariadb-connector-cpp_1.1.7-1+maria~bookworm_amd64.deb \
&& dpkg -i mariadb-connector-cpp_1.1.7-1+maria~bookworm_amd64.deb || apt-get install -y -f \
&& rm mariadb-connector-cpp_1.1.7-1+maria~bookworm_amd64.deb
COPY dolt/integration-tests/mysql-client-tests/cpp/ /build/cpp/
WORKDIR /build/cpp/
RUN make
FROM python:3.14-slim-bookworm AS python_clients_build
RUN apt-get update && apt-get install -y binutils libmariadb-dev gcc && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir mysql-connector-python==8.0.33 PyMySQL==1.0.2 sqlalchemy==1.4.46 mariadb pyinstaller
COPY dolt/integration-tests/mysql-client-tests/python/ /build/python/
WORKDIR /build/python/
RUN pyinstaller --onefile pymysql-test.py
RUN pyinstaller --onefile --collect-all mysql.connector sqlalchemy-test.py
RUN pyinstaller --onefile --collect-all mysql.connector mysql-connector-test.py
RUN pyinstaller --onefile mariadb-connector-test.py
FROM elixir:1.18-slim AS elixir_clients_build
RUN apt-get update && apt-get install -y ca-certificates xz-utils curl && rm -rf /var/lib/apt/lists/*
RUN curl -sSL https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz | tar -xJ
ENV PATH="/zig-x86_64-linux-0.14.1:${PATH}"
COPY dolt/integration-tests/mysql-client-tests/elixir/myxql/mix.exs /build/elixir/myxql/
WORKDIR /build/elixir/myxql/
RUN mix local.hex --force && mix local.rebar --force
RUN mix deps.get
COPY dolt/integration-tests/mysql-client-tests/elixir/myxql/ /build/elixir/myxql/
RUN MIX_ENV=prod mix release simple
COPY dolt/integration-tests/mysql-client-tests/elixir/mysql/mix.exs /build/elixir/mysql/
WORKDIR /build/elixir/mysql/
RUN mix deps.get
COPY dolt/integration-tests/mysql-client-tests/elixir/mysql/ /build/elixir/mysql/
RUN MIX_ENV=prod mix release mysql_otp
RUN mkdir -p /build/bin && \
cp /build/elixir/myxql/burrito_out/simple_linux /build/bin/myxql-driver-test && \
cp /build/elixir/mysql/burrito_out/mysql_otp_linux /build/bin/mysql-otp-test
FROM maven:3.8-openjdk-17-slim AS java_clients_build
RUN apt-get update && apt-get install -y binutils && rm -rf /var/lib/apt/lists/*
COPY dolt/integration-tests/mysql-client-tests/java/ /build/java/
WORKDIR /build/java/
RUN mvn package
RUN jlink \
--add-modules java.base,java.sql,java.naming,java.xml,java.logging,java.management \
--strip-debug \
--no-man-pages \
--no-header-files \
--output /build/jre
FROM node:22-bookworm-slim AS node_clients_build
COPY dolt/integration-tests/mysql-client-tests/node/package.json /build/bin/
WORKDIR /build/bin/
RUN npm install --omit=dev
COPY dolt/integration-tests/mysql-client-tests/node/ /build/bin/
FROM ruby:3.4-bookworm AS ruby_clients_build
RUN apt-get update && apt-get install -y default-libmysqlclient-dev ruby3.1-dev bundler && rm -rf /var/lib/apt/lists/*
COPY dolt/integration-tests/mysql-client-tests/ruby/Gemfile /build/ruby/
WORKDIR /build/ruby/
RUN bundle install
COPY dolt/integration-tests/mysql-client-tests/ruby/ /build/bin/
FROM swift:5.10-bookworm AS swift_clients_build
RUN apt-get update && apt-get install -y libmariadb-dev pkg-config libstdc++-12-dev && rm -rf /var/lib/apt/lists/*
COPY dolt/integration-tests/mysql-client-tests/swift/ /build/swift/
WORKDIR /build/swift/
RUN swift build -c release --static-swift-stdlib
RUN mkdir -p /build/bin && cp .build/release/MariaDBTest /build/bin/mariadb-swift-test
FROM php:8.3-bookworm AS runtime
RUN apt-get update && apt-get install -y \
libmysqlcppconn-dev \
libdbd-mysql-perl \
libdbd-mariadb-perl \
default-mysql-client \
postgresql-15-mysql-fdw \
r-base-core \
lsof \
bats \
&& rm -rf /var/lib/apt/lists/*
# Runtime Libraries
COPY --from=cpp_clients_build /usr/lib/x86_64-linux-gnu/libmariadb* /usr/lib/x86_64-linux-gnu/
COPY --from=c_clients_build /usr/lib/x86_64-linux-gnu/ /usr/lib/x86_64-linux-gnu/
COPY --from=c_clients_build /etc/odbcinst.ini /etc/odbcinst.ini
COPY --from=java_clients_build /build/jre/ /opt/jre/
COPY --from=node_clients_build /usr/local/bin/node /usr/local/bin/
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 docker-php-ext-install mysqli pdo_mysql && \
R -e "install.packages('DBI', repos='https://cloud.r-project.org/')" && \
R -e "install.packages('RMySQL', repos='https://cloud.r-project.org/')" && \
R -e "install.packages('RMariaDB', repos='https://cloud.r-project.org/')"
ENV PATH="/opt/jre/bin:${PATH}" GEM_HOME="/usr/local/bundle"
RUN ldconfig
COPY --from=go_clients_build /build/bin/ /build/bin/go/
COPY --from=rust_clients_build /build/bin/release/ /build/bin/rust/
COPY --from=dotnet_clients_build /build/bin/ /build/bin/dotnet/
COPY --from=c_clients_build /build/bin/ /build/bin/c/
COPY --from=cpp_clients_build /build/bin/ /build/bin/cpp/
COPY --from=python_clients_build /build/python/dist/ /build/bin/python/
COPY --from=elixir_clients_build /build/bin/ /build/bin/elixir/
COPY --from=java_clients_build /build/java/target/ /build/bin/java/
COPY --from=node_clients_build /build/bin/ /build/bin/node/
COPY --from=swift_clients_build /build/bin/ /build/bin/swift/
COPY dolt/integration-tests/mysql-client-tests/php/ /build/bin/php/
COPY --from=ruby_clients_build /build/bin/ /build/bin/ruby/
COPY dolt/integration-tests/mysql-client-tests/r/ /build/bin/r/
COPY dolt/integration-tests/mysql-client-tests/perl/ /build/bin/perl/
COPY dolt/integration-tests/mysql-client-tests/mysql-client-tests.bats /build/bin/
COPY dolt/integration-tests/mysql-client-tests/helpers.bash /build/bin/
COPY dolt/integration-tests/mysql-client-tests/mysql-client-tests-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
COPY --from=dolt_build /build/bin/ /usr/local/bin/
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,15 @@
**/.git/
**/.github/
**/docker/
**/images/
**/node_modules/
**/*Dockerfile
**/transactions/
**/orm-tests/
**/out/
**/bats/archive-test-repos/
**/bats/performance-repo/
**/bats/corrupt_dbs/
**/integration-tests/data-dump-loading-tests/
**/.dolt/
**/.doltcfg/

View File

@@ -1,11 +1,34 @@
CFLAGS := $(shell pkg-config --cflags mysqlclient)
LDFLAGS := $(shell pkg-config --libs mysqlclient)
CC = gcc
all: mysql-connector-c-test
CFLAGS_MYSQL = -I/usr/include/mysql -Wall -O2
CFLAGS_MARIADB = -I/usr/include/mariadb -Wall -O2
CFLAGS_ODBC = -Wall -O2
mysql-connector-c-test: mysql-connector-c-test.c
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
LDFLAGS_MYSQL = -lmysqlclient
LDFLAGS_MARIADB = -lmariadb
LDFLAGS_ODBC = -lodbc
TARGET_MYSQL = /build/bin/mysql-client-test
TARGET_MARIADB = /build/bin/mariadb-client-test
TARGET_ODBC = /build/bin/mariadb-odbc-test
SRCS_MYSQL = mysql-connector-test.c
SRCS_MARIADB = mariadb-connector-test.c
SRCS_ODBC = mariadb-odbc-test.c
all: $(TARGET_MYSQL) $(TARGET_MARIADB) $(TARGET_ODBC)
$(TARGET_MYSQL): $(SRCS_MYSQL)
@mkdir -p /build/bin
$(CC) $(CFLAGS_MYSQL) -o $@ $^ $(LDFLAGS_MYSQL)
$(TARGET_MARIADB): $(SRCS_MARIADB)
@mkdir -p /build/bin
$(CC) $(CFLAGS_MARIADB) -o $@ $^ $(LDFLAGS_MARIADB)
$(TARGET_ODBC): $(SRCS_ODBC)
@mkdir -p /build/bin
$(CC) $(CFLAGS_ODBC) -o $@ $^ $(LDFLAGS_ODBC)
.PHONY: clean
clean:
rm -f mysql-connector-c-test
rm -f $(TARGET_MYSQL) $(TARGET_MARIADB) $(TARGET_ODBC)

View File

@@ -0,0 +1,199 @@
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <mariadb/mysql.h>
#define QUERIES_SIZE 14
char *queries[QUERIES_SIZE] =
{
"create table test (pk int, `value` int, primary key(pk))",
"describe test",
"select * from test",
"insert into test (pk, `value`) values (0,0)",
"select * from test",
"call dolt_add('-A');",
"call dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt_log",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (10,10)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')",
"select COUNT(*) FROM dolt_log",
};
typedef struct statement_t {
char *query;
MYSQL_BIND bind[10];
int expect_prepare_error;
int expect_exec_error;
int expect_result_metadata;
} statement;
void test_statement(MYSQL *con, statement *stmt) {
MYSQL_STMT *mstmt = mysql_stmt_init(con);
if (!mstmt) {
fprintf(stderr, "failed to init stmt: %s\n", mysql_error(con));
exit(1);
}
if ( mysql_stmt_prepare(mstmt, stmt->query, strlen(stmt->query)) ) {
if ( !stmt->expect_prepare_error) {
fprintf(stderr, "failed to prepare stmt: %s: %s\n", stmt->query, mysql_stmt_error(mstmt));
exit(1);
} else {
goto close;
}
}
if ( mysql_stmt_bind_param(mstmt, stmt->bind) ) {
fprintf(stderr, "failed to bind stmt: %s: %s\n", stmt->query, mysql_stmt_error(mstmt));
exit(1);
}
MYSQL_RES *metadata = mysql_stmt_result_metadata(mstmt);
if (stmt->expect_result_metadata && metadata == NULL) {
fprintf(stderr, "result metadata was unexpectedly NULL: %s\n", stmt->query);
exit(1);
} else if (!stmt->expect_result_metadata && metadata != NULL) {
fprintf(stderr, "result metadata was unexpectedly non-NULL: %s\n", stmt->query);
exit(1);
}
if ( mysql_stmt_execute(mstmt) ) {
if ( !stmt->expect_exec_error) {
fprintf(stderr, "failed to execute stmt: %s: %s\n", stmt->query, mysql_stmt_error(mstmt));
exit(1);
}
}
// TODO: Add test for mysql_stmt_store_result when supported
close:
if ( mysql_stmt_close(mstmt) ) {
fprintf(stderr, "failed to close stmt: %s: %s\n", stmt->query, mysql_error(con));
exit(1);
}
}
statement LAST_STATEMENT = {
};
int main(int argc, char **argv) {
char* user = argv[1];
int port = atoi(argv[2]);
char* db = argv[3];
MYSQL *con = mysql_init(NULL);
if ( con == NULL ) {
fprintf(stderr, "%s\n", mysql_error(con));
exit(1);
}
#ifndef MARIADB_CLIENT_VERSION_STR
fprintf(stderr, "Error: Not using MariaDB connector!\n");
exit(1);
#endif
if ( mysql_real_connect(con,
"127.0.0.1",
user,
"",
db,
port,
NULL,
0 ) == NULL) {
fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
exit(1);
}
for ( int i = 0; i < QUERIES_SIZE; i++ ) {
if ( mysql_query(con, queries[i]) ) {
printf("QUERY FAILED: %s\n", queries[i]);
fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
exit(1);
} else {
// Not checking validity of results for now
MYSQL_RES* result = mysql_use_result(con);
mysql_free_result(result);
}
}
int pk = 1;
int value = 12;
unsigned long string_len = 16;
statement statements[] = {
{
.query = "select * from test where pk = ?",
.bind = {
[0] = {
.buffer_type = MYSQL_TYPE_LONG,
.buffer = (void *)(&pk),
.buffer_length = sizeof(pk),
},
},
.expect_result_metadata = 1,
},
{
.query = "select * from test where pk = ?",
.bind = {
[0] = {
.buffer_type = MYSQL_TYPE_LONG,
.buffer = (void *)(&pk),
.buffer_length = sizeof(pk),
.is_unsigned = 1,
},
},
.expect_result_metadata = 1,
},
{
.query = "insert into test values (?, ?)",
.bind = {
[0] = {
.buffer_type = MYSQL_TYPE_LONG,
.buffer = (void *)(&pk),
.buffer_length = sizeof(pk),
},
[1] = {
.buffer_type = MYSQL_TYPE_LONG,
.buffer = (void *)(&value),
.buffer_length = sizeof(value),
},
},
.expect_result_metadata = 0,
},
{
.query = "update test set `value` = ?",
.bind = {
[0] = {
.buffer_type = MYSQL_TYPE_STRING,
.buffer = (void *)"test string here",
.buffer_length = string_len,
.length = &string_len,
},
},
.expect_exec_error = 1,
.expect_result_metadata = 0,
},
{
.query = "select * from test SYNTAX ERROR where pk = ?",
.bind = {
[0] = {
.buffer_type = MYSQL_TYPE_LONG,
.buffer = (void *)(&pk),
.buffer_length = sizeof(pk),
},
},
.expect_prepare_error = 1,
},
LAST_STATEMENT,
};
for (int i = 0; statements[i].query; i++) {
test_statement(con, &statements[i]);
}
mysql_close(con);
return 0;
}

View File

@@ -0,0 +1,186 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sql.h>
#include <sqlext.h>
#define QUERIES_SIZE 14
char *queries[QUERIES_SIZE] = {
"create table test (pk int, `value` int, primary key(pk))",
"describe test",
"select * from test",
"insert into test (pk, `value`) values (0,0)",
"select * from test",
"call dolt_add('-A')",
"call dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt_log",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (10,10)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')",
"select COUNT(*) FROM dolt_log",
};
void check_error(SQLRETURN ret, SQLHANDLE handle, SQLSMALLINT type, const char *msg) {
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
SQLCHAR sqlstate[6];
SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER native_error;
SQLSMALLINT text_length;
SQLGetDiagRec(type, handle, 1, sqlstate, &native_error, message, sizeof(message), &text_length);
fprintf(stderr, "%s\nSQLSTATE: %s\nMessage: %s\n", msg, sqlstate, message);
exit(1);
}
}
typedef struct prepared_statement_t {
char *query;
int num_params;
int pk_param;
int value_param;
int expect_prepare_error;
int expect_exec_error;
} prepared_statement;
void test_prepared_statement(SQLHDBC dbc, prepared_statement *pstmt) {
SQLHSTMT stmt;
SQLRETURN ret;
ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
check_error(ret, dbc, SQL_HANDLE_DBC, "Failed to allocate statement handle");
ret = SQLPrepare(stmt, (SQLCHAR *)pstmt->query, SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
if (!pstmt->expect_prepare_error) {
check_error(ret, stmt, SQL_HANDLE_STMT, "Failed to prepare statement");
} else {
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return;
}
}
if (pstmt->num_params > 0) {
if (pstmt->pk_param) {
SQLINTEGER pk = 1;
ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &pk, 0, NULL);
check_error(ret, stmt, SQL_HANDLE_STMT, "Failed to bind pk parameter");
}
if (pstmt->num_params > 1 && pstmt->value_param) {
SQLINTEGER value = 12;
ret = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &value, 0, NULL);
check_error(ret, stmt, SQL_HANDLE_STMT, "Failed to bind value parameter");
}
}
ret = SQLExecute(stmt);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
if (!pstmt->expect_exec_error) {
check_error(ret, stmt, SQL_HANDLE_STMT, "Failed to execute statement");
}
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
int main(int argc, char **argv) {
if (argc < 4) {
fprintf(stderr, "Usage: %s <user> <port> <database>\n", argv[0]);
return 1;
}
char *user = argv[1];
int port = atoi(argv[2]);
char *db = argv[3];
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN ret;
// Allocate environment handle
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
check_error(ret, env, SQL_HANDLE_ENV, "Failed to allocate environment handle");
// Set ODBC version
ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
check_error(ret, env, SQL_HANDLE_ENV, "Failed to set ODBC version");
// Allocate connection handle
ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
check_error(ret, env, SQL_HANDLE_ENV, "Failed to allocate connection handle");
// Build connection string
char connStr[512];
snprintf(connStr, sizeof(connStr),
"DRIVER=MariaDB ODBC 3.2 Driver;SERVER=127.0.0.1;PORT=%d;DATABASE=%s;UID=%s;PWD=;",
port, db, user);
// Connect to database
ret = SQLDriverConnect(dbc, NULL, (SQLCHAR *)connStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
check_error(ret, dbc, SQL_HANDLE_DBC, "Failed to connect to database");
printf("Connected to database successfully\n");
// Execute test queries
for (int i = 0; i < QUERIES_SIZE; i++) {
ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
check_error(ret, dbc, SQL_HANDLE_DBC, "Failed to allocate statement handle");
ret = SQLExecDirect(stmt, (SQLCHAR *)queries[i], SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
printf("QUERY FAILED: %s\n", queries[i]);
check_error(ret, stmt, SQL_HANDLE_STMT, "Query execution failed");
}
// Fetch and discard results
while (SQLFetch(stmt) == SQL_SUCCESS) {
// Just consume the results
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
// Test prepared statements
prepared_statement statements[] = {
{
.query = "select * from test where pk = ?",
.num_params = 1,
.pk_param = 1,
.expect_prepare_error = 0,
.expect_exec_error = 0,
},
{
.query = "insert into test values (?, ?)",
.num_params = 2,
.pk_param = 1,
.value_param = 1,
.expect_prepare_error = 0,
.expect_exec_error = 0,
},
{
.query = "select * from test SYNTAX ERROR where pk = ?",
.num_params = 1,
.pk_param = 1,
.expect_prepare_error = 1,
.expect_exec_error = 0,
},
{NULL, 0, 0, 0, 0, 0}, // Sentinel
};
for (int i = 0; statements[i].query; i++) {
test_prepared_statement(dbc, &statements[i]);
}
printf("All tests passed\n");
// Cleanup
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return 0;
}

View File

@@ -1,17 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(DoltCxxConnectorTest
VERSION 0.1
DESCRIPTION "A smoke test for mysql-connector-c++ connecting to Dolt"
LANGUAGES CXX)
add_executable(test_mysql_connector_cxx mysql-connector-cpp-test.cpp)
set_property(TARGET test_mysql_connector_cxx PROPERTY CXX_STANDARD 11)
if(WITH_JDBC)
add_subdirectory(third_party/mysql-connector-cpp EXCLUDE_FROM_ALL)
target_link_libraries(test_mysql_connector_cxx connector-jdbc)
else()
find_library(LIBMYSQLCPPCONN "mysqlcppconn")
target_link_libraries(test_mysql_connector_cxx "${LIBMYSQLCPPCONN}")
endif()

View File

@@ -1,14 +1,26 @@
MYSQL_CONCPP_DIR = /usr/local/Cellar/mysql-connector-c++/8.0.21
CPPFLAGS = -I $(MYSQL_CONCPP_DIR)/include -L $(MYSQL_CONCPP_DIR)/lib64
LDLIBS = -lmysqlcppconn8
CXX = clang++ -stdlib=libc++
CXXFLAGS = -std=c++11
CXX = g++
all: mysql-connector-cpp-test
CXXFLAGS_MYSQL = -I/usr/include/cppconn -std=c++11 -Wall -O2
CXXFLAGS_MARIADB = -I/usr/include/mariadb -std=c++11 -Wall -O2
mysql-connector-cpp-test: mysql-connector-cpp-test.cpp
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ $(LDLIBS)
LDFLAGS_MYSQL = -lmysqlcppconn
LDFLAGS_MARIADB = -lmariadbcpp
TARGET_MYSQL = /build/bin/mysql-connector-test
TARGET_MARIADB = /build/bin/mariadb-connector-test
SRCS_MYSQL = mysql-connector-test.cpp
SRCS_MARIADB = mariadb-connector-test.cpp
all: $(TARGET_MYSQL) $(TARGET_MARIADB)
$(TARGET_MYSQL): $(SRCS_MYSQL)
@mkdir -p /build/bin
$(CXX) $(CXXFLAGS_MYSQL) -o $@ $^ $(LDFLAGS_MYSQL)
$(TARGET_MARIADB): $(SRCS_MARIADB)
@mkdir -p /build/bin
$(CXX) $(CXXFLAGS_MARIADB) -o $@ $^ $(LDFLAGS_MARIADB)
.PHONY: clean
clean:
rm -f mysql-connector-cpp-test
rm -f $(TARGET_MYSQL) $(TARGET_MARIADB)

View File

@@ -1,28 +0,0 @@
# General
This code uses git submodules. You need to recursively pull all the submodules
in order for it to build.
# Building on OS X
```sh
$ brew install cmake openssl mysql-client boost
$ export PATH=/usr/local/Cellar/mysql-client/8.0.21/bin/:"$PATH"
$ mkdir _build
$ cd _build
$ cmake .. -DWITH_SSL=/usr/local/Cellar/openssl@1.1/1.1.1g/ -DWITH_JDBC=yes
$ make -j 10
```
TODO: These instructions are coupled to openssl and mysql-client version that
happen to be installed...
# Build on Ubuntu / Debian
```sh
$ apt-get install g++ cmake libmysqlcppconn-dev
$ mkdir _build
$ cd _build
$ cmake ..
$ make -j 10
```

View File

@@ -0,0 +1,88 @@
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <memory>
#include <mariadb/conncpp.hpp>
#define QUERIES_SIZE 14
std::string queries[QUERIES_SIZE] =
{
"create table test (pk int, `value` int, primary key(pk))",
"describe test",
"select * from test",
"insert into test (pk, `value`) values (0,0)",
"select * from test",
"call dolt_add('-A');",
"call dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt_log",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (1,1)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')",
"select COUNT(*) FROM dolt_log",
};
int is_update[QUERIES_SIZE] = {1,0,0,1,0,0,0,0,0,1,0,0,0,0};
int main(int argc, char **argv) {
if (argc < 4) {
std::cerr << "Usage: " << argv[0] << " <user> <port> <database>" << std::endl;
return 1;
}
std::string user = argv[1];
std::string port = argv[2];
std::string db = argv[3];
try {
// Get the driver instance
sql::Driver* driver = sql::mariadb::get_driver_instance();
// Create connection properties
sql::SQLString url("jdbc:mariadb://127.0.0.1:" + port + "/" + db);
sql::Properties properties({{"user", user}, {"password", ""}});
// Establish connection
std::unique_ptr<sql::Connection> con(driver->connect(url, properties));
for ( int i = 0; i < QUERIES_SIZE; i++ ) {
try {
std::unique_ptr<sql::Statement> stmt(con->createStatement());
if ( is_update[i] ) {
stmt->executeUpdate(queries[i]);
} else {
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(queries[i]));
// Assert that all columns have column name metadata populated
sql::ResultSetMetaData* metadata = res->getMetaData();
const uint32_t columnCount = metadata->getColumnCount();
for (uint32_t columnIndex = 1; columnIndex <= columnCount; ++columnIndex) {
sql::SQLString columnName = metadata->getColumnName(columnIndex);
if (columnName.length() == 0) {
std::cerr << "Column name is empty at index " << columnIndex << std::endl;
return 1;
}
}
}
} catch (sql::SQLException &e) {
std::cout << "QUERY: " << queries[i] << std::endl;
std::cout << "# ERR: " << e.what();
std::cout << " (MariaDB error code: " << e.getErrorCode();
std::cout << ", SQLState: " << e.getSQLState() << " )" << std::endl;
return 1;
}
}
return 0;
} catch (sql::SQLException &e) {
std::cerr << "Connection error: " << e.what() << std::endl;
std::cerr << " (MariaDB error code: " << e.getErrorCode();
std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
return 1;
}
}

View File

@@ -46,6 +46,11 @@ std::string queries[QUERIES_SIZE] =
int is_update[QUERIES_SIZE] = {1,0,0,1,0,0,0,0,0,1,0,0,0,0};
int main(int argc, char **argv) {
if (argc < 4) {
std::cerr << "Usage: " << argv[0] << " <user> <port> <database>" << std::endl;
return 1;
}
std::string user = argv[1];
std::string port = argv[2];
std::string db = argv[3];
@@ -63,7 +68,7 @@ int main(int argc, char **argv) {
sql::Statement *stmt = con->createStatement();
if ( is_update[i] ) {
int affected_rows = stmt->executeUpdate(queries[i]);
stmt->executeUpdate(queries[i]);
} else {
sql::ResultSet *res = stmt->executeQuery(queries[i]);

View File

@@ -1,136 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
.vs
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.svclog
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
*.pubxml
*.azurePubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
packages/
## TODO: If the tool you use requires repositories.config, also uncomment the next line
!packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
![Ss]tyle[Cc]op.targets
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
# =========================
# Windows detritus
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac desktop service store files
.DS_Store
_NCrunch*
ZZ

View File

@@ -3,6 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>mysql-client-test</AssemblyName>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
<ItemGroup>

View File

@@ -3,6 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>mysql-connector-test</AssemblyName>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,19 +0,0 @@
defmodule Simple.MixProject do
use Mix.Project
def project do
[
app: :simple,
version: "0.1.0",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:myxql, "~> 0.5.0"},
]
end
end

View File

@@ -0,0 +1,25 @@
defmodule MySQLOTP.Application do
use Application
@impl true
def start(_type, _args) do
IO.puts("MySQLOTP.Application.start/2 called")
# This is a CLI app, so we run the command and exit
# Start the application supervisor first
children = []
opts = [strategy: :one_for_one, name: MySQLOTP.Supervisor]
{:ok, pid} = Supervisor.start_link(children, opts)
IO.puts("Supervisor started")
# Spawn a task to run the CLI command
IO.puts("Spawning MySQLOTPTest.main/1")
spawn(fn ->
MySQLOTPTest.main([])
end)
{:ok, pid}
end
end

View File

@@ -0,0 +1,93 @@
defmodule MySQLOTPTest do
@moduledoc """
Test for MySQL/OTP (Erlang native MySQL client)
Uses the :mysql Erlang library
"""
def main(_args \\ []) do
IO.puts("Starting MySQL/OTP Test")
cli_args = Burrito.Util.Args.get_arguments()
IO.puts("Received CLI args: #{inspect(cli_args)}")
result = run(cli_args)
System.halt(0)
result
end
defp run(args) do
if length(args) < 3 do
IO.puts("Usage: mysql-otp-test <user> <port> <database>")
System.halt(1)
end
user = Enum.at(args, 0)
port_str = Enum.at(args, 1)
database = Enum.at(args, 2)
{port, _} = Integer.parse(port_str)
# Start MySQL/OTP connection using Erlang :mysql module
{:ok, pid} = :mysql.start_link([
host: '127.0.0.1',
port: port,
user: String.to_charlist(user),
password: '',
database: String.to_charlist(database)
])
IO.puts("Connected using MySQL/OTP (Erlang connector)")
# Test queries
queries = [
"create table test (pk int, `value` int, primary key(pk))",
"describe test",
"select * from test",
"insert into test (pk, `value`) values (0,0)",
"select * from test",
"call dolt_add('-A')",
"call dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt_log",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (1,1)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')",
"select COUNT(*) FROM dolt_log"
]
# Execute each query
Enum.each(queries, fn query ->
IO.puts("Executing: #{query}")
case :mysql.query(pid, query) do
:ok ->
IO.puts(" → OK")
{:ok, column_names, rows} ->
IO.puts("#{length(rows)} row(s) returned with #{length(column_names)} column(s)")
{:error, reason} ->
IO.puts("Query failed: #{query}")
IO.puts("Error: #{inspect(reason)}")
:mysql.stop(pid)
System.halt(1)
end
end)
# Verify final log count
{:ok, _columns, rows} = :mysql.query(pid, "select COUNT(*) FROM dolt_log")
[[count]] = rows
if count != 3 do
IO.puts("Expected 3 commits in dolt_log, got #{count}")
:mysql.stop(pid)
System.halt(1)
end
:mysql.stop(pid)
IO.puts("\nAll MySQL/OTP tests passed!")
:ok
end
end

View File

@@ -0,0 +1,43 @@
defmodule MySQLOTP.MixProject do
use Mix.Project
def project do
[
app: :mysql_otp_test,
version: "0.1.0",
elixir: "~> 1.18",
start_permanent: Mix.env() == :prod,
deps: deps(),
releases: releases()
]
end
def application do
[
extra_applications: [:logger, :crypto, :public_key, :ssl],
mod: {MySQLOTP.Application, []}
]
end
defp releases do
[
mysql_otp: [
steps: [:assemble, &Burrito.wrap/1],
burrito: [
targets: [
linux: [os: :linux, cpu: :x86_64]
],
no_native_archivers: true
]
]
]
end
defp deps do
[
{:mysql, "~> 1.9.0"},
{:burrito, "~> 1.4.0"}
]
end
end

View File

@@ -1,16 +1,26 @@
defmodule SmokeTest do
def myTestFunc(arg1, arg2) do
if arg1 != arg2 do
raise "Test error"
end
def main(_args \\ []) do
IO.puts("Starting SmokeTest.main/1")
cli_args = Burrito.Util.Args.get_arguments()
IO.puts("Received CLI args: #{inspect(cli_args)}")
result = run(cli_args)
System.halt(0)
result
end
@spec run :: nil
def run do
args = System.argv()
defp run(args) do
if length(args) < 3 do
IO.puts("Usage: simple <user> <port> <database>")
System.halt(1)
end
user = Enum.at(args, 0)
{port, _} = Integer.parse(Enum.at(args, 1))
port_str = Enum.at(args, 1)
database = Enum.at(args, 2)
{port, _} = Integer.parse(port_str)
{:ok, pid} = MyXQL.start_link(username: user, port: port, database: database)
{:ok, _} = MyXQL.query(pid, "drop table if exists test")
@@ -21,8 +31,6 @@ defmodule SmokeTest do
myTestFunc(result.num_rows, 0)
{:ok, _} = MyXQL.query(pid, "insert into test (pk, `value`) values (0,0)")
# MyXQL uses the CLIENT_FOUND_ROWS flag so we should return the number of rows matched
{:ok, result} = MyXQL.query(pid, "UPDATE test SET pk = pk where pk = 0")
myTestFunc(result.num_rows, 1)
@@ -31,7 +39,7 @@ defmodule SmokeTest do
{:ok, result} = MyXQL.query(pid, "SELECT * FROM test")
myTestFunc(result.num_rows, 1)
myTestFunc(result.rows, [[0,0]])
myTestFunc(result.rows, [[0, 0]])
{:ok, _} = MyXQL.query(pid, "call dolt_add('-A');")
{:ok, _} = MyXQL.query(pid, "call dolt_commit('-m', 'my commit')")
@@ -45,5 +53,12 @@ defmodule SmokeTest do
{:ok, result} = MyXQL.query(pid, "select COUNT(*) FROM dolt_log")
myTestFunc(result.num_rows, 1)
myTestFunc(result.rows, [[3]])
:ok
end
defp myTestFunc(arg1, arg2) do
if arg1 != arg2 do
raise "Test error: expected #{inspect(arg2)}, got #{inspect(arg1)}"
end
end
end

View File

@@ -0,0 +1,25 @@
defmodule Simple.Application do
use Application
@impl true
def start(_type, _args) do
IO.puts("Simple.Application.start/2 called")
# This is a CLI app, so we run the command and exit
# Start the application supervisor first
children = []
opts = [strategy: :one_for_one, name: Simple.Supervisor]
{:ok, pid} = Supervisor.start_link(children, opts)
IO.puts("Supervisor started")
# Spawn a task to run the CLI command
IO.puts("Spawning SmokeTest.main/1")
spawn(fn ->
SmokeTest.main([])
end)
{:ok, pid}
end
end

View File

@@ -0,0 +1,43 @@
defmodule Simple.MixProject do
use Mix.Project
def project do
[
app: :simple,
version: "0.1.0",
elixir: "~> 1.18",
start_permanent: Mix.env() == :prod,
deps: deps(),
releases: releases()
]
end
def application do
[
extra_applications: [:logger, :crypto, :public_key, :ssl],
mod: {Simple.Application, []}
]
end
defp releases do
[
simple: [
steps: [:assemble, &Burrito.wrap/1],
burrito: [
targets: [
linux: [os: :linux, cpu: :x86_64]
],
no_native_archivers: true
]
]
]
end
defp deps do
[
{:myxql, "~> 0.5.0"},
{:burrito, "~> 1.4.0"}
]
end
end

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>j</artifactId>
<version>1.0.0</version>
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.mariadb</groupId>
<artifactId>r2dbc-mariadb</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<id>mysql-connector-test</id>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<finalName>mysql-connector-test</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>MySQLConnectorTest</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
<execution>
<id>mysql-connector-test-collation</id>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<finalName>mysql-connector-test-collation</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>MySQLConnectorTest_Collation</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
<execution>
<id>mariadb-connector-test</id>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<finalName>mariadb-connector-test</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>MariaDBConnectorTest</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
<execution>
<id>mariadb-R2DBC-test</id>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<finalName>mariadb-R2DBC-test</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>MariaDBR2DBCTest</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,179 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.util.Objects;
public class MariaDBConnectorTest {
// TestCase represents a single query test case
static class TestCase {
public String query;
public String expectedResult;
public Object fieldAccessor; // String (column name) or Integer (field position)
public TestCase(String query, String expectedResult, Object fieldAccessor) {
this.query = query;
this.expectedResult = expectedResult;
this.fieldAccessor = fieldAccessor;
}
}
// test queries to be run against Dolt
private static final TestCase[] testCases = {
new TestCase("create table test (pk int, `value` int, primary key(pk))", "0", 1),
new TestCase("describe test", "pk", 1),
new TestCase("select * from test", null, "pk"),
new TestCase("insert into test (pk, `value`) values (0,0)", "1", 1),
new TestCase("select * from test", "0", "test.pk"),
new TestCase("call dolt_add('-A')", "0", 1),
new TestCase("call dolt_commit('-m', 'my commit')", "0", 1),
new TestCase("select COUNT(*) FROM dolt_log", "2", 1),
new TestCase("call dolt_checkout('-b', 'mybranch')", "0", 1),
new TestCase("insert into test (pk, `value`) values (1,1)", "1", 1),
new TestCase("call dolt_commit('-a', '-m', 'my commit2')", "1", 1),
new TestCase("call dolt_checkout('main')", "0", 1),
new TestCase("call dolt_merge('mybranch')", "", 1),
new TestCase("select COUNT(*) FROM dolt_log", "3", "COUNT(*)"),
};
public static void main(String[] args) {
testStatements(args);
testServerSideCursors(args);
testCollation(args);
System.exit(0);
}
// testServerSideCursors does a simple smoke test with server-side cursors to make sure
// results can be read. Note that we don't test results here; this is just a high level
// smoke test that we can execute server-side cursors logic without the server erroring out.
// This test was added for a regression where server-side cursor logic was getting
// corrupted result set memory and sending invalid data to the client, which caused the
// server to error out and crash the connection. If any errors are encountered, a stack trace
// is printed and this function exits with a non-zero code.
// For more details, see: https://github.com/dolthub/dolt/issues/9125
private static void testServerSideCursors(String[] args) {
String user = args[0];
String port = args[1];
String db = args[2];
try {
// MariaDB JDBC URL format
String url = "jdbc:mariadb://127.0.0.1:" + port + "/" + db +
"?useCursorFetch=true";
Connection conn = DriverManager.getConnection(url, user, "");
executePreparedQuery(conn, "SELECT 1;");
executePreparedQuery(conn, "SELECT database();");
executePreparedQuery(conn, "SHOW COLLATION;");
executePreparedQuery(conn, "SHOW COLLATION;");
executePreparedQuery(conn, "SHOW COLLATION;");
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
// executePreparedQuery executes the specified |query| using |conn| as a prepared statement,
// and uses server-side cursor to fetch results. This method does not do any validation of
// results from the query. It is simply a smoke test to ensure the connection doesn't crash.
private static void executePreparedQuery(Connection conn, String query) throws SQLException {
PreparedStatement stmt = conn.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY);
stmt.setFetchSize(25); // needed to ensure a server-side cursor is used
ResultSet rs = stmt.executeQuery();
while (rs.next()) {}
rs.close();
stmt.close();
}
// testStatements executes the queries from |queries| and asserts their results from
// |expectedResults|. If any errors are encountered, a stack trace is printed and this
// function exits with a non-zero code.
private static void testStatements(String[] args) {
Connection conn = null;
String user = args[0];
String port = args[1];
String db = args[2];
try {
// MariaDB JDBC URL format
String url = "jdbc:mariadb://127.0.0.1:" + port + "/" + db;
String password = "";
conn = DriverManager.getConnection(url, user, password);
Statement st = conn.createStatement();
for (TestCase test : testCases) {
if ( st.execute(test.query) ) {
ResultSet rs = st.getResultSet();
if (rs.next()) {
String result = "";
if (test.fieldAccessor instanceof String) {
result = rs.getString((String)test.fieldAccessor);
} else if (test.fieldAccessor instanceof Integer) {
result = rs.getString((Integer)test.fieldAccessor);
} else {
System.out.println("Unsupported field accessor value: " + test.fieldAccessor);
System.exit(1);
}
if (!Objects.equals(test.expectedResult, result) &&
!(test.query.contains("dolt_commit")) &&
!(test.query.contains("dolt_merge"))) {
System.out.println("Query: \n" + test.query);
System.out.println("Expected:\n" + test.expectedResult);
System.out.println("Result:\n" + result);
System.exit(1);
}
}
} else {
String result = Integer.toString(st.getUpdateCount());
if ( !Objects.equals(test.expectedResult, result) ) {
System.out.println("Query: \n" + test.query);
System.out.println("Expected:\n" + test.expectedResult);
System.out.println("Rows Updated:\n" + result);
System.exit(1);
}
}
}
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
// testCollation tests that metadata queries work properly with collations.
// This is a regression test for https://github.com/dolthub/dolt/issues/9890
private static void testCollation(String[] args) {
String user = args[0];
String port = args[1];
String db = args[2];
try {
// MariaDB JDBC URL format
String url = "jdbc:mariadb://127.0.0.1:" + port + "/" + db;
Connection conn = DriverManager.getConnection(url, user, "");
// This should not throw an exception
ResultSet result = conn.getMetaData().getColumns(null, null, null, null);
// Close the result set
if (result != null) {
result.close();
}
conn.close();
} catch (SQLException ex) {
System.out.println("Collation test failed.");
ex.printStackTrace();
System.exit(1);
}
}
}

View File

@@ -0,0 +1,107 @@
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.Result;
import org.mariadb.r2dbc.MariadbConnectionConfiguration;
import org.mariadb.r2dbc.MariadbConnectionFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
public class MariaDBR2DBCTest {
// TestCase represents a single query test case
static class TestCase {
public String query;
public boolean isUpdate;
public TestCase(String query, boolean isUpdate) {
this.query = query;
this.isUpdate = isUpdate;
}
}
// test queries to be run against Dolt
private static final TestCase[] testCases = {
new TestCase("create table test (pk int, `value` int, primary key(pk))", true),
new TestCase("insert into test (pk, `value`) values (0,0)", true),
new TestCase("select * from test", false),
new TestCase("call dolt_add('-A')", false),
new TestCase("call dolt_commit('-m', 'my commit')", false),
new TestCase("select COUNT(*) FROM dolt_log", false),
new TestCase("call dolt_checkout('-b', 'mybranch')", false),
new TestCase("insert into test (pk, `value`) values (1,1)", true),
new TestCase("call dolt_commit('-a', '-m', 'my commit2')", false),
new TestCase("call dolt_checkout('main')", false),
new TestCase("call dolt_merge('mybranch')", false),
new TestCase("select COUNT(*) FROM dolt_log", false),
};
public static void main(String[] args) {
if (args.length < 3) {
System.err.println("Usage: MariaDBR2DBCTest <user> <port> <database>");
System.exit(1);
}
String user = args[0];
int port = Integer.parseInt(args[1]);
String database = args[2];
try {
runTests(user, port, database);
System.out.println("All R2DBC tests passed!");
} catch (Exception e) {
System.err.println("R2DBC test failed: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
private static void runTests(String user, int port, String database) {
// Create connection configuration
MariadbConnectionConfiguration config = MariadbConnectionConfiguration.builder()
.host("127.0.0.1")
.port(port)
.username(user)
.password("")
.database(database)
.build();
ConnectionFactory connectionFactory = new MariadbConnectionFactory(config);
// Run tests reactively - block at the end for the test
Mono.from(connectionFactory.create())
.flatMapMany(connection ->
Flux.fromArray(testCases)
.concatMap(testCase -> executeTest(connection, testCase))
.doFinally(signalType ->
Mono.from(connection.close()).subscribe()
)
)
.blockLast(Duration.ofSeconds(30));
}
private static Mono<Void> executeTest(Connection connection, TestCase testCase) {
System.out.println("Executing: " + testCase.query);
return Mono.from(connection.createStatement(testCase.query).execute())
.flatMap(result -> {
if (testCase.isUpdate) {
// For updates, just get the rows affected
return Mono.from(result.getRowsUpdated()).then();
} else {
// For selects, consume all rows
return Flux.from(result.map((row, metadata) -> row))
.then();
}
})
.onErrorResume(e -> {
System.err.println("Error executing query: " + testCase.query);
System.err.println("Error: " + e.getMessage());
return Mono.error(e);
});
}
}

View File

@@ -1,173 +1,173 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
public class MySQLConnectorTest {
// test queries to be run against Dolt
private static final String[] queries = {
"create table test (pk int, `value` int, primary key(pk))",
"describe test",
"select * from test",
"insert into test (pk, `value`) values (0,0)",
"select * from test",
"call dolt_add('-A')",
"call dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt_log",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (1,1)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')",
"select COUNT(*) FROM dolt_log",
};
// We currently only test a single field value in the first row
private static final String[] expectedResults = {
"0",
"pk",
null,
"1",
"0",
"0",
"0",
"2",
"0",
"1",
"1",
"0",
"",
"3"
};
// fieldAccessors are the value used to access a field in a row in a result set. Currently, only
// String (i.e column name) and Integer (i.e. field position) values are supported.
private static final Object[] fieldAccessors = {
1,
1,
"pk",
1,
"test.pk",
1,
1,
1,
1,
1,
1,
1,
1,
"COUNT(*)",
};
public static void main(String[] args) {
testStatements(args);
testServerSideCursors(args);
System.exit(0);
}
// testServerSideCursors does a simple smoke test with server-side cursors to make sure
// results can be read. Note that we don't test results here; this is just a high level
// smoke test that we can execute server-side cursors logic without the server erroring out.
// This test was added for a regression where server-side cursor logic was getting
// corrupted result set memory and sending invalid data to the client, which caused the
// server to error out and crash the connection. If any errors are encountered, a stack trace
// is printed and this function exits with a non-zero code.
// For more details, see: https://github.com/dolthub/dolt/issues/9125
private static void testServerSideCursors(String[] args) {
String user = args[0];
String port = args[1];
String db = args[2];
try {
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db +
"?useServerPrepStmts=true&useCursorFetch=true";
Connection conn = DriverManager.getConnection(url, user, "");
executePreparedQuery(conn, "SELECT 1;");
executePreparedQuery(conn, "SELECT database();");
executePreparedQuery(conn, "SHOW COLLATION;");
executePreparedQuery(conn, "SHOW COLLATION;");
executePreparedQuery(conn, "SHOW COLLATION;");
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
// executePreparedQuery executes the specified |query| using |conn| as a prepared statement,
// and uses server-side cursor to fetch results. This method does not do any validation of
// results from the query. It is simply a smoke test to ensure the connection doesn't crash.
private static void executePreparedQuery(Connection conn, String query) throws SQLException {
PreparedStatement stmt = conn.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY);
stmt.setFetchSize(25); // needed to ensure a server-side cursor is used
ResultSet rs = stmt.executeQuery();
while (rs.next()) {}
rs.close();
stmt.close();
}
// testStatements executes the queries from |queries| and asserts their results from
// |expectedResults|. If any errors are encountered, a stack trace is printed and this
// function exits with a non-zero code.
private static void testStatements(String[] args) {
Connection conn = null;
String user = args[0];
String port = args[1];
String db = args[2];
try {
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db;
String password = "";
conn = DriverManager.getConnection(url, user, password);
Statement st = conn.createStatement();
for (int i = 0; i < queries.length; i++) {
String query = queries[i];
String expected = expectedResults[i];
if ( st.execute(query) ) {
ResultSet rs = st.getResultSet();
if (rs.next()) {
String result = "";
Object fieldAccessor = fieldAccessors[i];
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);
}
}
} else {
String result = Integer.toString(st.getUpdateCount());
if ( !expected.equals(result) ) {
System.out.println("Query: \n" + query);
System.out.println("Expected:\n" + expected);
System.out.println("Rows Updated:\n" + result);
System.exit(1);
}
}
}
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
public class MySQLConnectorTest {
// test queries to be run against Dolt
private static final String[] queries = {
"create table test (pk int, `value` int, primary key(pk))",
"describe test",
"select * from test",
"insert into test (pk, `value`) values (0,0)",
"select * from test",
"call dolt_add('-A')",
"call dolt_commit('-m', 'my commit')",
"select COUNT(*) FROM dolt_log",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (1,1)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')",
"select COUNT(*) FROM dolt_log",
};
// We currently only test a single field value in the first row
private static final String[] expectedResults = {
"0",
"pk",
null,
"1",
"0",
"0",
"0",
"2",
"0",
"1",
"1",
"0",
"",
"3"
};
// fieldAccessors are the value used to access a field in a row in a result set. Currently, only
// String (i.e column name) and Integer (i.e. field position) values are supported.
private static final Object[] fieldAccessors = {
1,
1,
"pk",
1,
"test.pk",
1,
1,
1,
1,
1,
1,
1,
1,
"COUNT(*)",
};
public static void main(String[] args) {
testStatements(args);
testServerSideCursors(args);
System.exit(0);
}
// testServerSideCursors does a simple smoke test with server-side cursors to make sure
// results can be read. Note that we don't test results here; this is just a high level
// smoke test that we can execute server-side cursors logic without the server erroring out.
// This test was added for a regression where server-side cursor logic was getting
// corrupted result set memory and sending invalid data to the client, which caused the
// server to error out and crash the connection. If any errors are encountered, a stack trace
// is printed and this function exits with a non-zero code.
// For more details, see: https://github.com/dolthub/dolt/issues/9125
private static void testServerSideCursors(String[] args) {
String user = args[0];
String port = args[1];
String db = args[2];
try {
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db +
"?useServerPrepStmts=true&useCursorFetch=true";
Connection conn = DriverManager.getConnection(url, user, "");
executePreparedQuery(conn, "SELECT 1;");
executePreparedQuery(conn, "SELECT database();");
executePreparedQuery(conn, "SHOW COLLATION;");
executePreparedQuery(conn, "SHOW COLLATION;");
executePreparedQuery(conn, "SHOW COLLATION;");
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
// executePreparedQuery executes the specified |query| using |conn| as a prepared statement,
// and uses server-side cursor to fetch results. This method does not do any validation of
// results from the query. It is simply a smoke test to ensure the connection doesn't crash.
private static void executePreparedQuery(Connection conn, String query) throws SQLException {
PreparedStatement stmt = conn.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY);
stmt.setFetchSize(25); // needed to ensure a server-side cursor is used
ResultSet rs = stmt.executeQuery();
while (rs.next()) {}
rs.close();
stmt.close();
}
// testStatements executes the queries from |queries| and asserts their results from
// |expectedResults|. If any errors are encountered, a stack trace is printed and this
// function exits with a non-zero code.
private static void testStatements(String[] args) {
Connection conn = null;
String user = args[0];
String port = args[1];
String db = args[2];
try {
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db;
String password = "";
conn = DriverManager.getConnection(url, user, password);
Statement st = conn.createStatement();
for (int i = 0; i < queries.length; i++) {
String query = queries[i];
String expected = expectedResults[i];
if ( st.execute(query) ) {
ResultSet rs = st.getResultSet();
if (rs.next()) {
String result = "";
Object fieldAccessor = fieldAccessors[i];
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);
}
}
} else {
String result = Integer.toString(st.getUpdateCount());
if ( !expected.equals(result) ) {
System.out.println("Query: \n" + query);
System.out.println("Expected:\n" + expected);
System.out.println("Rows Updated:\n" + result);
System.exit(1);
}
}
}
} catch (SQLException ex) {
System.out.println("An error occurred.");
ex.printStackTrace();
System.exit(1);
}
}
}

View File

@@ -7,4 +7,4 @@ dolt config --global --add user.name mysql-test-runner
dolt config --global --add user.email mysql-test-runner@liquidata.co
echo "Running mysql-client-tests:"
bats /mysql-client-tests/mysql-client-tests.bats
bats /build/bin/mysql-client-tests.bats

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helpers.bash
load /build/bin/helpers.bash
# MySQL client tests are set up to test Dolt as a MySQL server and
# standard MySQL Clients in a wide array of languages. I used BATS because
@@ -25,107 +25,112 @@ teardown() {
fi
}
@test "go go-sql-drive/mysql test" {
(cd $BATS_TEST_DIRNAME/go; go build .)
$BATS_TEST_DIRNAME/go/go $USER $PORT $REPO_NAME
@test "go go-sql-driver/mysql" {
/build/bin/go/sql-driver-mysql-test $USER $PORT $REPO_NAME
}
@test "go go-mysql test" {
(cd $BATS_TEST_DIRNAME/go-mysql; go build .)
$BATS_TEST_DIRNAME/go-mysql/go $USER $PORT $REPO_NAME
@test "go go-mysql" {
/build/bin/go/mysql-client-test $USER $PORT $REPO_NAME
}
@test "python mysql.connector client" {
python3.9 $BATS_TEST_DIRNAME/python/mysql.connector-test.py $USER $PORT $REPO_NAME
@test "python mysql.connector" {
/build/bin/python/mysql-connector-test $USER $PORT $REPO_NAME
}
@test "python mariadb connector" {
/build/bin/python/mariadb-connector-test $USER $PORT $REPO_NAME
}
@test "python pymysql client" {
python3.9 $BATS_TEST_DIRNAME/python/pymysql-test.py $USER $PORT $REPO_NAME
/build/bin/python/pymysql-test $USER $PORT $REPO_NAME
}
@test "python sqlachemy client" {
python3.9 $BATS_TEST_DIRNAME/python/sqlalchemy-test.py $USER $PORT $REPO_NAME
/build/bin/python/sqlalchemy-test $USER $PORT $REPO_NAME
}
@test "mysql-connector-java client" {
javac $BATS_TEST_DIRNAME/java/MySQLConnectorTest.java
java -cp $BATS_TEST_DIRNAME/java:$BATS_TEST_DIRNAME/java/mysql-connector-java-8.0.21.jar MySQLConnectorTest $USER $PORT $REPO_NAME
@test "java mysql-connector-j" {
java -jar /build/bin/java/mysql-connector-test.jar $USER $PORT $REPO_NAME
}
@test "mysql-connector-java client collations" {
javac $BATS_TEST_DIRNAME/java/MySQLConnectorTest_Collation.java
java -cp $BATS_TEST_DIRNAME/java:$BATS_TEST_DIRNAME/java/mysql-connector-java-8.0.21.jar MySQLConnectorTest_Collation $USER $PORT $REPO_NAME
@test "java mysql-connector-j collation" {
java -jar /build/bin/java/mysql-connector-test-collation.jar $USER $PORT $REPO_NAME
}
@test "java mariadb-java-client" {
java -jar /build/bin/java/mariadb-connector-test.jar $USER $PORT $REPO_NAME
}
@test "java r2dbc-mariadb connector" {
java -jar /build/bin/java/mariadb-R2DBC-test.jar $USER $PORT $REPO_NAME
}
@test "node mysql client" {
node $BATS_TEST_DIRNAME/node/index.js $USER $PORT $REPO_NAME
node $BATS_TEST_DIRNAME/node/knex.js $USER $PORT $REPO_NAME
node /build/bin/node/index.js $USER $PORT $REPO_NAME
node /build/bin/node/knex.js $USER $PORT $REPO_NAME
}
@test "node mariadb connector" {
node /build/bin/node/mariadb-connector.js $USER $PORT $REPO_NAME
}
@test "node mysql client, hosted workbench stability" {
node $BATS_TEST_DIRNAME/node/workbench.js $USER $PORT $REPO_NAME $BATS_TEST_DIRNAME/node/testdata
node /build/bin/node/workbench.js $USER $PORT $REPO_NAME /build/bin/node/testdata
}
@test "c mysql connector" {
(cd $BATS_TEST_DIRNAME/c; make clean; make)
$BATS_TEST_DIRNAME/c/mysql-connector-c-test $USER $PORT $REPO_NAME
@test "c mysql client" {
/build/bin/c/mysql-client-test $USER $PORT $REPO_NAME
}
@test "c mariadb client" {
/build/bin/c/mariadb-client-test $USER $PORT $REPO_NAME
}
@test "c mariadb odbc connector" {
/build/bin/c/mariadb-odbc-test $USER $PORT $REPO_NAME
}
@test "cpp mysql connector" {
if [ -d $BATS_TEST_DIRNAME/cpp/_build ]
then
rm -rf $BATS_TEST_DIRNAME/cpp/_build/*
else
mkdir $BATS_TEST_DIRNAME/cpp/_build
fi
cd $BATS_TEST_DIRNAME/cpp/_build
if [[ `uname` = "Darwin" ]]; then
PATH=/usr/local/Cellar/mysql-client/8.0.21/bin/:"$PATH" cmake .. -DWITH_SSL=/usr/local/Cellar/openssl@1.1/1.1.1g/ -DWITH_JDBC=yes;
else
cmake ..
fi
cmake ..
make -j 10
$BATS_TEST_DIRNAME/cpp/_build/test_mysql_connector_cxx $USER $PORT $REPO_NAME
cd -
/build/bin/cpp/mysql-connector-test $USER $PORT $REPO_NAME
}
@test "cpp mariadb connector" {
/build/bin/cpp/mariadb-connector-test $USER $PORT $REPO_NAME
}
@test "dotnet mysql connector" {
cd $BATS_TEST_DIRNAME/dotnet/MySqlConnector
# dotnet run uses output channel 3 which conflicts with bats so we pipe it to null
dotnet run -- $USER $PORT $REPO_NAME 3>&-
/build/bin/dotnet/mysql-connector-test $USER $PORT $REPO_NAME
}
@test "dotnet mysql client" {
cd $BATS_TEST_DIRNAME/dotnet/MySqlClient
# dotnet run uses output channel 3 which conflicts with bats so we pipe it to null
dotnet run -- $USER $PORT $REPO_NAME 3>&-
/build/bin/dotnet/mysql-client-test $USER $PORT $REPO_NAME
}
@test "perl DBD:mysql client" {
perl $BATS_TEST_DIRNAME/perl/dbd-mysql-test.pl $USER $PORT $REPO_NAME
perl /build/bin/perl/dbd-mysql-test.pl $USER $PORT $REPO_NAME
}
@test "ruby ruby/mysql test" {
ruby $BATS_TEST_DIRNAME/ruby/ruby-mysql-test.rb $USER $PORT $REPO_NAME
@test "perl DBD:MariaDB client" {
perl /build/bin/perl/dbd-mariadb-test.pl $USER $PORT $REPO_NAME
}
@test "ruby mysql2 test" {
ruby $BATS_TEST_DIRNAME/ruby/mysql2-test.rb $USER $PORT $REPO_NAME
@test "ruby ruby/mysql client" {
ruby /build/bin/ruby/mysql-client-test.rb $USER $PORT $REPO_NAME
}
@test "elixir myxql test" {
cd $BATS_TEST_DIRNAME/elixir/
# install some mix dependencies
mix local.hex --force
mix local.rebar --force
mix deps.get
# run the test
mix run -e "IO.puts(SmokeTest.run())" $USER $PORT $REPO_NAME
@test "ruby mysql2" {
ruby /build/bin/ruby/mysql2-test.rb $USER $PORT $REPO_NAME
}
@test "mysqldump works" {
@test "elixir myxql" {
/build/bin/elixir/myxql-driver-test $USER $PORT $REPO_NAME
}
@test "elixir mysql-otp" {
/build/bin/elixir/mysql-otp-test $USER $PORT $REPO_NAME
}
@test "mysqldump" {
mysqldump $REPO_NAME -P $PORT -h 0.0.0.0 -u $USER
}
@@ -163,27 +168,26 @@ EOF" -m "postgres"
}
@test "R RMySQL client" {
Rscript $BATS_TEST_DIRNAME/r/rmysql-test.r $USER $PORT $REPO_NAME
Rscript /build/bin/r/rmysql-test.r $USER $PORT $REPO_NAME
}
@test "R RMariaDB client" {
skip "Error loading RMariaDB library"
# ex: https://github.com/dolthub/dolt/actions/runs/4428743682/jobs/7770282852
Rscript $BATS_TEST_DIRNAME/r/rmariadb-test.r $USER $PORT $REPO_NAME
Rscript /build/bin/r/rmariadb-test.r $USER $PORT $REPO_NAME
}
@test "rust mysql client" {
cd $BATS_TEST_DIRNAME/rust
./target/debug/mysql_connector_test $USER $PORT $REPO_NAME
/build/bin/rust/mysql-client-test $USER $PORT $REPO_NAME
}
@test "swift perfect-mariadb client" {
/build/bin/swift/mariadb-swift-test $USER $PORT $REPO_NAME
}
@test "php mysqli mysql client" {
cd $BATS_TEST_DIRNAME/php
php mysqli_connector_test.php $USER $PORT $REPO_NAME
php /build/bin/php/mysqli_connector_test.php $USER $PORT $REPO_NAME
}
@test "php pdo mysql client" {
cd $BATS_TEST_DIRNAME/php
php pdo_connector_test.php $USER $PORT $REPO_NAME
php /build/bin/php/pdo_connector_test.php $USER $PORT $REPO_NAME
}

View File

@@ -0,0 +1,70 @@
import mariadb from "mariadb";
import { getArgs } from "./helpers.js";
const tests = [
{ q: "create table test (pk int, `value` int, primary key(pk))", isQuery: false },
{ q: "describe test", isQuery: true },
{ q: "select * from test", isQuery: true },
{ q: "insert into test (pk, `value`) values (0,0)", isQuery: false },
{ q: "select * from test", isQuery: true },
{ q: "call dolt_add('-A');", isQuery: true },
{ q: "call dolt_commit('-m', 'my commit')", isQuery: true },
{ q: "select COUNT(*) FROM dolt_log", isQuery: true },
{ q: "call dolt_checkout('-b', 'mybranch')", isQuery: true },
{ q: "insert into test (pk, `value`) values (1,1)", isQuery: false },
{ q: "call dolt_commit('-a', '-m', 'my commit2')", isQuery: true },
{ q: "call dolt_checkout('main')", isQuery: true },
{ q: "call dolt_merge('mybranch')", isQuery: true },
{ q: "select COUNT(*) FROM dolt_log", isQuery: true },
];
async function main() {
const { user, port, dbName } = getArgs();
let conn;
try {
// Create connection pool
const pool = mariadb.createPool({
host: "127.0.0.1",
port: port,
user: user,
database: dbName,
connectionLimit: 5,
});
// Get a connection from the pool
conn = await pool.getConnection();
console.log("Connected to MariaDB!");
// Run all tests
for (const test of tests) {
console.log(`Executing: ${test.q}`);
if (test.isQuery) {
// Execute query and fetch results
const rows = await conn.query(test.q);
console.log(`${rows.length} row(s) returned`);
} else {
// Execute update/insert
const result = await conn.query(test.q);
console.log(`${result.affectedRows} row(s) affected`);
}
}
console.log("\nAll MariaDB connector tests passed!");
// Close connection and pool
if (conn) conn.release();
await pool.end();
process.exit(0);
} catch (err) {
console.error("MariaDB connector test failed:");
console.error(err);
if (conn) conn.release();
process.exit(1);
}
}
main();

View File

@@ -11,6 +11,7 @@
"license": "ISC",
"dependencies": {
"knex": "^2.4.0",
"mariadb": "^3.4.5",
"mysql": "^2.18.1",
"mysql2": "^3.9.8",
"wtfnode": "^0.9.1",

View File

@@ -0,0 +1,46 @@
use strict;
use DBI;
my $QUERY_RESPONSE = [
{ "create table test (pk int, `value` int, primary key(pk))" => '0E0' },
{ "describe test" => 2 },
{ "insert into test (pk, `value`) values (0,0)" => 1 },
{ "select * from test" => 1 },
{"call dolt_add('-A');" => 1 },
{"call dolt_commit('-m', 'my commit')" => 1},
{"call dolt_checkout('-b', 'mybranch')" => 1 },
{"insert into test (pk, `value`) values (1,1)" => 1 },
{"call dolt_commit('-a', '-m', 'my commit2')" => 1 },
{"call dolt_checkout('main')" => 1 },
{"call dolt_merge('mybranch')" => 1 },
{"select COUNT(*) FROM dolt_log" => 1 },
];
my $user = $ARGV[0];
my $port = $ARGV[1];
my $db = $ARGV[2];
# Use DBD::MariaDB driver
my $dsn = "DBI:MariaDB:database=$db;host=127.0.0.1;port=$port";
my $dbh = DBI->connect($dsn, $user, "", {PrintError => 1, RaiseError => 1});
print "Connected using DBD::MariaDB\n";
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
}
}
print "All DBD::MariaDB tests passed!\n";
exit 0;

View File

@@ -0,0 +1,81 @@
import mariadb
import sys
QUERY_RESPONSE = [
{"create table test (pk int, `value` int, primary key(pk))": ()},
{"describe test": (
('pk', 'int', 'NO', 'PRI', None, ''),
('value', 'int', 'YES', '', None, '')
)},
{"insert into test (pk, `value`) values (0,0)": ()},
{"select * from test": ((0, 0),)},
{"call dolt_add('-A');": ((0,),)},
{"call dolt_commit('-m', 'my commit')": (('',),)},
{"select COUNT(*) FROM dolt_log": ((2,),)},
{"call dolt_checkout('-b', 'mybranch')": ((0, "Switched to branch 'mybranch'"),)},
{"insert into test (pk, `value`) values (1,1)": ()},
{"call dolt_commit('-a', '-m', 'my commit2')": (('',),)},
{"call dolt_checkout('main')": ((0, "Switched to branch 'main'"),)},
{"call dolt_merge('mybranch')": (('',1,0,),)},
{"select COUNT(*) FROM dolt_log": ((3,),)},
]
def main():
user = sys.argv[1]
port = int(sys.argv[2])
db = sys.argv[3]
try:
# Connect using MariaDB Connector/Python
connection = mariadb.connect(
user=user,
host="127.0.0.1",
port=port,
database=db
)
print(f"Connected to MariaDB using MariaDB Connector/Python v{mariadb.__version__}")
cursor = connection.cursor()
for query_response in QUERY_RESPONSE:
query = list(query_response.keys())[0]
exp_results = query_response[query]
cursor.execute(query)
try:
results = cursor.fetchall()
# MariaDB Connector/Python returns lists, convert to tuples for comparison
results = tuple(tuple(row) if isinstance(row, list) else row for row in results)
# Skip validation for dolt_commit and dolt_merge as their results vary
if ("dolt_commit" not in query) and ("dolt_merge" not in query):
if results != exp_results:
print("Query:")
print(query)
print("Expected:")
print(exp_results)
print("Received:")
print(results)
sys.exit(1)
except mariadb.Error:
# This is a write query with no results
pass
cursor.close()
connection.close()
print("All MariaDB Connector/Python tests passed!")
sys.exit(0)
except mariadb.Error as e:
print(f"Error connecting to MariaDB: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,70 +1,70 @@
import mysql.connector
import sys
QUERY_RESPONSE = [
{"create table test (pk int, `value` int, primary key(pk))": []},
{"describe test": [
('pk', 'int', 'NO', 'PRI', None, ''),
('value', 'int', 'YES', '', None, '')
]},
{"insert into test (pk, `value`) values (0,0)": []},
{"select * from test": [(0, 0)]},
# We used to have a bug where spaces after a semicolon in a query
# would cause a client/server disconnect.
# https://github.com/dolthub/vitess/pull/65
# The following regression tests it.
{"select * from test; ": [(0, 0)]},
{"select * from test; ": [(0, 0)]},
# Test the Dolt SQL functions
{"call dolt_add('-A');": [(0,)]},
{"call dolt_commit('-m', 'my commit')": [('',)]},
{"select COUNT(*) FROM dolt_log": [(2,)]},
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
{"insert into test (pk, `value`) values (1,1)": []},
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
{"call dolt_merge('mybranch')": [('',1,0,)]},
{"select COUNT(*) FROM dolt_log": [(3,)]},
]
def main():
user = sys.argv[1]
port = sys.argv[2]
db = sys.argv[3]
connection = mysql.connector.connect(user=user,
host="127.0.0.1",
port=port,
database=db)
for query_response in QUERY_RESPONSE:
query = list(query_response.keys())[0]
exp_results = query_response[query]
cursor = connection.cursor()
cursor.execute(query)
try:
results = cursor.fetchall()
print(exp_results)
print(results)
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)
exit(1)
except mysql.connector.errors.InterfaceError:
# This is a write query
pass
cursor.close()
connection.close()
exit(0)
main()
import mysql.connector
import sys
QUERY_RESPONSE = [
{"create table test (pk int, `value` int, primary key(pk))": []},
{"describe test": [
('pk', 'int', 'NO', 'PRI', None, ''),
('value', 'int', 'YES', '', None, '')
]},
{"insert into test (pk, `value`) values (0,0)": []},
{"select * from test": [(0, 0)]},
# We used to have a bug where spaces after a semicolon in a query
# would cause a client/server disconnect.
# https://github.com/dolthub/vitess/pull/65
# The following regression tests it.
{"select * from test; ": [(0, 0)]},
{"select * from test; ": [(0, 0)]},
# Test the Dolt SQL functions
{"call dolt_add('-A');": [(0,)]},
{"call dolt_commit('-m', 'my commit')": [('',)]},
{"select COUNT(*) FROM dolt_log": [(2,)]},
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
{"insert into test (pk, `value`) values (1,1)": []},
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
{"call dolt_merge('mybranch')": [('',1,0,)]},
{"select COUNT(*) FROM dolt_log": [(3,)]},
]
def main():
user = sys.argv[1]
port = sys.argv[2]
db = sys.argv[3]
connection = mysql.connector.connect(user=user,
host="127.0.0.1",
port=port,
database=db)
for query_response in QUERY_RESPONSE:
query = list(query_response.keys())[0]
exp_results = query_response[query]
cursor = connection.cursor()
cursor.execute(query)
try:
results = cursor.fetchall()
print(exp_results)
print(results)
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)
except mysql.connector.errors.InterfaceError:
# This is a write query
pass
cursor.close()
connection.close()
sys.exit(0)
main()

View File

@@ -44,11 +44,11 @@ def main():
print(exp_results)
print("Received:")
print(results)
exit(1)
sys.exit(1)
connection.close()
exit(0)
sys.exit(0)
main()

View File

@@ -1,67 +1,67 @@
import sqlalchemy
from sqlalchemy.engine import Engine
from sqlalchemy import create_engine
import sys
QUERY_RESPONSE = [
{"create table test (pk int, `value` int, primary key(pk))": []},
{"describe test": [
('pk', 'int', 'NO', 'PRI', None, ''),
('value', 'int', 'YES', '', None, '')
]},
{"insert into test (pk, `value`) values (0,0)": ()},
{"select * from test": [(0, 0)]},
{"call dolt_add('-A');": [(0,)]},
{"call dolt_commit('-m', 'my commit')": [('',)]},
{"select COUNT(*) FROM dolt_log": [(2,)]},
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
{"insert into test (pk, `value`) values (1,1)": []},
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
{"call dolt_merge('mybranch')": [('',1,0,)]},
{"select COUNT(*) FROM dolt_log": [(3,)]},
]
def main():
user = sys.argv[1]
port = int(sys.argv[2])
db = sys.argv[3]
conn_string_base = "mysql+mysqlconnector://"
engine = create_engine(conn_string_base +
"{user}@127.0.0.1:{port}/{db}".format(user=user,
port=port,
db=db)
)
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(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)
exit(1)
# You can't call fetchall on an insert
# so we'll just ignore the exception
except sqlalchemy.exc.ResourceClosedError:
pass
con.close()
exit(0)
main()
import sqlalchemy
from sqlalchemy.engine import Engine
from sqlalchemy import create_engine
import sys
QUERY_RESPONSE = [
{"create table test (pk int, `value` int, primary key(pk))": []},
{"describe test": [
('pk', 'int', 'NO', 'PRI', None, ''),
('value', 'int', 'YES', '', None, '')
]},
{"insert into test (pk, `value`) values (0,0)": ()},
{"select * from test": [(0, 0)]},
{"call dolt_add('-A');": [(0,)]},
{"call dolt_commit('-m', 'my commit')": [('',)]},
{"select COUNT(*) FROM dolt_log": [(2,)]},
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
{"insert into test (pk, `value`) values (1,1)": []},
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
{"call dolt_merge('mybranch')": [('',1,0,)]},
{"select COUNT(*) FROM dolt_log": [(3,)]},
]
def main():
user = sys.argv[1]
port = int(sys.argv[2])
db = sys.argv[3]
conn_string_base = "mysql+mysqlconnector://"
engine = create_engine(conn_string_base +
"{user}@127.0.0.1:{port}/{db}".format(user=user,
port=port,
db=db)
)
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(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()

View File

@@ -1,89 +1,89 @@
library(RMariaDB)
library(DBI)
args = commandArgs(trailingOnly=TRUE)
user = args[1]
port = args[2]
db = args[3]
conn = dbConnect(RMariaDB::MariaDB(), host="127.0.0.1", port = port,
username = user, dbname = db)
# check standard queries
queries = list("create table test (pk int, value int, primary key(pk))",
"describe test",
"insert into test (pk, `value`) values (0,0)",
"select * from test")
responses = list(NULL,
data.frame(Field = c("pk", "value"), Type = c("int", "int"), Null = c("NO", "YES"), Key = c("PRI", ""), Default = c(NA, NA), Extra = c("", ""), stringsAsFactors = FALSE),
NULL,
data.frame(pk = c(0), value = c(0), stringsAsFactors = FALSE))
for(i in 1:length(queries)) {
q = queries[[i]]
want = responses[[i]]
if (!is.null(want)) {
got <- dbGetQuery(conn, q)
if (!isTRUE(all.equal(want, got))) {
print(q)
print(want)
print(got)
quit(1)
}
} else {
dbExecute(conn, q)
}
}
# check prepared statements
stmt <- dbSendStatement(conn, "INSERT INTO test values (?, ?)")
rs <- dbBind(stmt, list(1,1))
rowsAff <- dbGetRowsAffected(rs)
dbClearResult(rs)
if (rowsAff != 1) {
print("failed to execute prepared statement")
quit(1)
}
got <- dbGetQuery(conn, "select * from test where pk = 1")
want = data.frame(pk = c(1), value = c(1))
if (!isTRUE(all.equal(want, got))) {
print("unexpected prepared statement result")
print(got)
quit(1)
}
dolt_queries = list("call DOLT_ADD('-A')",
"call dolt_commit('-m', 'my commit')",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (2,2)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')")
for(i in 1:length(dolt_queries)) {
q = dolt_queries[[i]]
dbExecute(conn, q)
}
count <- dbGetQuery(conn, "select COUNT(*) as c from dolt_log")
want <- data.frame(c = c(3))
ret <- all.equal(count, want)
if (!ret) {
print("Number of commits is incorrect")
quit(1)
}
# Add a failing query and ensure that the connection does not quit.
# cc. https://github.com/dolthub/dolt/issues/3418
try(dbExecute(conn, "insert into test values (0, 1)"), silent = TRUE)
one <- dbGetQuery(conn, "select 1 as pk")
ret <- one == data.frame(pk=1)
if (!ret) {
print("Number of commits is incorrect")
quit(1)
}
library(RMariaDB)
library(DBI)
args = commandArgs(trailingOnly=TRUE)
user = args[1]
port = args[2]
db = args[3]
conn = dbConnect(RMariaDB::MariaDB(), host="127.0.0.1", port = port,
username = user, dbname = db)
# check standard queries
queries = list("create table test (pk int, value int, primary key(pk))",
"describe test",
"insert into test (pk, `value`) values (0,0)",
"select * from test")
responses = list(NULL,
data.frame(Field = c("pk", "value"), Type = c("int", "int"), Null = c("NO", "YES"), Key = c("PRI", ""), Default = c(NA_character_, NA_character_), Extra = c("", ""), stringsAsFactors = FALSE),
NULL,
data.frame(pk = c(0), value = c(0), stringsAsFactors = FALSE))
for(i in 1:length(queries)) {
q = queries[[i]]
want = responses[[i]]
if (!is.null(want)) {
got <- dbGetQuery(conn, q)
if (!isTRUE(all.equal(want, got))) {
print(q)
print(want)
print(got)
quit(save="no", status=1)
}
} else {
dbExecute(conn, q)
}
}
# check prepared statements
stmt <- dbSendStatement(conn, "INSERT INTO test values (?, ?)")
rs <- dbBind(stmt, list(1,1))
rowsAff <- dbGetRowsAffected(rs)
dbClearResult(rs)
if (rowsAff != 1) {
print("failed to execute prepared statement")
quit(save="no", status=1)
}
got <- dbGetQuery(conn, "select * from test where pk = 1")
want = data.frame(pk = c(1), value = c(1))
if (!isTRUE(all.equal(want, got))) {
print("unexpected prepared statement result")
print(got)
quit(save="no", status=1)
}
dolt_queries = list("call DOLT_ADD('-A')",
"call dolt_commit('-m', 'my commit')",
"call dolt_checkout('-b', 'mybranch')",
"insert into test (pk, `value`) values (2,2)",
"call dolt_commit('-a', '-m', 'my commit2')",
"call dolt_checkout('main')",
"call dolt_merge('mybranch')")
for(i in 1:length(dolt_queries)) {
q = dolt_queries[[i]]
dbExecute(conn, q)
}
count <- dbGetQuery(conn, "select COUNT(*) as c from dolt_log")
want <- data.frame(c = c(3))
ret <- all.equal(count, want)
if (!ret) {
print("Number of commits is incorrect")
quit(save="no", status=1)
}
# Add a failing query and ensure that the connection does not quit.
# cc. https://github.com/dolthub/dolt/issues/3418
try(dbExecute(conn, "insert into test values (0, 1)"), silent = TRUE)
one <- dbGetQuery(conn, "select 1 as pk")
ret <- one == data.frame(pk=1)
if (!ret) {
print("Number of commits is incorrect")
quit(save="no", status=1)
}

View File

@@ -8,5 +8,5 @@ edition = "2018"
mysql = "*"
[[bin]]
name = "mysql_connector_test"
name = "mysql-client-test"
path = "src/mysql_connector_test.rs"

View File

@@ -0,0 +1,25 @@
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "MariaDBTest",
platforms: [
.macOS(.v10_15)
],
dependencies: [
.package(url: "https://github.com/PerfectlySoft/Perfect-MariaDB.git", from: "3.0.0")
],
targets: [
.executableTarget(
name: "MariaDBTest",
dependencies: [
.product(name: "MariaDB", package: "Perfect-MariaDB")
],
path: "Sources",
linkerSettings: [
.linkedLibrary("mariadb")
]
)
]
)

View File

@@ -0,0 +1,127 @@
import Foundation
import MariaDB
func main() throws {
// Get connection parameters from command line
guard CommandLine.arguments.count >= 4 else {
print("Usage: MariaDBTest <user> <port> <database>")
exit(1)
}
let user = CommandLine.arguments[1]
let port = UInt32(CommandLine.arguments[2]) ?? 3306
let database = CommandLine.arguments[3]
// Connect to the database
let mysql = MySQL()
guard mysql.connect(
host: "127.0.0.1",
user: user,
password: "",
db: database,
port: port
) else {
print("Connection failed: \(mysql.errorMessage())")
exit(1)
}
print("Connected to MariaDB successfully")
// Test queries
let queries = [
"CREATE TABLE test (pk INT, value INT, PRIMARY KEY(pk))",
"INSERT INTO test (pk, value) VALUES (0, 0)",
"SELECT * FROM test",
"CALL dolt_add('-A')",
"CALL dolt_commit('-m', 'my commit')",
"SELECT COUNT(*) FROM dolt_log",
"CALL dolt_checkout('-b', 'mybranch')",
"INSERT INTO test (pk, value) VALUES (10, 10)",
"CALL dolt_commit('-a', '-m', 'my commit2')",
"CALL dolt_checkout('main')",
"CALL dolt_merge('mybranch')",
"SELECT COUNT(*) FROM dolt_log"
]
for query in queries {
guard mysql.query(statement: query) else {
print("Query failed: \(query)")
print("Error: \(mysql.errorMessage())")
exit(1)
}
// Consume results if any
let results = mysql.storeResults()
results?.close()
}
// Test prepared statements
print("Testing prepared statements...")
let stmt = MySQLStmt(mysql)
// Test SELECT with prepared statement
let selectQuery = "SELECT * FROM test WHERE pk = ?"
guard stmt.prepare(statement: selectQuery) else {
print("Failed to prepare SELECT statement: \(stmt.errorMessage())")
exit(1)
}
stmt.bindParam(1)
guard stmt.execute() else {
print("Failed to execute SELECT statement: \(stmt.errorMessage())")
exit(1)
}
let resultSet = stmt.results()
resultSet.close()
stmt.close()
// Test INSERT with prepared statement
let insertStmt = MySQLStmt(mysql)
let insertQuery = "INSERT INTO test VALUES (?, ?)"
guard insertStmt.prepare(statement: insertQuery) else {
print("Failed to prepare INSERT statement: \(insertStmt.errorMessage())")
exit(1)
}
insertStmt.bindParam(2)
insertStmt.bindParam(20)
guard insertStmt.execute() else {
print("Failed to execute INSERT statement: \(insertStmt.errorMessage())")
exit(1)
}
insertStmt.close()
// Verify the insert
guard mysql.query(statement: "SELECT * FROM test WHERE pk = 2") else {
print("Failed to verify insert")
exit(1)
}
if let verifyResults = mysql.storeResults() {
let row = verifyResults.next()
guard row != nil else {
print("Expected row not found after insert")
exit(1)
}
verifyResults.close()
}
print("All Swift tests passed!")
mysql.close()
}
do {
try main()
} catch {
print("Error: \(error)")
exit(1)
}