mirror of
https://github.com/dolthub/dolt.git
synced 2025-12-17 03:38:55 -06:00
amend Dockerfile to embed interpreters from diff build stages
# Conflicts: # go/cmd/dolt/doltversion/version.go # go/go.mod # go/go.sum
This commit is contained in:
@@ -1,4 +0,0 @@
|
|||||||
**/.git
|
|
||||||
**/.github
|
|
||||||
**/docker/
|
|
||||||
**/images/
|
|
||||||
108
.github/actions/mysql-client-tests/Dockerfile
vendored
108
.github/actions/mysql-client-tests/Dockerfile
vendored
@@ -4,7 +4,6 @@ ENV CGO_ENABLED=1
|
|||||||
ENV GO_LDFLAGS="-linkmode external -extldflags '-static'"
|
ENV GO_LDFLAGS="-linkmode external -extldflags '-static'"
|
||||||
RUN apk add --no-cache build-base
|
RUN apk add --no-cache build-base
|
||||||
|
|
||||||
|
|
||||||
FROM golang_cgo125 AS dolt_build
|
FROM golang_cgo125 AS dolt_build
|
||||||
COPY dolt/go/go.mod /build/dolt/go/
|
COPY dolt/go/go.mod /build/dolt/go/
|
||||||
COPY go-mysql-server*/ /build/go-mysql-server/
|
COPY go-mysql-server*/ /build/go-mysql-server/
|
||||||
@@ -13,18 +12,16 @@ WORKDIR /build/dolt/go/
|
|||||||
RUN go mod download
|
RUN go mod download
|
||||||
RUN apk add --no-cache icu-dev icu-static
|
RUN apk add --no-cache icu-dev icu-static
|
||||||
COPY dolt/go/ /build/dolt/go/
|
COPY dolt/go/ /build/dolt/go/
|
||||||
RUN go build -tags icu_static -o /build/bin/dolt ./cmd/dolt
|
RUN go build -tags icu_static -ldflags "$GO_LDFLAGS" -o /build/bin/dolt ./cmd/dolt
|
||||||
|
|
||||||
|
|
||||||
FROM golang_cgo125 AS go_clients_build
|
FROM golang_cgo125 AS go_clients_build
|
||||||
COPY dolt/integration-tests/mysql-client-tests/go /build/go/
|
COPY dolt/integration-tests/mysql-client-tests/go /build/go/
|
||||||
WORKDIR /build/go/
|
WORKDIR /build/go/
|
||||||
RUN go build -o /build/bin/go-mysql-client-test
|
RUN go build -ldflags "$GO_LDFLAGS" -o /build/bin/go-mysql-client-test
|
||||||
|
|
||||||
COPY dolt/integration-tests/mysql-client-tests/go-mysql/ /build/go-mysql/
|
COPY dolt/integration-tests/mysql-client-tests/go-mysql/ /build/go-mysql/
|
||||||
WORKDIR /build/go-mysql/
|
WORKDIR /build/go-mysql/
|
||||||
RUN go build -o /build/bin/go-sql-driver-test
|
RUN go build -ldflags "$GO_LDFLAGS" -o /build/bin/go-sql-driver-test
|
||||||
|
|
||||||
|
|
||||||
FROM rust:1.90-alpine3.22 AS rust_clients_build
|
FROM rust:1.90-alpine3.22 AS rust_clients_build
|
||||||
COPY dolt/integration-tests/mysql-client-tests/rust/ /build/rust/
|
COPY dolt/integration-tests/mysql-client-tests/rust/ /build/rust/
|
||||||
@@ -32,7 +29,6 @@ WORKDIR /build/rust/
|
|||||||
RUN apk add --no-cache musl-dev
|
RUN apk add --no-cache musl-dev
|
||||||
RUN cargo build --release --target-dir /build/bin/ # exe is in release/
|
RUN cargo build --release --target-dir /build/bin/ # exe is in release/
|
||||||
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS dotnet_clients_build
|
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS dotnet_clients_build
|
||||||
COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlClient/ /build/dotnet/MySqlClient/
|
COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlClient/ /build/dotnet/MySqlClient/
|
||||||
WORKDIR /build/dotnet/MySqlClient/
|
WORKDIR /build/dotnet/MySqlClient/
|
||||||
@@ -42,19 +38,101 @@ COPY dolt/integration-tests/mysql-client-tests/dotnet/MySqlConnector/ /build/dot
|
|||||||
WORKDIR /build/dotnet/MySqlConnector/
|
WORKDIR /build/dotnet/MySqlConnector/
|
||||||
RUN dotnet publish -c Release -o /build/bin
|
RUN dotnet publish -c Release -o /build/bin
|
||||||
|
|
||||||
|
FROM gcc:12.5-bookworm AS c_clients_build
|
||||||
FROM mcr.microsoft.com/devcontainers/cpp:1-bookworm AS c_clients_build
|
RUN apt-get update && apt-get install -y \
|
||||||
COPY dolt/integration-tests/mysql-client-tests/c/vcpkg.json /build/c/vcpkg.json
|
default-libmysqlclient-dev \
|
||||||
WORKDIR /build/c/
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN vcpkg install --triplet=x64-linux-release
|
|
||||||
COPY dolt/integration-tests/mysql-client-tests/c/ /build/c/
|
COPY dolt/integration-tests/mysql-client-tests/c/ /build/c/
|
||||||
ENV CMAKE_TOOLCHAIN_FILE=/usr/local/vcpkg/scripts/buildsystems/vcpkg.cmake
|
WORKDIR /build/c/
|
||||||
RUN cmake -B build -S .
|
RUN make
|
||||||
RUN cmake --build build
|
|
||||||
|
|
||||||
|
FROM gcc:12.5-bookworm AS cpp_clients_build
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libmysqlcppconn-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
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 && 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 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 py-sqlalchemy-test.py
|
||||||
|
RUN pyinstaller --onefile --collect-all mysql.connector py-mysql-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/mix.exs /build/elixir/
|
||||||
|
WORKDIR /build/elixir/
|
||||||
|
RUN mix local.hex --force && mix local.rebar --force
|
||||||
|
RUN mix deps.get
|
||||||
|
COPY dolt/integration-tests/mysql-client-tests/elixir/ /build/elixir/
|
||||||
|
RUN MIX_ENV=prod mix release
|
||||||
|
RUN mkdir -p /build/bin && cp burrito_out/simple_linux /build/bin/elixir-mysql-client-test
|
||||||
|
|
||||||
|
FROM maven:3.9.11-amazoncorretto-21-debian-bookworm 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 php:8.3-bookworm AS php_clients_build
|
||||||
|
RUN docker-php-ext-install mysqli pdo_mysql
|
||||||
|
COPY dolt/integration-tests/mysql-client-tests/php/ /build/bin/
|
||||||
|
|
||||||
|
FROM ruby:3.4-bookworm AS ruby_clients_build
|
||||||
|
RUN apt-get update && apt-get install -y default-libmysqlclient-dev && 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 php:8.3-bookworm AS runtime
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
libmysqlcppconn-dev \
|
||||||
|
r-base \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN R -e "install.packages(c('RMariaDB', 'RMySQL', 'DBI'), repos='https://cloud.r-project.org/')"
|
||||||
|
|
||||||
|
COPY --from=dolt_build /build/bin/ /usr/local/bin/
|
||||||
|
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=php_clients_build /build/bin/ /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 --from=java_clients_build /build/jre/ /opt/jre/
|
||||||
|
COPY --from=node_clients_build /usr/local/bin/node /usr/local/bin/node
|
||||||
|
COPY --from=php_clients_build /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
|
||||||
|
COPY --from=php_clients_build /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
|
||||||
|
COPY --from=ruby_clients_build /usr/local/bin/ruby /usr/local/bin/ruby
|
||||||
|
COPY --from=ruby_clients_build /usr/local/lib/ /usr/local/lib/
|
||||||
|
COPY --from=ruby_clients_build /usr/local/bundle/ /usr/local/bundle/
|
||||||
|
ENV PATH="/opt/jre/bin:/usr/local/bundle/bin:${PATH}"
|
||||||
|
ENV GEM_HOME="/usr/local/bundle"
|
||||||
|
ENV BUNDLE_APP_CONFIG="/usr/local/bundle"
|
||||||
|
|||||||
5
.github/actions/mysql-client-tests/Dockerfile.dockerignore
vendored
Normal file
5
.github/actions/mysql-client-tests/Dockerfile.dockerignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
**/.git/
|
||||||
|
**/.github/
|
||||||
|
**/docker/
|
||||||
|
**/images/
|
||||||
|
**/node_modules/
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.24)
|
|
||||||
project(c_mysql_client_test C)
|
|
||||||
find_package(unofficial-libmysql REQUIRED)
|
|
||||||
add_executable("c-mysql-client-test" "mysql-connector-c-test.c")
|
|
||||||
target_link_libraries("c-mysql-client-test" PRIVATE unofficial::libmysql::libmysql stdc++)
|
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
CFLAGS := $(shell pkg-config --cflags mysqlclient)
|
CC = gcc
|
||||||
LDFLAGS := $(shell pkg-config --libs mysqlclient)
|
|
||||||
|
|
||||||
all: mysql-connector-c-test
|
CFLAGS = -I/usr/include/mysql -Wall -O2
|
||||||
|
|
||||||
mysql-connector-c-test: mysql-connector-c-test.c
|
LDFLAGS = -lmysqlclient
|
||||||
|
|
||||||
|
TARGET = /build/bin/c-mysql-client-test
|
||||||
|
|
||||||
|
SRCS = mysql-connector-c-test.c
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(SRCS)
|
||||||
|
@mkdir -p /build/bin
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
clean:
|
||||||
rm -f mysql-connector-c-test
|
rm -f /build/bin/c-mysql-client-test
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
#include <mysql/mysql.h>
|
#include <mysql.h>
|
||||||
|
|
||||||
#define QUERIES_SIZE 14
|
#define QUERIES_SIZE 14
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "mysql-client-test-c",
|
|
||||||
"dependencies": [ "libmysql" ]
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
MYSQL_CONCPP_DIR = /usr/local/Cellar/mysql-connector-c++/8.0.21
|
CXX = g++
|
||||||
CPPFLAGS = -I $(MYSQL_CONCPP_DIR)/include -L $(MYSQL_CONCPP_DIR)/lib64
|
CXXFLAGS = -I/usr/include/cppconn -std=c++11 -Wall -O2
|
||||||
LDLIBS = -lmysqlcppconn8
|
LDFLAGS = -lmysqlcppconn
|
||||||
CXX = clang++ -stdlib=libc++
|
TARGET = /build/bin/cpp-mysql-connector-test
|
||||||
CXXFLAGS = -std=c++11
|
SRCS = mysql-connector-cpp-test.cpp
|
||||||
|
|
||||||
all: mysql-connector-cpp-test
|
all: $(TARGET)
|
||||||
|
|
||||||
mysql-connector-cpp-test: mysql-connector-cpp-test.cpp
|
$(TARGET): $(SRCS)
|
||||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ $(LDLIBS)
|
@mkdir -p /build/bin
|
||||||
|
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
clean:
|
||||||
rm -f mysql-connector-cpp-test
|
rm -f $(TARGET)
|
||||||
|
|||||||
@@ -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 is_update[QUERIES_SIZE] = {1,0,0,1,0,0,0,0,0,1,0,0,0,0};
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
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 user = argv[1];
|
||||||
std::string port = argv[2];
|
std::string port = argv[2];
|
||||||
std::string db = argv[3];
|
std::string db = argv[3];
|
||||||
|
|||||||
Submodule integration-tests/mysql-client-tests/cpp/third_party/mysql-connector-cpp deleted from 857a8d63d8
@@ -1,16 +1,26 @@
|
|||||||
defmodule SmokeTest do
|
defmodule SmokeTest do
|
||||||
def myTestFunc(arg1, arg2) do
|
def main(_args \\ []) do
|
||||||
if arg1 != arg2 do
|
IO.puts("Starting SmokeTest.main/1")
|
||||||
raise "Test error"
|
|
||||||
end
|
cli_args = Burrito.Util.Args.get_arguments()
|
||||||
|
IO.puts("Received CLI args: #{inspect(cli_args)}")
|
||||||
|
|
||||||
|
result = run(cli_args)
|
||||||
|
System.halt(0)
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec run :: nil
|
defp run(args) do
|
||||||
def run do
|
if length(args) < 3 do
|
||||||
args = System.argv()
|
IO.puts("Usage: simple <user> <port> <database>")
|
||||||
|
System.halt(1)
|
||||||
|
end
|
||||||
|
|
||||||
user = Enum.at(args, 0)
|
user = Enum.at(args, 0)
|
||||||
{port, _} = Integer.parse(Enum.at(args, 1))
|
port_str = Enum.at(args, 1)
|
||||||
database = Enum.at(args, 2)
|
database = Enum.at(args, 2)
|
||||||
|
|
||||||
|
{port, _} = Integer.parse(port_str)
|
||||||
|
|
||||||
{:ok, pid} = MyXQL.start_link(username: user, port: port, database: database)
|
{:ok, pid} = MyXQL.start_link(username: user, port: port, database: database)
|
||||||
{:ok, _} = MyXQL.query(pid, "drop table if exists test")
|
{:ok, _} = MyXQL.query(pid, "drop table if exists test")
|
||||||
@@ -21,8 +31,6 @@ defmodule SmokeTest do
|
|||||||
myTestFunc(result.num_rows, 0)
|
myTestFunc(result.num_rows, 0)
|
||||||
|
|
||||||
{:ok, _} = MyXQL.query(pid, "insert into test (pk, `value`) values (0,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")
|
{:ok, result} = MyXQL.query(pid, "UPDATE test SET pk = pk where pk = 0")
|
||||||
myTestFunc(result.num_rows, 1)
|
myTestFunc(result.num_rows, 1)
|
||||||
|
|
||||||
@@ -31,7 +39,7 @@ defmodule SmokeTest do
|
|||||||
|
|
||||||
{:ok, result} = MyXQL.query(pid, "SELECT * FROM test")
|
{:ok, result} = MyXQL.query(pid, "SELECT * FROM test")
|
||||||
myTestFunc(result.num_rows, 1)
|
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_add('-A');")
|
||||||
{:ok, _} = MyXQL.query(pid, "call dolt_commit('-m', 'my commit')")
|
{: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")
|
{:ok, result} = MyXQL.query(pid, "select COUNT(*) FROM dolt_log")
|
||||||
myTestFunc(result.num_rows, 1)
|
myTestFunc(result.num_rows, 1)
|
||||||
myTestFunc(result.rows, [[3]])
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,15 +5,38 @@ defmodule Simple.MixProject do
|
|||||||
[
|
[
|
||||||
app: :simple,
|
app: :simple,
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.18",
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
deps: deps()
|
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
|
end
|
||||||
|
|
||||||
# Run "mix help deps" to learn about dependencies.
|
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:myxql, "~> 0.5.0"},
|
{:myxql, "~> 0.5.0"},
|
||||||
|
{:burrito, "~> 1.4.0"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,173 +1,173 @@
|
|||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.ResultSetMetaData;
|
import java.sql.ResultSetMetaData;
|
||||||
|
|
||||||
public class MySQLConnectorTest {
|
public class MySQLConnectorTest {
|
||||||
|
|
||||||
// test queries to be run against Dolt
|
// test queries to be run against Dolt
|
||||||
private static final String[] queries = {
|
private static final String[] queries = {
|
||||||
"create table test (pk int, `value` int, primary key(pk))",
|
"create table test (pk int, `value` int, primary key(pk))",
|
||||||
"describe test",
|
"describe test",
|
||||||
"select * from test",
|
"select * from test",
|
||||||
"insert into test (pk, `value`) values (0,0)",
|
"insert into test (pk, `value`) values (0,0)",
|
||||||
"select * from test",
|
"select * from test",
|
||||||
"call dolt_add('-A')",
|
"call dolt_add('-A')",
|
||||||
"call dolt_commit('-m', 'my commit')",
|
"call dolt_commit('-m', 'my commit')",
|
||||||
"select COUNT(*) FROM dolt_log",
|
"select COUNT(*) FROM dolt_log",
|
||||||
"call dolt_checkout('-b', 'mybranch')",
|
"call dolt_checkout('-b', 'mybranch')",
|
||||||
"insert into test (pk, `value`) values (1,1)",
|
"insert into test (pk, `value`) values (1,1)",
|
||||||
"call dolt_commit('-a', '-m', 'my commit2')",
|
"call dolt_commit('-a', '-m', 'my commit2')",
|
||||||
"call dolt_checkout('main')",
|
"call dolt_checkout('main')",
|
||||||
"call dolt_merge('mybranch')",
|
"call dolt_merge('mybranch')",
|
||||||
"select COUNT(*) FROM dolt_log",
|
"select COUNT(*) FROM dolt_log",
|
||||||
};
|
};
|
||||||
|
|
||||||
// We currently only test a single field value in the first row
|
// We currently only test a single field value in the first row
|
||||||
private static final String[] expectedResults = {
|
private static final String[] expectedResults = {
|
||||||
"0",
|
"0",
|
||||||
"pk",
|
"pk",
|
||||||
null,
|
null,
|
||||||
"1",
|
"1",
|
||||||
"0",
|
"0",
|
||||||
"0",
|
"0",
|
||||||
"0",
|
"0",
|
||||||
"2",
|
"2",
|
||||||
"0",
|
"0",
|
||||||
"1",
|
"1",
|
||||||
"1",
|
"1",
|
||||||
"0",
|
"0",
|
||||||
"",
|
"",
|
||||||
"3"
|
"3"
|
||||||
};
|
};
|
||||||
|
|
||||||
// fieldAccessors are the value used to access a field in a row in a result set. Currently, only
|
// 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.
|
// String (i.e column name) and Integer (i.e. field position) values are supported.
|
||||||
private static final Object[] fieldAccessors = {
|
private static final Object[] fieldAccessors = {
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
"pk",
|
"pk",
|
||||||
1,
|
1,
|
||||||
"test.pk",
|
"test.pk",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
"COUNT(*)",
|
"COUNT(*)",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
testStatements(args);
|
testStatements(args);
|
||||||
testServerSideCursors(args);
|
testServerSideCursors(args);
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// testServerSideCursors does a simple smoke test with server-side cursors to make sure
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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
|
// 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.
|
// is printed and this function exits with a non-zero code.
|
||||||
// For more details, see: https://github.com/dolthub/dolt/issues/9125
|
// For more details, see: https://github.com/dolthub/dolt/issues/9125
|
||||||
private static void testServerSideCursors(String[] args) {
|
private static void testServerSideCursors(String[] args) {
|
||||||
String user = args[0];
|
String user = args[0];
|
||||||
String port = args[1];
|
String port = args[1];
|
||||||
String db = args[2];
|
String db = args[2];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db +
|
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db +
|
||||||
"?useServerPrepStmts=true&useCursorFetch=true";
|
"?useServerPrepStmts=true&useCursorFetch=true";
|
||||||
Connection conn = DriverManager.getConnection(url, user, "");
|
Connection conn = DriverManager.getConnection(url, user, "");
|
||||||
|
|
||||||
executePreparedQuery(conn, "SELECT 1;");
|
executePreparedQuery(conn, "SELECT 1;");
|
||||||
executePreparedQuery(conn, "SELECT database();");
|
executePreparedQuery(conn, "SELECT database();");
|
||||||
executePreparedQuery(conn, "SHOW COLLATION;");
|
executePreparedQuery(conn, "SHOW COLLATION;");
|
||||||
executePreparedQuery(conn, "SHOW COLLATION;");
|
executePreparedQuery(conn, "SHOW COLLATION;");
|
||||||
executePreparedQuery(conn, "SHOW COLLATION;");
|
executePreparedQuery(conn, "SHOW COLLATION;");
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
System.out.println("An error occurred.");
|
System.out.println("An error occurred.");
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// executePreparedQuery executes the specified |query| using |conn| as a prepared statement,
|
// 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
|
// 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.
|
// 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 {
|
private static void executePreparedQuery(Connection conn, String query) throws SQLException {
|
||||||
PreparedStatement stmt = conn.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY);
|
PreparedStatement stmt = conn.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY);
|
||||||
stmt.setFetchSize(25); // needed to ensure a server-side cursor is used
|
stmt.setFetchSize(25); // needed to ensure a server-side cursor is used
|
||||||
|
|
||||||
ResultSet rs = stmt.executeQuery();
|
ResultSet rs = stmt.executeQuery();
|
||||||
while (rs.next()) {}
|
while (rs.next()) {}
|
||||||
|
|
||||||
rs.close();
|
rs.close();
|
||||||
stmt.close();
|
stmt.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// testStatements executes the queries from |queries| and asserts their results from
|
// testStatements executes the queries from |queries| and asserts their results from
|
||||||
// |expectedResults|. If any errors are encountered, a stack trace is printed and this
|
// |expectedResults|. If any errors are encountered, a stack trace is printed and this
|
||||||
// function exits with a non-zero code.
|
// function exits with a non-zero code.
|
||||||
private static void testStatements(String[] args) {
|
private static void testStatements(String[] args) {
|
||||||
Connection conn = null;
|
Connection conn = null;
|
||||||
|
|
||||||
String user = args[0];
|
String user = args[0];
|
||||||
String port = args[1];
|
String port = args[1];
|
||||||
String db = args[2];
|
String db = args[2];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db;
|
String url = "jdbc:mysql://127.0.0.1:" + port + "/" + db;
|
||||||
String password = "";
|
String password = "";
|
||||||
|
|
||||||
conn = DriverManager.getConnection(url, user, password);
|
conn = DriverManager.getConnection(url, user, password);
|
||||||
Statement st = conn.createStatement();
|
Statement st = conn.createStatement();
|
||||||
|
|
||||||
for (int i = 0; i < queries.length; i++) {
|
for (int i = 0; i < queries.length; i++) {
|
||||||
String query = queries[i];
|
String query = queries[i];
|
||||||
String expected = expectedResults[i];
|
String expected = expectedResults[i];
|
||||||
if ( st.execute(query) ) {
|
if ( st.execute(query) ) {
|
||||||
ResultSet rs = st.getResultSet();
|
ResultSet rs = st.getResultSet();
|
||||||
if (rs.next()) {
|
if (rs.next()) {
|
||||||
String result = "";
|
String result = "";
|
||||||
Object fieldAccessor = fieldAccessors[i];
|
Object fieldAccessor = fieldAccessors[i];
|
||||||
if (fieldAccessor instanceof String) {
|
if (fieldAccessor instanceof String) {
|
||||||
result = rs.getString((String)fieldAccessor);
|
result = rs.getString((String)fieldAccessor);
|
||||||
} else if (fieldAccessor instanceof Integer) {
|
} else if (fieldAccessor instanceof Integer) {
|
||||||
result = rs.getString((Integer)fieldAccessor);
|
result = rs.getString((Integer)fieldAccessor);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Unsupported field accessor value: " + fieldAccessor);
|
System.out.println("Unsupported field accessor value: " + fieldAccessor);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expected.equals(result) && !(query.contains("dolt_commit")) && !(query.contains("dolt_merge"))) {
|
if (!expected.equals(result) && !(query.contains("dolt_commit")) && !(query.contains("dolt_merge"))) {
|
||||||
System.out.println("Query: \n" + query);
|
System.out.println("Query: \n" + query);
|
||||||
System.out.println("Expected:\n" + expected);
|
System.out.println("Expected:\n" + expected);
|
||||||
System.out.println("Result:\n" + result);
|
System.out.println("Result:\n" + result);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String result = Integer.toString(st.getUpdateCount());
|
String result = Integer.toString(st.getUpdateCount());
|
||||||
if ( !expected.equals(result) ) {
|
if ( !expected.equals(result) ) {
|
||||||
System.out.println("Query: \n" + query);
|
System.out.println("Query: \n" + query);
|
||||||
System.out.println("Expected:\n" + expected);
|
System.out.println("Expected:\n" + expected);
|
||||||
System.out.println("Rows Updated:\n" + result);
|
System.out.println("Rows Updated:\n" + result);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
System.out.println("An error occurred.");
|
System.out.println("An error occurred.");
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,70 +1,70 @@
|
|||||||
import mysql.connector
|
import mysql.connector
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
QUERY_RESPONSE = [
|
QUERY_RESPONSE = [
|
||||||
{"create table test (pk int, `value` int, primary key(pk))": []},
|
{"create table test (pk int, `value` int, primary key(pk))": []},
|
||||||
{"describe test": [
|
{"describe test": [
|
||||||
('pk', 'int', 'NO', 'PRI', None, ''),
|
('pk', 'int', 'NO', 'PRI', None, ''),
|
||||||
('value', 'int', 'YES', '', None, '')
|
('value', 'int', 'YES', '', None, '')
|
||||||
]},
|
]},
|
||||||
{"insert into test (pk, `value`) values (0,0)": []},
|
{"insert into test (pk, `value`) values (0,0)": []},
|
||||||
{"select * from test": [(0, 0)]},
|
{"select * from test": [(0, 0)]},
|
||||||
# We used to have a bug where spaces after a semicolon in a query
|
# We used to have a bug where spaces after a semicolon in a query
|
||||||
# would cause a client/server disconnect.
|
# would cause a client/server disconnect.
|
||||||
# https://github.com/dolthub/vitess/pull/65
|
# https://github.com/dolthub/vitess/pull/65
|
||||||
# The following regression tests it.
|
# The following regression tests it.
|
||||||
{"select * from test; ": [(0, 0)]},
|
{"select * from test; ": [(0, 0)]},
|
||||||
{"select * from test; ": [(0, 0)]},
|
{"select * from test; ": [(0, 0)]},
|
||||||
# Test the Dolt SQL functions
|
# Test the Dolt SQL functions
|
||||||
{"call dolt_add('-A');": [(0,)]},
|
{"call dolt_add('-A');": [(0,)]},
|
||||||
{"call dolt_commit('-m', 'my commit')": [('',)]},
|
{"call dolt_commit('-m', 'my commit')": [('',)]},
|
||||||
{"select COUNT(*) FROM dolt_log": [(2,)]},
|
{"select COUNT(*) FROM dolt_log": [(2,)]},
|
||||||
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
|
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
|
||||||
{"insert into test (pk, `value`) values (1,1)": []},
|
{"insert into test (pk, `value`) values (1,1)": []},
|
||||||
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
|
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
|
||||||
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
|
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
|
||||||
{"call dolt_merge('mybranch')": [('',1,0,)]},
|
{"call dolt_merge('mybranch')": [('',1,0,)]},
|
||||||
{"select COUNT(*) FROM dolt_log": [(3,)]},
|
{"select COUNT(*) FROM dolt_log": [(3,)]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
user = sys.argv[1]
|
user = sys.argv[1]
|
||||||
port = sys.argv[2]
|
port = sys.argv[2]
|
||||||
db = sys.argv[3]
|
db = sys.argv[3]
|
||||||
|
|
||||||
connection = mysql.connector.connect(user=user,
|
connection = mysql.connector.connect(user=user,
|
||||||
host="127.0.0.1",
|
host="127.0.0.1",
|
||||||
port=port,
|
port=port,
|
||||||
database=db)
|
database=db)
|
||||||
|
|
||||||
for query_response in QUERY_RESPONSE:
|
for query_response in QUERY_RESPONSE:
|
||||||
query = list(query_response.keys())[0]
|
query = list(query_response.keys())[0]
|
||||||
exp_results = query_response[query]
|
exp_results = query_response[query]
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
try:
|
try:
|
||||||
results = cursor.fetchall()
|
results = cursor.fetchall()
|
||||||
print(exp_results)
|
print(exp_results)
|
||||||
print(results)
|
print(results)
|
||||||
if (results != exp_results) and ("dolt_commit" not in query) and ("dolt_merge" not in query):
|
if (results != exp_results) and ("dolt_commit" not in query) and ("dolt_merge" not in query):
|
||||||
print("Query:")
|
print("Query:")
|
||||||
print(query)
|
print(query)
|
||||||
print("Expected:")
|
print("Expected:")
|
||||||
print(exp_results)
|
print(exp_results)
|
||||||
print("Received:")
|
print("Received:")
|
||||||
print(results)
|
print(results)
|
||||||
exit(1)
|
exit(1)
|
||||||
except mysql.connector.errors.InterfaceError:
|
except mysql.connector.errors.InterfaceError:
|
||||||
|
|
||||||
# This is a write query
|
# This is a write query
|
||||||
pass
|
pass
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
@@ -1,67 +1,67 @@
|
|||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
QUERY_RESPONSE = [
|
QUERY_RESPONSE = [
|
||||||
{"create table test (pk int, `value` int, primary key(pk))": []},
|
{"create table test (pk int, `value` int, primary key(pk))": []},
|
||||||
{"describe test": [
|
{"describe test": [
|
||||||
('pk', 'int', 'NO', 'PRI', None, ''),
|
('pk', 'int', 'NO', 'PRI', None, ''),
|
||||||
('value', 'int', 'YES', '', None, '')
|
('value', 'int', 'YES', '', None, '')
|
||||||
]},
|
]},
|
||||||
{"insert into test (pk, `value`) values (0,0)": ()},
|
{"insert into test (pk, `value`) values (0,0)": ()},
|
||||||
{"select * from test": [(0, 0)]},
|
{"select * from test": [(0, 0)]},
|
||||||
{"call dolt_add('-A');": [(0,)]},
|
{"call dolt_add('-A');": [(0,)]},
|
||||||
{"call dolt_commit('-m', 'my commit')": [('',)]},
|
{"call dolt_commit('-m', 'my commit')": [('',)]},
|
||||||
{"select COUNT(*) FROM dolt_log": [(2,)]},
|
{"select COUNT(*) FROM dolt_log": [(2,)]},
|
||||||
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
|
{"call dolt_checkout('-b', 'mybranch')": [(0, "Switched to branch 'mybranch'")]},
|
||||||
{"insert into test (pk, `value`) values (1,1)": []},
|
{"insert into test (pk, `value`) values (1,1)": []},
|
||||||
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
|
{"call dolt_commit('-a', '-m', 'my commit2')": [('',)]},
|
||||||
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
|
{"call dolt_checkout('main')": [(0, "Switched to branch 'main'")]},
|
||||||
{"call dolt_merge('mybranch')": [('',1,0,)]},
|
{"call dolt_merge('mybranch')": [('',1,0,)]},
|
||||||
{"select COUNT(*) FROM dolt_log": [(3,)]},
|
{"select COUNT(*) FROM dolt_log": [(3,)]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
user = sys.argv[1]
|
user = sys.argv[1]
|
||||||
port = int(sys.argv[2])
|
port = int(sys.argv[2])
|
||||||
db = sys.argv[3]
|
db = sys.argv[3]
|
||||||
|
|
||||||
conn_string_base = "mysql+mysqlconnector://"
|
conn_string_base = "mysql+mysqlconnector://"
|
||||||
|
|
||||||
engine = create_engine(conn_string_base +
|
engine = create_engine(conn_string_base +
|
||||||
"{user}@127.0.0.1:{port}/{db}".format(user=user,
|
"{user}@127.0.0.1:{port}/{db}".format(user=user,
|
||||||
port=port,
|
port=port,
|
||||||
db=db)
|
db=db)
|
||||||
)
|
)
|
||||||
|
|
||||||
with engine.connect() as con:
|
with engine.connect() as con:
|
||||||
for query_response in QUERY_RESPONSE:
|
for query_response in QUERY_RESPONSE:
|
||||||
query = list(query_response.keys())[0]
|
query = list(query_response.keys())[0]
|
||||||
exp_results = query_response[query]
|
exp_results = query_response[query]
|
||||||
|
|
||||||
result_proxy = con.execute(query)
|
result_proxy = con.execute(query)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
results = result_proxy.fetchall()
|
results = result_proxy.fetchall()
|
||||||
if (results != exp_results) and ("dolt_commit" not in query) and ("dolt_merge" not in query):
|
if (results != exp_results) and ("dolt_commit" not in query) and ("dolt_merge" not in query):
|
||||||
print("Query:")
|
print("Query:")
|
||||||
print(query)
|
print(query)
|
||||||
print("Expected:")
|
print("Expected:")
|
||||||
print(exp_results)
|
print(exp_results)
|
||||||
print("Received:")
|
print("Received:")
|
||||||
print(results)
|
print(results)
|
||||||
exit(1)
|
exit(1)
|
||||||
# You can't call fetchall on an insert
|
# You can't call fetchall on an insert
|
||||||
# so we'll just ignore the exception
|
# so we'll just ignore the exception
|
||||||
except sqlalchemy.exc.ResourceClosedError:
|
except sqlalchemy.exc.ResourceClosedError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
con.close()
|
con.close()
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user