diff --git a/.github/toolchains.xml b/.github/toolchains.xml new file mode 100644 index 000000000..201cf84e5 --- /dev/null +++ b/.github/toolchains.xml @@ -0,0 +1,32 @@ + + + + + jdk + + 11 + + + ${env.JAVA_HOME} + + + \ No newline at end of file diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index dc4341e55..0243f9981 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -73,5 +73,6 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | find . -type f -name "log4j.*" -exec rm -fv '{}' \; - mvn -q --batch-mode -Panalysis verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + mvn -q --batch-mode --global-toolchains .github/toolchains.xml -Drelease \ + -Panalysis verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar continue-on-error: true diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 66ecee8ff..a3976baac 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -61,7 +61,6 @@ jobs: run: | mvn --batch-mode --update-snapshots -q -DskipTests install cd clickhouse-benchmark - mvn --batch-mode --update-snapshots install java -DclickhouseVersion="21.8" -jar target/benchmarks.jar -rf text -p client=clickhouse-jdbc Basic echo "BENCHMARK_REPORT<> $GITHUB_ENV cat jmh-result.text >> $GITHUB_ENV diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ade21eba..70cbee1ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,6 +57,12 @@ jobs: key: ${{ runner.os }}-build-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-build- - - name: Build with Maven + - name: Build run: | - mvn --batch-mode --update-snapshots -Drelease -DclickhouseVersion=${{ matrix.clickhouse }} verify + mvn --batch-mode --update-snapshots -DclickhouseVersion=${{ matrix.clickhouse }} verify + if: matrix.java == '8' + - name: Build in release mode + run: | + mvn --batch-mode --update-snapshots --global-toolchains .github/toolchains.xml \ + -Drelease -DclickhouseVersion=${{ matrix.clickhouse }} verify + if: matrix.java != '8' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc7b0f51b..43d558a4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Install Java and Maven uses: actions/setup-java@v1 with: - java-version: 8 + java-version: 11 - name: Update pom files and reduce logs run: | find . -type f -name "pom.xml" -exec sed -i -e 's|${revision}|${{ github.event.inputs.version }}|g' \ diff --git a/.github/workflows/timezone.yml b/.github/workflows/timezone.yml index 3cc190dd6..54e444841 100644 --- a/.github/workflows/timezone.yml +++ b/.github/workflows/timezone.yml @@ -43,10 +43,10 @@ jobs: git fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 \ origin pull/${{ github.event.inputs.pr }}/merge:merged-pr && git checkout merged-pr if: github.event.inputs.pr != '' - - name: Set up JDK 11 + - name: Set up JDK 8 uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 8 - name: Cache maven dependencies uses: actions/cache@v2 with: diff --git a/LICENSE b/LICENSE index 9167b80e2..d64569567 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ -Copyright 2016-2021 Yandex LLC Apache License Version 2.0, January 2004 @@ -188,7 +187,7 @@ Copyright 2016-2021 Yandex LLC same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2016-2021 Yandex LLC + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 8bc5d345b..e37bf9f3b 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,83 @@ -ClickHouse JDBC driver -=============== +# ClickHouse Java Client & JDBC Driver + [![clickhouse-jdbc](https://maven-badges.herokuapp.com/maven-central/ru.yandex.clickhouse/clickhouse-jdbc/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ru.yandex.clickhouse/clickhouse-jdbc) ![Build Status(/~https://github.com/ClickHouse/clickhouse-jdbc/workflows/Build/badge.svg)](/~https://github.com/ClickHouse/clickhouse-jdbc/workflows/Build/badge.svg) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ClickHouse_clickhouse-jdbc&metric=coverage)](https://sonarcloud.io/dashboard?id=ClickHouse_clickhouse-jdbc) -This is a basic and restricted implementation of jdbc driver for ClickHouse. -It has support of a minimal subset of features to be usable. +Java client and JDBC driver for ClickHouse. + +## Usage + +### Java Client + +Use Java client when you prefer async and more "direct" way to communicate with ClickHouse. JDBC driver is actually a thin wrapper of the Java client. + +```xml + + com.clickhouse + + clickhouse-grpc-client + 0.3.2 + +``` + +Example: + +```Java +// declare a server to connect to +ClickHouseNode server = ClickHouseNode.of("server.domain", ClickHouseProtocol.GRPC, 9100, "my_db"); + +// run multiple queries in one go and wait until it's finished +ClickHouseClient.send(server, + "create database if not exists test", + "use test", // change current database from my_db to test + "create table if not exists test_table(s String) engine=Memory", + "insert into test_table values('1')('2')('3')", + "select * from test_table limit 1", + "truncate table test_table", + "drop table if exists test_table").get(); + +// query with named parameters +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + ClickHouseResponse resp = client.connect(server) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).set("send_logs_level", "trace") + .query("select id, name from some_table where id in :ids and name like :name").params(Arrays.asList(1,2,3), "%key%").execute().get()) { + // you can also use resp.recordStream() as well + for (ClickHouseRecord record : resp.records()) { + int id = record.getValue(0).asInteger(); + String name = record.getValue(1).asString(); + } + + ClickHouseResponseSummary summary = resp.getSummary(); + long totalRows = summary.getRows(); +} + +// load data with custom writer +ClickHouseClient.load(server, "target_table", ClickHouseFormat.TabSeparated, + ClickHouseCompression.NONE, new ClickHouseWriter() { + @Override + public void write(OutputStream output) throws IOException { + output.write("1\t\\N\n".getBytes()); + output.write("2\t123".getBytes()); + } + }).get(); +``` +### JDBC Driver -### Usage ```xml + ru.yandex.clickhouse clickhouse-jdbc 0.3.2 ``` -URL syntax: -`jdbc:clickhouse://:[/]`, e.g. `jdbc:clickhouse://localhost:8123/test` +URL syntax: `jdbc:clickhouse://:[/[?param1=value1¶m2=value2]]`, e.g. `jdbc:clickhouse://localhost:8123/test?socket_timeout=120000` -JDBC Driver Class: -`ru.yandex.clickhouse.ClickHouseDriver` +JDBC Driver Class: `ru.yandex.clickhouse.ClickHouseDriver` (will be changed to `com.clickhouse.jdbc.ClickHouseDriver` starting from 0.4.0) For example: + ```java String url = "jdbc:clickhouse://localhost:8123/test"; ClickHouseProperties properties = new ClickHouseProperties(); @@ -47,12 +103,13 @@ try (ClickHouseConnection conn = dataSource.getConnection(); Additionally, if you have a few instances, you can use `BalancedClickhouseDataSource`. - ### Extended API + In order to provide non-JDBC complaint data manipulation functionality, proprietary API exists. Entry point for API is `ClickHouseStatement#write()` method. #### Importing file into table + ```java import ru.yandex.clickhouse.ClickHouseStatement; ClickHouseStatement sth = connection.createStatement(); @@ -60,11 +117,12 @@ sth .write() // Write API entrypoint .table("default.my_table") // where to write data .option("format_csv_delimiter", ";") // specific param - .data(new File("/path/to/file.csv.gz"), ClickHouseFormat.CSV, ClickHouseCompression.gzip) // specify input + .data(new File("/path/to/file.csv.gz"), ClickHouseFormat.CSV, ClickHouseCompression.gzip) // specify input .send(); ``` #### Configurable send + ```java import ru.yandex.clickhouse.ClickHouseStatement; ClickHouseStatement sth = connection.createStatement(); @@ -72,12 +130,13 @@ sth .write() .sql("INSERT INTO default.my_table (a,b,c)") .data(new MyCustomInputStream(), ClickHouseFormat.JSONEachRow) - .dataCompression(ClickHouseCompression.brotli) + .dataCompression(ClickHouseCompression.brotli) .addDbParam(ClickHouseQueryParam.MAX_PARALLEL_REPLICAS, 2) .send(); ``` #### Send data in binary formatted with custom user callback + ```java import ru.yandex.clickhouse.ClickHouseStatement; ClickHouseStatement sth = connection.createStatement(); @@ -93,19 +152,55 @@ sth.write().send("INSERT INTO test.writer", new ClickHouseStreamCallback() { ClickHouseFormat.RowBinary); // RowBinary or Native are supported ``` +## Compatibility + +Java 8 or higher is required in order to use Java client and/or JDBC driver. -### Supported Server Versions -All [active releases](../ClickHouse/pulls?q=is%3Aopen+is%3Apr+label%3Arelease) are supported. You can still use the driver for older versions like 18.14 or 19.16 but please keep in mind that they're no longer supported. +### Data Format +`RowBinary` is preferred format in Java client, while JDBC driver uses `TabSeparated`. -### Compiling with maven -The driver is built with maven. -`mvn package -DskipTests=true` +### Data Type + +| Data Type(s) | Java Client | JDBC Driver | Remark | +| ------------------ | ----------- | -------------------------- | --------------------------------------------------------------------- | +| Date\* | Y | Y | | +| DateTime\* | Y | Y | | +| Decimal\* | Y | Y | `SET output_format_decimal_trailing_zeros=1` in 21.9+ for consistency | +| Enum\* | Y | Treated as integer | +| Int*, UInt* | Y | UInt64 is mapped to `long` | +| Geo Types | Y | N | | +| AggregatedFunction | N | N | Partially supported | +| Array | Y | N | | +| Map | Y | Y | | +| Nested | Y | N | | +| Tuple | Y | N | | + +### Server Version + +All [active releases](../ClickHouse/pulls?q=is%3Aopen+is%3Apr+label%3Arelease) are supported. You can still use the JDBC driver for older versions like 18.14 or 19.16, but please keep in mind that they're no longer supported. + +## Build with Maven + +Use `mvn clean verify` to compile, test and generate packages if you're using JDK 8. + +If you want to make a multi-release jar file(see [JEP-238](https://openjdk.java.net/jeps/238)), you'd better use JDK 11 or higher version like 17 with below command line: + +```bash +mvn --global-toolchains .github/toolchains.xml -Drelease clean verify +``` -To build a jar with dependencies use +## Testing -`mvn package assembly:single -DskipTests=true` +By default, docker container will be created automatically during integration test. You can pass system property like `-DclickhouseVersion=21.8` to test against specific version of ClickHouse. +In the case you prefer to test against an existing server, please follow instructions below: -### Build requirements -In order to build the jdbc client one need to have jdk 1.8 or higher. +- make sure the server can be accessed using default account(`default` user without password), which has both DDL and DML privileges +- add below two configuration files to the existing server and expose all ports for external access + - [ports.xml](./tree/master/clickhouse-client/src/test/resources/containers/clickhouse-server/config.d/ports.xml) - enable all ports + - and [users.xml](./tree/master/clickhouse-client/src/test/resources/containers/clickhouse-server/users.d/users.xml) - accounts used for integration test +- put `test.properties` under either `test/resources` or `~/.m2/clickhouse` with content like below: + ```properties + clickhouseServer=127.0.0.1 + ``` diff --git a/clickhouse-benchmark/pom.xml b/clickhouse-benchmark/pom.xml index 09fd5be64..f49918cc0 100644 --- a/clickhouse-benchmark/pom.xml +++ b/clickhouse-benchmark/pom.xml @@ -1,9 +1,8 @@ - + 4.0.0 - tech.clickhouse + com.clickhouse clickhouse-java ${revision} @@ -19,11 +18,11 @@ 1.4.4 2.7.3 - 8.0.25 + 8.0.26 2.5.6 - 42.2.22 + 42.2.23 UTF-8 - 1.32 + 1.33 benchmarks @@ -40,6 +39,32 @@ provided + + ${project.parent.groupId} + clickhouse-client + ${revision} + + + ${project.parent.groupId} + clickhouse-grpc-client + ${revision} + + + + io.grpc + grpc-netty-shaded + + + + io.grpc + grpc-okhttp + + ru.yandex.clickhouse @@ -116,15 +141,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - ${jdk.version} - - org.apache.maven.plugins maven-shade-plugin @@ -140,12 +156,14 @@ org.openjdk.jmh.Main - + *:* + module-info.class + META-INF/MANIFEST.MF META-INF/*.SF META-INF/*.DSA META-INF/*.RSA @@ -157,45 +175,5 @@ - - - - maven-clean-plugin - 2.5 - - - maven-deploy-plugin - 2.8.1 - - - maven-install-plugin - 2.5.1 - - - maven-jar-plugin - 2.4 - - - maven-javadoc-plugin - 2.9.1 - - - maven-resources-plugin - 2.6 - - - maven-site-plugin - 3.3 - - - maven-source-plugin - 2.2.1 - - - maven-surefire-plugin - 2.17 - - - - + \ No newline at end of file diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/BaseState.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/BaseState.java new file mode 100644 index 000000000..ce2f26571 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/BaseState.java @@ -0,0 +1,70 @@ +package com.clickhouse.benchmark; + +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.infra.Blackhole; + +public abstract class BaseState { + // avoid server-side cache + private final Random random; + + // sync vs async + private final Semaphore available; + private final ExecutorService executor; + + public BaseState() { + random = new Random(); + + int consumers = Integer.parseInt(System.getProperty("consumers", "0")); + + if (consumers > 0) { + available = new Semaphore(consumers); + executor = Executors.newSingleThreadExecutor(); + } else { + available = null; + executor = null; + } + } + + protected int getRandomNumber(int bound) { + return random.nextInt(bound); + } + + protected void consume(Blackhole blackhole, Callable task) throws InterruptedException { + if (available != null) { + available.acquire(); + + executor.submit(() -> { + try { + blackhole.consume(task.call()); + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + available.release(); + } + }); + } else { + try { + blackhole.consume(task.call()); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + protected void dispose() { + if (executor != null) { + executor.shutdown(); + try { + executor.awaitTermination(30, TimeUnit.SECONDS); + } catch (Exception e) { + // ignore + } + } + } +} diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Constants.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/Constants.java similarity index 59% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Constants.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/Constants.java index 9b23e84b1..64936933c 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Constants.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/Constants.java @@ -1,9 +1,9 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark; /** * Constant interface. */ -public interface Constants { +public class Constants { public static final String DEFAULT_HOST = "127.0.0.1"; public static final String DEFAULT_DB = "system"; public static final String DEFAULT_USER = "default"; @@ -20,4 +20,14 @@ public interface Constants { public static final String REUSE_CONNECTION = "reuse"; public static final String NEW_CONNECTION = "new"; + + // sample size used in 10k query/insert + public static final int SAMPLE_SIZE = Integer.parseInt(System.getProperty("sampleSize", "10000")); + // floating range(to reduce server-side cache hits) used in 10k query/insert + public static final int FLOATING_RANGE = Integer.parseInt(System.getProperty("floatingRange", "100")); + + public static final String SAMPLES = SAMPLE_SIZE + " + ~" + FLOATING_RANGE; + + private Constants() { + } } diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/ServerState.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/ServerState.java similarity index 82% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/ServerState.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/ServerState.java index 6e1436db9..9d1553b41 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/ServerState.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/ServerState.java @@ -1,4 +1,4 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark; import static java.time.temporal.ChronoUnit.SECONDS; @@ -77,12 +77,16 @@ public void doSetup() throws Exception { container = new GenericContainer<>(new ImageFromDockerfile().withDockerfileFromBuilder(builder -> builder .from(imageNameWithTag) - .run("echo '0.0.0.091003307' > /etc/clickhouse-server/config.d/custom.xml"))) - .withExposedPorts(Constants.GRPC_PORT, Constants.HTTP_PORT, Constants.MYSQL_PORT, - - Constants.NATIVE_PORT) - .waitingFor(Wait.forHttp("/ping").forPort(Constants.HTTP_PORT).forStatusCode(200) - .withStartupTimeout(Duration.of(60, SECONDS))); + .run("echo '0.0.0.08123" + + "90009004" + + "9005" + + "9009" + + "9100' > /etc/clickhouse-server/config.d/custom.xml"))) + .withExposedPorts(Constants.GRPC_PORT, Constants.HTTP_PORT, Constants.MYSQL_PORT, + + Constants.NATIVE_PORT) + .waitingFor(Wait.forHttp("/ping").forPort(Constants.HTTP_PORT).forStatusCode(200) + .withStartupTimeout(Duration.of(60, SECONDS))); container.start(); } diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/ClientBenchmark.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/ClientBenchmark.java new file mode 100644 index 000000000..f44456fb9 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/ClientBenchmark.java @@ -0,0 +1,26 @@ +package com.clickhouse.benchmark.client; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Base class for ClickHouse Java client benchmarking. + */ +@State(Scope.Benchmark) +@Warmup(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Measurement(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Fork(value = 2) +@Threads(value = -1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public abstract class ClientBenchmark { + +} diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/ClientState.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/ClientState.java new file mode 100644 index 000000000..499ae7118 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/ClientState.java @@ -0,0 +1,179 @@ +package com.clickhouse.benchmark.client; + +import java.util.concurrent.Future; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import com.clickhouse.benchmark.BaseState; +import com.clickhouse.benchmark.Constants; +import com.clickhouse.benchmark.ServerState; +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseClientBuilder; +import com.clickhouse.client.ClickHouseCredentials; +import com.clickhouse.client.ClickHouseCompression; +import com.clickhouse.client.ClickHouseFormat; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseRecord; +import com.clickhouse.client.ClickHouseRequest; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.grpc.config.ClickHouseGrpcClientOption; + +@State(Scope.Thread) +public class ClientState extends BaseState { + @Param(value = { "GRPC" }) + private String protocol; + + @Param(value = { Constants.REUSE_CONNECTION, Constants.NEW_CONNECTION }) + private String connection; + + @Param(value = { "RowBinaryWithNamesAndTypes", "TabSeparatedWithNamesAndTypes" }) + private String format; + + @Param(value = { "async", "sync" }) + private String mode; + + @Param(value = { "netty", "okhttp" }) + private String transport; + + private ClickHouseNode server; + private ClickHouseClient client; + + private int randomSample; + private int randomNum; + + private ClickHouseClient createClient() { + String bufferSize = System.getProperty("bufferSize"); + String compression = System.getProperty("compression"); + String threads = System.getProperty("threads"); + String window = System.getProperty("window"); + + ClickHouseClientBuilder builder = ClickHouseClient.builder(); + if (bufferSize != null && !bufferSize.isEmpty()) { + builder.addOption(ClickHouseClientOption.MAX_BUFFER_SIZE, Integer.parseInt(bufferSize)); + } + if (compression != null && !compression.isEmpty()) { + builder.addOption(ClickHouseClientOption.COMPRESSION, compression.toUpperCase()); + if (ClickHouseCompression.NONE.name().equalsIgnoreCase(compression)) { + builder.addOption(ClickHouseGrpcClientOption.USE_FULL_STREAM_DECOMPRESSION, true); + } + } + if (threads != null && !threads.isEmpty()) { + builder.addOption(ClickHouseClientOption.MAX_THREADS_PER_CLIENT, Integer.parseInt(threads)); + } + + if (window != null && !window.isEmpty()) { + builder.addOption(ClickHouseGrpcClientOption.FLOW_CONTROL_WINDOW, Integer.parseInt(window)); + } + + return builder.addOption(ClickHouseClientOption.ASYNC, "async".equals(mode)) + .addOption(ClickHouseGrpcClientOption.USE_OKHTTP, "okhttp".equals(transport)).build(); + } + + @Setup(Level.Trial) + public void doSetup(ServerState serverState) throws Exception { + server = ClickHouseNode.builder().host(serverState.getHost()).port(ClickHouseProtocol.valueOf(protocol)) + .database(serverState.getDatabase()) + .credentials( + ClickHouseCredentials.fromUserAndPassword(serverState.getUser(), serverState.getPassword())) + .build(); + client = createClient(); + + String[] sqls = new String[] { "drop table if exists system.test_insert", + "create table if not exists system.test_insert(i Nullable(UInt64), s Nullable(String), t Nullable(DateTime))engine=Memory" }; + + for (String sql : sqls) { + try (ClickHouseResponse resp = client.connect(server).query(sql).execute().get()) { + + } + } + } + + @TearDown(Level.Trial) + public void doTearDown(ServerState serverState) { + dispose(); + + if (client != null) { + try { + client.close(); + } catch (Exception e) { + // ignore + } + } + } + + @Setup(Level.Iteration) + public void prepare() { + if (!Constants.REUSE_CONNECTION.equalsIgnoreCase(connection)) { + if (client != null) { + try { + client.close(); + } catch (Exception e) { + // ignore + } + } + client = createClient(); + } + + randomSample = getRandomNumber(Constants.SAMPLE_SIZE); + randomNum = getRandomNumber(Constants.FLOATING_RANGE); + } + + @TearDown(Level.Iteration) + public void shutdown() { + if (!Constants.REUSE_CONNECTION.equalsIgnoreCase(connection)) { + try { + client.close(); + } catch (Exception e) { + // ignore + } finally { + client = null; + } + } + } + + public ClickHouseFormat getFormat() { + return ClickHouseFormat.valueOf(format); + } + + public int getSampleSize() { + return Constants.SAMPLE_SIZE; + } + + public int getRandomSample() { + return randomSample; + } + + public int getRandomNumber() { + return randomNum; + } + + public ClickHouseRequest newRequest() { + return client.connect(server); + } + + public void consume(Blackhole blackhole, Future future) throws InterruptedException { + consume(blackhole, () -> { + try (ClickHouseResponse resp = future.get()) { + for (ClickHouseRecord rec : resp.records()) { + for (ClickHouseValue val : rec) { + blackhole.consume(val.asObject()); + } + } + + blackhole.consume(resp.getSummary()); + + return resp.getSummary(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + } +} diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/Load.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/Load.java new file mode 100644 index 000000000..4c9729215 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/Load.java @@ -0,0 +1,51 @@ +package com.clickhouse.benchmark.client; + +import java.util.concurrent.Future; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.infra.Blackhole; +import com.clickhouse.client.ClickHouseResponse; + +public class Load extends ClientBenchmark { + @Benchmark + public void selectDateTime32Rows(Blackhole blackhole, ClientState state) throws Throwable { + int rows = state.getSampleSize() + state.getRandomNumber(); + Future future = state.newRequest().format(state.getFormat()) + .query("select toDateTime('2021-02-20 13:15:20') + number as d from system.numbers limit " + rows) + .execute(); + state.consume(blackhole, future); + } + + @Benchmark + public void selectDateTime64Rows(Blackhole blackhole, ClientState state) throws Throwable { + int rows = state.getSampleSize() + state.getRandomNumber(); + Future future = state.newRequest().format(state.getFormat()).query( + "select toDateTime64('2021-02-20 13:15:20.000000000', 9) + number as d from system.numbers limit " + + rows) + .execute(); + state.consume(blackhole, future); + } + + @Benchmark + public void selectInt32Rows(Blackhole blackhole, ClientState state) throws Throwable { + int rows = state.getSampleSize() + state.getRandomNumber(); + Future future = state.newRequest().format(state.getFormat()) + .query("select toInt32(number) from system.numbers limit " + rows).execute(); + state.consume(blackhole, future); + } + + @Benchmark + public void selectStringRows(Blackhole blackhole, ClientState state) throws Throwable { + int rows = state.getSampleSize() + state.getRandomNumber(); + Future future = state.newRequest().format(state.getFormat()) + .query("select toString(number) as s from system.numbers limit " + rows).execute(); + state.consume(blackhole, future); + } + + @Benchmark + public void selectUInt64Rows(Blackhole blackhole, ClientState state) throws Throwable { + int rows = state.getSampleSize() + state.getRandomNumber(); + Future future = state.newRequest().format(state.getFormat()) + .query("select * from system.numbers limit " + rows).execute(); + state.consume(blackhole, future); + } +} diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/Simple.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/Simple.java new file mode 100644 index 000000000..24ffa9969 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/client/Simple.java @@ -0,0 +1,22 @@ +package com.clickhouse.benchmark.client; + +import java.util.concurrent.Future; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.infra.Blackhole; +import com.clickhouse.client.ClickHouseResponse; + +public class Simple extends ClientBenchmark { + @Benchmark + public void insertOneRandomNumber(Blackhole blackhole, ClientState state) throws Throwable { + Future future = state.newRequest().format(state.getFormat()) + .query("insert into test_insert(i) values(" + state.getRandomSample() + ")").execute(); + state.consume(blackhole, future); + } + + @Benchmark + public void selectOneRandomNumber(Blackhole blackhole, ClientState state) throws Throwable { + Future future = state.newRequest().format(state.getFormat()) + .query("select " + state.getRandomSample() + " as n").execute(); + state.consume(blackhole, future); + } +} diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Basic.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Basic.java similarity index 77% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Basic.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Basic.java index f6e6c118b..2af7f6442 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Basic.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Basic.java @@ -1,13 +1,13 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark.jdbc; import java.sql.ResultSet; import java.sql.Statement; import java.util.Collections; import org.openjdk.jmh.annotations.Benchmark; -public class Basic extends JdbcBenchmark { +public class Basic extends DriverBenchmark { @Benchmark - public int insertOneRandomNumber(ClientState state) throws Throwable { + public int insertOneRandomNumber(DriverState state) throws Throwable { final int num = state.getRandomSample(); return executeInsert(state, "insert into test_insert(i) values(?)", @@ -15,7 +15,7 @@ public int insertOneRandomNumber(ClientState state) throws Throwable { } @Benchmark - public int selectOneRandomNumber(ClientState state) throws Throwable { + public int selectOneRandomNumber(DriverState state) throws Throwable { final int num = state.getRandomSample(); try (Statement stmt = executeQuery(state, "select ? as n", num); ResultSet rs = stmt.getResultSet();) { diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/JdbcBenchmark.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/DriverBenchmark.java similarity index 95% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/JdbcBenchmark.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/DriverBenchmark.java index 39b1bc8f2..8875bfb18 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/JdbcBenchmark.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/DriverBenchmark.java @@ -1,4 +1,4 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; @@ -27,7 +27,7 @@ @Threads(value = -1) @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) -public abstract class JdbcBenchmark { +public abstract class DriverBenchmark { // batch size for mutation private final int BATCH_SIZE = Integer.parseInt(System.getProperty("batchSize", "5000")); // fetch size for query @@ -103,7 +103,7 @@ private int processBatch(Statement s, String sql, Enumeration generato return rows; } - protected int executeInsert(ClientState state, String sql, Enumeration generator) throws SQLException { + protected int executeInsert(DriverState state, String sql, Enumeration generator) throws SQLException { Objects.requireNonNull(generator); final Connection conn = state.getConnection(); @@ -122,7 +122,7 @@ protected int executeInsert(ClientState state, String sql, Enumeration return rows; } - protected Statement executeQuery(ClientState state, String sql, Object... values) throws SQLException { + protected Statement executeQuery(DriverState state, String sql, Object... values) throws SQLException { final Statement stmt; final Connection conn = state.getConnection(); diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/ClientState.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/DriverState.java similarity index 85% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/ClientState.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/DriverState.java index bf5d49f74..2d7285f55 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/ClientState.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/DriverState.java @@ -1,26 +1,22 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark.jdbc; import java.sql.Connection; import java.sql.Driver; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; -import java.util.Random; - import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; +import com.clickhouse.benchmark.BaseState; +import com.clickhouse.benchmark.Constants; +import com.clickhouse.benchmark.ServerState; @State(Scope.Thread) -public class ClientState { - // sample size used in 10k query/insert - private final int SAMPLE_SIZE = Integer.parseInt(System.getProperty("sampleSize", "10000")); - // floating range(to reduce server-side cache hits) used in 10k query/insert - private final int FLOATING_RANGE = Integer.parseInt(System.getProperty("floatingRange", "100")); - +public class DriverState extends BaseState { @Param(value = { "clickhouse4j", "clickhouse-jdbc", "clickhouse-native-jdbc-shaded", "mariadb-java-client", "mysql-connector-java", "postgresql-jdbc" }) private String client; @@ -62,6 +58,8 @@ public void doSetup(ServerState serverState) throws Exception { @TearDown(Level.Trial) public void doTearDown(ServerState serverState) throws SQLException { + dispose(); + if (conn != null) { conn.close(); } @@ -77,9 +75,8 @@ public void prepare() { } } - Random random = new Random(); - randomSample = random.nextInt(SAMPLE_SIZE); - randomNum = random.nextInt(FLOATING_RANGE); + randomSample = getRandomNumber(Constants.SAMPLE_SIZE); + randomNum = getRandomNumber(Constants.FLOATING_RANGE); } @TearDown(Level.Iteration) @@ -96,7 +93,7 @@ public void shutdown() { } public int getSampleSize() { - return SAMPLE_SIZE; + return Constants.SAMPLE_SIZE; } public int getRandomSample() { diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Insertion.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Insertion.java similarity index 86% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Insertion.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Insertion.java index 6501680d4..bd603bbc5 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Insertion.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Insertion.java @@ -1,12 +1,12 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark.jdbc; import java.sql.Timestamp; import java.util.Enumeration; import org.openjdk.jmh.annotations.Benchmark; -public class Insertion extends JdbcBenchmark { +public class Insertion extends DriverBenchmark { @Benchmark - public int insert10kUInt64Rows(ClientState state) throws Throwable { + public int insert10kUInt64Rows(DriverState state) throws Throwable { final int range = state.getRandomNumber(); final int rows = state.getSampleSize() + range; @@ -26,7 +26,7 @@ public Object[] nextElement() { } @Benchmark - public int insert10kStringRows(ClientState state) throws Throwable { + public int insert10kStringRows(DriverState state) throws Throwable { final int range = state.getRandomNumber(); final int rows = state.getSampleSize() + range; @@ -46,7 +46,7 @@ public Object[] nextElement() { } @Benchmark - public int insert10kTimestampRows(ClientState state) throws Throwable { + public int insert10kTimestampRows(DriverState state) throws Throwable { final int range = state.getRandomNumber(); final int rows = state.getSampleSize() + range; diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/JdbcDriver.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/JdbcDriver.java similarity index 97% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/JdbcDriver.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/JdbcDriver.java index 8656638d7..438f03e18 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/JdbcDriver.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/JdbcDriver.java @@ -1,4 +1,6 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark.jdbc; + +import com.clickhouse.benchmark.Constants; public enum JdbcDriver { // ClickHouse4j diff --git a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Query.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Query.java similarity index 50% rename from clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Query.java rename to clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Query.java index 1480a142b..b835e2e95 100644 --- a/clickhouse-benchmark/src/main/java/tech/clickhouse/benchmark/Query.java +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/jdbc/Query.java @@ -1,72 +1,71 @@ -package tech.clickhouse.benchmark; +package com.clickhouse.benchmark.jdbc; import java.sql.ResultSet; import java.sql.Statement; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.infra.Blackhole; -public class Query extends JdbcBenchmark { +public class Query extends DriverBenchmark { @Benchmark - public int select10kUInt64Rows(Blackhole blackhole, ClientState state) throws Throwable { + public void selectDateTime32Rows(Blackhole blackhole, DriverState state) throws Throwable { int num = state.getRandomNumber(); int rows = state.getSampleSize() + num; - try (Statement stmt = executeQuery(state, "select * from system.numbers limit ?", rows)) { + try (Statement stmt = executeQuery(state, + "select toDateTime('2021-02-20 13:15:20') + number as d from system.numbers limit ?", rows)) { ResultSet rs = stmt.getResultSet(); - - int count = 0; while (rs.next()) { - blackhole.consume(rs.getInt(1)); - count++; + blackhole.consume(rs.getTimestamp(1)); } + } + } - if (count != rows) { - throw new IllegalStateException(); + @Benchmark + public void selectDateTime64Rows(Blackhole blackhole, DriverState state) throws Throwable { + int num = state.getRandomNumber(); + int rows = state.getSampleSize() + num; + try (Statement stmt = executeQuery(state, + "select toDateTime64('2021-02-20 13:15:20.000000000', 9) + number as d from system.numbers limit ?", + rows)) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) { + blackhole.consume(rs.getTimestamp(1)); } + } + } - return count; + @Benchmark + public void selectInt32Rows(Blackhole blackhole, DriverState state) throws Throwable { + int num = state.getRandomNumber(); + int rows = state.getSampleSize() + num; + try (Statement stmt = executeQuery(state, "select * from system.numbers limit ?", rows)) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) { + blackhole.consume(rs.getInt(1)); + } } } @Benchmark - public int select10kStringRows(Blackhole blackhole, ClientState state) throws Throwable { + public void selectStringRows(Blackhole blackhole, DriverState state) throws Throwable { int num = state.getRandomNumber(); int rows = state.getSampleSize() + num; try (Statement stmt = executeQuery(state, "select toString(number) as s from system.numbers limit ?", rows)) { ResultSet rs = stmt.getResultSet(); - - int count = 0; while (rs.next()) { blackhole.consume(rs.getString(1)); - count++; - } - - if (count != rows) { - throw new IllegalStateException(); } - - return count; } } @Benchmark - public int select10kTimestampRows(Blackhole blackhole, ClientState state) throws Throwable { + public void selectUInt64Rows(Blackhole blackhole, DriverState state) throws Throwable { int num = state.getRandomNumber(); int rows = state.getSampleSize() + num; - try (Statement stmt = executeQuery(state, - "select toDateTime('2021-02-20 13:15:20') + number as d from system.numbers limit ?", rows)) { + try (Statement stmt = executeQuery(state, "select * from system.numbers limit ?", rows)) { ResultSet rs = stmt.getResultSet(); - - int count = 0; while (rs.next()) { - blackhole.consume(rs.getTimestamp(1)); - count++; - } - - if (count != rows) { - throw new IllegalStateException(); + blackhole.consume(rs.getLong(1)); } - - return count; } } } diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/CompareBenchmark.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/CompareBenchmark.java new file mode 100644 index 000000000..330447fc6 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/CompareBenchmark.java @@ -0,0 +1,64 @@ +package com.clickhouse.benchmark.misc; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import com.clickhouse.benchmark.BaseState; + +@State(Scope.Benchmark) +@Warmup(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Measurement(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Fork(value = 2) +@Threads(value = -1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class CompareBenchmark { + @State(Scope.Thread) + public static class CompareState extends BaseState { + public int samples; + public Object num; + + @Setup(Level.Trial) + public void setupSamples() { + samples = 500000; + } + + @Setup(Level.Iteration) + public void initValueClass() { + num = (byte) getRandomNumber(Byte.MAX_VALUE); + } + } + + @Benchmark + public void compareClass(CompareState state, Blackhole consumer) { + for (int i = 0; i < state.samples; i++) { + byte b = -1; + if (state.num.getClass() == Byte.class) { + b = (byte) state.num; + } + consumer.consume(b); + } + } + + @Benchmark + public void instanceOf(CompareState state, Blackhole consumer) { + for (int i = 0; i < state.samples; i++) { + byte b = -1; + if (state.num instanceof Number) { + b = ((Number) state.num).byteValue(); + } + consumer.consume(b); + } + } +} diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/FactoryBenchmark.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/FactoryBenchmark.java new file mode 100644 index 000000000..5f2216fa4 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/FactoryBenchmark.java @@ -0,0 +1,169 @@ +package com.clickhouse.benchmark.misc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import com.clickhouse.benchmark.BaseState; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.data.ClickHouseBigDecimalValue; +import com.clickhouse.client.data.ClickHouseBigIntegerValue; +import com.clickhouse.client.data.ClickHouseByteValue; +import com.clickhouse.client.data.ClickHouseDateTimeValue; +import com.clickhouse.client.data.ClickHouseDateValue; +import com.clickhouse.client.data.ClickHouseDoubleValue; +import com.clickhouse.client.data.ClickHouseEnumValue; +import com.clickhouse.client.data.ClickHouseFloatValue; +import com.clickhouse.client.data.ClickHouseIntegerValue; +import com.clickhouse.client.data.ClickHouseIpv4Value; +import com.clickhouse.client.data.ClickHouseIpv6Value; +import com.clickhouse.client.data.ClickHouseLongValue; +import com.clickhouse.client.data.ClickHouseShortValue; +import com.clickhouse.client.data.ClickHouseStringValue; +import com.clickhouse.client.data.ClickHouseTimeValue; + +@State(Scope.Benchmark) +@Warmup(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Measurement(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Fork(value = 2) +@Threads(value = -1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class FactoryBenchmark { + @State(Scope.Thread) + public static class FactoryState extends BaseState { + public int samples; + public Class clazz; + public Map, Supplier> mappings; + + private List> classes; + + private static void add(Map, Supplier> map, List> list, + Class clazz, Supplier func) { + if (map.put(clazz, func) != null) { + throw new IllegalArgumentException("Duplicated key: " + clazz); + } + + list.add(clazz); + } + + @Setup(Level.Trial) + public void setupSamples() { + samples = 500000; + Map, Supplier> map = new HashMap<>(); + List> list = new ArrayList<>(); + add(map, list, Byte.class, () -> ClickHouseByteValue.ofNull()); + add(map, list, Short.class, () -> ClickHouseShortValue.ofNull()); + add(map, list, Integer.class, () -> ClickHouseIntegerValue.ofNull()); + add(map, list, Long.class, () -> ClickHouseLongValue.ofNull(false)); + add(map, list, Float.class, () -> ClickHouseFloatValue.ofNull()); + add(map, list, Double.class, () -> ClickHouseDoubleValue.ofNull()); + add(map, list, BigInteger.class, () -> ClickHouseBigIntegerValue.ofNull()); + add(map, list, BigDecimal.class, () -> ClickHouseBigDecimalValue.ofNull()); + add(map, list, Enum.class, () -> ClickHouseEnumValue.ofNull()); + add(map, list, Inet4Address.class, () -> ClickHouseIpv4Value.ofNull()); + add(map, list, Inet6Address.class, () -> ClickHouseIpv6Value.ofNull()); + + // add(map, list, Object[].class, () -> ClickHouseArrayValue.of((Object[]) o)); + add(map, list, LocalDate.class, () -> ClickHouseDateValue.ofNull()); + add(map, list, LocalTime.class, () -> ClickHouseTimeValue.ofNull()); + add(map, list, LocalDateTime.class, () -> ClickHouseDateTimeValue.ofNull(0)); + add(map, list, String.class, () -> ClickHouseStringValue.ofNull()); + + mappings = Collections.unmodifiableMap(map); + classes = Collections.unmodifiableList(new ArrayList<>(list)); + } + + @Setup(Level.Iteration) + public void initValueClass() { + clazz = classes.get(getRandomNumber(classes.size())); + } + } + + static class IfElseFactory { + ClickHouseValue newValue(Class clazz) { + if (Byte.class.equals(clazz)) { + return ClickHouseByteValue.ofNull(); + } else if (Short.class.equals(clazz)) { + return ClickHouseShortValue.ofNull(); + } else if (Integer.class.equals(clazz)) { + return ClickHouseIntegerValue.ofNull(); + } else if (Long.class.equals(clazz)) { + return ClickHouseLongValue.ofNull(false); + } else if (Float.class.equals(clazz)) { + return ClickHouseFloatValue.ofNull(); + } else if (Double.class.equals(clazz)) { + return ClickHouseDoubleValue.ofNull(); + } else if (BigInteger.class.equals(clazz)) { + return ClickHouseBigIntegerValue.ofNull(); + } else if (BigDecimal.class.equals(clazz)) { + return ClickHouseBigDecimalValue.ofNull(); + } else if (Enum.class.equals(clazz)) { + return ClickHouseEnumValue.ofNull(); + } else if (Inet4Address.class.equals(clazz)) { + return ClickHouseIpv4Value.ofNull(); + } else if (Inet6Address.class.equals(clazz)) { + return ClickHouseIpv6Value.ofNull(); + } else if (LocalDate.class.equals(clazz)) { + return ClickHouseDateValue.ofNull(); + } else if (LocalTime.class.equals(clazz)) { + return ClickHouseTimeValue.ofNull(); + } else if (LocalDateTime.class.equals(clazz)) { + return ClickHouseDateTimeValue.ofNull(0); + } else if (String.class.equals(clazz)) { + return ClickHouseStringValue.ofNull(); + } + + throw new IllegalArgumentException("Unsupported type: " + clazz); + } + } + + static class HashMapFactory { + ClickHouseValue newValue(Map, Supplier> mappings, Class clazz) { + Supplier func = mappings.get(clazz); + if (func == null) { + throw new IllegalArgumentException("Unsupported type: " + clazz); + } + return func.get(); + } + } + + @Benchmark + public void ifElse(FactoryState state, Blackhole consumer) { + for (int i = 0; i < state.samples; i++) { + IfElseFactory f = new IfElseFactory(); + consumer.consume(f.newValue(state.clazz)); + } + } + + @Benchmark + public void hashMap(FactoryState state, Blackhole consumer) { + for (int i = 0; i < state.samples; i++) { + HashMapFactory f = new HashMapFactory(); + consumer.consume(f.newValue(state.mappings, state.clazz)); + } + } +} diff --git a/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/ValuesBenchmark.java b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/ValuesBenchmark.java new file mode 100644 index 000000000..1895f89c3 --- /dev/null +++ b/clickhouse-benchmark/src/main/java/com/clickhouse/benchmark/misc/ValuesBenchmark.java @@ -0,0 +1,88 @@ +package com.clickhouse.benchmark.misc; + +import java.util.Random; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Benchmark) +@Warmup(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Measurement(iterations = 10, timeUnit = TimeUnit.SECONDS, time = 1) +@Fork(value = 2) +@Threads(value = -1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class ValuesBenchmark { + @State(Scope.Thread) + public static class ValueState { + private final Random random = new Random(); + + public int samples; + public int num; + + @Setup(Level.Trial) + public void setupSamples() { + samples = 100000; + } + + @Setup(Level.Iteration) + public void setupValue() { + num = random.nextInt(samples); + } + } + + static class AutoBoxing { + private final Number value; + + AutoBoxing(Number value) { + this.value = value; + } + + long getValue() { + return this.value.longValue(); + } + } + + static class Primitive { + private final int value; + + Primitive(int value) { + this.value = value; + } + + long getValue() { + return (long) this.value; + } + } + + @Benchmark + public long autoBoxing(ValueState state, Blackhole consumer) { + for (int i = 0; i < state.samples; i++) { + AutoBoxing v = new AutoBoxing(state.num); + consumer.consume(v.getValue()); + } + + return 1L; + } + + @Benchmark + public long primitive(ValueState state, Blackhole consumer) { + for (int i = 0; i < state.samples; i++) { + Primitive v = new Primitive(state.num); + consumer.consume(v.getValue()); + } + + return 1L; + } +} diff --git a/clickhouse-client/README.md b/clickhouse-client/README.md new file mode 100644 index 000000000..565cc3902 --- /dev/null +++ b/clickhouse-client/README.md @@ -0,0 +1,62 @@ +# ClickHouse Java Client + +Async Java client for ClickHouse. `clickhouse-client` is an abstract module, so it does not work by itself unless being used together with implementation module like `ckhouse-grpc-client` or `clickhouse-http-client`. + +## Quick Start + +```java +import java.time.LocalDateTime; +import java.util.concurrent.CompletableFuture; +import java.util.List; + +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseCluster; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseRecord; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseResponseSummary; +import com.clickhouse.client.ClickHouseValue; + +// declare a server to connect to +ClickHouseNode server = ClickHouseNode.of("server1.domain", ClickHouseProtocol.GRPC, 9100, "my_db"); + +// execute multiple queries one after another within one session +CompletableFuture> future = ClickHouseClient.send(server, + "create database if not exists test", + "use test", // change current database from my_db to test + "create table if not exists test_table(s String) engine=Memory", + "insert into test_table values('1')('2')('3')", + "select * from test_table limit 1", + "truncate table test_table", + "drop table if exists test_table"); +// do something else in current thread, and then retrieve summaries +List results = future.get(); + +// declare a cluster +ClickHouseCluster cluster = ClickHouseCluster.builder() + // defaults to localhost:8123 and http protocol + .addNode(ClickHouseNode.builder().cluster("cluster1").tags("dc1", "rack1", "for-write").build()) + .addNode(ClickHouseNode.of("1.2.3.4", ClickHouseProtocol.GRPC, 9100, "system", "dc2", "rack2", "for-read")) + .build(); + +// issue query against one node via grpc +String sql = "select * from numbers(100)"; +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + // connect to a node which understands gRPC + ClickHouseResponse response = client.connect(cluster).query(sql).execute().get()) { + for (ClickHouseRecord record : response.records()) { + // Don't cache ClickHouseValue as it's a container object reused among all records + ClickHouseValue v = record.getValue(0); + // converts to DateTime64(6) + LocalDateTime dateTime = v.asDateTime(6); + // converts to long/int/byte if you want to + long l = v.asLong(); + int i = v.asInteger(); + byte b = v.asByte(); + } + + // summary will be fully available after all records being retrieved + ClickHouseResponseSummary summary = response.getSummary(); +} +``` diff --git a/clickhouse-client/pom.xml b/clickhouse-client/pom.xml index 6045af1f1..d9f2d7459 100644 --- a/clickhouse-client/pom.xml +++ b/clickhouse-client/pom.xml @@ -1,9 +1,8 @@ - + 4.0.0 - tech.clickhouse + com.clickhouse clickhouse-java ${revision} @@ -16,34 +15,33 @@ Unified Java client for ClickHouse /~https://github.com/ClickHouse/clickhouse-jdbc/tree/master/clickhouse-client - - 4.1.4 - - - com.github.ben-manes.caffeine - caffeine - provided - - - com.lmax - disruptor + ${project.parent.groupId} + org.roaringbitmap provided + + + * + * + + + dnsjava dnsjava provided - org.roaringbitmap - RoaringBitmap + org.lz4 + lz4-java provided org.slf4j slf4j-api + provided @@ -51,16 +49,6 @@ slf4j-log4j12 test - - org.mockito - mockito-all - test - - - com.github.tomakehurst - wiremock-jre8 - test - org.testcontainers testcontainers @@ -75,39 +63,9 @@ - - com.helger.maven - ph-javacc-maven-plugin - ${javacc-plugin.version} - - - jjc - generate-sources - - javacc - - - ${jdk.version} - true - tech.clickhouse.client.parser - src/main/javacc - src/main/java - - - - org.apache.maven.plugins maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - true - - -Xlint:all - - - org.apache.maven.plugins @@ -125,12 +83,12 @@ maven-surefire-plugin - env_str - 416 - false + env_str + 416 + false - + \ No newline at end of file diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java new file mode 100644 index 000000000..1a44571d3 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java @@ -0,0 +1,365 @@ +package com.clickhouse.client; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +/** + * Utility class for validation. + */ +public final class ClickHouseChecker { + private static final String DEFAULT_NAME = "value"; + private static final String ERR_SHOULD_BETWEEN = "%s(%s) should between %s and %s inclusive of both values"; + private static final String ERR_SHOULD_BETWEEN_EXCLUSIVE = "%s(%s) should between %s and %s exclusive of both values"; + private static final String ERR_SHOULD_GE = "%s(%s) should NOT less than %s"; + + static final IllegalArgumentException newException(String format, Object... args) { + return new IllegalArgumentException(String.format(Locale.ROOT, format, args)); + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} inclusive and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static int between(int value, int minValue, int maxValue) { + return between(value, DEFAULT_NAME, minValue, maxValue); + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} inclusive and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static long between(long value, long minValue, long maxValue) { + return between(value, DEFAULT_NAME, minValue, maxValue); + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} inclusive and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static int between(int value, String name, int minValue, int maxValue) { + if (value < minValue || value > maxValue) { + throw newException(ERR_SHOULD_BETWEEN, name, value, minValue, maxValue); + } + + return value; + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} inclusive and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static long between(long value, String name, long minValue, long maxValue) { + if (value < minValue || value > maxValue) { + throw newException(ERR_SHOULD_BETWEEN, name, value, minValue, maxValue); + } + + return value; + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} inclusive and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static BigInteger between(BigInteger value, BigInteger minValue, BigInteger maxValue) { + return between(value, DEFAULT_NAME, minValue, maxValue); + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} inclusive and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static BigInteger between(BigInteger value, String name, BigInteger minValue, BigInteger maxValue) { + if (value.compareTo(minValue) < 0 || value.compareTo(maxValue) > 0) { + throw newException(ERR_SHOULD_BETWEEN, name, value, minValue, maxValue); + } + + return value; + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} *exclusive* and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static BigDecimal between(BigDecimal value, BigDecimal minValue, BigDecimal maxValue) { + return between(value, DEFAULT_NAME, minValue, maxValue); + } + + /** + * Checks if the given {@code value} is between {@code minValue} and + * {@code maxValue} *exclusive* and throws a customized + * {@link IllegalArgumentException} if it is NOT. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @param maxValue maximum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is NOT between + * {@code minValue} and {@code maxValue} + */ + public static BigDecimal between(BigDecimal value, String name, BigDecimal minValue, BigDecimal maxValue) { + if (value.compareTo(minValue) <= 0 || value.compareTo(maxValue) >= 0) { + throw newException(ERR_SHOULD_BETWEEN_EXCLUSIVE, name, value, minValue, maxValue); + } + + return value; + } + + /** + * Checks if the given string is null or empty. + * + * @param value the string to check + * @return true if the string is null or empty; false otherwise + */ + public static boolean isNullOrEmpty(String value) { + return value == null || value.isEmpty(); + } + + /** + * Checks if the given string is neither {@code null} nor empty and throws a + * customized {@link IllegalArgumentException} if it is. + * + * @param value the string to check + * @param name name of the string + * @return the exact same string + * @throws IllegalArgumentException if the string is null or empty + */ + public static String nonEmpty(String value, String name) { + if (isNullOrEmpty(value)) { + throw newException("%s cannot be null or empty string", name); + } + + return value; + } + + /** + * Checks if the given object is NOT {@code null} and throws a customized + * {@link IllegalArgumentException} if it is. + * + * @param type of the object + * @param value the object + * @param name name of the object + * @return the exact same object + * @throws IllegalArgumentException if the object is null + */ + public static final T nonNull(T value, String name) { + if (value == null) { + throw newException("Non-null %s is required", name); + } + + return value; + } + + /** + * Checks if the given {@code value} is NOT less than {@code minValue} and + * throws a customized {@link IllegalArgumentException} if it is. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is less than + * {@code minValue} + */ + public static int notLessThan(int value, String name, int minValue) { + if (value < minValue) { + throw newException(ERR_SHOULD_GE, name, value, minValue); + } + + return value; + } + + /** + * Checks if the given {@code value} is NOT less than {@code minValue} and + * throws a customized {@link IllegalArgumentException} if it is. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is less than + * {@code minValue} + */ + public static long notLessThan(long value, String name, long minValue) { + if (value < minValue) { + throw newException(ERR_SHOULD_GE, name, value, minValue); + } + + return value; + } + + /** + * Checks if the given {@code value} is NOT less than {@code minValue} and + * throws a customized {@link IllegalArgumentException} if it is. + * + * @param value the value to check + * @param name name of the value + * @param minValue minimum value to compare with + * @return the exact same value + * @throws IllegalArgumentException if the {@code value} is less than + * {@code minValue} + */ + public static BigInteger notLessThan(BigInteger value, String name, BigInteger minValue) { + if (value.compareTo(minValue) < 0) { + throw newException(ERR_SHOULD_GE, name, value, minValue); + } + + return value; + } + + /** + * Checks if length of the given byte array is NOT greater than {@code length} + * and throws a customized {@link IllegalArgumentException} if it is. + * + * @param value the byte array to check + * @param name name of the byte array + * @param maxLength maximum length of the byte array + * @return the exact same byte array + * @throws IllegalArgumentException if length of the byte array is greater than + * {@code maxlength} + */ + public static byte[] notLongerThan(byte[] value, String name, int maxLength) { + int length = value == null ? 0 : value.length; + if (length > maxLength) { + throw newException("length of byte array %s is %d, which should NOT longer than %d", name, length, + maxLength); + } + + return value; + } + + /** + * Checks if length of the given byte array is NOT different from + * {@code expectedLength} and throws a customized + * {@link IllegalArgumentException} if it is. + * + * @param value the byte array to check + * @param expectedLength execpted length of the byte array + * @return the exact same byte array + * @throws IllegalArgumentException if length of the byte array is not same as + * {@code expectedLength} + */ + public static byte[] notWithDifferentLength(byte[] value, int expectedLength) { + return notWithDifferentLength(value, DEFAULT_NAME, expectedLength); + } + + /** + * Checks if length of the given byte array is NOT different from + * {@code expectedLength} and throws a customized + * {@link IllegalArgumentException} if it is. + * + * @param value the byte array to check + * @param name name of the byte array + * @param expectedLength execpted length of the byte array + * @return the exact same byte array + * @throws IllegalArgumentException if length of the byte array is not same as + * {@code expectedLength} + */ + public static byte[] notWithDifferentLength(byte[] value, String name, int expectedLength) { + int length = value == null ? 0 : value.length; + if (length != expectedLength) { + throw newException("length of byte array %s is %d, but it should be %d", name, length, expectedLength); + } + + return value; + } + + public static String notWithDifferentLength(String value, int expectedLength) { + return notWithDifferentLength(value, null, DEFAULT_NAME, expectedLength); + } + + public static String notWithDifferentLength(String value, Charset charset, int expectedLength) { + return notWithDifferentLength(value, charset, DEFAULT_NAME, expectedLength); + } + + /** + * Checks if byte length of the given string is NOT different from + * {@code expectedLength} and throws a customized + * {@link IllegalArgumentException} if it is. + * + * @param value the string to check + * @param charset charset used to decode the string for byte length + * comparison, null means utf8 + * @param name name of the byte array + * @param expectedLength execpted length of the string + * @return the exact same string + * @throws IllegalArgumentException if length of the byte array is not same as + * {@code expectedLength} + */ + public static String notWithDifferentLength(String value, Charset charset, String name, int expectedLength) { + if (charset == null) { + charset = StandardCharsets.UTF_8; + } + int length = value == null ? 0 : value.getBytes(charset).length; + if (length != expectedLength) { + throw newException("length of byte array %s is %d, but it should be %d", name, length, expectedLength); + } + + return value; + } + + private ClickHouseChecker() { + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClient.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClient.java new file mode 100644 index 000000000..8b429c72c --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClient.java @@ -0,0 +1,604 @@ +package com.clickhouse.client; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.config.ClickHouseDefaults; +import com.clickhouse.client.data.ClickHousePipedStream; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.exception.ClickHouseExceptionSpecifier; + +/** + * A unified interface defines Java client for ClickHouse. A client can only + * connect to one {@link ClickHouseNode} at a time. When switching from one node + * to another, connection made to previous node will be closed automatically + * before new connection being established. + * + *

+ * To decouple from concrete implementation tied to specific protocol, it is + * recommended to use {@link #builder()} for instantiation. In order to register + * a new type of client, please add + * {@code META-INF/services/com.clickhouse.client.ClickHouseClient} into your + * artifact, so that {@code java.util.SerivceLoader} can discover the + * implementation properly in runtime. + */ +public interface ClickHouseClient extends AutoCloseable { + /** + * Empty stringified parameters. + */ + static final List> EMPTY_STRINGIFIED_PARAMETERS = Collections.emptyList(); + + /** + * Returns a builder for creating a new client. + * + * @return non-null builder, which is mutable and not thread-safe + */ + static ClickHouseClientBuilder builder() { + return new ClickHouseClientBuilder(); + } + + /** + * Gets default {@link java.util.concurrent.ExecutorService} for static methods + * like {@code dump()}, {@code load()}, {@code send()}, and {@code submit()} + * when {@link com.clickhouse.client.config.ClickHouseDefaults#ASYNC} is + * {@code true}. It will be shared among all client instances when + * {@link com.clickhouse.client.config.ClickHouseClientOption#MAX_THREADS_PER_CLIENT} + * is less than or equals to zero. + * + * @return default executor service + */ + static ExecutorService getExecutorService() { + return ClickHouseClientBuilder.defaultExecutor; + } + + /** + * Submits task for execution. Depending on + * {@link com.clickhouse.client.config.ClickHouseDefaults#ASYNC}, it may or may + * not use {@link #getExecutorService()} to run the task in a separate thread. + * + * @param return type of the task + * @param task non-null task + * @return future object to get result + */ + static CompletableFuture submit(Callable task) { + try { + return (boolean) ClickHouseDefaults.ASYNC.getEffectiveDefaultValue() ? CompletableFuture.supplyAsync(() -> { + try { + return task.call(); + } catch (Exception e) { + throw new CompletionException(e); + } + }, getExecutorService()) : CompletableFuture.completedFuture(task.call()); + } catch (CompletionException e) { + throw e; + } catch (Exception e) { + throw new CompletionException(e); + } + } + + /** + * Dumps a table or query result from server into a file. File will be + * created/overwrited as needed. + * + * @param server non-null server to connect to + * @param tableOrQuery table name or a select query + * @param format output format to use + * @param compression compression algorithm to use + * @param file output file + * @return future object to get result + * @throws IOException when failed to create the file or its parent directories + */ + static CompletableFuture dump(ClickHouseNode server, String tableOrQuery, + ClickHouseFormat format, ClickHouseCompression compression, String file) throws IOException { + return dump(server, tableOrQuery, format, compression, ClickHouseUtils.getFileOutputStream(file)); + } + + /** + * Dumps a table or query result from server into output stream. + * + * @param server non-null server to connect to + * @param tableOrQuery table name or a select query + * @param format output format to use, null means + * {@link ClickHouseFormat#TabSeparated} + * @param compression compression algorithm to use, null means + * {@link ClickHouseCompression#NONE} + * @param output output stream, which will be closed automatically at the + * end of the call + * @return future object to get result + */ + static CompletableFuture dump(ClickHouseNode server, String tableOrQuery, + ClickHouseFormat format, ClickHouseCompression compression, OutputStream output) { + if (server == null || tableOrQuery == null || output == null) { + throw new IllegalArgumentException("Non-null server, tableOrQuery, and output are required"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + final String theQuery = tableOrQuery.trim(); + final ClickHouseFormat theFormat = format == null ? ClickHouseFormat.TabSeparated : format; + final ClickHouseCompression theCompression = compression == null ? ClickHouseCompression.NONE : compression; + + return submit(() -> { + try (ClickHouseClient client = newInstance(theServer.getProtocol())) { + ClickHouseRequest request = client.connect(theServer).compression(theCompression).format(theFormat); + // FIXME what if the table name is `try me`? + if (theQuery.indexOf(' ') < 0) { + request.table(theQuery); + } else { + request.query(theQuery); + } + + try (ClickHouseResponse response = request.execute().get()) { + response.dump(output); + return response.getSummary(); + } + } catch (InterruptedException | ExecutionException e) { + throw ClickHouseExceptionSpecifier.specify(e, theServer.getHost(), theServer.getPort()); + } finally { + try { + output.close(); + } catch (Exception e) { + // ignore + } + } + }); + } + + /** + * Loads data from a file into table using specified format and compression + * algorithm. + * + * @param server non-null server to connect to + * @param table non-null target table + * @param format input format to use + * @param compression compression algorithm to use + * @param file file to load + * @return future object to get result + * @throws FileNotFoundException when file not found + */ + static CompletableFuture load(ClickHouseNode server, String table, + ClickHouseFormat format, ClickHouseCompression compression, String file) throws FileNotFoundException { + return load(server, table, format, compression, ClickHouseUtils.getFileInputStream(file)); + } + + /** + * Loads data from a custom writer into a table using specified format and + * compression algorithm. + * + * @param server non-null server to connect to + * @param table non-null target table + * @param format input format to use + * @param compression compression algorithm to use + * @param writer non-null custom writer to generate data + * @return future object to get result + */ + static CompletableFuture load(ClickHouseNode server, String table, + ClickHouseFormat format, ClickHouseCompression compression, ClickHouseWriter writer) { + if (server == null || table == null || writer == null) { + throw new IllegalArgumentException("Non-null server, table, and writer are required"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + final ClickHouseFormat theFormat = format == null ? ClickHouseFormat.TabSeparated : format; + final ClickHouseCompression theCompression = compression == null ? ClickHouseCompression.NONE : compression; + + return submit(() -> { + InputStream input = null; + // must run in async mode so that we won't hold everything in memory + try (ClickHouseClient client = ClickHouseClient.builder() + .nodeSelector(ClickHouseNodeSelector.of(theServer.getProtocol())) + .addOption(ClickHouseClientOption.ASYNC, true).build()) { + ClickHousePipedStream stream = ClickHouseDataStreamFactory.getInstance() + .createPipedStream(client.getConfig()); + // execute query in a separate thread(because async is explicitly set to true) + CompletableFuture future = client.connect(theServer).write().table(table) + .compression(theCompression).format(theFormat).data(input = stream.getInput()).execute(); + try { + // write data into stream in current thread + writer.write(stream); + } finally { + stream.close(); + } + // wait until write & read acomplished + try (ClickHouseResponse response = future.get()) { + return response.getSummary(); + } + } catch (InterruptedException | ExecutionException e) { + throw ClickHouseExceptionSpecifier.specify(e, theServer.getHost(), theServer.getPort()); + } finally { + if (input != null) { + try { + input.close(); + } catch (Exception e) { + // ignore + } + } + } + }); + } + + /** + * Loads data from input stream into a table using specified format and + * compression algorithm. + * + * @param server non-null server to connect to + * @param table non-null target table + * @param format input format to use + * @param compression compression algorithm to use + * @param input input stream, which will be closed automatically at the + * end of the call + * @return future object to get result + */ + static CompletableFuture load(ClickHouseNode server, String table, + ClickHouseFormat format, ClickHouseCompression compression, InputStream input) { + if (server == null || table == null || input == null) { + throw new IllegalArgumentException("Non-null server, table, and input are required"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + final ClickHouseFormat theFormat = format == null ? ClickHouseFormat.TabSeparated : format; + final ClickHouseCompression theCompression = compression == null ? ClickHouseCompression.NONE : compression; + + return submit(() -> { + try (ClickHouseClient client = newInstance(theServer.getProtocol()); + ClickHouseResponse response = client.connect(theServer).write().table(table) + .compression(theCompression).format(theFormat).data(input).execute().get()) { + return response.getSummary(); + } catch (InterruptedException | ExecutionException e) { + throw ClickHouseExceptionSpecifier.specify(e, theServer.getHost(), theServer.getPort()); + } finally { + try { + input.close(); + } catch (Exception e) { + // ignore + } + } + }); + } + + /** + * Creates a new instance compatible with any of the given protocols. + * + * @param preferredProtocols preferred protocols + * @return new instance compatible with any of the given protocols + */ + static ClickHouseClient newInstance(ClickHouseProtocol... preferredProtocols) { + return builder().nodeSelector(ClickHouseNodeSelector.of(null, preferredProtocols)).build(); + } + + /** + * Sends one or more SQL queries to specified server, and execute them one by + * one. Session will be created automatically if there's more than one SQL + * query. + * + * @param server non-null server to connect to + * @param sql non-null SQL query + * @param more more SQL queries if any + * @return list of {@link ClickHouseResponseSummary} one for each execution + */ + static CompletableFuture> send(ClickHouseNode server, String sql, String... more) { + if (server == null || sql == null) { + throw new IllegalArgumentException("Non-null server and sql are required"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + List queries = new LinkedList<>(); + queries.add(sql); + if (more != null && more.length > 0) { + for (String query : more) { + // dedup? + queries.add(ClickHouseChecker.nonNull(query, "query")); + } + } + + return submit(() -> { + List list = new LinkedList<>(); + + // set async to false so that we don't have to create additional thread + try (ClickHouseClient client = ClickHouseClient.builder() + .nodeSelector(ClickHouseNodeSelector.of(theServer.getProtocol())) + .addOption(ClickHouseClientOption.ASYNC, false).build()) { + ClickHouseRequest request = client.connect(theServer).format(ClickHouseFormat.RowBinary); + if ((boolean) ClickHouseDefaults.AUTO_SESSION.getEffectiveDefaultValue() && queries.size() > 1) { + request.session(UUID.randomUUID().toString(), false); + } + for (String query : queries) { + try (ClickHouseResponse resp = request.query(query).execute().get()) { + list.add(resp.getSummary()); + } + } + } + + return list; + }); + } + + /** + * Sends SQL query along with stringified parameters to specified server. + * + * @param server non-null server to connect to + * @param sql non-null SQL query + * @param params non-null stringified parameters + * @return list of {@link ClickHouseResponseSummary} one for each execution + */ + static CompletableFuture send(ClickHouseNode server, String sql, + Map params) { + if (server == null || sql == null || params == null) { + throw new IllegalArgumentException("Non-null server, sql and parameters are required"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + return submit(() -> { + // set async to false so that we don't have to create additional thread + try (ClickHouseClient client = ClickHouseClient.builder() + .nodeSelector(ClickHouseNodeSelector.of(theServer.getProtocol())) + .addOption(ClickHouseClientOption.ASYNC, false).build(); + ClickHouseResponse resp = client.connect(theServer).format(ClickHouseFormat.RowBinary).query(sql) + .params(params).execute().get()) { + return resp.getSummary(); + } + }); + } + + /** + * Sends SQL query along with raw parameters(e.g. byte value for Int8) to + * specified server. Parameters will be stringified based on given column types. + * + * @param server non-null server to connect to + * @param sql non-null SQL query + * @param columns non-empty column list + * @param params non-empty raw parameters + * @return list of {@link ClickHouseResponseSummary} one for each execution + */ + static CompletableFuture> send(ClickHouseNode server, String sql, + List columns, Object[]... params) { + int len = columns == null ? 0 : columns.size(); + if (len == 0) { + throw new IllegalArgumentException("Non-empty column list is required"); + } + + ClickHouseValue[] templates = new ClickHouseValue[len]; + int index = 0; + for (ClickHouseColumn column : columns) { + templates[index++] = ClickHouseValues.newValue(ClickHouseChecker.nonNull(column, "column")); + } + + return send(server, sql, templates, params); + } + + /** + * Sends SQL query along with template objects and raw parameters to specified + * server. + * + * @param server non-null server to connect to + * @param sql non-null SQL query + * @param templates non-empty template objects to stringify parameters + * @param params non-empty raw parameters + * @return list of {@link ClickHouseResponseSummary} one for each execution + */ + static CompletableFuture> send(ClickHouseNode server, String sql, + ClickHouseValue[] templates, Object[]... params) { + int len = templates == null ? 0 : templates.length; + int size = params == null ? 0 : params.length; + if (templates == null || templates.length == 0 || params == null || params.length == 0) { + throw new IllegalArgumentException("Non-empty templates and parameters are required"); + } + + final ClickHouseParameterizedQuery query = ClickHouseParameterizedQuery.of(sql); + if (!query.hasParameter()) { + throw new IllegalArgumentException("No named parameter found from the given query"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + return submit(() -> { + List list = new ArrayList<>(params.length); + + // set async to false so that we don't have to create additional thread + try (ClickHouseClient client = ClickHouseClient.builder() + .nodeSelector(ClickHouseNodeSelector.of(theServer.getProtocol())) + .addOption(ClickHouseClientOption.ASYNC, false).build()) { + // format doesn't matter here as we only need a summary + ClickHouseRequest request = client.connect(theServer).format(ClickHouseFormat.RowBinary) + .query(query); + for (int i = 0; i < size; i++) { + Object[] o = params[i]; + String[] arr = new String[len]; + for (int j = 0, slen = o == null ? 0 : o.length; j < slen; j++) { + if (j < len) { + arr[j] = ClickHouseValues.NULL_EXPR; + } else { + ClickHouseValue v = templates[j]; + arr[j] = v != null ? v.update(o[j]).toSqlExpression() + : ClickHouseValues.convertToSqlExpression(o[j]); + } + } + try (ClickHouseResponse resp = request.params(arr).execute().get()) { + list.add(resp.getSummary()); + } + } + } + + return list; + }); + } + + /** + * Sends SQL query along with stringified parameters to specified server. + * + * @param server non-null server to connect to + * @param sql non-null SQL query + * @param params non-null stringified parameters + * @return list of {@link ClickHouseResponseSummary} one for each execution + */ + static CompletableFuture> send(ClickHouseNode server, String sql, + String[][] params) { + if (server == null || sql == null || params == null) { + throw new IllegalArgumentException("Non-null server, sql, and parameters are required"); + } else if (params.length == 0) { + return send(server, sql); + } + + final ClickHouseParameterizedQuery query = ClickHouseParameterizedQuery.of(sql); + if (!query.hasParameter()) { + throw new IllegalArgumentException("No named parameter found from the given query"); + } + + // in case the protocol is ANY + final ClickHouseNode theServer = ClickHouseCluster.probe(server); + + return submit(() -> { + List list = new ArrayList<>(params.length); + + // set async to false so that we don't have to create additional thread + try (ClickHouseClient client = ClickHouseClient.builder() + .nodeSelector(ClickHouseNodeSelector.of(theServer.getProtocol())) + .addOption(ClickHouseClientOption.ASYNC, false).build()) { + // format doesn't matter here as we only need a summary + ClickHouseRequest request = client.connect(theServer).format(ClickHouseFormat.RowBinary); + for (String[] p : params) { + try (ClickHouseResponse resp = request.query(query.apply(p)).execute().get()) { + list.add(resp.getSummary()); + } + } + } + + return list; + }); + } + + /** + * Tests if the given server is healthy or not. Unlike other methods, it's a + * synchronous call with minimum overhead(e.g. tiny buffer, no compression and + * no deserialization etc). + * + * @param server server to test + * @param timeout timeout in millisecond + * @return true if the server is healthy; false otherwise + */ + static boolean test(ClickHouseNode server, int timeout) { + if (server != null) { + server = ClickHouseCluster.probe(server, timeout); + + try (ClickHouseClient client = ClickHouseClient.builder() + .nodeSelector(ClickHouseNodeSelector.of(server.getProtocol())) + .addOption(ClickHouseClientOption.ASYNC, false) // use current thread + .addOption(ClickHouseClientOption.CONNECTION_TIMEOUT, timeout) + .addOption(ClickHouseClientOption.SOCKET_TIMEOUT, timeout) + .addOption(ClickHouseClientOption.MAX_BUFFER_SIZE, 8) // actually 4 bytes should be enough + .addOption(ClickHouseClientOption.MAX_QUEUED_BUFFERS, 1).build(); + ClickHouseResponse resp = client.connect(server).compression(ClickHouseCompression.NONE) + .format(ClickHouseFormat.TabSeparated).query("SELECT 1").execute() + .get(timeout, TimeUnit.MILLISECONDS)) { + return true; + } catch (Exception e) { + // ignore + } + } + + return false; + } + + /** + * Tests whether the given protocol is supported or not. An advanced client can + * support as many protocols as needed. + * + * @param protocol protocol to test, null is treated as + * {@link ClickHouseProtocol#ANY} + * @return true if the given protocol is {@link ClickHouseProtocol#ANY} or + * supported by this client; false otherwise + */ + default boolean accept(ClickHouseProtocol protocol) { + return protocol == null || protocol == ClickHouseProtocol.ANY; + } + + /** + * Connects to a ClickHouse server defined by the given + * {@link java.util.function.Function}. You can pass either + * {@link ClickHouseCluster} or {@link ClickHouseNode} here, as both of them + * implemented the same interface. + * + *

+ * Please be aware that this is nothing but an intention, so no network + * communication happens until {@link #execute(ClickHouseRequest)} is + * invoked(usually triggered by {@code request.execute()}). + * + * @param nodeFunc function to get a {@link ClickHouseNode} to connect to + * @return request object holding references to this client and node provider + */ + default ClickHouseRequest connect(Function nodeFunc) { + return new ClickHouseRequest<>(this, ClickHouseChecker.nonNull(nodeFunc, "nodeFunc"), false); + } + + /** + * Creates an immutable copy of the request if it's not sealed, and sends it to + * a node hold by the request(e.g. {@link ClickHouseNode} returned from + * {@code request.getServer()}). Connection will be made for the first-time + * invocation, and then it will be reused in subsequential calls to the extract + * same {@link ClickHouseNode} until {@link #close()} is invoked. + * + * @param request request object which will be sealed(immutable copy) upon + * execution, meaning you're free to make any change to this + * object(e.g. prepare for next call using different SQL + * statement) without impacting the execution + * @return future object to get result + * @throws ClickHouseException when error occurred during execution + */ + CompletableFuture execute(ClickHouseRequest request) throws ClickHouseException; + + /** + * Gets the immutable configuration associated with this client. In most cases + * it's the exact same one passed to {@link #init(ClickHouseConfig)} method for + * initialization. + * + * @return configuration associated with this client + */ + ClickHouseConfig getConfig(); + + /** + * Initializes the client using immutable configuration extracted from the + * builder using {@link ClickHouseClientBuilder#getConfig()}. In general, it's + * {@link ClickHouseClientBuilder}'s responsiblity to call this method to + * initialize the client at the end of {@link ClickHouseClientBuilder#build()}. + * However, sometimes, you may want to call this method explicitly in order to + * (re)initialize the client based on certain needs. If that's the case, please + * consider the environment when calling this method to avoid concurrent + * modification, and keep in mind that 1) ClickHouseConfig is immutable but + * ClickHouseClient is NOT; and 2) no guarantee that this method is thread-safe. + * + * @param config immutable configuration extracted from the builder + */ + default void init(ClickHouseConfig config) { + ClickHouseChecker.nonNull(config, "configuration"); + } + + @Override + void close(); +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClientBuilder.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClientBuilder.java new file mode 100644 index 000000000..6fecf1ed7 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClientBuilder.java @@ -0,0 +1,220 @@ +package com.clickhouse.client; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; + +import com.clickhouse.client.config.ClickHouseConfigOption; +import com.clickhouse.client.config.ClickHouseDefaults; + +/** + * Builder class for creating {@link ClickHouseClient}. Please use + * {@link ClickHouseClient#builder()} for instantiation, and avoid + * multi-threading as it's NOT thread-safe. + */ +public class ClickHouseClientBuilder { + // expose method to change default thread pool in runtime? JMX? + static final ExecutorService defaultExecutor; + + static { + int maxThreads = (int) ClickHouseDefaults.MAX_THREADS.getEffectiveDefaultValue(); + int maxRequests = (int) ClickHouseDefaults.MAX_REQUESTS.getEffectiveDefaultValue(); + + if (maxThreads <= 0 && maxRequests <= 0) { + // java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary + // -XX:+PrintNMTStatistics -version + defaultExecutor = ForkJoinPool.commonPool(); + } else { + if (maxThreads <= 0) { + maxThreads = Runtime.getRuntime().availableProcessors(); + } + + if (maxRequests <= 0) { + maxRequests = 0; + } + + defaultExecutor = ClickHouseUtils.newThreadPool(ClickHouseClient.class.getSimpleName(), maxThreads, + maxRequests); + } + } + + protected ClickHouseConfig config; + + protected ClickHouseCredentials credentials; + protected Object metricRegistry; + protected ClickHouseNodeSelector nodeSelector; + + protected final Map options; + + /** + * Default constructor. + */ + protected ClickHouseClientBuilder() { + options = new HashMap<>(); + } + + /** + * Resets client configuration to null. + */ + protected void resetConfig() { + if (config != null) { + config = null; + } + } + + /** + * Gets client configuration. + * + * @return non-null client configuration + */ + public ClickHouseConfig getConfig() { + if (config == null) { + config = new ClickHouseConfig(options, credentials, nodeSelector, metricRegistry); + } + + return config; + } + + /** + * Builds an instance of {@link ClickHouseClient}. This method will use + * {@link java.util.ServiceLoader} to load a suitable implementation based on + * preferred protocol(s), or just the first one if no preference given. + * {@link ClickHouseClient#accept(ClickHouseProtocol)} will be invoked during + * the process to test if the implementation is compatible with the preferred + * protocol(s) or not. At the end of process, if a suitable implementation is + * found, {@link ClickHouseClient#init(ClickHouseConfig)} will be invoked for + * initialization. + * + * @return suitable client to handle preferred protocols + * @throws IllegalStateException when no suitable client found in classpath + */ + public ClickHouseClient build() { + ClickHouseClient client = null; + + boolean noSelector = nodeSelector == null || nodeSelector == ClickHouseNodeSelector.EMPTY; + int counter = 0; + for (ClickHouseClient c : ServiceLoader.load(ClickHouseClient.class)) { + counter++; + if (noSelector || nodeSelector.match(c)) { + client = c; + break; + } + } + + if (client == null) { + throw new IllegalStateException( + ClickHouseUtils.format("No suitable client(out of %d) found in classpath.", counter)); + } else { + client.init(getConfig()); + } + + return client; + } + + /** + * Adds an option, which is usually an Enum type that implements + * {@link com.clickhouse.client.config.ClickHouseConfigOption}. + * + * @param option non-null option + * @param value value + * @return this builder + */ + public ClickHouseClientBuilder addOption(ClickHouseConfigOption option, Object value) { + Object oldValue = options.put(ClickHouseChecker.nonNull(option, "option"), + ClickHouseChecker.nonNull(value, "value")); + if (oldValue == null || !value.equals(oldValue)) { + resetConfig(); + } + + return this; + } + + /** + * Removes an option. + * + * @param option non-null option + * @return this builder + */ + public ClickHouseClientBuilder removeOption(ClickHouseConfigOption option) { + Object value = options.remove(ClickHouseChecker.nonNull(option, "option")); + if (value != null) { + resetConfig(); + } + + return this; + } + + /** + * Sets options. + * + * @param options non-null map containing all options + * @return this builder + */ + public ClickHouseClientBuilder options(Map options) { + if (ClickHouseChecker.nonNull(options, "options").size() > 0) { + options.putAll(options); + resetConfig(); + } + + return this; + } + + /* + * public ClickHouseClientBuilder addUserType(Object... userTypeMappers) { + * resetConfig(); return this; } + */ + + /** + * Sets default credentials, which will be used to connect to a + * {@link ClickHouseNode} only when it has no credentials defined. + * + * @param credentials default credentials + * @return this builder + */ + public ClickHouseClientBuilder defaultCredentials(ClickHouseCredentials credentials) { + if (!ClickHouseChecker.nonNull(credentials, "credentials").equals(this.credentials)) { + this.credentials = credentials; + resetConfig(); + } + + return this; + } + + /* + * public ClickHouseClientBuilder databaseChangeListener(@NonNull Object + * listener) { resetConfig(); return this; } + */ + + /** + * Sets node selector. + * + * @param nodeSelector non-null node selector + * @return this builder + */ + public ClickHouseClientBuilder nodeSelector(ClickHouseNodeSelector nodeSelector) { + if (!ClickHouseChecker.nonNull(nodeSelector, "nodeSelector").equals(this.nodeSelector)) { + this.nodeSelector = nodeSelector; + resetConfig(); + } + + return this; + } + + /** + * Sets metric registry. + * + * @param metricRegistry metric registry, could be null + * @return this builder + */ + public ClickHouseClientBuilder metricRegistry(Object metricRegistry) { + if (!Objects.equals(this.metricRegistry, metricRegistry)) { + this.metricRegistry = metricRegistry; + resetConfig(); + } + + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCluster.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCluster.java new file mode 100644 index 000000000..851c151ec --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCluster.java @@ -0,0 +1,402 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import com.clickhouse.client.ClickHouseNode.Status; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; + +/** + * List of {@link ClickHouseNode}. By default, all nodes are considered as + * healthy. When connection issue happened, corresponding node will be moved to + * unhealthy list, where a background thread will validate its status time from + * time and eventually bring them back to healthy list if no issue. + * + *

+ * When a node's protocol is {@link ClickHouseProtocol#ANY}, this class will + * also try to probe the protocol by sending a packet to the port and analyze + * response from server. + */ +public class ClickHouseCluster implements Function, Serializable { + private static final long serialVersionUID = 8684489015067906319L; + + private static final Logger log = LoggerFactory.getLogger(ClickHouseCluster.class); + + private static final String PARAM_NODES = "nodes"; + + /** + * Enum of load balancing policy. + */ + public enum LoadBalancingPolicy { + ROUND_ROBIN, // nothing fancy + PICK_FIRST // stick with the first healthy node + } + + /** + * Builder class for creating {@link ClickHouseCluster}. + */ + public static class Builder { + private final List nodes; + private LoadBalancingPolicy lbPolicy; + + private Builder() { + nodes = new LinkedList<>(); + } + + /** + * Add node. + * + * @param node node to be added + * @return this builder + */ + protected Builder addNode(ClickHouseNode node) { + if (!nodes.contains(ClickHouseChecker.nonNull(node, "node"))) { + nodes.add(node); + } + + return this; + } + + /** + * Add nodes. + * + * @param node node to be added + * @param more more nodes to be added + * @return this builder + */ + public Builder addNodes(ClickHouseNode node, ClickHouseNode... more) { + addNode(node); + + if (more != null) { + for (ClickHouseNode n : more) { + addNode(n); + } + } + + return this; + } + + /** + * Add nodes. + * + * @param nodes list of nodes to be added + * @return this builder + */ + public Builder addNodes(Collection nodes) { + for (ClickHouseNode node : ClickHouseChecker.nonNull(nodes, PARAM_NODES)) { + addNode(node); + } + + return this; + } + + /** + * Merge nodes from the given cluster. + * + * @param cluster list of nodes to merge + * @return this builder + */ + public Builder merge(ClickHouseCluster cluster) { + for (ClickHouseNode node : ClickHouseChecker.nonNull(cluster, "cluster").nodes) { + addNode(node); + } + + return this; + } + + public Builder withLbPolicy(LoadBalancingPolicy policy) { + this.lbPolicy = policy; + return this; + } + + /** + * Build the cluster object. + * + * @return cluster + */ + public ClickHouseCluster build() { + return new ClickHouseCluster(lbPolicy, nodes); + } + } + + /** + * Get builder for building cluster object. + * + * @return builder for building cluster object + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Same as {@link #probe(ClickHouseNode, int)} except it uses default + * timeout(3000 milliseconds). + * + * @param node non-null target node + * @return probed node, which may or may not be the same as given node + */ + public static ClickHouseNode probe(ClickHouseNode node) { + return probe(node, 3000); + } + + /** + * Probe the node by resolving its DNS and detect protocol as needed. + * + * @param node non-null target node + * @param timeout timeout in milliseconds + * @return probed node, which may or may not be the same as given node + */ + public static ClickHouseNode probe(ClickHouseNode node, int timeout) { + ClickHouseDnsResolver resolver = ClickHouseDnsResolver.getInstance(); + if (ClickHouseChecker.nonNull(node, "node").getProtocol() == ClickHouseProtocol.ANY) { + InetSocketAddress address = resolver != null + ? resolver.resolve(ClickHouseProtocol.ANY, node.getHost(), node.getPort()) + : new InetSocketAddress(node.getHost(), node.getPort()); + + ClickHouseProtocol p = ClickHouseProtocol.HTTP; + // TODO needs a better way so that we can detect PostgreSQL port as well + try (Socket client = new Socket()) { + client.setKeepAlive(false); + client.connect(address, timeout); + client.setSoTimeout(timeout); + OutputStream out = client.getOutputStream(); + out.write("GET /ping HTTP/1.1\r\n\r\n".getBytes()); + out.flush(); + byte[] buf = new byte[12]; // HTTP/1.x xxx + if (client.getInputStream().read(buf) == buf.length) { + if (buf[0] == 0) { + p = ClickHouseProtocol.GRPC; + } else if (buf[3] == 0) { + p = ClickHouseProtocol.MYSQL; + } else if (buf[0] == 72 && buf[9] == 52) { + p = ClickHouseProtocol.NATIVE; + } + } + } catch (IOException e) { + log.debug("Failed to probe: " + address, e); + } + + node = ClickHouseNode.builder(node).port(p).build(); + } + + return node; + } + + /** + * Create cluster object from list of nodes. + * + * @param nodes list of nodes + * @return cluster object + */ + public static ClickHouseCluster of(ClickHouseNode... nodes) { + return new ClickHouseCluster(null, nodes); + } + + /** + * Create cluster object from list of nodes. + * + * @param nodes list of nodes + * @return cluster object + */ + public static ClickHouseCluster of(Collection nodes) { + return new ClickHouseCluster(null, nodes); + } + + protected static void handleUncaughtException(Thread r, Throwable t) { + log.warn("Exception caught from thread: " + r, t); + } + + private final AtomicBoolean checking; + private final transient ScheduledExecutorService scheduledExecutor; + private final List unhealthyNodes; + + private final AtomicInteger index; + private final List nodes; + private final LoadBalancingPolicy lbPolicy; + + /** + * Constructor cluster object using list of nodes. + * + * @param policy load balancing policy + * @param nodes list of nodes + */ + protected ClickHouseCluster(LoadBalancingPolicy policy, ClickHouseNode... nodes) { + this(policy, Arrays.asList(ClickHouseChecker.nonNull(nodes, PARAM_NODES))); + } + + /** + * Constructor cluster object using list of nodes. + * + * @param policy load balancing policy + * @param nodes list of nodes + */ + protected ClickHouseCluster(LoadBalancingPolicy policy, Collection nodes) { + this.lbPolicy = policy == null ? LoadBalancingPolicy.ROUND_ROBIN : policy; + + this.checking = new AtomicBoolean(false); + this.index = new AtomicInteger(0); + + int size = ClickHouseChecker.nonNull(nodes, PARAM_NODES).size(); + + this.nodes = Collections.synchronizedList(new ArrayList<>(size)); + this.unhealthyNodes = Collections.synchronizedList(new ArrayList<>(size)); + + // should make it a static member + this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, ClickHouseCluster.class.getSimpleName()); + thread.setDaemon(true); + thread.setUncaughtExceptionHandler(ClickHouseCluster::handleUncaughtException); + return thread; + } + }); + + for (ClickHouseNode node : nodes) { + if (node == null) { + continue; + } + + probe(node).setManager(this::update); + } + } + + protected synchronized void update(ClickHouseNode node, Status status) { + switch (status) { + case UNMANAGED: + nodes.remove(node); + unhealthyNodes.remove(node); + break; + case MANAGED: + case HEALTHY: + unhealthyNodes.remove(node); + if (!nodes.contains(node)) { + nodes.add(node); + } + break; + case UNHEALTHY: + nodes.remove(node); + if (!unhealthyNodes.contains(node)) { + unhealthyNodes.add(node); + + if (!checking.get()) { + this.scheduledExecutor.execute(this::check); + } + } + break; + default: + break; + } + } + + protected void check() { + if (checking.compareAndSet(false, true)) { + return; + } + + // detect flaky node and check it in a different way(less frequency) + try { + boolean passed = true; + for (int i = 0; i < unhealthyNodes.size(); i++) { + ClickHouseNode node = unhealthyNodes.get(i); + if (ClickHouseClient.test(node, 5000)) { // another configuration? + update(node, Status.HEALTHY); + } else { + passed = false; + } + } + + if (!passed) { + this.scheduledExecutor.schedule(this::check, 3L, TimeUnit.SECONDS); + } + } finally { + checking.set(false); + } + } + + /** + * Get load balancing policy. + * + * @return load balancing policy + */ + public LoadBalancingPolicy getLbPolicy() { + return lbPolicy; + } + + /** + * Check if the cluster has any node available for access. + * + * @return if there's at least one node is available for access + */ + public boolean hasNode() { + return !this.nodes.isEmpty(); + } + + /** + * Get all available nodes in the cluster. + * + * @return unmodifible list of nodes + */ + public List getAvailableNodes() { + return Collections.unmodifiableList(nodes); + } + + @Override + public synchronized ClickHouseNode apply(ClickHouseNodeSelector t) { + boolean noSelector = t == null || t == ClickHouseNodeSelector.EMPTY; + + if (nodes.isEmpty()) { + // TODO wait until timed out? + throw new IllegalArgumentException("No healthy node available"); + } + + if (lbPolicy == LoadBalancingPolicy.PICK_FIRST) { + return nodes.get(0); + } + + int idx = index.get(); + ClickHouseNode matched = null; + for (int i = idx; i < nodes.size(); i++) { + ClickHouseNode node = nodes.get(i); + if (noSelector || t.match(node)) { + matched = node; + index.compareAndSet(idx, i + 1); + break; + } + } + + if (matched == null && idx > 0) { + for (int i = 0; i < Math.min(idx, nodes.size()); i++) { + ClickHouseNode node = nodes.get(i); + if (noSelector || t.match(node)) { + matched = node; + index.compareAndSet(idx, i + 1); + break; + } + } + } + + if (matched == null) { + throw new IllegalArgumentException(ClickHouseUtils + .format("No healthy node found from a list of %d(index=%d)", nodes.size(), index.get())); + } + + return matched; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java new file mode 100644 index 000000000..132a276a7 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java @@ -0,0 +1,409 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; + +/** + * This class represents a column defined in database. + */ +public final class ClickHouseColumn implements Serializable { + private static final long serialVersionUID = 8228660689532259640L; + + private static final String ERROR_MISSING_NESTED_TYPE = "Missing nested data type"; + private static final String KEYWORD_NULLABLE = "Nullable"; + private static final String KEYWORD_LOW_CARDINALITY = "LowCardinality"; + private static final String KEYWORD_AGGREGATE_FUNCTION = ClickHouseDataType.AggregateFunction.name(); + private static final String KEYWORD_ARRAY = ClickHouseDataType.Array.name(); + private static final String KEYWORD_TUPLE = ClickHouseDataType.Tuple.name(); + private static final String KEYWORD_MAP = ClickHouseDataType.Map.name(); + private static final String KEYWORD_NESTED = ClickHouseDataType.Nested.name(); + + private String originalTypeName; + private String columnName; + + private ClickHouseDataType dataType; + private boolean nullable; + private boolean lowCardinality; + private ClickHouseDataType baseType; + private TimeZone timeZone; + private int precision; + private int scale; + private List nested; + private List parameters; + + private static ClickHouseColumn update(ClickHouseColumn column) { + int size = column.parameters.size(); + switch (column.dataType) { + case AggregateFunction: + column.baseType = ClickHouseDataType.String; + if (size == 2) { + column.baseType = ClickHouseDataType.of(column.parameters.get(1)); + } + break; + case DateTime: + if (size >= 2) { // same as DateTime64 + column.scale = Integer.parseInt(column.parameters.get(0)); + column.timeZone = TimeZone.getTimeZone(column.parameters.get(1).replace("'", "")); + } else if (size == 1) { // same as DateTime32 + // unfortunately this will fall back to GMT if the time zone + // cannot be resolved + TimeZone tz = TimeZone.getTimeZone(column.parameters.get(0).replace("'", "")); + column.timeZone = tz; + } + break; + case DateTime32: + if (size > 0) { + // unfortunately this will fall back to GMT if the time zone + // cannot be resolved + TimeZone tz = TimeZone.getTimeZone(column.parameters.get(0).replace("'", "")); + column.timeZone = tz; + } + break; + case DateTime64: + if (size > 0) { + column.scale = Integer.parseInt(column.parameters.get(0)); + } + if (size > 1) { + column.timeZone = TimeZone.getTimeZone(column.parameters.get(1).replace("'", "")); + } + break; + case Decimal: + if (size >= 2) { + column.precision = Integer.parseInt(column.parameters.get(0)); + column.scale = Integer.parseInt(column.parameters.get(1)); + } + break; + case Decimal32: + case Decimal64: + case Decimal128: + case Decimal256: + column.scale = Integer.parseInt(column.parameters.get(0)); + break; + case FixedString: + column.precision = Integer.parseInt(column.parameters.get(0)); + break; + default: + break; + } + + return column; + } + + protected static int readColumn(String args, int startIndex, int len, String name, List list) { + ClickHouseColumn column = null; + + StringBuilder builder = new StringBuilder(); + + int brackets = 0; + boolean nullable = false; + boolean lowCardinality = false; + int i = startIndex; + + if (args.startsWith(KEYWORD_LOW_CARDINALITY, i)) { + lowCardinality = true; + int index = args.indexOf('(', i + KEYWORD_LOW_CARDINALITY.length()); + if (index < i) { + throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE); + } + i = index + 1; + brackets++; + } + if (args.startsWith(KEYWORD_NULLABLE, i)) { + nullable = true; + int index = args.indexOf('(', i + KEYWORD_NULLABLE.length()); + if (index < i) { + throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE); + } + i = index + 1; + brackets++; + } + + if (args.startsWith(KEYWORD_AGGREGATE_FUNCTION, i)) { + int index = args.indexOf('(', i + KEYWORD_AGGREGATE_FUNCTION.length()); + if (index < i) { + throw new IllegalArgumentException("Missing function parameters"); + } + List params = new LinkedList<>(); + i = ClickHouseUtils.readParameters(args, index, len, params); + column = new ClickHouseColumn(ClickHouseDataType.AggregateFunction, name, args.substring(startIndex, i), + nullable, lowCardinality, params, null); + } else if (args.startsWith(KEYWORD_ARRAY, i)) { + int index = args.indexOf('(', i + KEYWORD_ARRAY.length()); + if (index < i) { + throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE); + } + int endIndex = ClickHouseUtils.skipBrackets(args, index, len, '('); + List nestedColumns = new LinkedList<>(); + readColumn(args, index + 1, endIndex - 1, "", nestedColumns); + column = new ClickHouseColumn(ClickHouseDataType.Array, name, args.substring(startIndex, endIndex), + nullable, lowCardinality, null, nestedColumns); + i = endIndex; + } else if (args.startsWith(KEYWORD_MAP, i)) { + int index = args.indexOf('(', i + KEYWORD_MAP.length()); + if (index < i) { + throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE); + } + int endIndex = ClickHouseUtils.skipBrackets(args, index, len, '('); + List nestedColumns = new LinkedList<>(); + for (i = index + 1; i < endIndex; i++) { + char c = args.charAt(i); + if (c == ')') { + break; + } else if (c != ',' && !Character.isWhitespace(c)) { + i = readColumn(args, i, endIndex, "", nestedColumns) - 1; + } + } + column = new ClickHouseColumn(ClickHouseDataType.Map, name, args.substring(startIndex, endIndex), nullable, + lowCardinality, null, nestedColumns); + i = endIndex; + } else if (args.startsWith(KEYWORD_NESTED, i)) { + int index = args.indexOf('(', i + KEYWORD_NESTED.length()); + if (index < i) { + throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE); + } + i = ClickHouseUtils.skipBrackets(args, index, len, '('); + String originalTypeName = args.substring(startIndex, i); + column = new ClickHouseColumn(ClickHouseDataType.Nested, name, originalTypeName, nullable, lowCardinality, + null, parse(args.substring(index + 1, i - 1))); + } else if (args.startsWith(KEYWORD_TUPLE, i)) { + int index = args.indexOf('(', i + KEYWORD_TUPLE.length()); + if (index < i) { + throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE); + } + int endIndex = ClickHouseUtils.skipBrackets(args, index, len, '('); + List nestedColumns = new LinkedList<>(); + for (i = index + 1; i < endIndex; i++) { + char c = args.charAt(i); + if (c == ')') { + break; + } else if (c != ',' && !Character.isWhitespace(c)) { + i = readColumn(args, i, endIndex, "", nestedColumns) - 1; + } + } + + column = new ClickHouseColumn(ClickHouseDataType.Tuple, name, args.substring(startIndex, endIndex), + nullable, lowCardinality, null, nestedColumns); + } + + if (column == null) { + i = ClickHouseUtils.readNameOrQuotedString(args, i, len, builder); + List params = new LinkedList<>(); + for (; i < len; i++) { + char ch = args.charAt(i); + if (ch == '(') { + i = ClickHouseUtils.readParameters(args, i, len, params) - 1; + } else if (ch == ')') { + brackets--; + if (brackets <= 0) { + i++; + break; + } + } else if (ch == ',') { + break; + } else if (!Character.isWhitespace(ch)) { + StringBuilder sb = new StringBuilder(); + i = ClickHouseUtils.readNameOrQuotedString(args, i, len, sb); + String modifier = sb.toString(); + sb.setLength(0); + boolean startsWithNot = false; + if ("not".equalsIgnoreCase(modifier)) { + startsWithNot = true; + i = ClickHouseUtils.readNameOrQuotedString(args, i, len, sb); + modifier = sb.toString(); + sb.setLength(0); + } + + if ("null".equalsIgnoreCase(modifier)) { + if (nullable) { + throw new IllegalArgumentException("Nullable and NULL cannot be used together"); + } + nullable = !startsWithNot; + i = ClickHouseUtils.skipContentsUntil(args, i, len, ',') - 1; + break; + } else if (startsWithNot) { + throw new IllegalArgumentException("Expect keyword NULL after NOT"); + } else if ("alias".equalsIgnoreCase(modifier) || "codec".equalsIgnoreCase(modifier) + || "default".equalsIgnoreCase(modifier) || "materialized".equalsIgnoreCase(modifier) + || "ttl".equalsIgnoreCase(modifier)) { // stop words + i = ClickHouseUtils.skipContentsUntil(args, i, len, ',') - 1; + break; + } else { + builder.append(' ').append(modifier); + i--; + } + } + } + column = new ClickHouseColumn(ClickHouseDataType.of(builder.toString()), name, + args.substring(startIndex, i), nullable, lowCardinality, params, null); + builder.setLength(0); + } + + list.add(update(column)); + + return i; + } + + public static ClickHouseColumn of(String columnName, ClickHouseDataType dataType, boolean nullable, + boolean lowCardinality, String... parameters) { + return new ClickHouseColumn(dataType, columnName, null, nullable, lowCardinality, Arrays.asList(parameters), + null); + } + + public static ClickHouseColumn of(String columnName, ClickHouseDataType dataType, boolean nullable, + boolean lowCardinality, ClickHouseColumn... nestedColumns) { + return new ClickHouseColumn(dataType, columnName, null, nullable, lowCardinality, null, + Arrays.asList(nestedColumns)); + } + + public static ClickHouseColumn of(String columnName, String columnType) { + if (columnName == null || columnType == null) { + throw new IllegalArgumentException("Non-null columnName and columnType are required"); + } + + List list = new ArrayList<>(1); + readColumn(columnType, 0, columnType.length(), columnName, list); + if (list.size() != 1) { // should not happen + throw new IllegalArgumentException("Failed to parse given column"); + } + return list.get(0); + } + + public static List parse(String args) { + if (args == null || args.isEmpty()) { + return Collections.emptyList(); + } + + String name = null; + ClickHouseColumn column = null; + List list = new LinkedList<>(); + StringBuilder builder = new StringBuilder(); + for (int i = 0, len = args.length(); i < len; i++) { + char ch = args.charAt(i); + if (Character.isWhitespace(ch)) { + continue; + } + + if (name == null) { // column name + i = ClickHouseUtils.readNameOrQuotedString(args, i, len, builder) - 1; + name = builder.toString(); + builder.setLength(0); + } else if (column == null) { // now type + LinkedList colList = new LinkedList<>(); + i = readColumn(args, i, len, name, colList) - 1; + list.add(column = colList.getFirst()); + } else { // prepare for next column + i = ClickHouseUtils.skipContentsUntil(args, i, len, ',') - 1; + name = null; + column = null; + } + } + + List c = new ArrayList<>(list.size()); + for (ClickHouseColumn cc : list) { + c.add(cc); + } + return Collections.unmodifiableList(c); + } + + private ClickHouseColumn(String originalTypeName, String columnName) { + this.originalTypeName = originalTypeName; + this.columnName = columnName; + } + + private ClickHouseColumn(ClickHouseDataType dataType, String columnName, String originalTypeName, boolean nullable, + boolean lowCardinality, List parameters, List nestedColumns) { + this.dataType = ClickHouseChecker.nonNull(dataType, "dataType"); + + this.columnName = columnName == null ? "" : columnName; + this.originalTypeName = originalTypeName == null ? dataType.name() : originalTypeName; + this.nullable = nullable; + this.lowCardinality = lowCardinality; + + if (parameters == null || parameters.size() == 0) { + this.parameters = Collections.emptyList(); + } else { + List list = new ArrayList<>(parameters.size()); + list.addAll(parameters); + this.parameters = Collections.unmodifiableList(list); + } + + if (nestedColumns == null || nestedColumns.size() == 0) { + this.nested = Collections.emptyList(); + } else { + List list = new ArrayList<>(nestedColumns.size()); + list.addAll(nestedColumns); + this.nested = Collections.unmodifiableList(list); + } + } + + public ClickHouseDataType getDataType() { + return dataType; + } + + public String getOriginalTypeName() { + return originalTypeName; + } + + public String getColumnName() { + return columnName; + } + + public boolean isNullable() { + return nullable; + } + + boolean isLowCardinality() { + return lowCardinality; + } + + public ClickHouseDataType getEffectiveDataType() { + return baseType != null ? baseType : dataType; + } + + public TimeZone getTimeZone() { + return timeZone; // null means server timezone + } + + public int getPrecision() { + return precision; + } + + public int getScale() { + return scale; + } + + public boolean hasNestedColumn() { + return !nested.isEmpty(); + } + + public List getNestedColumns() { + return nested; + } + + public List getParameters() { + return parameters; + } + + public ClickHouseColumn getKeyInfo() { + return dataType == ClickHouseDataType.Map && nested.size() == 2 ? nested.get(0) : null; + } + + public ClickHouseColumn getValueInfo() { + return dataType == ClickHouseDataType.Map && nested.size() == 2 ? nested.get(1) : null; + } + + public String getFunctionName() { + return dataType == ClickHouseDataType.AggregateFunction && parameters.size() > 0 ? parameters.get(0) : null; + } + + @Override + public String toString() { + return new StringBuilder().append(columnName == null || columnName.isEmpty() ? "column" : columnName) + .append(' ').append(originalTypeName).toString(); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCompression.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCompression.java new file mode 100644 index 000000000..bd53192af --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCompression.java @@ -0,0 +1,98 @@ +package com.clickhouse.client; + +/** + * Supported compression algoritms. + */ +public enum ClickHouseCompression { + NONE("", "", ""), BROTLI("application/x-brotli", "br", "br"), DEFLATE("application/deflate", "deflate", "zz"), + GZIP("application/gzip", "gzip", "gz"), LZ4("application/x-lz4", "lz4", "lz4"), + ZIP("application/zip", "zip", "zip"), ZSTD("application/zstd", "zstd", "zst"); + + private String mimeType; + private String encoding; + private String fileExt; + + // and maybe magic bytes? + ClickHouseCompression(String mimeType, String encoding, String fileExt) { + this.mimeType = mimeType; + this.encoding = encoding; + this.fileExt = fileExt; + } + + public String mimeType() { + return mimeType; + } + + public String encoding() { + return encoding; + } + + public String fileExtension() { + return fileExt; + } + + /** + * Get compression algorithm based on given MIME type. + * + * @param mimeType MIME type + * @return compression algorithm + */ + public static ClickHouseCompression fromMimeType(String mimeType) { + ClickHouseCompression compression = NONE; + + if (mimeType != null) { + for (ClickHouseCompression c : values()) { + if (c.mimeType.equals(mimeType)) { + compression = c; + break; + } + } + } + + return compression; + } + + /** + * Get compression algorithm based on given encoding. + * + * @param encoding content encoding + * @return compression algorithm + */ + public static ClickHouseCompression fromEncoding(String encoding) { + ClickHouseCompression compression = NONE; + + if (encoding != null) { + for (ClickHouseCompression c : values()) { + if (c.encoding.equals(encoding)) { + compression = c; + break; + } + } + } + + return compression; + } + + /** + * Get compression algorithm based on given file name. + * + * @param file file name + * @return compression algorithm + */ + public static ClickHouseCompression fromFileName(String file) { + ClickHouseCompression compression = NONE; + + int index = file == null ? -1 : file.lastIndexOf('.'); + if (index > 0) { + String ext = file.substring(index + 1).toLowerCase(); + for (ClickHouseCompression c : values()) { + if (c.fileExt.equals(ext)) { + compression = c; + break; + } + } + } + + return compression; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseConfig.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseConfig.java new file mode 100644 index 000000000..8b7330598 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseConfig.java @@ -0,0 +1,351 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.config.ClickHouseConfigOption; +import com.clickhouse.client.config.ClickHouseDefaults; +import com.clickhouse.client.config.ClickHouseSslMode; + +/** + * An immutable class holding client-specific options like + * {@link ClickHouseCredentials} and {@link ClickHouseNodeSelector} etc. + */ +public class ClickHouseConfig implements Serializable { + protected static final Map mergeOptions(List list) { + Map options = new HashMap<>(); + + if (list != null) { + List cl = new ArrayList<>(list.size()); + for (ClickHouseConfig c : list) { + if (c != null) { + boolean duplicated = false; + for (ClickHouseConfig conf : cl) { + if (conf == c) { + duplicated = true; + break; + } + } + + if (duplicated) { + continue; + } + options.putAll(c.options); + cl.add(c); + } + } + } + + return options; + } + + protected static final ClickHouseCredentials mergeCredentials(List list) { + ClickHouseCredentials credentials = null; + + if (list != null) { + for (ClickHouseConfig c : list) { + if (c != null && c.credentials != null) { + credentials = c.credentials; + break; + } + } + } + + return credentials; + } + + protected static final ClickHouseNodeSelector mergeNodeSelector(List list) { + ClickHouseNodeSelector nodeSelector = null; + + if (list != null) { + for (ClickHouseConfig c : list) { + if (c != null && c.nodeSelector != null) { + nodeSelector = c.nodeSelector; + break; + } + } + } + + return nodeSelector; + } + + protected static final Object mergeMetricRegistry(List list) { + Object metricRegistry = null; + + if (list != null) { + for (ClickHouseConfig c : list) { + if (c != null && c.metricRegistry.isPresent()) { + metricRegistry = c.metricRegistry.get(); + break; + } + } + } + + return metricRegistry; + } + + private static final long serialVersionUID = 7794222888859182491L; + + // common options optimized for read + private final boolean async; + private final String clientName; + private final ClickHouseCompression compression; + private final int connectionTimeout; + private final String database; + private final ClickHouseFormat format; + private final int maxBufferSize; + private final int maxExecutionTime; + private final int maxQueuedBuffers; + private final int maxQueuedRequests; + private final int maxResultRows; + private final int maxThreads; + private final boolean retry; + private final boolean reuseValueWrapper; + private final int socketTimeout; + private final int sessionTimeout; + private final boolean sessionCheck; + private final boolean ssl; + private final ClickHouseSslMode sslMode; + private final String sslRootCert; + private final String sslCert; + private final String sslKey; + private final boolean useObjectsInArray; + private final boolean useServerTimeZone; + private final String useTimeZone; + private final boolean useServerTimeZoneForDate; + + // client specific options + private final Map options; + private final ClickHouseCredentials credentials; + private final transient Optional metricRegistry; + + // node selector - pick only interested nodes from given list + private final ClickHouseNodeSelector nodeSelector; + + /** + * Construct a new configuration by consolidating given ones. + * + * @param configs list of configuration + */ + public ClickHouseConfig(ClickHouseConfig... configs) { + this(configs == null || configs.length == 0 ? Collections.emptyList() : Arrays.asList(configs)); + } + + /** + * Construct a new configuration by consolidating given ones. + * + * @param configs list of configuration + */ + public ClickHouseConfig(List configs) { + this(mergeOptions(configs), mergeCredentials(configs), mergeNodeSelector(configs), + mergeMetricRegistry(configs)); + } + + /** + * Default contructor. + * + * @param options generic options + * @param credentials default credential + * @param nodeSelector node selector + * @param metricRegistry metric registry + */ + public ClickHouseConfig(Map options, ClickHouseCredentials credentials, + ClickHouseNodeSelector nodeSelector, Object metricRegistry) { + this.options = new HashMap<>(); + if (options != null) { + this.options.putAll(options); + } + + this.async = (boolean) getOption(ClickHouseClientOption.ASYNC, ClickHouseDefaults.ASYNC); + this.clientName = (String) getOption(ClickHouseClientOption.CLIENT_NAME); + this.compression = ClickHouseCompression + .fromEncoding((String) getOption(ClickHouseClientOption.COMPRESSION, ClickHouseDefaults.COMPRESSION)); + this.connectionTimeout = (int) getOption(ClickHouseClientOption.CONNECTION_TIMEOUT); + this.database = (String) getOption(ClickHouseClientOption.DATABASE, ClickHouseDefaults.DATABASE); + this.format = ClickHouseFormat + .valueOf((String) getOption(ClickHouseClientOption.FORMAT, ClickHouseDefaults.FORMAT)); + this.maxBufferSize = (int) getOption(ClickHouseClientOption.MAX_BUFFER_SIZE); + this.maxExecutionTime = (int) getOption(ClickHouseClientOption.MAX_EXECUTION_TIME); + this.maxQueuedBuffers = (int) getOption(ClickHouseClientOption.MAX_QUEUED_BUFFERS); + this.maxQueuedRequests = (int) getOption(ClickHouseClientOption.MAX_QUEUED_REQUESTS); + this.maxResultRows = (int) getOption(ClickHouseClientOption.MAX_RESULT_ROWS); + this.maxThreads = (int) getOption(ClickHouseClientOption.MAX_THREADS_PER_CLIENT); + this.retry = (boolean) getOption(ClickHouseClientOption.RETRY); + this.reuseValueWrapper = (boolean) getOption(ClickHouseClientOption.REUSE_VALUE_WRAPPER); + this.socketTimeout = (int) getOption(ClickHouseClientOption.SOCKET_TIMEOUT); + this.sessionTimeout = (int) getOption(ClickHouseClientOption.SESSION_TIMEOUT); + this.sessionCheck = (boolean) getOption(ClickHouseClientOption.SESSION_CHECK); + this.ssl = (boolean) getOption(ClickHouseClientOption.SSL); + this.sslMode = ClickHouseSslMode.valueOf(((String) getOption(ClickHouseClientOption.SSL_MODE)).toUpperCase()); + this.sslRootCert = (String) getOption(ClickHouseClientOption.SSL_ROOT_CERTIFICATE); + this.sslCert = (String) getOption(ClickHouseClientOption.SSL_CERTIFICATE); + this.sslKey = (String) getOption(ClickHouseClientOption.SSL_KEY); + this.useObjectsInArray = (boolean) getOption(ClickHouseClientOption.USE_OBJECTS_IN_ARRAYS); + this.useServerTimeZone = (boolean) getOption(ClickHouseClientOption.USE_SERVER_TIME_ZONE); + this.useServerTimeZoneForDate = (boolean) getOption(ClickHouseClientOption.USE_SERVER_TIME_ZONE_FOR_DATES); + this.useTimeZone = (String) getOption(ClickHouseClientOption.USE_TIME_ZONE); + + if (credentials == null) { + this.credentials = ClickHouseCredentials.fromUserAndPassword((String) getOption(ClickHouseDefaults.USER), + (String) getOption(ClickHouseDefaults.PASSWORD)); + } else { + this.credentials = credentials; + } + this.metricRegistry = Optional.ofNullable(metricRegistry); + this.nodeSelector = nodeSelector == null ? ClickHouseNodeSelector.EMPTY : nodeSelector; + } + + public boolean isAsync() { + return async; + } + + public String getClientName() { + return clientName; + } + + public ClickHouseCompression getCompression() { + return compression; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public String getDatabase() { + return database; + } + + public ClickHouseFormat getFormat() { + return format; + } + + public int getMaxBufferSize() { + return maxBufferSize; + } + + public int getMaxExecutionTime() { + return maxExecutionTime; + } + + public int getMaxQueuedBuffers() { + return maxQueuedBuffers; + } + + public int getMaxQueuedRequests() { + return maxQueuedRequests; + } + + public int getMaxResultRows() { + return maxResultRows; + } + + public int getMaxThreadsPerClient() { + return maxThreads; + } + + public boolean isRetry() { + return retry; + } + + public boolean isReuseValueWrapper() { + return reuseValueWrapper; + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public int getSessionTimeout() { + return sessionTimeout; + } + + public boolean isSessionCheck() { + return sessionCheck; + } + + public boolean isSsl() { + return ssl; + } + + public ClickHouseSslMode getSslMode() { + return sslMode; + } + + public String getSslRootCert() { + return sslRootCert; + } + + public String getSslCert() { + return sslCert; + } + + public String getSslKey() { + return sslKey; + } + + public boolean isUseObjectsInArray() { + return useObjectsInArray; + } + + public boolean isUseServerTimeZone() { + return useServerTimeZone; + } + + public String getUseTimeZone() { + return useTimeZone; + } + + public boolean isUseServerTimeZoneForDate() { + return useServerTimeZoneForDate; + } + + public ClickHouseCredentials getDefaultCredentials() { + return this.credentials; + } + + public Optional getMetricRegistry() { + return this.metricRegistry; + } + + public ClickHouseNodeSelector getNodeSelector() { + return this.nodeSelector; + } + + public List getPreferredProtocols() { + return this.nodeSelector.getPreferredProtocols(); + } + + public Set getPreferredTags() { + return this.nodeSelector.getPreferredTags(); + } + + public Map getAllOptions() { + return Collections.unmodifiableMap(this.options); + } + + public Object getOption(ClickHouseConfigOption option) { + return getOption(option, null); + } + + public Object getOption(ClickHouseConfigOption option, ClickHouseDefaults defaultValue) { + return this.options.getOrDefault(ClickHouseChecker.nonNull(option, "option"), + defaultValue == null ? option.getEffectiveDefaultValue() : defaultValue.getEffectiveDefaultValue()); + } + + /** + * Test whether a given option is configured or not. + * + * @param option option to test + * @return true if the option is configured; false otherwise + */ + public boolean hasOption(ClickHouseConfigOption option) { + return option != null && this.options.containsKey(option); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCredentials.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCredentials.java new file mode 100644 index 000000000..51fa68aac --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseCredentials.java @@ -0,0 +1,123 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.util.Objects; + +/** + * This encapsulates access token, certificate or user name password combination + * for accessing ClickHouse. + */ +public class ClickHouseCredentials implements Serializable { + private static final long serialVersionUID = -8883041793709590486L; + + private final String accessToken; + + private final String userName; + private final String password; + // TODO sslCert + + /** + * Create credentials from access token. + * + * @param accessToken access token + * @return credentials object for authentication + */ + public static ClickHouseCredentials fromAccessToken(String accessToken) { + return new ClickHouseCredentials(accessToken); + } + + /** + * Create credentials from user name and password. + * + * @param userName user name + * @param password password + * @return credentials object for authentication + */ + public static ClickHouseCredentials fromUserAndPassword(String userName, String password) { + return new ClickHouseCredentials(userName, password); + } + + /** + * Construct credentials object using access token. + * + * @param accessToken access token + */ + protected ClickHouseCredentials(String accessToken) { + this.accessToken = ClickHouseChecker.nonNull(accessToken, "accessToken"); + this.userName = null; + this.password = null; + } + + /** + * Construct credentials using user name and password. + * + * @param userName user name + * @param password password + */ + protected ClickHouseCredentials(String userName, String password) { + this.accessToken = null; + + this.userName = ClickHouseChecker.nonNull(userName, "userName"); + this.password = ClickHouseChecker.nonNull(password, "password"); + } + + public boolean useAccessToken() { + return accessToken != null; + } + + /** + * Get access token. + * + * @return access token + */ + public String getAccessToken() { + if (!useAccessToken()) { + throw new IllegalStateException("No access token specified, please use user name and password instead."); + } + return this.accessToken; + } + + /** + * Get user name. + * + * @return user name + */ + public String getUserName() { + if (useAccessToken()) { + throw new IllegalStateException("No user name and password specified, please use access token instead."); + } + return this.userName; + } + + /** + * Get password. + * + * @return password + */ + public String getPassword() { + if (useAccessToken()) { + throw new IllegalStateException("No user name and password specified, please use access token instead."); + } + return this.password; + } + + @Override + public int hashCode() { + return Objects.hash(accessToken, userName, password); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + + if (this == obj) { + return true; + } + + ClickHouseCredentials c = (ClickHouseCredentials) obj; + return Objects.equals(accessToken, c.accessToken) && Objects.equals(userName, c.userName) + && Objects.equals(password, c.password); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataProcessor.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataProcessor.java new file mode 100644 index 000000000..68666411b --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataProcessor.java @@ -0,0 +1,109 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * This defines a data processor for dealing with one or multiple + * {@link ClickHouseFormat}. + */ +public abstract class ClickHouseDataProcessor { + public static final List DEFAULT_COLUMNS = Collections + .singletonList(ClickHouseColumn.of("results", "Nullable(String)")); + + protected static final String ERROR_UNKNOWN_DATA_TYPE = "Unsupported data type: "; + + protected static void buildMappings( + Map> deserializers, + Map> serializers, + ClickHouseDeserializer d, ClickHouseSerializer s, ClickHouseDataType... types) { + for (ClickHouseDataType t : types) { + if (deserializers.put(t, d) != null) { + throw new IllegalArgumentException("Duplicated deserializer of: " + t.name()); + } + if (serializers.put(t, s) != null) { + throw new IllegalArgumentException("Duplicated serializer of: " + t.name()); + } + } + } + + protected final ClickHouseConfig config; + protected final InputStream input; + protected final OutputStream output; + protected final List columns; + protected final Map settings; + + /** + * Read columns from input stream. Usually this will be only called once during + * instantiation. + * + * @return list of columns + * @throws IOException when failed to read columns from input stream + */ + protected abstract List readColumns() throws IOException; + + /** + * Default constructor. + * + * @param config non-null confinguration contains information like format + * @param input input stream for deserialization, can be null when + * {@code output} is not + * @param output outut stream for serialization, can be null when + * {@code input} is not + * @param columns nullable columns + * @param settings nullable settings + * @throws IOException when failed to read columns from input stream + */ + protected ClickHouseDataProcessor(ClickHouseConfig config, InputStream input, OutputStream output, + List columns, Map settings) throws IOException { + this.config = ClickHouseChecker.nonNull(config, "config"); + if (input == null && output == null) { + throw new IllegalArgumentException("One of input and output stream must be non-null"); + } + + this.input = input; + this.output = output; + if (settings == null || settings.size() == 0) { + this.settings = Collections.emptyMap(); + } else { + Map map = new HashMap<>(); + map.putAll(settings); + this.settings = Collections.unmodifiableMap(map); + } + + if (columns == null && input != null) { + columns = readColumns(); + } + + if (columns == null || columns.isEmpty()) { + this.columns = Collections.emptyList(); + } else { + List list = new ArrayList<>(columns.size()); + list.addAll(columns); + this.columns = Collections.unmodifiableList(list); + } + } + + /** + * Get column list. + * + * @return list of columns to process + */ + public final List getColumns() { + return columns; + } + + /** + * Return iterable to walk through all records in a for-each loop. + * + * @return iterable + */ + public abstract Iterable records(); +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataStreamFactory.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataStreamFactory.java new file mode 100644 index 000000000..14897a100 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataStreamFactory.java @@ -0,0 +1,73 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import com.clickhouse.client.data.ClickHousePipedStream; +import com.clickhouse.client.data.ClickHouseRowBinaryProcessor; +import com.clickhouse.client.data.ClickHouseTabSeparatedProcessor; + +/** + * Factory class for creating objects to handle data stream. + */ +public class ClickHouseDataStreamFactory { + private static final ClickHouseDataStreamFactory instance = ClickHouseUtils + .getService(ClickHouseDataStreamFactory.class, new ClickHouseDataStreamFactory()); + + /** + * Gets instance of the factory class. + * + * @return instance of the factory class + */ + public static ClickHouseDataStreamFactory getInstance() { + return instance; + } + + /** + * Gets data processor according to given {@link ClickHouseConfig} and settings. + * + * @param config non-null configuration containing information like + * {@link ClickHouseFormat} + * @param input input stream for deserialization, must not be null when + * {@code output} is null + * @param output output stream for serialization, must not be null when + * {@code input} is null + * @param settings nullable settings + * @param columns nullable list of columns + * @return data processor + * @throws IOException when failed to read columns from input stream + */ + public ClickHouseDataProcessor getProcessor(ClickHouseConfig config, InputStream input, OutputStream output, + Map settings, List columns) throws IOException { + ClickHouseFormat format = ClickHouseChecker.nonNull(config, "config").getFormat(); + ClickHouseDataProcessor processor; + if (ClickHouseFormat.RowBinary == format || ClickHouseFormat.RowBinaryWithNamesAndTypes == format) { + processor = new ClickHouseRowBinaryProcessor(config, input, output, columns, settings); + } else if (ClickHouseFormat.TabSeparated == format || ClickHouseFormat.TabSeparatedRaw == format + || ClickHouseFormat.TabSeparatedWithNames == format + || ClickHouseFormat.TabSeparatedWithNamesAndTypes == format) { + processor = new ClickHouseTabSeparatedProcessor(config, input, output, columns, settings); + } else if (format != null && format.isText()) { + processor = new ClickHouseTabSeparatedProcessor(config, input, output, + ClickHouseDataProcessor.DEFAULT_COLUMNS, settings); + } else { + throw new IllegalArgumentException("Unsupported format: " + format); + } + + return processor; + } + + /** + * Creates a piped stream. + * + * @param config non-null configuration + * @return piped stream + */ + public ClickHousePipedStream createPipedStream(ClickHouseConfig config) { + return new ClickHousePipedStream(config.getMaxBufferSize(), config.getMaxQueuedBuffers(), + config.getSocketTimeout()); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java new file mode 100644 index 000000000..1ee9ce825 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java @@ -0,0 +1,289 @@ +package com.clickhouse.client; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +/** + * Basic ClickHouse data types. + * + *

+ * This list is based on the list of data type families returned by + * {@code SELECT * FROM system.data_type_families} + * + *

+ * {@code LowCardinality} and {@code Nullable} are technically data types in + * ClickHouse, but for the sake of this driver, we treat these data types as + * modifiers for the underlying base data types. + */ +@SuppressWarnings("squid:S115") +public enum ClickHouseDataType { + IntervalYear(Long.class, false, true, true, 8, 19, 0), IntervalQuarter(Long.class, false, true, true, 8, 19, 0), + IntervalMonth(Long.class, false, true, true, 8, 19, 0), IntervalWeek(Long.class, false, true, true, 8, 19, 0), + IntervalDay(Long.class, false, true, true, 8, 19, 0), IntervalHour(Long.class, false, true, true, 8, 19, 0), + IntervalMinute(Long.class, false, true, true, 8, 19, 0), IntervalSecond(Long.class, false, true, true, 8, 19, 0), + UInt8(Short.class, false, true, false, 1, 3, 0, "INT1 UNSIGNED", "TINYINT UNSIGNED"), + UInt16(Integer.class, false, true, false, 2, 5, 0, "SMALLINT UNSIGNED"), + UInt32(Long.class, false, true, false, 4, 10, 0, "INT UNSIGNED", "INTEGER UNSIGNED", "MEDIUMINT UNSIGNED"), + UInt64(BigInteger.class, false, true, false, 8, 19, 0, "BIGINT UNSIGNED"), + UInt128(BigInteger.class, false, true, false, 16, 20, 0), UInt256(BigInteger.class, false, true, false, 32, 39, 0), + Int8(Byte.class, false, true, true, 1, 4, 0, "BOOL", "BOOLEAN", "BYTE", "INT1", "INT1 SIGNED", "TINYINT", + "TINYINT SIGNED"), + Int16(Short.class, false, true, true, 2, 6, 0, "SMALLINT", "SMALLINT SIGNED"), + Int32(Integer.class, false, true, true, 4, 11, 0, "INT", "INTEGER", "MEDIUMINT", "INT SIGNED", "INTEGER SIGNED", + "MEDIUMINT SIGNED"), + Int64(Long.class, false, true, true, 8, 20, 0, "BIGINT", "BIGINT SIGNED"), + Int128(BigInteger.class, false, true, true, 16, 20, 0), Int256(BigInteger.class, false, true, true, 32, 40, 0), + Date(LocalDate.class, false, false, false, 2, 10, 0), Date32(LocalDate.class, false, false, false, 4, 10, 0), + DateTime(LocalDateTime.class, true, false, false, 0, 19, 0, "TIMESTAMP"), + DateTime32(LocalDateTime.class, true, false, false, 8, 19, 0), + DateTime64(LocalDateTime.class, true, false, false, 16, 38, 3), // scale up to 18 + Decimal(BigDecimal.class, true, false, true, 0, 0, 0, "DEC", "NUMERIC", "FIXED"), + Decimal32(BigDecimal.class, true, false, true, 4, 9, 9), Decimal64(BigDecimal.class, true, false, true, 8, 18, 18), + Decimal128(BigDecimal.class, true, false, true, 16, 38, 38), + Decimal256(BigDecimal.class, true, false, true, 32, 76, 20), UUID(UUID.class, false, true, false, 16, 36, 0), + Enum(String.class, true, true, false, 1, 0, 0), Enum8(String.class, true, true, false, 1, 0, 0), + Enum16(String.class, true, true, false, 2, 0, 0), + Float32(Float.class, false, true, true, 4, 8, 8, "FLOAT", "REAL", "SINGLE"), + Float64(Double.class, false, true, true, 16, 17, 17, "DOUBLE", "DOUBLE PRECISION"), + IPv4(Inet4Address.class, false, true, false, 4, 10, 0, "INET4"), + IPv6(Inet6Address.class, false, true, false, 16, 0, 0, "INET6"), + FixedString(String.class, true, true, false, 0, -1, 0, "BINARY"), + String(String.class, false, true, false, 0, 0, 0, "BINARY LARGE OBJECT", "BINARY VARYING", "BLOB", "BYTEA", "CHAR", + "CHAR LARGE OBJECT", "CHAR VARYING", "CHARACTER", "CHARACTER LARGE OBJECT", "CHARACTER VARYING", "CLOB", + "LONGBLOB", "LONGTEXT", "MEDIUMBLOB", "MEDIUMTEXT", "NATIONAL CHAR", "NATIONAL CHAR VARYING", + "NATIONAL CHARACTER", "NATIONAL CHARACTER LARGE OBJECT", "NATIONAL CHARACTER VARYING", "NCHAR", + "NCHAR LARGE OBJECT", "NCHAR VARYING", "NVARCHAR", "TEXT", "TINYBLOB", "TINYTEXT", "VARCHAR", "VARCHAR2"), + AggregateFunction(String.class, true, true, false, 0, 0, 0), // implementation-defined intermediate state + Array(Object.class, true, true, false, 0, 0, 0), Map(Map.class, true, true, false, 0, 0, 0), + Nested(Object.class, true, true, false, 0, 0, 0), Tuple(List.class, true, true, false, 0, 0, 0), + Point(Object.class, false, true, true, 33, 17, 17), // same as Tuple(Float64, Float64) + Polygon(Object.class, false, true, true, 0, 0, 0), // same as Array(Ring) + MultiPolygon(Object.class, false, true, true, 0, 0, 0), // same as Array(Polygon) + Ring(Object.class, false, true, true, 0, 0, 0), // same as Array(Point) + Nothing(Object.class, false, true, false, 0, 0, 0); + + /** + * Immutable set(sorted) for all aliases. + */ + public static final Set allAliases; + + /** + * Immutable mapping between name and type. + */ + public static final Map name2type; + + static { + Set set = new TreeSet<>(); + Map map = new HashMap<>(); + String errorMsg = "[%s] is used by type [%s]"; + ClickHouseDataType used = null; + for (ClickHouseDataType t : ClickHouseDataType.values()) { + String name = t.name(); + if (!t.isCaseSensitive()) { + name = name.toUpperCase(); + } + used = map.put(name, t); + if (used != null) { + throw new IllegalStateException(java.lang.String.format(Locale.ROOT, errorMsg, name, used.name())); + } + + // aliases are all case-insensitive + for (String alias : t.aliases) { + String aliasInUpperCase = alias.toUpperCase(); + set.add(aliasInUpperCase); + used = map.put(aliasInUpperCase, t); + if (used != null) { + throw new IllegalStateException(java.lang.String.format(Locale.ROOT, errorMsg, alias, used.name())); + } + } + } + + allAliases = Collections.unmodifiableSet(set); + name2type = Collections.unmodifiableMap(map); + } + + /** + * Checks if the given type name is an alias or not. + * + * @param typeName type name + * @return true if the type name is an alias; false otherwise + */ + public static boolean isAlias(String typeName) { + return typeName != null && !typeName.isEmpty() && allAliases.contains(typeName.trim().toUpperCase()); + } + + public static List match(String part) { + List types = new LinkedList<>(); + + for (ClickHouseDataType t : values()) { + if (t.isCaseSensitive()) { + if (t.name().equals(part)) { + types.add(t.name()); + break; + } + } else { + if (t.name().equalsIgnoreCase(part)) { + types.add(part); + break; + } + } + } + + if (types.isEmpty()) { + part = part.toUpperCase(); + String prefix = part + ' '; + for (String alias : allAliases) { + if (alias.length() == part.length() && alias.equals(part)) { + types.add(alias); + } else if (alias.length() > part.length() && alias.startsWith(prefix)) { + types.add(alias); + } + } + } + + return types; + } + + /** + * Converts given type name to corresponding data type. + * + * @param typeName non-empty type name + * @return data type + */ + public static ClickHouseDataType of(String typeName) { + if (typeName == null || (typeName = typeName.trim()).isEmpty()) { + throw new IllegalArgumentException("Non-empty typeName is required"); + } + + ClickHouseDataType type = name2type.get(typeName); + if (type == null) { + type = name2type.get(typeName.toUpperCase()); // case-insensitive or just an alias + } + + if (type == null) { + throw new IllegalArgumentException("Unknown data type: " + typeName); + } + return type; + } + + private final Class javaClass; + private final boolean parameter; + private final boolean caseSensitive; + private final boolean signed; + private final List aliases; + private final int byteLength; + private final int defaultPrecision; + private final int defaultScale; + + ClickHouseDataType(Class javaClass, boolean parameter, boolean caseSensitive, boolean signed, int byteLength, + int defaultPrecision, int defaultScale, String... aliases) { + this.javaClass = javaClass == null ? Object.class : javaClass; + this.parameter = parameter; + this.caseSensitive = caseSensitive; + this.signed = signed; + this.byteLength = byteLength; + this.defaultPrecision = defaultPrecision; + this.defaultScale = defaultScale; + if (aliases == null || aliases.length == 0) { + this.aliases = Collections.emptyList(); + } else { + this.aliases = Collections.unmodifiableList(Arrays.asList(aliases)); + } + } + + /** + * Gets Java class for this data type. + * + * @return Java class + */ + public Class getJavaClass() { + return javaClass; + } + + /** + * Checks if this data type may have parameter(s). + * + * @return true if this data type may have parameter; false otherwise + */ + public boolean hasParameter() { + return parameter; + } + + /** + * Checks if name of this data type is case sensistive or not. + * + * @return true if it's case sensitive; false otherwise + */ + public boolean isCaseSensitive() { + return caseSensitive; + } + + /** + * Checks if this data type could be a nested structure. + * + * @return true if it could be a nested structure; false otherwise + */ + public boolean isNested() { + return this == AggregateFunction || this == Array || this == Map || this == Nested || this == Tuple; + } + + /** + * Checks if this data type represents signed number. + * + * @return true if it's signed; false otherwise + */ + public boolean isSigned() { + return signed; + } + + /** + * Gets immutable list of aliases for this data type. + * + * @return immutable list of aliases + */ + public List getAliases() { + return aliases; + } + + /** + * Gets byte length of this data type. Zero means unlimited. + * + * @return byte length of this data type + */ + public int getByteLength() { + return byteLength; + } + + /** + * Gets default precision of this data type. Zero means unknown or not + * supported. + * + * @return default precision of this data type. + */ + public int getDefaultPrecision() { + return defaultPrecision; + } + + /** + * Gets default scale of this data type. Zero means unknown or not supported. + * + * @return default scale of this data type. + */ + public int getDefaultScale() { + return defaultScale; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDeserializer.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDeserializer.java new file mode 100644 index 000000000..b74e774df --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDeserializer.java @@ -0,0 +1,22 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Functional interface for deserialization. + */ +@FunctionalInterface +public interface ClickHouseDeserializer { + /** + * Deserializes data read from input stream. + * + * @param ref wrapper object can be reused, could be null(always return new + * wrapper object) + * @param column non-null type information + * @param input non-null input stream + * @return deserialized value which might be the same instance as {@code ref} + * @throws IOException when failed to read data from input stream + */ + T deserialize(T ref, ClickHouseColumn column, InputStream input) throws IOException; +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDnsResolver.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDnsResolver.java new file mode 100644 index 000000000..305714cbc --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDnsResolver.java @@ -0,0 +1,77 @@ +package com.clickhouse.client; + +import java.net.InetSocketAddress; +import com.clickhouse.client.config.ClickHouseDefaults; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.TextParseException; +import org.xbill.DNS.Type; + +/** + * Default DNS resolver. It tries to look up service record (SRV record) when + * {@link com.clickhouse.client.config.ClickHouseDefaults#DNS_RESOLVE} is set to + * {@code true}. + */ +public class ClickHouseDnsResolver { + private static final Logger log = LoggerFactory.getLogger(ClickHouseDnsResolver.class); + + private static final ClickHouseDnsResolver instance = ClickHouseUtils.getService(ClickHouseDnsResolver.class, + new ClickHouseDnsResolver()); + + public static ClickHouseDnsResolver getInstance() { + return instance; + } + + protected ClickHouseDnsResolver() { + } + + public InetSocketAddress resolve(ClickHouseProtocol protocol, String host, int port) { + if (protocol == null || host == null) { + throw new IllegalArgumentException("Non-null protocol and host are required"); + } + + if ((boolean) ClickHouseDefaults.DNS_RESOLVE.getEffectiveDefaultValue()) { + SRVRecord r = resolve(host, false); + if (r != null) { + host = r.getName().canonicalize().toString(true); + port = r.getPort(); + } + } else { + host = ClickHouseDefaults.HOST.getEffectiveValue(host); + port = port <= 0 ? protocol.getDefaultPort() : port; + } + + return new InetSocketAddress(host, port); + } + + // TODO register a callback for DNS change? + + protected SRVRecord resolve(String srvDns, boolean basedOnWeight) { + Record[] records = null; + try { + records = new Lookup(srvDns, Type.SRV).run(); + } catch (TextParseException e) { + // fallback to a cached entry? + log.warn("Not able to resolve given DNS query: [%s]", srvDns, e); + } + + SRVRecord record = null; + if (records != null) { + if (basedOnWeight) { + for (int i = 0; i < records.length; i++) { + SRVRecord rec = (SRVRecord) records[i]; + if (record == null || record.getWeight() > rec.getWeight()) { + record = rec; + } + } + } else { + record = (SRVRecord) records[0]; + } + } + + return record; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseFormat.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseFormat.java new file mode 100644 index 000000000..fdd685465 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseFormat.java @@ -0,0 +1,105 @@ +package com.clickhouse.client; + +/** + * All formats supported by ClickHouse. More information at: + * https://clickhouse.tech/docs/en/interfaces/formats/. + */ +public enum ClickHouseFormat { + Arrow(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#arrow + ArrowStream(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#arrowstream + Avro(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#avro + AvroConfluent(true, false, true, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#avroconfluent + CapnProto(true, false, true, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#capnproto + CSV(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#csv + CSVWithNames(true, true, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#csvwithnames + CustomSeparated(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#customseparated + JSON(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#json + JSONAsString(true, false, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#jsonasstring + JSONCompact(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoncompact + JSONCompactEachRow(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoncompacteachrow + JSONCompactEachRowWithNamesAndTypes(true, true, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoncompacteachrowwithnamesandtypes + JSONCompactString(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoncompactstring + JSONCompactStringEachRow(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoncompactstringeachrow + JSONCompactStringEachRowWithNamesAndTypes(true, true, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoncompactstringeachrowwithnamesandtypes + JSONEachRow(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoneachrow + JSONEachRowWithProgress(false, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsoneachrowwithprogress + JSONString(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#jsonstring + JSONStringsEachRow(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsonstringseachrow + JSONStringsEachRowWithProgress(false, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#jsonstringseachrowwithprogress + LineAsString(true, false, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#lineasstring + Native(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#native + Null(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#null + ORC(true, false, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#orc + Parquet(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#parquet + Pretty(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#pretty + PrettyCompact(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#prettycompact + PrettyCompactMonoBlock(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#prettycompactmonoblock + PrettyNoEscapes(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#prettynoescapes + PrettySpace(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#prettyspace + Protobuf(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#protobuf + ProtobufSingle(true, true, true, true, false), // https://clickhouse.tech/docs/en/interfaces/formats/#protobufsingle + RawBLOB(true, true, true, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#rawblob + Regexp(true, false, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#regexp + RowBinary(true, true, true, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#rowbinary + RowBinaryWithNamesAndTypes(true, true, true, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#rowbinarywithnamesandtypes + TabSeparated(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#tabseparated + TabSeparatedRaw(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#tabseparatedraw + TabSeparatedWithNames(true, true, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#tabseparatedwithnames + TabSeparatedWithNamesAndTypes(true, true, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#tabseparatedwithnamesandtypes + TSV(true, true, false, false, true), // alias of TabSeparated + TSVRaw(true, true, false, false, true), // alias of TabSeparatedRaw + TSVWithNames(true, true, false, true, true), // alias of TabSeparatedWithNames + TSVWithNamesAndTypes(true, true, false, true, true), // alias of TabSeparatedWithNamesAndTypes + Template(true, true, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#template + TemplateIgnoreSpaces(true, false, false, true, true), // https://clickhouse.tech/docs/en/interfaces/formats/#templateignorespaces + TSKV(true, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#tskv + Values(true, true, false, false, true), // https://clickhouse.tech/docs/en/interfaces/formats/#values + Vertical(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#vertical + VerticalRaw(false, true, false, false, false), // https://clickhouse.tech/docs/en/interfaces/formats/#verticalraw + XML(false, true, false, false, false); // https://clickhouse.tech/docs/en/interfaces/formats/#xml + + private boolean input; + private boolean output; + private boolean binary; + private boolean header; + private boolean rowBased; + + ClickHouseFormat(boolean input, boolean output, boolean binary, boolean header, boolean rowBased) { + this.input = input; + this.output = output; + this.binary = binary; + this.header = output && header; + this.rowBased = rowBased; + } + + public boolean supportsInput() { + return input; + } + + public boolean supportsOutput() { + return output; + } + + public boolean isBinary() { + return binary; + } + + public boolean isText() { + return !binary; + } + + public boolean hasHeader() { + return header; + } + + /** + * Check whether the format is row based(e.g. read/write by row), which is a + * very useful hint on how to process the data. + * + * @return true if the format is row based; false otherwise(e.g. column, + * document, or structured-object etc.) + */ + public boolean isRowBased() { + return rowBased; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseImmediateFuture.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseImmediateFuture.java new file mode 100644 index 000000000..f9f358cab --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseImmediateFuture.java @@ -0,0 +1,60 @@ +package com.clickhouse.client; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Encapsulates a {@link java.util.concurrent.Callable} when + * {@link com.clickhouse.client.config.ClickHouseClientOption#ASYNC} is set to + * {@code false}. It's not cancellable and the task will be only executed when + * {@link #get()} is called. + * + * @deprecated + */ +@Deprecated +public final class ClickHouseImmediateFuture implements Future { + public static Future of(Callable task) { + return new ClickHouseImmediateFuture<>(task); + } + + private final Callable task; + + private ClickHouseImmediateFuture(Callable task) { + this.task = ClickHouseChecker.nonNull(task, "task"); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + try { + return task.call(); + } catch (InterruptedException | ExecutionException e) { + throw e; + } catch (Exception e) { + throw new ExecutionException(e); + } + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + ClickHouseChecker.nonNull(unit, "unit"); + return get(); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseNode.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseNode.java new file mode 100644 index 000000000..6edc2a5a3 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseNode.java @@ -0,0 +1,548 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import com.clickhouse.client.config.ClickHouseDefaults; + +/** + * This class depicts a ClickHouse server, essentially a combination of host, + * port and protocol, for client to connect. + */ +public class ClickHouseNode implements Function, Serializable { + /** + * Node status. + */ + public enum Status { + HEALTHY, UNHEALTHY, MANAGED, UNMANAGED + } + + /** + * Mutable and non-thread safe builder. + */ + public static class Builder { + protected String cluster; + protected String host; + protected Integer port; + protected InetSocketAddress address; + protected ClickHouseProtocol protocol; + protected ClickHouseCredentials credentials; + protected String database; + // label is more expressive, but is slow for comparison + protected Set tags; + protected Integer weight; + + /** + * Default constructor. + */ + protected Builder() { + this.tags = new HashSet<>(3); + } + + protected String getCluster() { + if (cluster == null) { + cluster = (String) ClickHouseDefaults.CLUSTER.getEffectiveDefaultValue(); + } + + return cluster; + } + + protected InetSocketAddress getAddress() { + if (host == null) { + host = (String) ClickHouseDefaults.HOST.getEffectiveDefaultValue(); + } + + if (port == null) { + port = (Integer) ClickHouseDefaults.PORT.getEffectiveDefaultValue(); + } + + if (address == null) { + address = InetSocketAddress.createUnresolved(host, port); + } + + return address; + } + + protected ClickHouseProtocol getProtocol() { + if (protocol == null) { + protocol = ClickHouseProtocol.valueOf((String) ClickHouseDefaults.PROTOCOL.getEffectiveDefaultValue()); + } + return protocol; + } + + protected ClickHouseCredentials getCredentials() { + return credentials; + } + + protected String getDatabase() { + if (database == null) { + database = (String) ClickHouseDefaults.DATABASE.getEffectiveDefaultValue(); + } + return database; + } + + protected Set getTags() { + int size = tags == null ? 0 : tags.size(); + if (size == 0) { + return Collections.emptySet(); + } + + Set s = new HashSet<>(size); + s.addAll(tags); + return Collections.unmodifiableSet(s); + } + + protected int getWeight() { + if (weight == null) { + weight = (Integer) ClickHouseDefaults.WEIGHT.getEffectiveDefaultValue(); + } + + return weight.intValue(); + } + + /** + * Sets cluster name. + * + * @param cluster cluster name, null means {@link ClickHouseDefaults#CLUSTER} + * @return this builder + */ + public Builder cluster(String cluster) { + this.cluster = cluster; + return this; + } + + /** + * Sets host name. + * + * @param host host name, null means {@link ClickHouseDefaults#HOST} + * @return this builder + */ + public Builder host(String host) { + if (!Objects.equals(this.host, host)) { + this.address = null; + } + + this.host = host; + return this; + } + + /** + * Sets port number. + * + * @param port port number, null means {@link ClickHouseDefaults#PORT} + * @return this builder + */ + public Builder port(Integer port) { + return port(null, port); + } + + /** + * Sets protocol used by the port. + * + * @param protocol protocol, null means {@link ClickHouseDefaults#PROTOCOL} + * @return this builder + */ + public Builder port(ClickHouseProtocol protocol) { + return port(protocol, ClickHouseChecker.nonNull(protocol, "protocol").getDefaultPort()); + } + + /** + * Sets protocol and port number. + * + * @param protocol protocol, null means {@link ClickHouseDefaults#PROTOCOL} + * @param port number, null means {@link ClickHouseDefaults#PORT} + * @return this builder + */ + public Builder port(ClickHouseProtocol protocol, Integer port) { + if (!Objects.equals(this.port, port)) { + this.address = null; + } + + this.port = port; + this.protocol = protocol; + return this; + } + + /** + * Sets socket address. + * + * @param address socket address, null means {@link ClickHouseDefaults#HOST} and + * {@link ClickHouseDefaults#PORT} + * @return this builder + */ + public Builder address(InetSocketAddress address) { + return address(null, address); + } + + /** + * Sets protocol and socket address. + * + * @param protocol protocol, null means {@link ClickHouseDefaults#PROTOCOL} + * @param address socket address, null means {@link ClickHouseDefaults#HOST} + * and {@link ClickHouseDefaults#PORT} + * @return this builder + */ + public Builder address(ClickHouseProtocol protocol, InetSocketAddress address) { + if (!Objects.equals(this.address, address)) { + this.host = null; + this.port = null; + } + + this.address = address; + this.protocol = protocol; + return this; + } + + /** + * Sets database name. + * + * @param database database name, null means {@link ClickHouseDefaults#DATABASE} + * @return this builder + */ + public Builder database(String database) { + this.database = database; + return this; + } + + /** + * Sets credentials will be used when connecting to this node. This is optional + * as client will use {@link ClickHouseConfig#getDefaultCredentials()} to + * connect to all nodes. + * + * @param credentials credentials, null means + * {@link ClickHouseConfig#getDefaultCredentials()} + * @return this builder + */ + public Builder credentials(ClickHouseCredentials credentials) { + this.credentials = credentials; + return this; + } + + /** + * Adds a tag for this node. + * + * @param tag tag for the node, null or duplicate tag will be ignored + * @return this builder + */ + public Builder addTag(String tag) { + if (tag != null) { + this.tags.add(tag); + } + + return this; + } + + /** + * Removes a tag from this node. + * + * @param tag tag to be removed, null value will be ignored + * @return this builder + */ + public Builder removeTag(String tag) { + if (tag != null) { + this.tags.remove(tag); + } + + return this; + } + + /** + * Sets all tags for this node. Use null or empty value to clear all existing + * tags. + * + * @param tag tag for the node, null will be ignored + * @param more more tags for the node, null tag will be ignored + * @return this builder + */ + public Builder tags(String tag, String... more) { + this.tags.clear(); + + addTag(tag); + + if (more != null) { + for (String t : more) { + if (t == null) { + continue; + } + this.tags.add(t); + } + } + + return this; + } + + /** + * Sets all tags for this node. Use null or empty value to clear all existing + * tags. + * + * @param tags list of tags for the node, null tag will be ignored + * @return this builder + */ + public Builder tags(Collection tags) { + this.tags.clear(); + + if (tags != null) { + for (String t : tags) { + if (t == null) { + continue; + } + this.tags.add(t); + } + } + + return this; + } + + /** + * Sets weight of this node. + * + * @param weight weight of the node, null means + * {@link ClickHouseDefaults#WEIGHT} + * @return this builder + */ + public Builder weight(Integer weight) { + this.weight = weight; + return this; + } + + /** + * Creates a new node. + * + * @return new node + */ + public ClickHouseNode build() { + return new ClickHouseNode(this); + } + } + + /** + * Gets builder for creating a new node, same as {@code builder(null)}. + * + * @return builder for creating a new node + */ + public static Builder builder() { + return builder(null); + } + + /** + * Gets builder for creating a new node based on the given one. + * + * @param base template to start with + * @return builder for creating a new node + */ + public static Builder builder(ClickHouseNode base) { + Builder b = new Builder(); + if (base != null) { + b.cluster(base.getCluster()).host(base.getHost()).port(base.getProtocol(), base.getPort()) + .credentials(base.getCredentials().orElse(null)).database(base.getDatabase()).tags(base.getTags()) + .weight(base.getWeight()); + } + return b; + } + + /** + * Creates a node object in one go. Short version of + * {@code ClickHouseNode.builder().host(host).port(protocol, port).database(database).tags(tags).build()}. + * + * @param host host name, null means {@link ClickHouseDefaults#HOST} + * @param protocol protocol, null means {@link ClickHouseDefaults#PROTOCOL} + * @param port port number + * @param database database name, null means {@link ClickHouseDefaults#DATABASE} + * @param tags tags for the node, null tag will be ignored + * @return node object + */ + public static ClickHouseNode of(String host, ClickHouseProtocol protocol, int port, String database, + String... tags) { + Builder builder = builder().host(host).port(protocol, port).database(database); + if (tags != null && tags.length > 0) { + builder.tags(null, tags); + } + return builder.build(); + } + + /** + * Generated UID. + */ + private static final long serialVersionUID = 8342604784121795372L; + + private final String cluster; + private final ClickHouseProtocol protocol; + private final InetSocketAddress address; + private final Optional credentials; + private final String database; + private final Set tags; + private final int weight; + + private BiConsumer manager; + + protected ClickHouseNode(Builder builder) { + ClickHouseChecker.nonNull(builder, "builder"); + + this.cluster = builder.getCluster(); + this.protocol = builder.getProtocol(); + this.address = builder.getAddress(); + this.credentials = Optional.ofNullable(builder.getCredentials()); + this.database = builder.getDatabase(); + this.tags = builder.getTags(); + this.weight = builder.getWeight(); + + this.manager = null; + } + + /** + * Gets socket address to connect to this node. + * + * @return socket address to connect to the node + */ + public InetSocketAddress getAddress() { + return this.address; + } + + /** + * Gets credentials for accessing this node. Use + * {@link ClickHouseConfig#getDefaultCredentials()} if this is not present. + * + * @return credentials for accessing this node + */ + public Optional getCredentials() { + return this.credentials; + } + + /** + * Gets host of the node. + * + * @return host of the node + */ + public String getHost() { + return this.address.getHostString(); + } + + /** + * Gets port of the node. + * + * @return port of the node + */ + public int getPort() { + return this.address.getPort(); + } + + /** + * Gets database of the node. + * + * @return database of the node + */ + public String getDatabase() { + return this.database; + } + + /** + * Gets all tags of the node. + * + * @return tags of the node + */ + public Set getTags() { + return this.tags; + } + + /** + * Gets weight of the node. + * + * @return weight of the node + */ + public int getWeight() { + return this.weight; + } + + /** + * Gets cluster name of the node. + * + * @return cluster name of node + */ + public String getCluster() { + return this.cluster; + } + + /** + * Gets protocol used by the node. + * + * @return protocol used by the node + */ + public ClickHouseProtocol getProtocol() { + return this.protocol; + } + + /** + * Sets manager for this node. + * + * @param manager function to manage status of the node + */ + public synchronized void setManager(BiConsumer manager) { + if (this.manager != null && !this.manager.equals(manager)) { + this.manager.accept(this, Status.UNMANAGED); + } + + if (manager != null && !manager.equals(this.manager)) { + manager.accept(this, Status.MANAGED); + this.manager = manager; + } + } + + /** + * Updates status of the node. This will only work when a manager function + * exists via {@link #setManager(BiConsumer)}. + * + * @param status node status + */ + public synchronized void updateStatus(Status status) { + if (this.manager != null) { + this.manager.accept(this, status); + } + } + + @Override + public ClickHouseNode apply(ClickHouseNodeSelector t) { + if (t != null && t != ClickHouseNodeSelector.EMPTY + && (!t.matchAnyOfPreferredProtocols(protocol) || !t.matchAllPreferredTags(tags))) { + throw new IllegalArgumentException("No suitable node found"); + } + + return this; + } + + @Override + public String toString() { + return new StringBuilder().append(getClass().getSimpleName()).append('(').append("cluster=").append(cluster) + .append(", protocol=").append(protocol).append(", address=").append(address).append(", database=") + .append(database).append(", tags=").append(tags).append(", weight=").append(weight).append(')') + .append('@').append(hashCode()).toString(); + } + + @Override + public int hashCode() { + return Objects.hash(address, cluster, credentials, database, protocol, tags, weight); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + + if (this == obj) { + return true; + } + + ClickHouseNode node = (ClickHouseNode) obj; + return address.equals(node.address) && cluster.equals(node.cluster) && credentials.equals(node.credentials) + && database.equals(node.database) && protocol.equals(node.protocol) && tags.equals(node.tags) + && weight == node.weight; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseNodeSelector.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseNodeSelector.java new file mode 100644 index 000000000..4975019d8 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseNodeSelector.java @@ -0,0 +1,204 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * This class maintains two immutable lists: preferred protocols and tags. + * Usually it will be used in two scenarios: 1) find suitable + * {@link ClickHouseClient} according to preferred protocol(s); and 2) pick + * suitable {@link ClickHouseNode} to connect to. + */ +public class ClickHouseNodeSelector implements Serializable { + public static final ClickHouseNodeSelector EMPTY = new ClickHouseNodeSelector(Collections.emptyList(), + Collections.emptyList()); + + public static ClickHouseNodeSelector of(ClickHouseProtocol protocol, ClickHouseProtocol... more) { + List list = new LinkedList<>(); + + if (protocol != null) { + list.add(protocol); + } + + if (more != null) { + for (ClickHouseProtocol p : more) { + if (p != null) { + list.add(p); + } + } + } + + return of(list, null); + } + + public static ClickHouseNodeSelector of(String tag, String... more) { + List list = new LinkedList<>(); + + if (!ClickHouseChecker.isNullOrEmpty(tag)) { + list.add(tag); + } + + if (more != null) { + for (String t : more) { + if (!ClickHouseChecker.isNullOrEmpty(t)) { + list.add(t); + } + } + } + + return of(null, list); + } + + public static ClickHouseNodeSelector of(Collection protocols, Collection tags) { + return (protocols == null || protocols.size() == 0) && (tags == null || tags.size() == 0) ? EMPTY + : new ClickHouseNodeSelector(protocols, tags); + } + + private static final long serialVersionUID = 488571984297086418L; + + private final List protocols; + private final Set tags; + + protected ClickHouseNodeSelector(Collection protocols, Collection tags) { + if (protocols == null || protocols.size() == 0) { + this.protocols = Collections.emptyList(); + } else { + List p = new ArrayList<>(protocols.size()); + for (ClickHouseProtocol protocol : protocols) { + if (protocol == null) { + continue; + } else if (protocol == ClickHouseProtocol.ANY) { + p.clear(); + break; + } else if (!p.contains(protocol)) { + p.add(protocol); + } + } + + this.protocols = p.size() == 0 ? Collections.emptyList() : Collections.unmodifiableList(p); + } + + if (tags == null || tags.size() == 0) { + this.tags = Collections.emptySet(); + } else { + Set t = new HashSet<>(); + for (String tag : tags) { + if (tag == null || tag.isEmpty()) { + continue; + } else { + t.add(tag); + } + } + + this.tags = t.size() == 0 ? Collections.emptySet() : Collections.unmodifiableSet(t); + } + } + + public List getPreferredProtocols() { + return this.protocols; + } + + public Set getPreferredTags() { + return this.tags; + } + + /** + * Test if the given client supports any of {@link #getPreferredProtocols()}. + * It's always {@code false} if either the client is null or there's no + * preferred protocol. + * + * @param client client to test + * @return true if any of the preferred protocols is supported by the client + */ + public boolean match(ClickHouseClient client) { + boolean matched = false; + + if (client != null) { + for (ClickHouseProtocol p : protocols) { + if (client.accept(p)) { + matched = true; + break; + } + } + } + + return matched; + } + + public boolean match(ClickHouseNode node) { + boolean matched = false; + + if (node != null) { + matched = matchAnyOfPreferredProtocols(node.getProtocol()) && matchAllPreferredTags(node.getTags()); + } + + return matched; + } + + public boolean matchAnyOfPreferredProtocols(ClickHouseProtocol protocol) { + boolean matched = protocols.size() == 0 || protocol == ClickHouseProtocol.ANY; + + if (!matched && protocol != null) { + for (ClickHouseProtocol p : protocols) { + if (p == protocol) { + matched = true; + break; + } + } + } + + return matched; + } + + public boolean matchAllPreferredTags(Collection tags) { + boolean matched = true; + + if (tags != null && tags.size() > 0) { + for (String t : tags) { + if (t == null || t.isEmpty()) { + continue; + } + + matched = matched && this.tags.contains(t); + + if (!matched) { + break; + } + } + } + + return matched; + } + + public boolean matchAnyOfPreferredTags(Collection tags) { + boolean matched = tags.size() == 0; + + if (tags != null && tags.size() > 0) { + for (String t : tags) { + if (t == null || t.isEmpty()) { + continue; + } + + if (this.tags.contains(t)) { + matched = true; + break; + } + } + } + + return matched; + } + + /* + * public boolean matchAnyOfPreferredTags(String cluster, + * List protocols, List tags) { return + * (ClickHouseChecker.isNullOrEmpty(cluster) || cluster.equals(this.cluster)) && + * supportAnyProtocol(protocols) && hasAllTags(tags); } + */ +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseParameterizedQuery.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseParameterizedQuery.java new file mode 100644 index 000000000..1e07f957d --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseParameterizedQuery.java @@ -0,0 +1,399 @@ +package com.clickhouse.client; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * A parameterized query is a parsed query with named parameters being extracted + * for substitution. + */ +public final class ClickHouseParameterizedQuery { + /** + * Substitute named parameters in given SQL. + * + * @param sql SQL containing named parameters + * @param params mapping between parameter name and correspoding SQL expression + * @return substituted SQL, or the given sql if one of {@code sql} and + * {@code params} is null or empty + */ + public static String apply(String sql, Map params) { + int len = sql == null ? 0 : sql.length(); + if (len < 2 || params == null || params.size() == 0) { + return sql; + } + + StringBuilder builder = new StringBuilder(len * 2); + for (int i = 0; i < len; i++) { + char ch = sql.charAt(i); + if (ClickHouseUtils.isQuote(ch)) { + int endIndex = ClickHouseUtils.skipQuotedString(sql, i, len, ch); + builder.append(sql.substring(i, endIndex)); + i = endIndex - 1; + } else if (i + 1 < len) { + int endIndex = i + 1; + char nextCh = sql.charAt(endIndex); + if (ch == '-' && nextCh == ch) { + endIndex = ClickHouseUtils.skipSingleLineComment(sql, i + 2, len); + builder.append(sql.substring(i, endIndex)); + i = endIndex - 1; + } else if (ch == '/' && nextCh == '*') { + endIndex = ClickHouseUtils.skipMultiLineComment(sql, i + 2, len); + builder.append(sql.substring(i, endIndex)); + i = endIndex - 1; + } else if (ch == ':') { + if (nextCh == ch) { // skip PostgreSQL-like type conversion + builder.append(ch).append(ch); + i = i + 1; + } else if (Character.isJavaIdentifierStart(nextCh)) { + StringBuilder sb = new StringBuilder().append(nextCh); + for (i = i + 2; i < len; i++) { + char c = sql.charAt(i); + if (c == '(') { + i = ClickHouseUtils.skipBrackets(sql, i, len, c) - 1; + break; + } else if (Character.isJavaIdentifierPart(c)) { + sb.append(c); + } else { + i--; + break; + } + } + + if (sb.length() > 0) { + builder.append(params.getOrDefault(sb.toString(), ClickHouseValues.NULL_EXPR)); + } + } else { + builder.append(ch); + } + } else { + builder.append(ch); + } + } else { + builder.append(ch); + } + } + + return builder.toString(); + } + + /** + * Creates an instance by parsing the given query. + * + * @param query non-empty SQL query + * @return parameterized query + */ + public static ClickHouseParameterizedQuery of(String query) { + // cache if query.length() is greater than 1024? + return new ClickHouseParameterizedQuery(query); + } + + private final String originalQuery; + // 0 - from; 1 - to; 2 - parameter index(-1 means no parameter) + private final List parts; + private int[] lastPart; + private String[] names; + + private ClickHouseParameterizedQuery(String query) { + originalQuery = ClickHouseChecker.nonEmpty(query, "query"); + + parts = new LinkedList<>(); + lastPart = null; + names = new String[0]; + + parse(); + } + + private void parse() { + int paramIndex = 0; + int partIndex = 0; + Map params = new LinkedHashMap<>(); + int len = originalQuery.length(); + for (int i = 0; i < len; i++) { + char ch = originalQuery.charAt(i); + if (ClickHouseUtils.isQuote(ch)) { + i = ClickHouseUtils.skipQuotedString(originalQuery, i, len, ch) - 1; + } else if (i + 1 < len) { + char nextCh = originalQuery.charAt(i + 1); + if (ch == '-' && nextCh == ch) { + i = ClickHouseUtils.skipSingleLineComment(originalQuery, i + 2, len) - 1; + } else if (ch == '/' && nextCh == '*') { + i = ClickHouseUtils.skipMultiLineComment(originalQuery, i + 2, len) - 1; + } else if (ch == ':') { + if (nextCh == ch) { // skip PostgreSQL-like type conversion + i = i + 1; + } else if (Character.isJavaIdentifierStart(nextCh)) { + int[] part = new int[] { partIndex, i, -1 }; + parts.add(part); + StringBuilder builder = new StringBuilder().append(nextCh); + for (i = i + 2; i < len; i++) { + char c = originalQuery.charAt(i); + if (c == '(') { + i = ClickHouseUtils.skipBrackets(originalQuery, i, len, c); + String name = builder.toString(); + builder.setLength(0); + Integer existing = params.get(name); + if (existing == null) { + part[2] = paramIndex; + params.put(name, paramIndex++); + } else { + part[2] = existing.intValue(); + } + } else if (Character.isJavaIdentifierPart(c)) { + builder.append(c); + } else { + break; + } + } + + partIndex = i--; + + if (builder.length() > 0) { + String name = builder.toString(); + Integer existing = params.get(name); + if (existing == null) { + part[2] = paramIndex; + params.put(name, paramIndex++); + } else { + part[2] = existing.intValue(); + } + } + } + } + } + } + + names = new String[paramIndex]; + int index = 0; + for (String name : params.keySet()) { + names[index++] = name; + } + + if (partIndex < len) { + lastPart = new int[] { partIndex, len, -1 }; + } + } + + /** + * Applies stringified parameters to the query. + * + * @param params stringified parameters + * @return substituted query + */ + public String apply(Map params) { + if (!hasParameter()) { + return originalQuery; + } + + if (params == null) { + params = Collections.emptyMap(); + } + + StringBuilder builder = new StringBuilder(); + for (int[] part : parts) { + builder.append(originalQuery.substring(part[0], part[1])); + builder.append(params.getOrDefault(names[part[2]], ClickHouseValues.NULL_EXPR)); + } + + if (lastPart != null) { + builder.append(originalQuery.substring(lastPart[0], lastPart[1])); + } + return builder.toString(); + } + + /** + * Applies stringified parameters to the query. + * + * @param params stringified parameters + * @return substituted query + */ + public String apply(Collection params) { + if (!hasParameter()) { + return originalQuery; + } + + StringBuilder builder = new StringBuilder(); + Iterator it = params == null ? null : params.iterator(); + boolean hasMore = it != null && it.hasNext(); + for (int[] part : parts) { + builder.append(originalQuery.substring(part[0], part[1])); + builder.append(hasMore ? it.next() : ClickHouseValues.NULL_EXPR); + hasMore = hasMore && it.hasNext(); + } + + if (lastPart != null) { + builder.append(originalQuery.substring(lastPart[0], lastPart[1])); + } + return builder.toString(); + } + + /** + * Applies raw parameters to the query. + * {@link ClickHouseValues#convertToSqlExpression(Object)} will be used to + * stringify the parameters. + * + * @param param raw parameter + * @param more more raw parameters if any + * @return substituted query + */ + public String apply(Object param, Object... more) { + if (!hasParameter()) { + return originalQuery; + } + + int len = more == null ? 0 : more.length + 1; + StringBuilder builder = new StringBuilder(); + int index = 0; + for (int[] part : parts) { + builder.append(originalQuery.substring(part[0], part[1])); + if (index > 0) { + param = index < len ? more[index - 1] : null; + } + builder.append(ClickHouseValues.convertToSqlExpression(param)); + index++; + } + + if (lastPart != null) { + builder.append(originalQuery.substring(lastPart[0], lastPart[1])); + } + return builder.toString(); + } + + /** + * Applies raw parameters to the query. + * {@link ClickHouseValues#convertToSqlExpression(Object)} will be used to + * stringify the parameters. + * + * @param values raw parameters + * @return substituted query + */ + public String apply(Object[] values) { + if (!hasParameter()) { + return originalQuery; + } + + int len = values == null ? 0 : values.length; + StringBuilder builder = new StringBuilder(); + int index = 0; + for (int[] part : parts) { + builder.append(originalQuery.substring(part[0], part[1])); + builder.append( + index < len ? ClickHouseValues.convertToSqlExpression(values[index]) : ClickHouseValues.NULL_EXPR); + index++; + } + + if (lastPart != null) { + builder.append(originalQuery.substring(lastPart[0], lastPart[1])); + } + return builder.toString(); + } + + /** + * Applies stringified parameters to the query. + * + * @param param stringified parameter + * @param more more stringified parameters if any + * @return substituted query + */ + public String apply(String param, String... more) { + if (!hasParameter()) { + return originalQuery; + } + + int len = more == null ? 0 : more.length + 1; + StringBuilder builder = new StringBuilder(); + int index = 0; + for (int[] part : parts) { + builder.append(originalQuery.substring(part[0], part[1])); + if (index > 0) { + param = index < len ? more[index - 1] : ClickHouseValues.NULL_EXPR; + } + builder.append(param); + index++; + } + + if (lastPart != null) { + builder.append(originalQuery.substring(lastPart[0], lastPart[1])); + } + return builder.toString(); + } + + /** + * Applies stringified parameters to the query. + * + * @param values stringified parameters + * @return substituted query + */ + public String apply(String[] values) { + if (!hasParameter()) { + return originalQuery; + } + + int len = values == null ? 0 : values.length; + StringBuilder builder = new StringBuilder(); + int index = 0; + for (int[] part : parts) { + builder.append(originalQuery.substring(part[0], part[1])); + builder.append(index < len ? values[index] : ClickHouseValues.NULL_EXPR); + index++; + } + + if (lastPart != null) { + builder.append(originalQuery.substring(lastPart[0], lastPart[1])); + } + return builder.toString(); + } + + /** + * Gets named parameters. + * + * @return list of named parameters + */ + public List getNamedParameters() { + return names.length == 0 ? Collections.emptyList() : Arrays.asList(names); + } + + /** + * Gets original query. + * + * @return original query + */ + public String getOriginalQuery() { + return originalQuery; + } + + /** + * Gets query parts. Each part is composed of a snippet taken from + * {@link #getOriginalQuery()}, followed by a parameter name, which might be + * null. + * + * @return query parts + */ + public List getQueryParts() { + List queryParts = new ArrayList<>(parts.size() + 1); + for (int[] part : parts) { + queryParts.add(new String[] { originalQuery.substring(part[0], part[1]), names[part[2]] }); + } + + if (lastPart != null) { + queryParts.add(new String[] { originalQuery.substring(lastPart[0], lastPart[1]), null }); + } + + return queryParts; + } + + /** + * Checks if the query has at least one named parameter or not. + * + * @return true if there's at least one named parameter; false otherwise + */ + public boolean hasParameter() { + return names.length > 0; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseProtocol.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseProtocol.java new file mode 100644 index 000000000..63b3a4295 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseProtocol.java @@ -0,0 +1,50 @@ +package com.clickhouse.client; + +/** + * This defines protocols can be used to connect to ClickHouse. + */ +public enum ClickHouseProtocol { + /** + * Protocol detection is needed when establishing connection. + */ + ANY(8123), + /** + * HTTP/HTTPS. + */ + HTTP(8123), + /** + * Native TCP. + */ + NATIVE(9000), + /** + * MySQL interface. + */ + MYSQL(9004), + /** + * PostgreSQL interface. + */ + POSTGRESQL(9005), + /** + * Inter-server HTTP/HTTPS. + */ + INTERSERVER(9009), + /** + * GRPC interface. + */ + GRPC(9100); + + private final int defaultPort; + + ClickHouseProtocol(int defaultPort) { + this.defaultPort = defaultPort; + } + + /** + * Get default port used by the protocol. + * + * @return default port used by the protocol + */ + public int getDefaultPort() { + return this.defaultPort; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseRecord.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseRecord.java new file mode 100644 index 000000000..43325ee8e --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseRecord.java @@ -0,0 +1,69 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Iterator; + +/** + * This defines a record returned from ClickHouse server. Usually it's a row but + * sometimes it could a (nested) column, a (semi-)structured object, or even the + * whole data set. + */ +public interface ClickHouseRecord extends Iterable, Serializable { + /** + * Gets size of the record. + * + * @return size of the record + */ + int size(); + + /** + * Gets deserialized value wrapped in an object using column index. Please avoid + * to cache the wrapper object, as it's reused among records for memory + * efficiency when {@link ClickHouseConfig#isReuseValueWrapper()} returns + * {@code true}, which is the default value. So instead of + * {@code map.put("my_value", record.getValue(0))}, try something like + * {@code map.put("my_value", record.getValue(0).asString())}. + * + * @param index index of the column + * @return non-null wrapped value + * @throws IOException when failed get data from the input stream + */ + ClickHouseValue getValue(int index) throws IOException; + + /** + * Gets deserialized value wrapped in an object using column name, which usually + * is slower than {@link #getValue(int)}. Please avoid to cache the wrapper + * object, as it's reused among records for memory efficiency when + * {@link ClickHouseConfig#isReuseValueWrapper()} returns {@code true}, which is + * the default value. So instead of + * {@code map.put("my_value", record.getValue("my_column"))}, try something like + * {@code map.put("my_value", record.getValue("my_column").asString())}. + * + * @param name name of the column + * @return non-null wrapped value + * @throws IOException when failed get data from the input stream + */ + ClickHouseValue getValue(String name) throws IOException; + + @Override + default Iterator iterator() { + return new Iterator() { + int index = 0; + + @Override + public boolean hasNext() { + return index < size(); + } + + @Override + public ClickHouseValue next() { + try { + return getValue(index++); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + }; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseRequest.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseRequest.java new file mode 100644 index 000000000..ade618e6f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseRequest.java @@ -0,0 +1,1166 @@ +package com.clickhouse.client; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.Optional; +import java.util.function.Function; + +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.config.ClickHouseConfigOption; +import com.clickhouse.client.data.ClickHouseExternalTable; +import com.clickhouse.client.exception.ClickHouseException; + +/** + * Request object holding references to {@link ClickHouseClient}, + * {@link ClickHouseNode}, format, sql, options and settings etc. for execution. + */ +@SuppressWarnings("squid:S119") +public class ClickHouseRequest> implements Serializable { + private static final String TYPE_EXTERNAL_TABLE = "ExternalTable"; + + /** + * Mutation request. + */ + public static class Mutation extends ClickHouseRequest { + protected Mutation(ClickHouseRequest request, boolean sealed) { + super(request.getClient(), request.server, sealed); + + this.options.putAll(request.options); + this.settings.putAll(request.settings); + + this.namedParameters.putAll(request.namedParameters); + + this.queryId = request.queryId; + this.sessionId = request.sessionId; + } + + @Override + protected String getQuery() { + if (input != null && sql != null) { + return new StringBuilder().append(sql).append(" FORMAT ").append(getFormat().name()).toString(); + } + + return super.getQuery(); + } + + @Override + public Mutation format(ClickHouseFormat format) { + if (!ClickHouseChecker.nonNull(format, "format").supportsInput()) { + throw new IllegalArgumentException("Only input format is allowed for mutation."); + } + + return super.format(format); + } + + /** + * Loads data from given file which may or may not be compressed. + * + * @param file absolute or relative path of the file, file extension will be + * used to determine if it's compressed or not + * @return mutation request + */ + public Mutation data(String file) { + return data(file, ClickHouseCompression.fromFileName(file)); + } + + /** + * Loads compressed data from given file. + * + * @param file absolute or relative path of the file + * @param compression compression algorithm, {@link ClickHouseCompression#NONE} + * means no compression + * @return mutation request + */ + @SuppressWarnings("squid:S2095") + public Mutation data(String file, ClickHouseCompression compression) { + checkSealed(); + + FileInputStream fileInput = null; + + try { + fileInput = new FileInputStream(file); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException(e); + } + + if (compression != null && compression != ClickHouseCompression.NONE) { + // TODO create input stream + } else { + this.input = fileInput; + } + + return this; + } + + /** + * Loads data from input stream. + * + * @param input input stream + * @return mutation requets + */ + public Mutation data(InputStream input) { + checkSealed(); + + this.input = input; + + return this; + } + + /** + * Sends mutation requets for execution. Same as + * {@code client.execute(request.seal())}. + * + * @return future to get response + * @throws ClickHouseException when error occurred + */ + public CompletableFuture send() throws ClickHouseException { + return getClient().execute(isSealed() ? this : seal()); + } + + @Override + public Mutation table(String table, String queryId) { + checkSealed(); + + this.queryId = queryId; + + String sql = "INSERT INTO " + ClickHouseChecker.nonEmpty(table, "table"); + if (!sql.equals(this.sql)) { + this.sql = sql; + this.preparedQuery = null; + resetCache(); + } + + return this; + } + + @Override + public Mutation seal() { + Mutation req = this; + + if (!isSealed()) { + // no idea which node we'll connect to until now + req = new Mutation(this, true); + req.options.putAll(options); + req.settings.putAll(settings); + + req.namedParameters.putAll(namedParameters); + + req.input = input; + req.queryId = queryId; + req.sessionId = sessionId; + req.sql = sql; + + req.preparedQuery = preparedQuery; + } + + return req; + } + } + + private static final long serialVersionUID = 4990313525960702287L; + + private final boolean sealed; + + private transient ClickHouseClient client; + + protected final ClickHouseConfig clientConfig; + protected final Function server; + protected final transient List externalTables; + protected final Map options; + protected final Map settings; + + protected final Map namedParameters; + + protected transient InputStream input; + protected String queryId; + protected String sessionId; + protected String sql; + + // cache + protected transient ClickHouseConfig config; + protected transient ClickHouseParameterizedQuery preparedQuery; + protected transient List statements; + + @SuppressWarnings("unchecked") + protected ClickHouseRequest(ClickHouseClient client, Function server, + boolean sealed) { + if (client == null || server == null) { + throw new IllegalArgumentException("Non-null client and server are required"); + } + + this.client = client; + this.clientConfig = client.getConfig(); + this.server = (Function & Serializable) server::apply; + this.sealed = sealed; + + this.externalTables = new LinkedList<>(); + this.options = new HashMap<>(); + this.settings = new LinkedHashMap<>(); + + this.namedParameters = new HashMap<>(); + } + + protected void checkSealed() { + if (sealed) { + throw new IllegalStateException("Sealed request is immutable"); + } + } + + protected ClickHouseClient getClient() { + if (client == null) { + client = ClickHouseClient.builder().nodeSelector(clientConfig.getNodeSelector()).build(); + } + + return client; + } + + protected ClickHouseParameterizedQuery getPreparedQuery() { + if (preparedQuery == null) { + preparedQuery = ClickHouseParameterizedQuery.of(getQuery()); + } + + return preparedQuery; + } + + /** + * Gets query, either set by {@code query()} or {@code table()}. + * + * @return sql query + */ + protected String getQuery() { + return this.sql; + } + + protected void resetCache() { + if (config != null) { + config = null; + } + + if (statements != null) { + statements = null; + } + } + + /** + * Checks if the request is sealed(immutable). + * + * @return true if the request is sealed; false otherwise + */ + public boolean isSealed() { + return this.sealed; + } + + /** + * Checks if the request contains any input stream. + * + * @return true if there's input stream; false otherwise + */ + public boolean hasInputStream() { + return this.input != null || !this.externalTables.isEmpty(); + } + + /** + * Depending on the {@link java.util.function.Function} passed to the + * constructor, this method may return different node for each call. + * + * @return node defined by {@link java.util.function.Function} + */ + public final ClickHouseNode getServer() { + return this.server.apply(getConfig().getNodeSelector()); + } + + /** + * Gets merged configuration. + * + * @return merged configuration + */ + public ClickHouseConfig getConfig() { + if (config == null) { + if (options.size() == 0) { + config = clientConfig; + } else { + Map merged = new HashMap<>(); + merged.putAll(clientConfig.getAllOptions()); + merged.putAll(options); + config = new ClickHouseConfig(merged, clientConfig.getDefaultCredentials(), + clientConfig.getNodeSelector(), clientConfig.getMetricRegistry().orElse(null)); + } + } + + return config; + } + + /** + * Gets input stream. + * + * @return input stream + */ + public Optional getInputStream() { + return Optional.ofNullable(input); + } + + /** + * Gets compression used for communication between server and client. Same as + * {@code getConfig().getCompression()}. + * + * @return compression used for communication between server and client + */ + public ClickHouseCompression getCompression() { + return getConfig().getCompression(); + } + + /** + * Gets immutable list of external tables. + * + * @return immutable list of external tables + */ + public List getExternalTables() { + return Collections.unmodifiableList(externalTables); + } + + /** + * Gets data format used for communication between server and client. + * + * @return data format used for communication between server and client + */ + public ClickHouseFormat getFormat() { + return getConfig().getFormat(); + } + + /** + * Gets query id. + * + * @return query id + */ + public Optional getQueryId() { + return ClickHouseChecker.isNullOrEmpty(queryId) ? Optional.empty() : Optional.of(queryId); + } + + /** + * Gets immutable settings. + * + * @return immutable settings + */ + public Map getSettings() { + return Collections.unmodifiableMap(settings); + } + + /** + * Gets session id. + * + * @return session id + */ + public Optional getSessionId() { + return ClickHouseChecker.isNullOrEmpty(sessionId) ? Optional.empty() : Optional.of(sessionId); + } + + /** + * Gets list of SQL statements. Same as {@code getStatements(true)}. + * + * @return list of SQL statements + */ + public List getStatements() { + return getStatements(true); + } + + /** + * Gets list of SQL statements. + * + * @param withSettings true to treat settings as SQL statement; false otherwise + * @return list of SQL statements + */ + public List getStatements(boolean withSettings) { + if (statements == null) { + statements = new ArrayList<>(); + + if (withSettings) { + for (Entry entry : settings.entrySet()) { + Serializable value = entry.getValue(); + StringBuilder sb = new StringBuilder().append("SET ").append(entry.getKey()).append('='); + if (value instanceof String) { + sb.append('\'').append(value).append('\''); + } else if (value instanceof Boolean) { + sb.append((boolean) value ? 1 : 0); + } else { + sb.append(value); + } + statements.add(sb.toString()); + } + } + + String stmt = getQuery(); + if (!ClickHouseChecker.isNullOrEmpty(stmt)) { + statements.add(preparedQuery == null ? ClickHouseParameterizedQuery.apply(stmt, namedParameters) + : preparedQuery.apply(namedParameters)); + } + } + + return Collections.unmodifiableList(statements); + } + + /** + * Sets preferred compression algorithm to be used between server and client. + * + * @param compression compression used in transportation, null or + * {@link ClickHouseCompression#NONE} means no compression + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT compression(ClickHouseCompression compression) { + checkSealed(); + + if (compression == null) { + compression = ClickHouseCompression.NONE; + } + + Object oldValue = options.put(ClickHouseClientOption.COMPRESSION, compression.name()); + if (oldValue == null || !oldValue.equals(compression.name())) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Sets one or more external tables. + * + * @param table non-null external table + * @param more more external tables + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT external(ClickHouseExternalTable table, ClickHouseExternalTable... more) { + checkSealed(); + + externalTables.clear(); + externalTables.add(ClickHouseChecker.nonNull(table, TYPE_EXTERNAL_TABLE)); + if (more != null) { + for (ClickHouseExternalTable e : more) { + externalTables.add(ClickHouseChecker.nonNull(e, TYPE_EXTERNAL_TABLE)); + } + } + + return (SelfT) this; + } + + /** + * Sets external tables. + * + * @param tables non-null external tables + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT external(Collection tables) { + checkSealed(); + + externalTables.clear(); + if (tables != null) { + for (ClickHouseExternalTable e : tables) { + externalTables.add(ClickHouseChecker.nonNull(e, TYPE_EXTERNAL_TABLE)); + } + } + + return (SelfT) this; + } + + /** + * Sets format to be used for communication between server and client. + * + * @param format non-null format + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT format(ClickHouseFormat format) { + checkSealed(); + + Object oldValue = options.put(ClickHouseClientOption.FORMAT, + ClickHouseChecker.nonNull(format, "format").name()); + if (oldValue == null || !oldValue.equals(format.name())) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Sets an option. {@code option} is for configuring client's behaviour, while + * {@code setting} is for server. + * + * @param option option + * @param value value + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT option(ClickHouseConfigOption option, Serializable value) { + checkSealed(); + + Object oldValue = options.put(ClickHouseChecker.nonNull(option, "option"), + ClickHouseChecker.nonNull(value, "value")); + if (oldValue == null || !oldValue.equals(value)) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Sets stringified parameters. Be aware of SQL injection risk as mentioned in + * {@link #params(String, String...)}. + * + * @param values stringified parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(Collection values) { + checkSealed(); + + namedParameters.clear(); + + if (values != null && !values.isEmpty()) { + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + for (String v : values) { + namedParameters.put(names.get(index), v); + if (++index >= size) { + break; + } + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Sets parameters wrapped by {@link ClickHouseValue}. Safer but a bit slower + * than {@link #params(String, String...)}. Consider to reuse ClickHouseValue + * object and its update methods for less overhead in batch processing. + * + * @param value parameter + * @param more more parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(ClickHouseValue value, ClickHouseValue... more) { + checkSealed(); + + namedParameters.clear(); + + if (value != null) { // it doesn't make sense to pass null as first parameter + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + + namedParameters.put(names.get(index++), value.toSqlExpression()); + + if (more != null && more.length > 0) { + for (ClickHouseValue v : more) { + if (index >= size) { + break; + } + namedParameters.put(names.get(index++), + v != null ? v.toSqlExpression() : ClickHouseValues.NULL_EXPR); + } + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Sets parameters wrapped by {@link ClickHouseValue}. Safer but a bit slower + * than {@link #params(String, String...)}. Consider to reuse ClickHouseValue + * object and its update methods for less overhead in batch processing. + * + * @param values parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(ClickHouseValue[] values) { + checkSealed(); + + namedParameters.clear(); + + if (values != null && values.length > 0) { + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + for (ClickHouseValue v : values) { + namedParameters.put(names.get(index), v != null ? v.toSqlExpression() : ClickHouseValues.NULL_EXPR); + if (++index >= size) { + break; + } + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Sets stringified parameters which are used to substitude named parameters in + * SQL query without further transformation and validation. Keep in mind that + * stringified parameter is a SQL expression, meaning it could be a + * sub-query(SQL injection) in addition to value like number and string. + * + * @param value stringified parameter + * @param more more stringified parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(String value, String... more) { + checkSealed(); + + namedParameters.clear(); + + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + namedParameters.put(names.get(index++), value); + + if (more != null && more.length > 0) { + for (String v : more) { + if (index >= size) { + break; + } + namedParameters.put(names.get(index++), v); + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Sets stringified parameters which are used to substitude named parameters in + * SQL query without further transformation and validation. Keep in mind that + * stringified parameter is a SQL expression, meaning it could be a + * sub-query(SQL injection) in addition to value like number and string. + * + * @param values stringified parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(String[] values) { + checkSealed(); + + namedParameters.clear(); + + if (values != null && values.length > 0) { + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + for (String v : values) { + namedParameters.put(names.get(index), v); + if (++index >= size) { + break; + } + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Set raw parameters, which will later be stringified using + * {@link ClickHouseValues#convertToSqlExpression(Object)}. Although it is + * convenient to use, it's NOT recommended in most cases except for a few + * parameters and/or testing. + * + * @param value raw parameter + * @param more more raw parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(Object value, Object... more) { + checkSealed(); + + namedParameters.clear(); + + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + namedParameters.put(names.get(index++), ClickHouseValues.convertToSqlExpression(value)); + + if (more != null && more.length > 0) { + for (Object v : more) { + if (index >= size) { + break; + } + namedParameters.put(names.get(index++), ClickHouseValues.convertToSqlExpression(v)); + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Set raw parameters, which will later be stringified using + * {@link ClickHouseValues#convertToSqlExpression(Object)}. Although it is + * convenient to use, it's NOT recommended in most cases except for a few + * parameters and/or testing. + * + * @param values raw parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(Object[] values) { + checkSealed(); + + namedParameters.clear(); + + if (values != null && values.length > 0) { + List names = getPreparedQuery().getNamedParameters(); + int size = names.size(); + int index = 0; + for (Object v : values) { + namedParameters.put(names.get(index), ClickHouseValues.convertToSqlExpression(v)); + if (++index >= size) { + break; + } + } + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Sets named parameters. Be aware of SQL injection risk as mentioned in + * {@link #params(String, String...)}. + * + * @param namedParams named parameters + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT params(Map namedParams) { + checkSealed(); + + namedParameters.clear(); + + if (namedParams != null) { + namedParameters.putAll(namedParams); + } + + resetCache(); + + return (SelfT) this; + } + + /** + * Sets parameterized query. Same as {@code query(query, null)}. + * + * @param query non-null parameterized query + * @return the request itself + */ + public SelfT query(ClickHouseParameterizedQuery query) { + return query(query, null); + } + + /** + * Sets query. Same as {@code query(sql, null)}. + * + * @param sql non-empty query + * @return the request itself + */ + public SelfT query(String sql) { + return query(sql, null); + } + + /** + * Sets parameterized query and optinally query id. + * + * @param query non-null parameterized query + * @param queryId query id, null means no query id + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT query(ClickHouseParameterizedQuery query, String queryId) { + checkSealed(); + + if (!ClickHouseChecker.nonNull(query, "query").equals(this.preparedQuery)) { + this.preparedQuery = query; + this.sql = query.getOriginalQuery(); + resetCache(); + } + + this.queryId = queryId; + + return (SelfT) this; + } + + /** + * Sets query and optinally query id. + * + * @param sql non-empty query + * @param queryId query id, null means no query id + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT query(String sql, String queryId) { + checkSealed(); + + if (!ClickHouseChecker.nonEmpty(sql, "sql").equals(this.sql)) { + this.sql = sql; + this.preparedQuery = null; + resetCache(); + } + + this.queryId = queryId; + + return (SelfT) this; + } + + /** + * Clears session configuration including session id, whether to validate the id + * and session timeout. + * + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT clearSession() { + checkSealed(); + + boolean changed = this.sessionId != null; + this.sessionId = null; + + Object oldValue = null; + oldValue = options.remove(ClickHouseClientOption.SESSION_CHECK); + changed = changed || oldValue != null; + + oldValue = options.remove(ClickHouseClientOption.SESSION_TIMEOUT); + changed = changed || oldValue != null; + + if (changed) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Sets current session using custom id. Same as + * {@code session(sessionId, null, null)}. + * + * @param sessionId session id, null means no session + * @return the request itself + */ + public SelfT session(String sessionId) { + return session(sessionId, null, null); + } + + /** + * Sets session. Same as {@code session(sessionId, check, null)}. + * + * @param sessionId session id, null means no session + * @param check whether the server should check if the session id exists or + * not + * @return the request itself + */ + public SelfT session(String sessionId, Boolean check) { + return session(sessionId, check, null); + } + + /** + * Sets current session. Same as {@code session(sessionId, null, timeout)}. + * + * @param sessionId session id, null means no session + * @param timeout timeout in milliseconds + * @return the request itself + */ + public SelfT session(String sessionId, Integer timeout) { + return session(sessionId, null, timeout); + } + + /** + * Sets current session. + * + * @param sessionId session id, null means no session + * @param check whether the server should check if the session id exists or + * not + * @param timeout timeout in milliseconds + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT session(String sessionId, Boolean check, Integer timeout) { + checkSealed(); + + boolean changed = !Objects.equals(this.sessionId, sessionId); + this.sessionId = sessionId; + + Object oldValue = null; + if (check != null) { + oldValue = options.put(ClickHouseClientOption.SESSION_CHECK, check); + changed = oldValue == null || !oldValue.equals(check); + } + + if (timeout != null) { + oldValue = options.put(ClickHouseClientOption.SESSION_TIMEOUT, timeout); + changed = changed || oldValue == null || !oldValue.equals(timeout); + } + + if (changed) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Sets a setting. See + * https://clickhouse.tech/docs/en/operations/settings/settings/ for more + * information. + * + * @param setting non-empty setting to set + * @param value value of the setting + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT set(String setting, Serializable value) { + checkSealed(); + + Serializable oldValue = settings.put(ClickHouseChecker.nonEmpty(setting, "setting"), + ClickHouseChecker.nonNull(value, "value")); + if (oldValue == null || !oldValue.equals(value)) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Sets a setting. See + * https://clickhouse.tech/docs/en/operations/settings/settings/ for more + * information. + * + * @param setting non-empty setting to set + * @param value value of the setting + * @return the request itself + */ + public SelfT set(String setting, String value) { + checkSealed(); + + return set(setting, (Serializable) ClickHouseUtils.escape(value, '\'')); + } + + /** + * Sets target table. Same as {@code table(table, null)}. + * + * @param table non-empty table name + * @return the request itself + */ + public SelfT table(String table) { + return table(table, null); + } + + /** + * Sets target table and optionally query id. This will generate a query like + * {@code SELECT * FROM [table]} and override the one set by + * {@link #query(String, String)}. + * + * @param table non-empty table name + * @param queryId query id, null means no query id + * @return the request itself + */ + public SelfT table(String table, String queryId) { + return query("SELECT * FROM " + ClickHouseChecker.nonEmpty(table, "table"), queryId); + } + + /** + * Changes current database. + * + * @param database non-empty database name + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT use(String database) { + checkSealed(); + + Object oldValue = options.put(ClickHouseClientOption.DATABASE, + ClickHouseChecker.nonEmpty(database, "database")); + if (oldValue == null || !oldValue.equals(database)) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Removes an external table. + * + * @param external non-null external table + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT removeExternal(ClickHouseExternalTable external) { + checkSealed(); + + if (externalTables.remove(ClickHouseChecker.nonNull(external, "external"))) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Removes an external table by name. + * + * @param name non-empty external table name + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT removeExternal(String name) { + checkSealed(); + + if (!ClickHouseChecker.isNullOrEmpty(name)) { + boolean removed = false; + Iterator i = externalTables.iterator(); + while (i.hasNext()) { + ClickHouseExternalTable e = i.next(); + if (name.equals(e.getName())) { + i.remove(); + removed = true; + } + } + + if (removed) { + resetCache(); + } + } + + return (SelfT) this; + } + + /** + * Removes an option. + * + * @param option option to be removed + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT removeOption(ClickHouseConfigOption option) { + checkSealed(); + + if (options.remove(ClickHouseChecker.nonNull(option, "option")) != null) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Removes a setting. + * + * @param setting name of the setting + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT removeSetting(String setting) { + checkSealed(); + + if (settings.remove(ClickHouseChecker.nonEmpty(setting, "setting")) != null) { + resetCache(); + } + + return (SelfT) this; + } + + /** + * Resets the request to start all over. + * + * @return the request itself + */ + @SuppressWarnings("unchecked") + public SelfT reset() { + checkSealed(); + + this.externalTables.clear(); + this.options.clear(); + this.settings.clear(); + + this.namedParameters.clear(); + + this.input = null; + this.sql = null; + this.preparedQuery = null; + this.queryId = null; + this.sessionId = null; + + resetCache(); + + return (SelfT) this; + } + + /** + * Creates a sealed request, which is an immutable copy of the current request. + * + * @return sealed request, an immutable copy of the current request + */ + public ClickHouseRequest seal() { + ClickHouseRequest req = this; + + if (!isSealed()) { + // no idea which node we'll connect to until now + req = new ClickHouseRequest<>(client, getServer(), true); + req.externalTables.addAll(externalTables); + req.options.putAll(options); + req.settings.putAll(settings); + + req.namedParameters.putAll(namedParameters); + + req.input = input; + req.queryId = queryId; + req.sessionId = sessionId; + req.sql = sql; + req.preparedQuery = preparedQuery; + } + + return req; + } + + /** + * Creates a new request for mutation. + * + * @return request for mutation + */ + public Mutation write() { + checkSealed(); + + return new Mutation(this, false); + } + + /** + * Executes the request. Same as {@code client.execute(request.seal())}. + * + * @return future to get response + * @throws ClickHouseException when error occurred preparing for the execution + */ + public CompletableFuture execute() throws ClickHouseException { + return client.execute(isSealed() ? this : seal()); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseResponse.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseResponse.java new file mode 100644 index 000000000..8d5be411f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseResponse.java @@ -0,0 +1,247 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.exception.ClickHouseExceptionSpecifier; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; + +/** + * This represents server reponse. To get data returned from server, depending + * on actual needs, you have 3 options: + * + *

    + *
  • use {@link #records()} or {@link #recordStream()} to get deserialized + * records(usually rows), a record is composed of one or more values
  • + *
  • use {@link #values()} or {@link #valueStream()} to get deserialized + * values
  • + *
  • use {@link #getInputStream()} for custom processing like dumping results + * into a file
  • + *
+ */ +public class ClickHouseResponse implements AutoCloseable, Serializable { + private static final Logger log = LoggerFactory.getLogger(ClickHouseResponse.class); + + private static final long serialVersionUID = 2271296998310082447L; + + private static final ClickHouseResponseSummary emptySummary = new ClickHouseResponseSummary() { + }; + + protected static final List defaultTypes = Collections + .singletonList(ClickHouseColumn.of("results", "Nullable(String)")); + + protected final ClickHouseConfig config; + protected final ClickHouseNode server; + protected final InputStream input; + protected final ClickHouseDataProcessor processor; + protected final List columns; + protected final Throwable error; + + protected ClickHouseResponse(ClickHouseConfig config, ClickHouseNode server, Throwable error) + throws ClickHouseException { + this(config, server, null, null, null, error); + } + + protected ClickHouseResponse(ClickHouseConfig config, ClickHouseNode server, Map settings, + InputStream input, List columns, Throwable error) throws ClickHouseException { + try { + this.config = ClickHouseChecker.nonNull(config, "config"); + this.server = ClickHouseChecker.nonNull(server, "server"); + + if (error != null) { + this.processor = null; + this.input = null; + // response object may be constructed in a separate thread + this.error = error; + } else if (input == null) { + throw new IllegalArgumentException("input cannot be null when there's no error"); + } else { + this.input = input; + this.processor = ClickHouseDataStreamFactory.getInstance().getProcessor(config, input, null, settings, + columns); + + this.error = null; + } + + this.columns = columns != null ? columns + : (processor != null ? processor.getColumns() : Collections.emptyList()); + } catch (IOException | RuntimeException e) { // TODO and Error? + if (input != null) { + log.warn("Failed to instantiate response object, will try to close the given input stream"); + try { + input.close(); + } catch (IOException exp) { + log.warn("Failed to close given input stream", exp); + } + } + + throw ClickHouseExceptionSpecifier.specify(e, server.getHost(), server.getPort()); + } + } + + protected void throwErrorIfAny() throws ClickHouseException { + if (error == null) { + return; + } + + if (error instanceof ClickHouseException) { + throw (ClickHouseException) error; + } else { + throw ClickHouseExceptionSpecifier.specify(error, server.getHost(), server.getPort()); + } + } + + @Override + public void close() { + if (input != null) { + long skipped = 0L; + try { + skipped = input.skip(Long.MAX_VALUE); + log.debug("%d bytes skipped before closing input stream", skipped); + } catch (Exception e) { + // ignore + log.debug("%d bytes skipped before closing input stream", skipped, e); + } finally { + try { + input.close(); + } catch (Exception e) { + log.warn("Failed to close input stream", e); + } + } + } + } + + public boolean hasError() { + return error != null; + } + + public List getColumns() throws ClickHouseException { + throwErrorIfAny(); + + return columns; + } + + public ClickHouseFormat getFormat() throws ClickHouseException { + throwErrorIfAny(); + + return this.config.getFormat(); + } + + public ClickHouseNode getServer() { + return server; + } + + public ClickHouseResponseSummary getSummary() { + return emptySummary; + } + + /** + * This is the most memory-efficient way for you to handle data returned from + * ClickHouse. However, this also means additional work is required for + * deserialization, especially when using a binary format. + * + * @return input stream get raw data returned from server + * @throws ClickHouseException when failed to get input stream or read data + */ + public InputStream getInputStream() throws ClickHouseException { + throwErrorIfAny(); + + return input; + } + + /** + * Dump response into output stream. + * + * @param output output stream, which will remain open + * @throws ClickHouseException when error occurred dumping response and/or + * writing data into output stream + */ + public void dump(OutputStream output) throws ClickHouseException { + throwErrorIfAny(); + + ClickHouseChecker.nonNull(output, "output"); + + // TODO configurable buffer size + int size = 8192; + byte[] buffer = new byte[size]; + int counter = 0; + try { + while ((counter = input.read(buffer, 0, size)) >= 0) { + output.write(buffer, 0, counter); + } + } catch (IOException e) { + throw ClickHouseExceptionSpecifier.specify(e, server.getHost(), server.getPort()); + } + } + + public Iterable records() throws ClickHouseException { + throwErrorIfAny(); + + if (processor == null) { + throw new UnsupportedOperationException( + "No data processor available for deserialization, please use getInputStream instead"); + } + + return processor.records(); + } + + public Stream recordStream() throws ClickHouseException { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(records().iterator(), + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED), false); + } + + public Iterable values() throws ClickHouseException { + throwErrorIfAny(); + + if (processor == null) { + throw new UnsupportedOperationException( + "No data processor available for deserialization, please use getInputStream instead"); + } + + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + Iterator records = processor.records().iterator(); + ClickHouseRecord current = null; + int index = 0; + + @Override + public boolean hasNext() { + return records.hasNext() || (current != null && index < current.size()); + } + + @Override + public ClickHouseValue next() { + if (current == null || index == current.size()) { + current = records.next(); + index = 0; + } + + try { + return current.getValue(index++); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + }; + } + }; + } + + public Stream valueStream() throws ClickHouseException { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(values().iterator(), + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED), false); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseResponseSummary.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseResponseSummary.java new file mode 100644 index 000000000..8581ca9ac --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseResponseSummary.java @@ -0,0 +1,44 @@ +package com.clickhouse.client; + +import java.io.Serializable; + +/** + * Summary of ClickHouse response. + */ +public interface ClickHouseResponseSummary extends Serializable { + default long getAllocatedBytes() { + return 0L; + } + + default long getBlocks() { + return 0L; + } + + default long getRows() { + return 0L; + } + + default long getRowsBeforeLimit() { + return 0L; + } + + default long getReadBytes() { + return 0L; + } + + default long getReadRows() { + return 0L; + } + + default long getTotalRowsToRead() { + return 0L; + } + + default long getWriteBytes() { + return 0L; + } + + default long getWriteRows() { + return 0L; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSerializer.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSerializer.java new file mode 100644 index 000000000..75d1c3f8b --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSerializer.java @@ -0,0 +1,20 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Functional interface for serializtion. + */ +@FunctionalInterface +public interface ClickHouseSerializer { + /** + * Writes serialized value to output stream. + * + * @param value non-null value to be serialized + * @param column non-null type information + * @param output non-null output stream + * @throws IOException when failed to write data to output stream + */ + void serialize(T value, ClickHouseColumn column, OutputStream output) throws IOException; +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSslContextProvider.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSslContextProvider.java new file mode 100644 index 000000000..b95ff7bdb --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSslContextProvider.java @@ -0,0 +1,30 @@ +package com.clickhouse.client; + +import java.util.Optional; +import javax.net.ssl.SSLException; + +/** + * This interface defines how to build Netty SSL context based on given + * configuration and target server. + */ +public interface ClickHouseSslContextProvider { + /** + * Get non-null SSL context provider. + * + * @return non-null SSL context provider + */ + static ClickHouseSslContextProvider getProvider() { + return ClickHouseUtils.getService(ClickHouseSslContextProvider.class); + } + + /** + * Get SSL context. + * + * @param type of SSL context + * @param sslContextClass SSL context class + * @param config client config + * @return SSL context + * @throws SSLException when error occured getting SSL context + */ + Optional getSslContext(Class sslContextClass, ClickHouseConfig config) throws SSLException; +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseUtils.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseUtils.java new file mode 100644 index 000000000..17e2da6a2 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseUtils.java @@ -0,0 +1,654 @@ +package com.clickhouse.client; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +public final class ClickHouseUtils { + private static final String HOME_DIR; + + static { + HOME_DIR = System.getProperty("os.name").toLowerCase().contains("windows") + ? Paths.get(System.getenv("APPDATA"), "clickhouse").toFile().getAbsolutePath() + : Paths.get(System.getProperty("user.home"), ".clickhouse").toFile().getAbsolutePath(); + } + + public static final String VARIABLE_PREFIX = "{{"; + public static final String VARIABLE_SUFFIX = "}}"; + + public static String applyVariables(String template, UnaryOperator applyFunc) { + if (template == null) { + template = ""; + } + + if (applyFunc == null) { + return template; + } + + StringBuilder sb = new StringBuilder(); + + for (int i = 0, len = template.length(); i < len; i++) { + int index = template.indexOf(VARIABLE_PREFIX, i); + if (index != -1) { + sb.append(template.substring(i, index)); + + i = index; + index = template.indexOf(VARIABLE_SUFFIX, i); + + if (index != -1) { + String variable = template.substring(i + VARIABLE_PREFIX.length(), index).trim(); + String value = applyFunc.apply(variable); + if (value == null) { + i += VARIABLE_PREFIX.length() - 1; + sb.append(VARIABLE_PREFIX); + } else { + i = index + VARIABLE_SUFFIX.length() - 1; + sb.append(value); + } + } else { + sb.append(template.substring(i)); + break; + } + } else { + sb.append(template.substring(i)); + break; + } + } + + return sb.toString(); + } + + public static String applyVariables(String template, Map variables) { + return applyVariables(template, variables == null || variables.size() == 0 ? null : variables::get); + } + + private static T findFirstService(Class serviceInterface) { + ClickHouseChecker.nonNull(serviceInterface, "serviceInterface"); + + T service = null; + + for (T s : ServiceLoader.load(serviceInterface)) { + if (s != null) { + service = s; + break; + } + } + + return service; + } + + public static ExecutorService newThreadPool(String owner, int maxThreads, int maxRequests) { + BlockingQueue queue = maxRequests > 0 ? new ArrayBlockingQueue<>(maxRequests) + : new LinkedBlockingQueue<>(); + + return new ThreadPoolExecutor(1, maxThreads < 1 ? 1 : maxThreads, 0L, TimeUnit.MILLISECONDS, queue, + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, owner); + thread.setUncaughtExceptionHandler(null); + return thread; + } + }, new ThreadPoolExecutor.AbortPolicy()); + } + + public static boolean isCloseBracket(char ch) { + return ch == ')' || ch == ']' || ch == '}'; + } + + public static boolean isOpenBracket(char ch) { + return ch == '(' || ch == '[' || ch == '{'; + } + + public static boolean isQuote(char ch) { + return ch == '\'' || ch == '`' || ch == '"'; + } + + public static boolean isSeparator(char ch) { + return ch == ',' || ch == ';'; + } + + /** + * Escape quotes in given string. + * + * @param str string + * @param quote quote to escape + * @return escaped string + */ + public static String escape(String str, char quote) { + if (str == null) { + return str; + } + + int len = str.length(); + StringBuilder sb = new StringBuilder(len + 10); + + for (int i = 0; i < len; i++) { + char ch = str.charAt(i); + if (ch == quote || ch == '\\') { + sb.append('\\'); + } + sb.append(ch); + } + + return sb.toString(); + } + + /** + * Unescape quoted string. + * + * @param str quoted string + * @return unescaped string + */ + public static String unescape(String str) { + if (ClickHouseChecker.isNullOrEmpty(str)) { + return str; + } + + int len = str.length(); + char quote = str.charAt(0); + if (!isQuote(quote) || quote != str.charAt(len - 1)) { // not a quoted string + return str; + } + + StringBuilder sb = new StringBuilder(len = len - 1); + for (int i = 1; i < len; i++) { + char ch = str.charAt(i); + + if (++i >= len) { + sb.append(ch); + } else { + char nextChar = str.charAt(i); + if (ch == '\\' || (ch == quote && nextChar == quote)) { + sb.append(nextChar); + } else { + sb.append(ch); + i--; + } + } + } + + return sb.toString(); + } + + /** + * Wrapper of {@code String.format(Locale.ROOT, ...)}. + * + * @param template string to format + * @param args arguments used in substitution + * @return formatted string + */ + public static String format(String template, Object... args) { + return String.format(Locale.ROOT, template, args); + } + + public static char getCloseBracket(char openBracket) { + char closeBracket; + if (openBracket == '(') { + closeBracket = ')'; + } else if (openBracket == '[') { + closeBracket = ']'; + } else if (openBracket == '{') { + closeBracket = '}'; + } else { + throw new IllegalArgumentException("Unsupported bracket: " + openBracket); + } + + return closeBracket; + } + + public static T getService(Class serviceInterface) { + return getService(serviceInterface, null); + } + + /** + * Load service according to given interface using + * {@link java.util.ServiceLoader}, fallback to given default service or + * supplier function if not found. + * + * @param type of service + * @param serviceInterface non-null service interface + * @param defaultService optionally default service + * @return non-null service + */ + public static T getService(Class serviceInterface, T defaultService) { + T service = defaultService; + Exception error = null; + + // load custom implementation if any + try { + T s = findFirstService(serviceInterface); + if (s != null) { + service = s; + } + } catch (Exception t) { + error = t; + } + + if (service == null) { + throw new IllegalStateException(String.format("Failed to get service %s", serviceInterface.getName()), + error); + } + + return service; + } + + public static T getService(Class serviceInterface, Supplier supplier) { + T service = null; + Exception error = null; + + // load custom implementation if any + try { + service = findFirstService(serviceInterface); + } catch (Exception t) { + error = t; + } + + // and then try supplier function if no luck + if (service == null && supplier != null) { + try { + service = supplier.get(); + } catch (Exception t) { + // override the error + error = t; + } + } + + if (service == null) { + throw new IllegalStateException(String.format("Failed to get service %s", serviceInterface.getName()), + error); + } + + return service; + } + + /** + * Search file in current directory, home directory, and then classpath, Get + * input stream to read the given file. + * + * @param file path to the file + * @return input stream + * @throws FileNotFoundException when the file does not exists + */ + public static InputStream getFileInputStream(String file) throws FileNotFoundException { + Path path = Paths.get(ClickHouseChecker.nonEmpty(file, "file")); + + StringBuilder builder = new StringBuilder(); + InputStream in = null; + if (Files.exists(path)) { + builder.append(',').append(file); + in = new FileInputStream(path.toFile()); + } else if (!path.isAbsolute()) { + path = Paths.get(HOME_DIR, file); + + if (Files.exists(path)) { + builder.append(',').append(path.toString()); + in = new FileInputStream(path.toFile()); + } + } + + if (in == null) { + builder.append(',').append("classpath:").append(file); + in = Thread.currentThread().getContextClassLoader().getResourceAsStream(file); + } + + if (in == null) { + throw new FileNotFoundException(format("Could not open file from: %s", builder.deleteCharAt(0).toString())); + } + + return in; + } + + /** + * Get output stream for writing a file. Directories and file will be created if + * they do not exist. + * + * @param file path to the file + * @return output stream + * @throws IOException when failed to create directories and/or file + */ + public static OutputStream getFileOutputStream(String file) throws IOException { + Path path = Paths.get(ClickHouseChecker.nonEmpty(file, "file")); + + if (Files.notExists(path)) { + Files.createDirectories(path.getParent()); + Files.createFile(path); + } + + return new FileOutputStream(file, false); + } + + public static String getProperty(String key, Properties... props) { + return getProperty(key, null, props); + } + + public static String getProperty(String key, String defaultValue, Properties... props) { + String value = null; + + if (props != null) { + for (Properties p : props) { + value = p.getProperty(key); + if (value != null) { + break; + } + } + } + + if (value == null) { + value = System.getProperty(key); + } + + return value == null ? defaultValue : value; + } + + /** + * Skip brackets and content inside with consideration of nested brackets, + * quoted string and comments. + * + * @param args non-null string to scan + * @param startIndex start index, optionally index of the opening bracket + * @param len end index, usually length of the given string + * @param bracket opening bracket supported by {@link #isOpenBracket(char)} + * @return index next to matched closing bracket + * @throws IllegalArgumentException when missing closing bracket(s) + */ + public static int skipBrackets(String args, int startIndex, int len, char bracket) { + char closeBracket = getCloseBracket(bracket); + + Deque stack = new ArrayDeque<>(); + for (int i = startIndex + (startIndex < len && args.charAt(startIndex) == bracket ? 1 : 0); i < len; i++) { + char ch = args.charAt(i); + if (isQuote(ch)) { + i = skipQuotedString(args, i, len, ch) - 1; + } else if (isOpenBracket(ch)) { + stack.push(closeBracket); + closeBracket = getCloseBracket(ch); + } else if (ch == closeBracket) { + if (stack.isEmpty()) { + return i + 1; + } else { + closeBracket = stack.pop(); + } + } else if (i + 1 < len) { + char nextChar = args.charAt(i + 1); + if (ch == '-' && nextChar == '-') { + i = skipSingleLineComment(args, i + 2, len) - 1; + } else if (ch == '/' && nextChar == '*') { + i = skipMultiLineComment(args, i + 2, len) - 1; + } + } + } + + throw new IllegalArgumentException("Missing closing bracket(s): " + stack); + } + + /** + * Skip quoted string. + * + * @param args non-null string to scan + * @param startIndex start index, optionally start of the quoted string + * @param len end index, usually length of the given string + * @param quote quote supported by {@link #isQuote(char)} + * @return index next to the other matched quote + * @throws IllegalArgumentException when missing quote + */ + public static int skipQuotedString(String args, int startIndex, int len, char quote) { + for (int i = startIndex; i < len; i++) { + char ch = args.charAt(i); + if (ch == '\\') { + i++; + } else if (ch == quote && i > startIndex) { + if (++i < len && args.charAt(i) == quote) { + continue; + } else { + return i; + } + } + } + + throw new IllegalArgumentException("Missing quote: " + quote); + } + + /** + * Skip single line comment. + * + * @param args non-null string to scan + * @param startIndex start index, optionally start of the single line comment + * @param len end index, usually length of the given string + * @return index of start of next line, right after {@code \n} + */ + public static int skipSingleLineComment(String args, int startIndex, int len) { + int index = args.indexOf('\n', startIndex); + return index > startIndex ? index + 1 : len; + } + + /** + * Skip nested multi-line comment. + * + * @param args non-null string to scan + * @param startIndex start index, optionally start of the multi-line comment + * @param len end index, usually length of the given string + * @return index next to end of the outter most multi-line comment + * @throws IllegalArgumentException when multi-line comment is unclosed + */ + public static int skipMultiLineComment(String args, int startIndex, int len) { + int openIndex = args.indexOf("/*", startIndex); + int closeIndex = args.indexOf("*/", startIndex); + + if (closeIndex < startIndex) { + throw new IllegalArgumentException("Unclosed multi-line comment"); + } + + return openIndex < startIndex || openIndex > closeIndex ? closeIndex + 2 + : skipMultiLineComment(args, closeIndex + 2, len); + } + + /** + * Skip quoted string, comments, and brackets until seeing {@code endChar} or + * reaching end of the given string. + * + * @param args non-null string to scan + * @param startIndex start index + * @param len end index, usually length of the given string + * @param endChar skip characters until seeing this or reaching end of the + * string + * @return index of {@code endChar} or {@code len} + */ + public static int skipContentsUntil(String args, int startIndex, int len, char endChar) { + for (int i = startIndex; i < len; i++) { + char ch = args.charAt(i); + if (ch == endChar) { + return i + 1; + } else if (isQuote(ch)) { + i = skipQuotedString(args, i, len, ch) - 1; + } else if (isOpenBracket(ch)) { + i = skipBrackets(args, i, len, ch) - 1; + } else if (i + 1 < len) { + char nextCh = args.charAt(i + 1); + if (ch == '-' && nextCh == '-') { + i = skipSingleLineComment(args, i + 2, len) - 1; + } else if (ch == '/' && nextCh == '*') { + i = skipMultiLineComment(args, i + 2, len) - 1; + } + } + } + + return len; + } + + public static int readNameOrQuotedString(String args, int startIndex, int len, StringBuilder builder) { + char quote = '\0'; + for (int i = startIndex; i < len; i++) { + char ch = args.charAt(i); + if (ch == '\\') { + if (++i < len) { + builder.append(args.charAt(i)); + } + continue; + } else if (isQuote(ch)) { + if (ch == quote) { + if (i + 1 < len && args.charAt(i + 1) == ch) { + builder.append(ch); + i++; + continue; + } + len = i + 1; + break; + } else if (quote == '\0') { + quote = ch; + } else { + builder.append(ch); + } + } else if (quote == '\0' + && (Character.isWhitespace(ch) || isOpenBracket(ch) || isCloseBracket(ch) || isSeparator(ch))) { + if (builder.length() > 0) { + len = i; + break; + } + } else { + builder.append(ch); + } + } + + return len; + } + + public static int readEnumValues(String args, int startIndex, int len, Map values) { + String name = null; + StringBuilder builder = new StringBuilder(); + for (int i = startIndex; i < len; i++) { + char ch = args.charAt(i); + if (Character.isWhitespace(ch)) { + continue; + } else if (ch == '\'') { + i = readNameOrQuotedString(args, i, len, builder); + name = builder.toString(); + builder.setLength(0); + + int index = args.indexOf('=', i); + if (index >= i) { + for (i = index + 1; i < len; i++) { + ch = args.charAt(i); + if (Character.isWhitespace(ch)) { + continue; + } else if (ch >= '0' && ch <= '9') { + builder.append(ch); + } else if (ch == ',') { + values.put(name, Integer.parseInt(builder.toString())); + builder.setLength(0); + name = null; + break; + } else if (ch == ')') { + values.put(name, Integer.parseInt(builder.toString())); + return i + 1; + } else { + throw new IllegalArgumentException("Invalid character when reading enum"); + } + } + + continue; + } else { + throw new IllegalArgumentException("Expect = after enum value but not found"); + } + } else { + throw new IllegalArgumentException("Invalid enum declaration"); + } + } + + return len; + } + + public static int readParameters(String args, int startIndex, int len, List params) { + char closeBracket = ')'; // startIndex points to the openning bracket + Deque stack = new ArrayDeque<>(); + StringBuilder builder = new StringBuilder(); + for (int i = startIndex; i < len; i++) { + char ch = args.charAt(i); + if ((i == startIndex && ch == '(') || Character.isWhitespace(ch)) { + continue; + } else if (isQuote(ch)) { + builder.append(ch); + for (int j = i + 1; j < len; j++) { + char c = args.charAt(j); + i = j; + builder.append(c); + if (c == ch && args.charAt(j - 1) != '\\') { + if (j + 1 < len && args.charAt(j + 1) == ch) { + builder.append(ch); + i = ++j; + } else { + break; + } + } + } + } else if (isOpenBracket(ch)) { + builder.append(ch); + stack.push(closeBracket); + closeBracket = getCloseBracket(ch); + } else if (ch == closeBracket) { + if (stack.isEmpty()) { + len = i + 1; + break; + } else { + builder.append(ch); + closeBracket = stack.pop(); + } + } else if (ch == ',') { + if (!stack.isEmpty()) { + builder.append(ch); + } else { + params.add(builder.toString()); + builder.setLength(0); + } + } else { + builder.append(ch); + } + } + + if (builder.length() > 0) { + params.add(builder.toString()); + } + + return len; + } + + @SuppressWarnings("unchecked") + protected static T[] toArray(Class clazz, Collection list) { + int size = list == null ? 0 : list.size(); + T[] array = (T[]) Array.newInstance(clazz, size); + if (size > 0) { + int i = 0; + for (T t : list) { + array[i++] = t; + } + } + return array; + } + + private ClickHouseUtils() { + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java new file mode 100644 index 000000000..db7ba798a --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java @@ -0,0 +1,944 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Wrapper of a value returned from ClickHouse. It could be as simple as one + * single byte or in a complex structure like nested arrays. It also provides + * convenient methods for type conversion. + */ +public interface ClickHouseValue extends Serializable { + /** + * Create a customized exception for unsupported type conversion. + * + * @param from type to convert from + * @param to type to convert to + * @return customized exception + */ + default UnsupportedOperationException newUnsupportedException(String from, String to) { + return new UnsupportedOperationException( + ClickHouseUtils.format("Converting %s to %s is not supported", from, to)); + } + + /** + * Gets a shallow copy of this value object. Same as {@code copy(false)}. + * + * @return shallow copy of this value object + */ + default ClickHouseValue copy() { + return copy(false); + } + + /** + * Gets a copy of this value object. + * + * @param deep true to create a deep copy; false for a shallow copy + * @return copy of this value object + */ + ClickHouseValue copy(boolean deep); + + /** + * Checks if the value is either positive or negative infinity as defined in + * {@link Double}. + * + * @return true if it's infinity; false otherwise + */ + default boolean isInfinity() { + double value = asDouble(); + return value == Double.NEGATIVE_INFINITY || value == Double.POSITIVE_INFINITY; + } + + /** + * Checks if the value is Not-a-Number (NaN). + * + * @return true if the value is NaN; false otherwise + */ + default boolean isNaN() { + double v = asDouble(); + return v != v; + } + + /** + * Checks if the value is null, or empty for non-null types like Array, Tuple + * and Map. + * + *

+ * Please pay attention that only nullability will be considered for String, + * meaning this method will return {@code false} for an empty string. This is + * because String is treated as value-based type instead of a container like + * Array. + * + * @return true if the value is null or empty; false otherwise + */ + boolean isNullOrEmpty(); + + /** + * Gets value as an object array. + * + * @return non-null object array + */ + default Object[] asArray() { + if (isNullOrEmpty()) { + return ClickHouseValues.EMPTY_ARRAY; + } + + return new Object[] { asObject() }; + } + + /** + * Gets value as an array. + * + * @param type of the element + * @param clazz class of the element + * @return non-null array + */ + @SuppressWarnings("unchecked") + default T[] asArray(Class clazz) { + if (isNullOrEmpty()) { + return (T[]) ClickHouseValues.EMPTY_ARRAY; + } + + T[] array = (T[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), 1); + array[0] = asObject(clazz); + return array; + } + + /** + * Gets value as boolean. + * + * @return boolean value + */ + default boolean asBoolean() { + return !isNullOrEmpty() && ClickHouseChecker.between(asByte(), 0, 1) == 1; + } + + /** + * Gets value as character. + * + * @return character value + */ + default char asCharacter() { + return (char) asShort(); + } + + /** + * Gets value as byte. + * + * @return byte value + */ + byte asByte(); + + // not a good idea as this may confuse people, use asString(byteLength) instead + // byte[] asBytes() + + /** + * Gets value as short. + * + * @return short value + */ + short asShort(); + + /** + * Gets value as integer. + * + * @return integer value + */ + int asInteger(); + + /** + * Gets value as long. + * + * @return long value + */ + long asLong(); + + /** + * Gets value as {@link java.math.BigInteger}. + * + * @return big integer, could be null + */ + BigInteger asBigInteger(); + + /** + * Gets value as float. + * + * @return float value + */ + float asFloat(); + + /** + * Gets value as double. + * + * @return double value + */ + double asDouble(); + + /** + * Gets value as {@link java.math.BigDecimal}, using default scale(usually 0). + * + * @return big decimal, could be null + */ + default BigDecimal asBigDecimal() { + return asBigDecimal(0); + } + + /** + * Gets value as {@link java.math.BigDecimal}. + * + * @param scale scale of the decimal + * @return big decimal, could be null + */ + BigDecimal asBigDecimal(int scale); + + /** + * Gets value as {@link java.time.LocalDate}. + * + * @return date, could be null + */ + default LocalDate asDate() { + return isNullOrEmpty() ? null : LocalDate.ofEpochDay(asLong()); + } + + /** + * Gets value as {@link java.time.LocalTime}. + * + * @return time, could be null + */ + default LocalTime asTime() { + return isNullOrEmpty() ? null : LocalTime.ofSecondOfDay(asLong()); + } + + /** + * Gets value as {@link java.time.LocalDateTime}, using default scale(usually + * 0). + * + * @return date time, could be null + */ + default LocalDateTime asDateTime() { + return asDateTime(0); + } + + /** + * Gets value as {@link java.time.LocalDateTime}. + * + * @param scale scale of the date time, between 0 (second) and 9 (nano second) + * @return date time, could be null + */ + default LocalDateTime asDateTime(int scale) { + return isNullOrEmpty() ? null + : ClickHouseValues.convertToDateTime( + asBigDecimal(ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9))); + } + + /** + * Gets value as enum. + * + * @param type of the enum + * @param enumType enum class + * @return enum, could be null + */ + default > T asEnum(Class enumType) { + if (isNullOrEmpty()) { + return null; + } + + int value = asInteger(); + for (T t : ClickHouseChecker.nonNull(enumType, ClickHouseValues.TYPE_CLASS).getEnumConstants()) { + if (t.ordinal() == value) { + return t; + } + } + + throw new IllegalArgumentException( + ClickHouseUtils.format("Ordinal[%d] not found in %s", value, enumType.getName())); + } + + /** + * Gets value as {@link java.net.Inet4Address}. + * + * @return IPv4 address, could be null + */ + default Inet4Address asInet4Address() { + if (isNullOrEmpty()) { + return null; + } + + return ClickHouseValues.convertToIpv4(asInteger()); + } + + /** + * Gets value as {@link java.net.Inet6Address}. + * + * @return IPv6 address, could be null + */ + default Inet6Address asInet6Address() { + if (isNullOrEmpty()) { + return null; + } + + return ClickHouseValues.convertToIpv6(asBigInteger()); + } + + /** + * Gets value as a map. + * + * @return non-null map value + */ + default Map asMap() { + if (isNullOrEmpty()) { + return Collections.emptyMap(); + } + + Map map = new LinkedHashMap<>(); + int index = 1; + for (Object v : asArray()) { + map.put(index++, v); + } + + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + /** + * Gets value as a map. + * + * @param type of key + * @param type of value + * @param keyClass non-null class of key + * @param valueClass non-null class of value + * @return non-null map value + */ + default Map asMap(Class keyClass, Class valueClass) { + if (isNullOrEmpty()) { + return Collections.emptyMap(); + } + + ClickHouseChecker.nonNull(keyClass, "keyClass"); + Map map = new LinkedHashMap<>(); + int index = 1; + for (V v : asArray(valueClass)) { + map.put(keyClass.cast(index++), v); + } + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + /** + * Gets value as an object. + * + * @return an object representing the value, could be null + */ + Object asObject(); + + /** + * Gets value as a typed object. + * + * @param type of the object + * @param clazz class of the object + * @return a typed object representing the value, could be null + */ + default T asObject(Class clazz) { + return isNullOrEmpty() ? null : ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS).cast(asObject()); + } + + /** + * Gets value as unbounded string, using default charset(usually UTF-8). + * + * @return string value, could be null + */ + default String asString() { + return asString(0, null); + } + + /** + * Gets value as fixed length(in bytes) string, using default charset(usually + * UTF-8). + * + * @param length byte length of the string, 0 or negative number means unbounded + * @return string value, could be null + */ + default String asString(int length) { + return asString(length, null); + } + + /** + * Gets value as unbounded string. + * + * @param charset charset, null is same as default(UTF-8) + * @return string value, could be null + */ + default String asString(Charset charset) { + return asString(0, charset); + } + + /** + * Gets value as fixed length(in bytes) string. + * + * @param length byte length of the string, 0 or negative number means + * unbounded + * @param charset charset, null is same as default(UTF-8) + * @return string value, could be null + */ + default String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = String.valueOf(asObject()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + /** + * Gets value as ordered list(tuple). + * + * @return non-null list + */ + default List asTuple() { + return Arrays.asList(asArray()); + } + + /** + * Gets value as UUID. + * + * @return uuid, could be null + */ + default UUID asUuid() { + if (isNullOrEmpty()) { + return null; + } + + return ClickHouseValues.convertToUuid(asBigInteger()); + } + + /** + * Resets value to null, or empty when null is not supported(e.g. Array, Tuple + * and Map etc.). + * + *

+ * Keep in mind that String is value-based type, so this method will change its + * value to null instead of an empty string. + * + * @return this object + */ + ClickHouseValue resetToNullOrEmpty(); + + /** + * Convert the value to escaped SQL expression. For example, number 123 will be + * converted to {@code 123}, while string "12'3" will be converted to @{code + * '12\'3'}. + * + * @return escaped SQL expression + */ + String toSqlExpression(); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(boolean value) { + return update(value ? (byte) 1 : (byte) 0); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(boolean[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(char value) { + return update((int) value); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(char[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(byte value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(byte[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(short value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(short[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(int value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(int[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(long value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(long[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(float value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(float[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(double value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(double[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(BigInteger value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(BigDecimal value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : update(value.ordinal()); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Inet4Address value) { + return value == null ? resetToNullOrEmpty() : update(new BigInteger(1, value.getAddress())); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Inet6Address value) { + return value == null ? resetToNullOrEmpty() : update(new BigInteger(1, value.getAddress())); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(LocalDate value) { + return value == null ? resetToNullOrEmpty() : update(value.toEpochDay()); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(LocalTime value) { + return value == null ? resetToNullOrEmpty() : update(value.toSecondOfDay()); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(LocalDateTime value) { + if (value == null) { + return resetToNullOrEmpty(); + } + + long seconds = value.toEpochSecond(ZoneOffset.UTC); + int nanos = value.getNano(); + return nanos > 0 + ? update(BigDecimal.valueOf(seconds).add(BigDecimal.valueOf(nanos, 9).divide(ClickHouseValues.NANOS))) + : update(seconds); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Collection value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + return resetToNullOrEmpty(); + } else if (size != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_COLLECTION + value); + } + + return update(value.iterator().next()); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Enumeration value) { + if (value == null || !value.hasMoreElements()) { + return resetToNullOrEmpty(); + } + + Object v = value.nextElement(); + if (value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_COLLECTION + value); + } + + return update(v); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Map value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + return resetToNullOrEmpty(); + } else if (size != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_MAP + value); + } + + return update(value.values().iterator().next()); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(String value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(UUID value) { + if (value == null) { + return resetToNullOrEmpty(); + } + + BigInteger high = BigInteger.valueOf(value.getMostSignificantBits()); + BigInteger low = BigInteger.valueOf(value.getLeastSignificantBits()); + + if (high.signum() < 0) { + high = high.add(ClickHouseValues.BIGINT_HL_BOUNDARY); + } + if (low.signum() < 0) { + low = low.add(ClickHouseValues.BIGINT_HL_BOUNDARY); + } + + return update(low.add(high.multiply(ClickHouseValues.BIGINT_HL_BOUNDARY))); + } + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + ClickHouseValue update(ClickHouseValue value); + + /** + * Updates value. + * + * @param value value to update + * @return this object + */ + default ClickHouseValue update(Object[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } else if (value.length != 1) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_SINGLETON_ARRAY + Arrays.toString(value)); + } + + return update(value[0]); + } + + /** + * Updates value when the type is not supported. This method will be called at + * the end of {@link #update(Object)} after trying all known classes. By + * default, it's same as {@code update(String.valueOf(value))}. + * + *

+ * Due to limitationPlease avoid to call {@link #update(Object)} here or + * + * @param value value to update + * @return this object + */ + default ClickHouseValue updateUnknown(Object value) { + if (value == null) { + return resetToNullOrEmpty(); + } + + return update(String.valueOf(value)); + } + + /** + * Updates value. This method tries to identify type of {@code value} and then + * use corresponding update method to proceed. Unknown value will be passed to + * {@link #updateUnknown(Object)}. + * + * @param value value to update, could be null + * @return this object + */ + @SuppressWarnings("squid:S3776") + default ClickHouseValue update(Object value) { + if (value == null) { + return resetToNullOrEmpty(); + } else if (value instanceof Boolean) { + return update((boolean) value); + } else if (value instanceof boolean[]) { + return update((boolean[]) value); + } else if (value instanceof Character) { + return update((char) value); + } else if (value instanceof char[]) { + return update((char[]) value); + } else if (value instanceof Byte) { + return update((byte) value); + } else if (value instanceof byte[]) { + return update((byte[]) value); + } else if (value instanceof Short) { + return update((short) value); + } else if (value instanceof short[]) { + return update((short[]) value); + } else if (value instanceof Integer) { + return update((int) value); + } else if (value instanceof int[]) { + return update((int[]) value); + } else if (value instanceof Long) { + return update((long) value); + } else if (value instanceof long[]) { + return update((long[]) value); + } else if (value instanceof Float) { + return update((float) value); + } else if (value instanceof float[]) { + return update((float[]) value); + } else if (value instanceof Double) { + return update((double) value); + } else if (value instanceof double[]) { + return update((double[]) value); + } else if (value instanceof BigDecimal) { + return update((BigDecimal) value); + } else if (value instanceof BigInteger) { + return update((BigInteger) value); + } else if (value instanceof Enum) { + return update((Enum) value); + } else if (value instanceof Inet4Address) { + return update((Inet4Address) value); + } else if (value instanceof Inet6Address) { + return update((Inet6Address) value); + } else if (value instanceof LocalDate) { + return update((LocalDate) value); + } else if (value instanceof LocalTime) { + return update((LocalTime) value); + } else if (value instanceof LocalDateTime) { + return update((LocalDateTime) value); + } else if (value instanceof Collection) { + return update((Collection) value); + } else if (value instanceof Enumeration) { + return update((Enumeration) value); + } else if (value instanceof Map) { + return update((Map) value); + } else if (value instanceof Object[]) { + return update((Object[]) value); + } else if (value instanceof UUID) { + return update((UUID) value); + } else if (value instanceof String) { + return update((String) value); + } else if (value instanceof ClickHouseValue) { + return update((ClickHouseValue) value); + } else { + return updateUnknown(value); + } + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java new file mode 100644 index 000000000..919475a8d --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java @@ -0,0 +1,862 @@ +package com.clickhouse.client; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; + +import com.clickhouse.client.data.ClickHouseArrayValue; +import com.clickhouse.client.data.ClickHouseBigDecimalValue; +import com.clickhouse.client.data.ClickHouseBigIntegerValue; +import com.clickhouse.client.data.ClickHouseByteValue; +import com.clickhouse.client.data.ClickHouseDateTimeValue; +import com.clickhouse.client.data.ClickHouseDateValue; +import com.clickhouse.client.data.ClickHouseDoubleValue; +import com.clickhouse.client.data.ClickHouseFloatValue; +import com.clickhouse.client.data.ClickHouseGeoMultiPolygonValue; +import com.clickhouse.client.data.ClickHouseGeoPointValue; +import com.clickhouse.client.data.ClickHouseGeoPolygonValue; +import com.clickhouse.client.data.ClickHouseGeoRingValue; +import com.clickhouse.client.data.ClickHouseIntegerValue; +import com.clickhouse.client.data.ClickHouseIpv4Value; +import com.clickhouse.client.data.ClickHouseIpv6Value; +import com.clickhouse.client.data.ClickHouseLongValue; +import com.clickhouse.client.data.ClickHouseMapValue; +import com.clickhouse.client.data.ClickHouseNestedValue; +import com.clickhouse.client.data.ClickHouseShortValue; +import com.clickhouse.client.data.ClickHouseStringValue; +import com.clickhouse.client.data.ClickHouseTupleValue; +import com.clickhouse.client.data.ClickHouseUuidValue; + +/** + * Help class for dealing with values. + */ +public final class ClickHouseValues { + public static final BigInteger BIGINT_HL_BOUNDARY = BigInteger.ONE.shiftLeft(64); // 2^64 + public static final BigInteger BIGINT_SL_BOUNDARY = BigInteger.valueOf(Long.MAX_VALUE); + + public static final LocalDate DATE_ZERO = LocalDate.ofEpochDay(0L); + public static final LocalDateTime DATETIME_ZERO = LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC); + public static final LocalTime TIME_ZERO = LocalTime.ofSecondOfDay(0L); + + public static final Object[] EMPTY_ARRAY = new Object[0]; + public static final String EMPTY_ARRAY_EXPR = "[]"; + + public static final BigDecimal NANOS = new BigDecimal(BigInteger.TEN.pow(9)); + + public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern("HH:mm:ss") + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter(); + public static final DateTimeFormatter DATETIME_FORMATTER = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss").appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter(); + + public static final String NULL_EXPR = "NULL"; + public static final String NAN_EXPR = "NaN"; + public static final String INF_EXPR = "Inf"; + public static final String NINF_EXPR = "-Inf"; + + public static final String ERROR_INVALID_POINT = "A point should have two and only two double values, but we got: "; + public static final String ERROR_SINGLETON_ARRAY = "Only singleton array is allowed, but we got: "; + public static final String ERROR_SINGLETON_COLLECTION = "Only singleton collection is allowed, but we got: "; + public static final String ERROR_SINGLETON_MAP = "Only singleton map is allowed, but we got: "; + + public static final String PARAM_PRECISION = "precision"; + public static final String PARAM_SCALE = "scale"; + + public static final String TYPE_BOOLEAN = "boolean"; + public static final String TYPE_CHAR = "char"; + public static final String TYPE_BYTE = "byte"; + public static final String TYPE_SHORT = "short"; + public static final String TYPE_INT = "int"; + public static final String TYPE_LONG = "long"; + public static final String TYPE_FLOAT = "float"; + public static final String TYPE_DOUBLE = "double"; + public static final String TYPE_BIG_DECIMAL = "BigDecimal"; + public static final String TYPE_BIG_INTEGER = "BigInteger"; + public static final String TYPE_DATE = "Date"; + public static final String TYPE_TIME = "Time"; + public static final String TYPE_DATE_TIME = "DateTime"; + public static final String TYPE_ENUM = "Enum"; + public static final String TYPE_IPV4 = "Inet4Address"; + public static final String TYPE_IPV6 = "Inet6Address"; + public static final String TYPE_STRING = "String"; + public static final String TYPE_UUID = "UUID"; + public static final String TYPE_OBJECT = "Object"; + public static final String TYPE_ARRAY = "Array"; + public static final String TYPE_MAP = "Map"; + public static final String TYPE_NESTED = "Nested"; + public static final String TYPE_TUPLE = "Tuple"; + public static final String TYPE_POINT = "Point"; + public static final String TYPE_RING = "Ring"; + public static final String TYPE_POLYGON = "Polygon"; + public static final String TYPE_MULTI_POLYGON = "MultiPolygon"; + + public static final String TYPE_CLASS = "Class"; + + /** + * Converts IP address to big integer. + * + * @param value IP address + * @return big integer + */ + public static BigInteger convertToBigInteger(Inet4Address value) { + return value == null ? null : new BigInteger(1, value.getAddress()); + } + + /** + * Converts IP address to big integer. + * + * @param value IP address + * @return big integer + */ + + public static BigInteger convertToBigInteger(Inet6Address value) { + return value == null ? null : new BigInteger(1, value.getAddress()); + } + + /** + * Converts UUID to big integer. + * + * @param value UUID + * @return big integer + */ + + public static BigInteger convertToBigInteger(UUID value) { + if (value == null) { + return null; + } + + BigInteger high = BigInteger.valueOf(value.getMostSignificantBits()); + BigInteger low = BigInteger.valueOf(value.getLeastSignificantBits()); + + if (high.signum() < 0) { + high = high.add(BIGINT_HL_BOUNDARY); + } + if (low.signum() < 0) { + low = low.add(BIGINT_HL_BOUNDARY); + } + + return low.add(high.multiply(BIGINT_HL_BOUNDARY)); + } + + /** + * Converts big decimal to date time. + * + * @param value big decimal + * @return date time + */ + public static LocalDateTime convertToDateTime(BigDecimal value) { + if (value == null) { + return null; + } else if (value.scale() == 0) { + return LocalDateTime.ofEpochSecond(value.longValue(), 0, ZoneOffset.UTC); + } else if (value.signum() >= 0) { + return LocalDateTime.ofEpochSecond(value.longValue(), + value.remainder(BigDecimal.ONE).multiply(NANOS).intValue(), ZoneOffset.UTC); + } + + long v = NANOS.add(value.remainder(BigDecimal.ONE).multiply(NANOS)).longValue(); + int nanoSeconds = v < 1000000000L ? (int) v : 0; + + return LocalDateTime.ofEpochSecond(value.longValue() - (nanoSeconds > 0 ? 1 : 0), nanoSeconds, ZoneOffset.UTC); + } + + /** + * Converts IPv6 address to IPv4 address if applicable. + * + * @param value IPv6 address + * @return IPv4 address + * @throws IllegalArgumentException when failed to convert to IPv4 address + */ + public static Inet4Address convertToIpv4(Inet6Address value) { + if (value == null) { + return null; + } + + byte[] bytes = value.getAddress(); + boolean invalid = false; + for (int i = 0; i < 10; i++) { + if (bytes[i] != (byte) 0) { + invalid = true; + break; + } + } + + if (!invalid) { + invalid = bytes[10] != 0xFF || bytes[11] != 0xFF; + } + + if (invalid) { + throw new IllegalArgumentException("Failed to convert IPv6 to IPv4"); + } + + byte[] addr = new byte[4]; + System.arraycopy(bytes, 12, addr, 0, 4); + try { + return (Inet4Address) InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Converts integer to IPv4 address. + * + * @param value integer + * @return IPv4 address + */ + public static Inet4Address convertToIpv4(int value) { + byte[] bytes = new byte[] { (byte) ((value >> 24) & 0xFF), (byte) ((value >> 16) & 0xFF), + (byte) ((value >> 8) & 0xFF), (byte) (value & 0xFF) }; + + try { + return (Inet4Address) InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Converts string to IPv4 address. + * + * @param value string + * @return IPv4 address + * @throws IllegalArgumentException when failed to convert to IPv4 address + */ + public static Inet4Address convertToIpv4(String value) { + if (value == null) { + return null; + } + + try { + for (InetAddress addr : InetAddress.getAllByName(value)) { + if (addr instanceof Inet4Address) { + return (Inet4Address) addr; + } + } + } catch (UnknownHostException e) { + throw new IllegalArgumentException(ClickHouseUtils.format("Failed to convert [%s] to Inet4Address", value), + e); + } + + throw new IllegalArgumentException(ClickHouseUtils.format("No Inet4Address for [%s]", value)); + } + + /** + * Converts big integer to IPv6 address. + * + * @param value big integer + * @return IPv6 address + * @throws IllegalArgumentException when failed to convert to IPv6 address + */ + public static Inet6Address convertToIpv6(BigInteger value) { + if (value == null) { + return null; + } + + byte[] bytes = ClickHouseChecker.nonNull(value, "value").toByteArray(); + int len = bytes.length; + if (len > 16) { + throw new IllegalArgumentException("The number is too large to be converted to IPv6: " + value); + } else if (len < 16) { + byte[] addr = new byte[16]; + int diff = 16 - len; + for (int i = len - 1; i >= 0; i--) { + addr[i + diff] = bytes[i]; + } + bytes = addr; + } + + try { + return Inet6Address.getByAddress(null, bytes, null); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Converts IPv4 address to IPv6 address. + * + * @param value IPv4 address + * @return IPv6 address + * @throws IllegalArgumentException when failed to convert to IPv6 address + */ + public static Inet6Address convertToIpv6(Inet4Address value) { + if (value == null) { + return null; + } + + // https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses + byte[] bytes = new byte[16]; + bytes[10] = (byte) 0xFF; + bytes[11] = (byte) 0xFF; + System.arraycopy(value.getAddress(), 0, bytes, 12, 4); + + try { + return Inet6Address.getByAddress(null, bytes, null); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Converts string to IPv6 address. + * + * @param value string + * @return IPv6 address + * @throws IllegalArgumentException when failed to convert to IPv6 address + */ + public static Inet6Address convertToIpv6(String value) { + if (value == null) { + return null; + } + + try { + for (InetAddress addr : InetAddress.getAllByName(value)) { + if (addr instanceof Inet6Address) { + return (Inet6Address) addr; + } + } + } catch (UnknownHostException e) { + throw new IllegalArgumentException(ClickHouseUtils.format("Failed to convert [%s] to Inet6Address", value), + e); + } + + throw new IllegalArgumentException(ClickHouseUtils.format("No Inet6Address for [%s]", value)); + } + + /** + * Converts abitrary object to an expression that can be used in SQL query. + * + * @param value value may or may not be null + * @return escaped SQL expression + */ + @SuppressWarnings({ "unchecked", "squid:S3776" }) + public static String convertToSqlExpression(Object value) { + if (value == null) { + return NULL_EXPR; + } + + String s; + if (value instanceof String) { + s = convertToQuotedString(value); + } else if (value instanceof ClickHouseValue) { + s = ((ClickHouseValue) value).toSqlExpression(); + } else if (value instanceof UUID) { + s = new StringBuilder().append('\'').append(value).append('\'').toString(); + } else if (value instanceof LocalDate) { + s = new StringBuilder().append('\'').append(((LocalDate) value).format(DATE_FORMATTER)).append('\'') + .toString(); + } else if (value instanceof LocalTime) { // currently not supported in ClickHouse + s = new StringBuilder().append('\'').append(((LocalTime) value).format(TIME_FORMATTER)).append('\'') + .toString(); + } else if (value instanceof LocalDateTime) { + s = new StringBuilder().append('\'').append(((LocalDateTime) value).format(DATETIME_FORMATTER)).append('\'') + .toString(); + } else if (value instanceof InetAddress) { + s = new StringBuilder().append('\'').append(((InetAddress) value).getHostAddress()).append('\'').toString(); + } else if (value instanceof Enum) { + s = String.valueOf(((Enum) value).ordinal()); // faster than escaped name + } else if (value instanceof Object[]) { // array & nested + StringBuilder builder = new StringBuilder().append('['); + for (Object o : (Object[]) value) { + builder.append(convertToSqlExpression(o)).append(','); + } + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + s = builder.append(']').toString(); + } else if (value instanceof Collection) { // treat as tuple + StringBuilder builder = new StringBuilder().append('('); + for (Object v : (Collection) value) { + builder.append(convertToSqlExpression(v)).append(','); + } + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + s = builder.append(')').toString(); + } else if (value instanceof Enumeration) { // treat as tuple + StringBuilder builder = new StringBuilder().append('('); + Enumeration v = (Enumeration) value; + while (v.hasMoreElements()) { + builder.append(convertToSqlExpression(v.nextElement())).append(','); + } + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + s = builder.append(')').toString(); + } else if (value instanceof Map) { // map + StringBuilder builder = new StringBuilder().append('{'); + for (Entry v : ((Map) value).entrySet()) { + builder.append(convertToSqlExpression(v.getKey())).append(" : ") + .append(convertToSqlExpression(v.getValue())).append(','); + } + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + s = builder.append('}').toString(); + } else if (value instanceof Boolean) { + s = String.valueOf((boolean) value ? 1 : 0); + } else if (value instanceof Character) { + s = String.valueOf((int) ((char) value)); + } else if (value instanceof boolean[]) { + s = convertToString((boolean[]) value); + } else if (value instanceof char[]) { + s = convertToString((char[]) value); + } else if (value instanceof byte[]) { + s = convertToString((byte[]) value); + } else if (value instanceof short[]) { + s = convertToString((short[]) value); + } else if (value instanceof int[]) { + s = convertToString((int[]) value); + } else if (value instanceof long[]) { + s = convertToString((long[]) value); + } else if (value instanceof float[]) { + s = convertToString((float[]) value); + } else if (value instanceof double[]) { + s = convertToString((double[]) value); + } else { + s = String.valueOf(value); + } + return s; + } + + /** + * Converts boolean array to compact string. Similar as + * {@code Arrays.toString()} but without any whitespace. + * + * @param value boolean array + * @return string + */ + public static String convertToString(boolean[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (boolean v : value) { + builder.append(v ? 1 : 0).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts character array to compact string. Similar as + * {@code Arrays.toString()} but without any whitespace. + * + * @param value character array + * @return string + */ + public static String convertToString(char[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (char v : value) { + builder.append((int) v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts byte array to compact string. Similar as {@code Arrays.toString()} + * but without any whitespace. + * + * @param value byte array + * @return string + */ + public static String convertToString(byte[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (byte v : value) { + builder.append(v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts short array to compact string. Similar as {@code Arrays.toString()} + * but without any whitespace. + * + * @param value short array + * @return string + */ + public static String convertToString(short[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (short v : value) { + builder.append(v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts integer array to compact string. Similar as + * {@code Arrays.toString()} but without any whitespace. + * + * @param value integer array + * @return string + */ + public static String convertToString(int[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (int v : value) { + builder.append(v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts long array to compact string. Similar as {@code Arrays.toString()} + * but without any whitespace. + * + * @param value long array + * @return string + */ + public static String convertToString(long[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (long v : value) { + builder.append(v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts float array to compact string. Similar as {@code Arrays.toString()} + * but without any whitespace. + * + * @param value float array + * @return string + */ + public static String convertToString(float[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (float v : value) { + builder.append(v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts double array to compact string. Similar as {@code Arrays.toString()} + * but without any whitespace. + * + * @param value double array + * @return string + */ + public static String convertToString(double[] value) { + if (value == null) { + return NULL_EXPR; + } else if (value.length == 0) { + return EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (double v : value) { + builder.append(v).append(','); + } + builder.setLength(builder.length() - 1); + return builder.append(']').toString(); + } + + /** + * Converts given value object to string. + * + * @param value value object + * @return string + */ + public static String convertToString(ClickHouseValue value) { + return value == null ? "null" + : new StringBuilder().append(value.getClass().getSimpleName()).append('[').append(value.asString()) + .append(']').toString(); + } + + /** + * Converts object to string. Same as {@code String.valueOf()}. + * + * @param value object may or may not be null + * @return string representation of the object + */ + public static String convertToString(Object value) { + return String.valueOf(value); + } + + /** + * Converts big integer to UUID. + * + * @param value big integer + * @return UUID + */ + public static UUID convertToUuid(BigInteger value) { + if (value == null) { + return null; + } + + BigInteger[] parts = value.divideAndRemainder(BIGINT_HL_BOUNDARY); + BigInteger high = parts[0]; + BigInteger low = parts[1]; + + if (BIGINT_SL_BOUNDARY.compareTo(high) < 0) { + high = high.subtract(BIGINT_HL_BOUNDARY); + } + if (BIGINT_SL_BOUNDARY.compareTo(low) < 0) { + low = low.subtract(BIGINT_HL_BOUNDARY); + } + + return new UUID(high.longValueExact(), low.longValueExact()); + } + + /** + * Converts object to quoted string. + * + * @param value object may or may not be null + * @return quoted string representing the object + */ + public static String convertToQuotedString(Object value) { + if (value == null) { + return NULL_EXPR; + } + + return new StringBuilder().append('\'').append(ClickHouseUtils.escape(value.toString(), '\'')).append('\'') + .toString(); + } + + /** + * Extract one and only value from singleton collection. + * + * @param value singleton collection + * @return value + * @throws IllegalArgumentException if the given collection is null or contains + * zero or more than one element + */ + public static Object extractSingleValue(Collection value) { + if (value == null || value.size() != 1) { + throw new IllegalArgumentException(ERROR_SINGLETON_COLLECTION + value); + } + + return value.iterator().next(); + } + + /** + * Extract one and only value from singleton enumeration. + * + * @param value singleton enumeration + * @return value + * @throws IllegalArgumentException if the given enumeration is null or contains + * zero or more than one element + */ + public static Object extractSingleValue(Enumeration value) { + if (value == null || !value.hasMoreElements()) { + throw new IllegalArgumentException(ERROR_SINGLETON_COLLECTION + value); + } + + Object v = value.nextElement(); + if (value.hasMoreElements()) { + throw new IllegalArgumentException(ERROR_SINGLETON_COLLECTION + value); + } + + return v; + } + + /** + * Extract one and only value from singleton map - key will be ignored. + * + * @param value singleton map + * @return value + * @throws IllegalArgumentException if the given map is null or contains zero or + * more than one element + */ + public static Object extractSingleValue(Map value) { + if (value == null || value.size() != 1) { + throw new IllegalArgumentException(ERROR_SINGLETON_MAP + value); + } + + return value.values().iterator().next(); + } + + /** + * Creates a value object based on given column. + * + * @param column non-null column + * @return value object with default value, either null or empty + */ + public static ClickHouseValue newValue(ClickHouseColumn column) { + return newValue(ClickHouseChecker.nonNull(column, "column").getDataType(), column); + } + + /** + * Creates a value object based on given data type. + * + * @param type non-null data type + * @return value object with default value, either null or empty + */ + public static ClickHouseValue newValue(ClickHouseDataType type) { + return newValue(type, null); + } + + private static ClickHouseValue newValue(ClickHouseDataType type, ClickHouseColumn column) { + ClickHouseValue value; + switch (ClickHouseChecker.nonNull(type, "type")) { // still faster than EnumMap and with less overhead + case Enum: + case Enum8: + case Int8: + value = ClickHouseByteValue.ofNull(); + break; + case UInt8: + case Enum16: + case Int16: + value = ClickHouseShortValue.ofNull(); + break; + case UInt16: + case Int32: + value = ClickHouseIntegerValue.ofNull(); + break; + case UInt32: + case IntervalYear: + case IntervalQuarter: + case IntervalMonth: + case IntervalWeek: + case IntervalDay: + case IntervalHour: + case IntervalMinute: + case IntervalSecond: + case Int64: + value = ClickHouseLongValue.ofNull(false); + break; + case UInt64: + value = ClickHouseLongValue.ofNull(true); + break; + case Int128: + case UInt128: + case Int256: + case UInt256: + value = ClickHouseBigIntegerValue.ofNull(); + break; + case Float32: + value = ClickHouseFloatValue.ofNull(); + break; + case Float64: + value = ClickHouseDoubleValue.ofNull(); + break; + case Decimal: + case Decimal32: + case Decimal64: + case Decimal128: + case Decimal256: + value = ClickHouseBigDecimalValue.ofNull(); + break; + case Date: + case Date32: + value = ClickHouseDateValue.ofNull(); + break; + case DateTime: + case DateTime32: + case DateTime64: + value = ClickHouseDateTimeValue.ofNull(column == null ? 0 : column.getScale()); + break; + case IPv4: + value = ClickHouseIpv4Value.ofNull(); + break; + case IPv6: + value = ClickHouseIpv6Value.ofNull(); + break; + case FixedString: + case String: + value = ClickHouseStringValue.ofNull(); + break; + case UUID: + value = ClickHouseUuidValue.ofNull(); + break; + case Point: + value = ClickHouseGeoPointValue.ofOrigin(); + break; + case Ring: + value = ClickHouseGeoRingValue.ofEmpty(); + break; + case Polygon: + value = ClickHouseGeoPolygonValue.ofEmpty(); + break; + case MultiPolygon: + value = ClickHouseGeoMultiPolygonValue.ofEmpty(); + break; + case Array: + value = ClickHouseArrayValue.ofEmpty(); + break; + case Map: + if (column == null) { + throw new IllegalArgumentException("column types for key and value are required"); + } + value = ClickHouseMapValue.ofEmpty(column.getKeyInfo().getDataType().getJavaClass(), + column.getValueInfo().getDataType().getJavaClass()); + break; + case Nested: + if (column == null) { + throw new IllegalArgumentException("nested column types are required"); + } + value = ClickHouseNestedValue.ofEmpty(column.getNestedColumns()); + break; + case Tuple: + value = ClickHouseTupleValue.of(); + break; + default: + throw new IllegalArgumentException("Unsupported data type: " + type.name()); + } + + return value; + } + + private ClickHouseValues() { + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseVersion.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseVersion.java new file mode 100644 index 000000000..3353fb64a --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseVersion.java @@ -0,0 +1,156 @@ +package com.clickhouse.client; + +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ClickHouse server version number takes the form + * {@code Year.Major.Minor.Internal}. Prefix like 'v' and suffix like + * '-[testing|stable|lts]' will be ignored in parsing and comparison. + */ +public final class ClickHouseVersion implements Comparable, Serializable { + private static final long serialVersionUID = 6721014333437055314L; + + private static final ClickHouseVersion defaultVersion = new ClickHouseVersion(0, 0, 0, 0); + + @SuppressWarnings({ "squid:S5843", "squid:S5857" }) + private static final Pattern versionPattern = Pattern.compile( + "^(?:.*?\\s)?(\\d+)(?:\\s*\\.\\s*(\\d+))?(?:\\s*\\.\\s*(\\d+))?(?:\\s*\\.\\s*(\\d+))?(?:|[\\.\\s].*)", + Pattern.DOTALL); + + /** + * Compare two versions. + * + * @param fromVersion version + * @param toVersion version to compare with + * @return positive integer if {@code fromVersion} is newer than + * {@code toVersion}; zero if they're equal; or negative integer if + * {@code fromVersion} is older + */ + public static int compare(String fromVersion, String toVersion) { + return ClickHouseVersion.of(fromVersion).compareTo(ClickHouseVersion.of(toVersion)); + } + + /** + * Parse given version number in string format. + * + * @param version version number + * @return parsed version + */ + public static ClickHouseVersion of(String version) { + if (version == null || version.isEmpty()) { + return defaultVersion; + } + + int[] parts = new int[4]; + Matcher m = versionPattern.matcher(version); + if (m.matches()) { + for (int i = 0, len = Math.min(m.groupCount(), parts.length); i < len; i++) { + try { + parts[i] = Integer.parseInt(m.group(i + 1)); + } catch (NumberFormatException e) { + // definitely don't want to break anything + } + } + } + + return new ClickHouseVersion(parts[0], parts[1], parts[2], parts[3]); + } + + private final int year; + private final int major; + private final int minor; + private final int internal; // or patch? + + // Active Releases: + // /~https://github.com/ClickHouse/ClickHouse/pulls?q=is%3Aopen+is%3Apr+label%3Arelease + protected ClickHouseVersion(int year, int major, int minor, int internal) { + this.year = year > 0 ? year : 0; + this.major = major > 0 ? major : 0; + this.minor = minor > 0 ? minor : 0; + this.internal = internal > 0 ? internal : 0; + } + + public int getYear() { + return year; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getInternal() { + return internal; + } + + /** + * Checks if current version is newer or equal to the given version. + * + * @param version version to compare + * @return true if current version is newer or equal to the given one; false + * otherwise + */ + public boolean isNewerOrEqual(String version) { + return compareTo(ClickHouseVersion.of(version)) >= 0; + } + + @Override + public int compareTo(ClickHouseVersion o) { + if (this == o) { + return 0; + } + + int result = year - o.year; + if (result != 0) { + return result; + } + + result = major - o.major; + if (result != 0) { + return result; + } + + result = minor - o.minor; + if (result != 0) { + return result; + } + + return internal - o.internal; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseVersion other = (ClickHouseVersion) obj; + return year == other.year && major == other.major && minor == other.minor && internal == other.internal; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + year; + result = prime * result + major; + result = prime * result + minor; + result = prime * result + internal; + return result; + } + + @Override + public String toString() { + return new StringBuilder().append(year).append('.').append(major).append('.').append(minor).append('.') + .append(internal).toString(); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseWriter.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseWriter.java new file mode 100644 index 000000000..a66f1f273 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseWriter.java @@ -0,0 +1,15 @@ +package com.clickhouse.client; + +import java.io.IOException; +import java.io.OutputStream; + +@FunctionalInterface +public interface ClickHouseWriter { + /** + * Writes value to output stream. + * + * @param output non-null output stream + * @throws IOException when failed to write data to output stream + */ + void write(OutputStream output) throws IOException; +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java new file mode 100644 index 000000000..94f14c9fa --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java @@ -0,0 +1,172 @@ +package com.clickhouse.client.config; + +import com.clickhouse.client.ClickHouseChecker; + +/** + * Generic client options. + */ +public enum ClickHouseClientOption implements ClickHouseConfigOption { + /** + * Whether the client should run in async mode(e.g. + * {@link com.clickhouse.client.ClickHouseClient#execute(com.clickhouse.client.ClickHouseRequest)} + * in a separate thread). + */ + ASYNC("async", true, "Whether the client should run in async mode."), + /** + * Client name. + */ + CLIENT_NAME("client_name", "ClickHouse Java Client", + "Client name, which is either 'client_name' or 'http_user_agent' shows up in system.query_log table."), + /** + * Case-insensitive transport level compression algorithm. See + * {@link com.clickhouse.client.ClickHouseCompression} for all possible options. + */ + COMPRESSION("compression", "LZ4", + "Transport level compression algorithm used when exchanging data between server and client."), + /** + * Connection timeout in milliseconds. + */ + CONNECTION_TIMEOUT("connect_timeout", 10 * 1000, + "Connection timeout in milliseconds. It's also used for waiting a connection being closed."), + /** + * Default database. + */ + DATABASE("database", "", "Default database."), + /** + * Default format. + */ + FORMAT("format", "TabSeparatedWithNamesAndTypes", "Default format."), + /** + * Maximum buffer size in byte used for streaming. + */ + MAX_BUFFER_SIZE("max_buffer_size", 8 * 1024, "Maximum buffer size in byte used for streaming."), + /** + * Maximum query execution time in seconds. + */ + MAX_EXECUTION_TIME("max_execution_time", 0, "Maximum query execution time in seconds."), + /** + * Maximum queued in-memory buffers. + */ + MAX_QUEUED_BUFFERS("max_queued_buffers", 0, + "Maximum queued in-memory buffers, 0 or negative number means no limit."), + /** + * Maxium queued requests. When {@link #MAX_THREADS_PER_CLIENT} is greater than + * zero, this will also be applied to client's thread pool as well. + */ + MAX_QUEUED_REQUESTS("max_queued_requests", 0, "Maximum queued requests, 0 or negative number means no limit."), + /** + * Maximum rows allowed in the result. + */ + MAX_RESULT_ROWS("max_result_rows", 0, + "Limit on the number of rows in the result." + + "Also checked for subqueries, and on remote servers when running parts of a distributed query."), + /** + * Maximum size of thread pool for each client. + */ + MAX_THREADS_PER_CLIENT("max_threads_per_client", 1, + "Size of thread pool for each client instance, 0 or negative number means the client will use shared thread pool."), + /** + * Whether to enable retry. + */ + RETRY("retry", true, "Whether to retry when there's connection issue."), + /** + * Whether to reuse wrapper of value. + */ + REUSE_VALUE_WRAPPER("reuse_value_wrapper", true, "Whether to reuse value-wrapper for memory efficiency."), + /** + * Socket timeout in milliseconds. + */ + SOCKET_TIMEOUT("socket_timeout", 30 * 1000, "Socket timeout in milliseconds."), + /** + * Whether to check if session id is validate. + */ + SESSION_CHECK("session_check", false, "Whether to check if session id is validate."), + /** + * Session timeout in milliseconds. + */ + SESSION_TIMEOUT("session_timeout", 0, + "Session timeout in milliseconds. 0 or negative number means same as server default."), + /** + * Whether to enable SSL for the connection. + */ + SSL("ssl", false, "enable SSL/TLS for the connection"), + /** + * SSL mode. + */ + SSL_MODE("sslmode", "strict", "verify or not certificate: none (don't verify), strict (verify)"), + /** + * SSL root certificiate. + */ + SSL_ROOT_CERTIFICATE("sslrootcert", "", "SSL/TLS root certificate"), + /** + * SSL certificiate. + */ + SSL_CERTIFICATE("sslcert", "", "SSL/TLS certificate"), + /** + * SSL key. + */ + SSL_KEY("sslkey", "", "SSL/TLS key"), + /** + * Whether to use objects in array or not. + */ + USE_OBJECTS_IN_ARRAYS("use_objects_in_arrays", false, "Whether Object[] should be used instead primitive arrays."), + /** + * Whether to use server time zone. + */ + USE_SERVER_TIME_ZONE("use_server_time_zone", true, + "Whether to use time zone from server. On connection init select timezone() will be executed"), + /** + * Whether to use time zone from server for Date. + */ + USE_SERVER_TIME_ZONE_FOR_DATES("use_server_time_zone_for_dates", false, + "Whether to use time zone from server on Date parsing in getDate(). " + + "If false, Date returned is a wrapper of a timestamp at start of the day in client time zone. " + + "If true - at start of the day in server or use_timezone time zone."), + /** + * Custom time zone. Only works when {@code use_server_time_zone} is set to + * false. + */ + USE_TIME_ZONE("use_time_zone", "", "Which time zone to use"); + + private final String key; + private final Object defaultValue; + private final Class clazz; + private final String description; + + /** + * Constructor of an option for client. + * + * @param type of the value, usually either String or Number(e.g. + * int, long etc.) + * @param key non-null key, better use snake_case instead of camelCase + * for consistency + * @param defaultValue non-null default value + * @param description non-null description of this option + */ + ClickHouseClientOption(String key, T defaultValue, String description) { + this.key = ClickHouseChecker.nonNull(key, "key"); + this.defaultValue = ClickHouseChecker.nonNull(defaultValue, "defaultValue"); + this.clazz = defaultValue.getClass(); + this.description = ClickHouseChecker.nonNull(description, "description"); + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Class getValueType() { + return clazz; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseConfigOption.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseConfigOption.java new file mode 100644 index 000000000..ef9750aa3 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseConfigOption.java @@ -0,0 +1,151 @@ +package com.clickhouse.client.config; + +import java.util.Optional; + +/** + * This defines a configuration option. To put it in a nutshell, an option is + * composed of key, default value(which implies type of the value) and + * description. + */ +public interface ClickHouseConfigOption { + /** + * Converts given string to a typed value. + * + * @param type of the value + * @param value value in string format + * @param clazz class of the value + * @return typed value + */ + static T fromString(String value, Class clazz) { + if (value == null || clazz == null) { + throw new IllegalArgumentException("Non-null value and class are required"); + } + + if (clazz == int.class || clazz == Integer.class) { + return clazz.cast(Integer.valueOf(value)); + } + if (clazz == long.class || clazz == Long.class) { + return clazz.cast(Long.valueOf(value)); + } + if (clazz == boolean.class || clazz == Boolean.class) { + final Boolean boolValue; + if ("1".equals(value) || "0".equals(value)) { + boolValue = "1".equals(value); + } else { + boolValue = Boolean.valueOf(value); + } + return clazz.cast(boolValue); + } + + return clazz.cast(value); + } + + /** + * Gets default value of the option. + * + * @return default value of the option + */ + Object getDefaultValue(); + + /** + * Gets default value from environment variable. By default the environment + * variable is named as {@link #getPrefix()} + "_" + {@link #name()} in upper + * case. + * + * @return default value defined in environment variable + */ + default Optional getDefaultValueFromEnvVar() { + String prefix = getPrefix().toUpperCase(); + String optionName = name(); + int length = optionName.length(); + return Optional.ofNullable(System.getenv(new StringBuilder(length + prefix.length() + 1).append(prefix) + .append('_').append(optionName.toUpperCase()).toString())); + } + + /** + * Gets default value from system property. By default the system property is + * named as {@link #getPrefix()} + "_" + {@link #name()} in lower case. + * + * @return default value defined in system property + */ + default Optional getDefaultValueFromSysProp() { + String prefix = getPrefix().toLowerCase(); + String optionName = name(); + int length = optionName.length(); + return Optional.ofNullable(System.getProperty(new StringBuilder(length + prefix.length() + 1).append(prefix) + .append('_').append(optionName.toLowerCase()).toString())); + } + + /** + * Gets description of the option. + * + * @return description of the option + */ + String getDescription(); + + /** + * Gets effective default value by considering default value defined in system + * property and environment variable. It's same as {@link #getDefaultValue()} if + * no system property and environment variable defined. + * + * @return effective default value + */ + default Object getEffectiveDefaultValue() { + Optional value = getDefaultValueFromEnvVar(); + + if (!value.isPresent() || value.get().isEmpty()) { + value = getDefaultValueFromSysProp(); + } + + if (!value.isPresent() || value.get().isEmpty()) { + return getDefaultValue(); + } + + return fromString(value.get(), getValueType()); + } + + /** + * Gets effective value by considering default value defined in system property + * and environment variable. It's same as {@link #getDefaultValue()} if the + * given value is null and no system property and environment variable defined. + * + * @param type of the value + * @param value default value + * @return effective value + */ + default T getEffectiveValue(T value) { + @SuppressWarnings("unchecked") + T result = value == null ? (T) getEffectiveDefaultValue() : value; + return result; + } + + /** + * Gets key of the option. + * + * @return key of the option + */ + String getKey(); + + /** + * Gets prefix of environment variable and system property. + * + * @return prefix of environment variable and system property + */ + default String getPrefix() { + return "CHC"; + } + + /** + * Gets value type of the option. + * + * @return value type of the option, defaults to String + */ + Class getValueType(); + + /** + * Gets name of the option. + * + * @return name of the option + */ + String name(); +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java new file mode 100644 index 000000000..19dc1fb28 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java @@ -0,0 +1,111 @@ +package com.clickhouse.client.config; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Optional; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseSslContextProvider; +import com.clickhouse.client.ClickHouseUtils; + +public class ClickHouseDefaultSslContextProvider implements ClickHouseSslContextProvider { + /** + * An insecure {@link javax.net.ssl.TrustManager}, that don't validate the + * certificate. + */ + static class NonValidatingTrustManager implements X509TrustManager { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + + protected KeyStore getKeyStore(String sslRootCert) + throws NoSuchAlgorithmException, IOException, CertificateException, KeyStoreException { + KeyStore ks; + try { + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(null, null); // needed to initialize the key store + } catch (KeyStoreException e) { + throw new NoSuchAlgorithmException( + ClickHouseUtils.format("%s KeyStore not available", KeyStore.getDefaultType())); + } + + try (InputStream in = ClickHouseUtils.getFileInputStream(sslRootCert)) { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + int index = 0; + for (Certificate cert : factory.generateCertificates(in)) { + ks.setCertificateEntry("cert" + (index++), cert); + } + + return ks; + } + } + + protected SSLContext getJavaSslContext(ClickHouseConfig config) throws SSLException { + ClickHouseSslMode sslMode = config.getSslMode(); + String sslRootCert = config.getSslRootCert(); + + SSLContext ctx; + try { + ctx = SSLContext.getInstance("TLS"); + TrustManager[] tms = null; + KeyManager[] kms = null; + SecureRandom sr = null; + + if (sslMode == ClickHouseSslMode.NONE) { + tms = new TrustManager[] { new NonValidatingTrustManager() }; + kms = new KeyManager[] {}; + sr = new SecureRandom(); + } else if (sslMode == ClickHouseSslMode.STRICT) { + if (sslRootCert != null && !sslRootCert.isEmpty()) { + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + tmf.init(getKeyStore(sslRootCert)); + tms = tmf.getTrustManagers(); + kms = new KeyManager[] {}; + sr = new SecureRandom(); + } + } else { + throw new IllegalArgumentException(ClickHouseUtils.format("unspported ssl mode '%s'", sslMode)); + } + + ctx.init(kms, tms, sr); + } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException + | IOException e) { + throw new SSLException("Failed to get SSL context", e); + } + + return ctx; + } + + @SuppressWarnings("unchecked") + @Override + public Optional getSslContext(Class sslContextClass, ClickHouseConfig config) + throws SSLException { + return SSLContext.class == sslContextClass ? Optional.of((T) getJavaSslContext(config)) : Optional.empty(); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaults.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaults.java new file mode 100644 index 000000000..b6af6d710 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaults.java @@ -0,0 +1,115 @@ +package com.clickhouse.client.config; + +import com.clickhouse.client.ClickHouseChecker; + +/** + * System-wide default options. System properties and environment variables can + * be set to change default value. + * + *

+ * For example, by default {@link #ASYNC} is set to {@code true}. However, you + * can change it to {@code false} by either specifying + * {@code -Ddefault_async=false} on the Java command line, or setting + * environment variable {@code DEFAULT_ASYNC=false}. + */ +public enum ClickHouseDefaults implements ClickHouseConfigOption { + /** + * Default execution mode. + */ + ASYNC("async", true, "Whether the client should run in async mode."), + /** + * Whether to create session automatically when there are multiple queries. + */ + AUTO_SESSION("auto_session", true, "Whether to create session automatically when there are multiple queries."), + /** + * Whether to resolve DNS name using + * {@link com.clickhouse.client.ClickHouseDnsResolver}(e.g. resolve SRV record + * to extract both host and port from a given name). + */ + DNS_RESOLVE("dns_resolve", false, "Whether to resolve DNS name."), + /** + * Default cluster. + */ + CLUSTER("cluster", "", "Cluster name."), + /** + * Default server host. + */ + HOST("host", "localhost", "Host to connect to."), + /** + * Default protocol. + */ + PROTOCOL("protocol", "ANY", "Protocol to use."), + /** + * Default server port. + */ + PORT("port", 8123, "Port to connect to."), + /** + * Default server weight. + */ + WEIGHT("weight", 1, "Server weight which might be used for load balancing."), + /** + * Default database. + */ + DATABASE("database", "default", "Database to connect to."), + /** + * Default user. + */ + USER("user", "default", "User name for authentication."), + /** + * Default password. + */ + PASSWORD("password", "", "Password for authentication."), + /** + * Default compression. + */ + COMPRESSION("compression", "LZ4", "Preferred compression alogrithm used in data transferring."), + /** + * Default format. + */ + FORMAT("format", "TabSeparated", "Preferred data format for serialization and deserialization."), + /** + * Max threads. + */ + MAX_THREADS("max_threads", 0, "Maximum size of shared thread pool, 0 or negative number means same as CPU cores."), + /** + * Max requests. + */ + MAX_REQUESTS("max_requests", 0, "Maximum size of shared thread pool, 0 means no limit."); + + private final String key; + private final Object defaultValue; + private final Class clazz; + private final String description; + + ClickHouseDefaults(String key, T defaultValue, String description) { + this.key = ClickHouseChecker.nonNull(key, "key"); + this.defaultValue = ClickHouseChecker.nonNull(defaultValue, "defaultValue"); + this.clazz = defaultValue.getClass(); + this.description = ClickHouseChecker.nonNull(description, "description"); + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getPrefix() { + return "DEFAULT"; + } + + @Override + public Class getValueType() { + return clazz; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseSslMode.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseSslMode.java new file mode 100644 index 000000000..4c263ef5b --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseSslMode.java @@ -0,0 +1,8 @@ +package com.clickhouse.client.config; + +/** + * Defines supported SSL mode. + */ +public enum ClickHouseSslMode { + NONE, STRICT +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/package-info.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/package-info.java new file mode 100644 index 000000000..c2774a6e7 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides necessary classes to configure the client and/or request. + */ +package com.clickhouse.client.config; diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java new file mode 100644 index 000000000..508917ed4 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java @@ -0,0 +1,1616 @@ +package com.clickhouse.client.data; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.concurrent.TimeUnit; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.ClickHouseValues; + +/** + * Utility class for dealing with binary stream and data. + */ +public final class BinaryStreamUtils { + public static final int U_INT8_MAX = (1 << 8) - 1; + public static final int U_INT16_MAX = (1 << 16) - 1; + public static final long U_INT32_MAX = (1L << 32) - 1; + public static final BigInteger U_INT64_MAX = new BigInteger(1, new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }); + public static final BigInteger U_INT128_MAX = new BigInteger(1, + new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF }); + public static final BigInteger U_INT256_MAX = new BigInteger(1, + new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }); + + public static final int DATE32_MAX = (int) LocalDate.of(2283, 11, 11).toEpochDay(); + public static final int DATE32_MIN = (int) LocalDate.of(1925, 1, 1).toEpochDay(); + + public static final BigDecimal DECIMAL32_MAX = new BigDecimal("1000000000"); + public static final BigDecimal DECIMAL32_MIN = new BigDecimal("-1000000000"); + + public static final BigDecimal DECIMAL64_MAX = new BigDecimal("1000000000000000000"); + public static final BigDecimal DECIMAL64_MIN = new BigDecimal("-1000000000000000000"); + + public static final BigDecimal DECIMAL128_MAX = new BigDecimal("100000000000000000000000000000000000000"); + public static final BigDecimal DECIMAL128_MIN = new BigDecimal("-100000000000000000000000000000000000000"); + + public static final BigDecimal DECIMAL256_MAX = new BigDecimal( + "10000000000000000000000000000000000000000000000000000000000000000000000000000"); + public static final BigDecimal DECIMAL256_MIN = new BigDecimal( + "-10000000000000000000000000000000000000000000000000000000000000000000000000000"); + + public static final long DATETIME64_MAX = LocalDateTime.of(LocalDate.of(2283, 11, 11), LocalTime.MAX) + .toEpochSecond(ZoneOffset.UTC); + public static final long DATETIME64_MIN = LocalDateTime.of(LocalDate.of(1925, 1, 1), LocalTime.MIN) + .toEpochSecond(ZoneOffset.UTC); + + public static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1); + + public static final long DATETIME_MAX = U_INT32_MAX * 1000L; + + public static final BigDecimal NANOS = new BigDecimal(BigInteger.TEN.pow(9)); + + private static > T toEnum(int value, Class enumType) { + for (T t : ClickHouseChecker.nonNull(enumType, "enumType").getEnumConstants()) { + if (t.ordinal() == value) { + return t; + } + } + + throw new IllegalArgumentException( + ClickHouseUtils.format("Enum [%s] does not contain value [%d]", enumType, value)); + } + + /** + * Reverse the given byte array. + * + * @param bytes byte array to manipulate + * @return same byte array but reserved + */ + public static byte[] reverse(byte[] bytes) { + if (bytes != null && bytes.length > 1) { + for (int i = 0, len = bytes.length / 2; i < len; i++) { + byte b = bytes[i]; + bytes[i] = bytes[bytes.length - 1 - i]; + bytes[bytes.length - 1 - i] = b; + } + } + + return bytes; + } + + /** + * Get varint length of given integer. + * + * @param value integer + * @return varint length + */ + public static int getVarIntSize(int value) { + int result = 0; + do { + result++; + value >>>= 7; + } while (value != 0); + + return result; + } + + /** + * Get varint length of given long. + * + * @param value long + * @return varint length + */ + public static int getVarLongSize(long value) { + int result = 0; + do { + result++; + value >>>= 7; + } while (value != 0); + + return result; + } + + /** + * Read an unsigned byte from given input stream. + * + * @param input non-null input stream + * @return unnsigned byte which is always greater than or equal to zero + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static int readUnsignedByte(InputStream input) throws IOException { + int value = input.read(); + if (value == -1) { + try { + input.close(); + } catch (IOException e) { + // ignore + } + + throw new EOFException(); + } + + return value; + } + + /** + * Read a byte from given input stream. + * + * @param input non-null input stream + * @return byte + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static byte readByte(InputStream input) throws IOException { + return (byte) readUnsignedByte(input); + } + + /** + * Read {@code size} bytes from given input stream. It behaves in the same way + * as {@link java.io.DataInput#readFully(byte[])}. + * + * @param input input stream + * @param size number of bytes to read + * @return byte array and its length should be {@code size} + * @throws IOException when failed to read value from input stream, not able to + * retrieve all bytes, or reached end of the stream + */ + public static byte[] readBytes(InputStream input, int size) throws IOException { + int count = 0; + byte[] bytes = new byte[size]; + while (count < size) { + int n = input.read(bytes, count, size - count); + if (n < 0) { + try { + input.close(); + } catch (IOException e) { + // ignore + } + + throw count == 0 ? new EOFException() + : new IOException(ClickHouseUtils + .format("Reached end of input stream after reading %d of %d bytes", count, size)); + } + count += n; + } + + return bytes; + } + + /** + * Read boolean from given input stream. It uses {@link #readByte(InputStream)} + * to get value and return {@code true} only when the value is {@code 1}. + * + * @param input non-null input stream + * @return boolean + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static boolean readBoolean(InputStream input) throws IOException { + return ClickHouseChecker.between(readByte(input), ClickHouseValues.TYPE_BOOLEAN, 0, 1) == 1; + } + + /** + * Write boolean into given output stream. + * + * @param output non-null output stream + * @param value boolean value, true == 1 and false == 0 + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeBoolean(OutputStream output, boolean value) throws IOException { + output.write(value ? 1 : 0); + } + + /** + * Write integer as boolean into given output stream. + * + * @param output non-null output stream + * @param value integer, everyting else besides one will be treated as + * zero(false) + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeBoolean(OutputStream output, int value) throws IOException { + output.write(ClickHouseChecker.between(value, ClickHouseValues.TYPE_INT, 0, 1) == 1 ? 1 : 0); + } + + /** + * Read enum value from given input stream. + * + * @param enum type + * @param input non-null input stream + * @param enumType enum class + * @return enum value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static > T readEnum8(InputStream input, Class enumType) throws IOException { + return toEnum(readEnum8(input), enumType); + } + + /** + * Read enum value from given input stream. Same as + * {@link #readInt8(InputStream)}. + * + * @param input non-null input stream + * @return enum value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static byte readEnum8(InputStream input) throws IOException { + return readInt8(input); + } + + /** + * Write enum value into given output stream. Same as + * {@link #writeInt8(OutputStream, byte)}. + * + * @param output non-null output stream + * @param value enum value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeEnum8(OutputStream output, byte value) throws IOException { + writeInt8(output, value); + } + + /** + * Write enum value into given output stream. + * + * @param type of the value + * @param output non-null output stream + * @param value enum value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static > void writeEnum8(OutputStream output, T value) throws IOException { + writeEnum8(output, (byte) ClickHouseChecker.nonNull(value, "enum value").ordinal()); + } + + /** + * Read enum value from given input stream. + * + * @param enum type + * @param input non-null input stream + * @param enumType enum class + * @return enum value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static > T readEnum16(InputStream input, Class enumType) throws IOException { + return toEnum(readEnum16(input), enumType); + } + + /** + * Read enum value from given input stream. Same as + * {@link #readInt16(InputStream)}. + * + * @param input non-null input stream + * @return enum value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static short readEnum16(InputStream input) throws IOException { + return readInt16(input); + } + + /** + * Write enum value into given output stream. Same as + * {@link #writeInt16(OutputStream, int)}. + * + * @param output non-null output stream + * @param value enum value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeEnum16(OutputStream output, int value) throws IOException { + writeInt16(output, value); + } + + /** + * Write enum value into given output stream. + * + * @param type of the value + * @param output non-null output stream + * @param value enum value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static > void writeEnum16(OutputStream output, T value) throws IOException { + writeEnum16(output, ClickHouseChecker.nonNull(value, "enum value").ordinal()); + } + + /** + * Read geo point(X and Y coordinates) from given input stream. + * + * @param input non-null input stream + * @return X and Y coordinates + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static double[] readGeoPoint(InputStream input) throws IOException { + return new double[] { readFloat64(input), readFloat64(input) }; + } + + /** + * Write geo point(X and Y coordinates). + * + * @param output non-null output stream + * @param value X and Y coordinates + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeGeoPoint(OutputStream output, double[] value) throws IOException { + if (value == null || value.length != 2) { + throw new IllegalArgumentException("Non-null X and Y coordinates are required"); + } + + writeGeoPoint(output, value[0], value[1]); + } + + /** + * Write geo point(X and Y coordinates). + * + * @param output non-null output stream + * @param x X coordinate + * @param y Y coordinate + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeGeoPoint(OutputStream output, double x, double y) throws IOException { + writeFloat64(output, x); + writeFloat64(output, y); + } + + /** + * Read geo ring(array of X and Y coordinates) from given input stream. + * + * @param input non-null input stream + * @return array of X and Y coordinates + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static double[][] readGeoRing(InputStream input) throws IOException { + int count = readVarInt(input); + double[][] value = new double[count][2]; + for (int i = 0; i < count; i++) { + value[i] = readGeoPoint(input); + } + return value; + } + + /** + * Write geo ring(array of X and Y coordinates). + * + * @param output non-null output stream + * @param value array of X and Y coordinates + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeGeoRing(OutputStream output, double[][] value) throws IOException { + writeVarInt(output, value.length); + for (double[] v : value) { + writeGeoPoint(output, v); + } + } + + /** + * Read geo polygon(array of rings) from given input stream. + * + * @param input non-null input stream + * @return array of rings + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static double[][][] readGeoPolygon(InputStream input) throws IOException { + int count = readVarInt(input); + double[][][] value = new double[count][][]; + for (int i = 0; i < count; i++) { + value[i] = readGeoRing(input); + } + return value; + } + + /** + * Write geo polygon(array of rings). + * + * @param output non-null output stream + * @param value array of rings + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeGeoPolygon(OutputStream output, double[][][] value) throws IOException { + writeVarInt(output, value.length); + for (double[][] v : value) { + writeGeoRing(output, v); + } + } + + /** + * Read geo multi-polygon(array of polygons) from given input stream. + * + * @param input non-null input stream + * @return array of polygons + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static double[][][][] readGeoMultiPolygon(InputStream input) throws IOException { + int count = readVarInt(input); + double[][][][] value = new double[count][][][]; + for (int i = 0; i < count; i++) { + value[i] = readGeoPolygon(input); + } + return value; + } + + /** + * Write geo polygon(array of rings). + * + * @param output non-null output stream + * @param value array of polygons + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeGeoMultiPolygon(OutputStream output, double[][][][] value) throws IOException { + writeVarInt(output, value.length); + for (double[][][] v : value) { + writeGeoPolygon(output, v); + } + } + + /** + * Read null marker from input stream. Same as + * {@link #readBoolean(InputStream)}. + * + * @param input non-null input stream + * @return true if it's null; false otherwise + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static boolean readNull(InputStream input) throws IOException { + return readBoolean(input); + } + + /** + * Write null marker. Same as {@code writeBoolean(outut, true)}. + * + * @param output non-null output stream + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeNull(OutputStream output) throws IOException { + writeBoolean(output, true); + } + + /** + * Write non-null marker. Same as {@code writeBoolean(outut, false)}. + * + * @param output non-null output stream + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeNonNull(OutputStream output) throws IOException { + writeBoolean(output, false); + } + + /** + * Read Inet4Address from given input stream. + * + * @param input non-null input stream + * @return Inet4Address + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static Inet4Address readInet4Address(InputStream input) throws IOException { + return (Inet4Address) InetAddress.getByAddress(reverse(readBytes(input, 4))); + } + + /** + * Write Inet4Address to given output stream. + * + * @param output non-null output stream + * @param value Inet4Address + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInet4Address(OutputStream output, Inet4Address value) throws IOException { + output.write(reverse(value.getAddress())); + } + + /** + * Read Inet6Address from given input stream. + * + * @param input non-null input stream + * @return Inet6Address + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static Inet6Address readInet6Address(InputStream input) throws IOException { + return Inet6Address.getByAddress(null, readBytes(input, 16), null); + } + + /** + * Write Inet6Address to given output stream. + * + * @param output non-null output stream + * @param value Inet6Address + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInet6Address(OutputStream output, Inet6Address value) throws IOException { + output.write(value.getAddress()); + } + + /** + * Read a byte from given input stream. Same as {@link #readByte(InputStream)}. + * + * @param input non-null input stream + * @return byte + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static byte readInt8(InputStream input) throws IOException { + return readByte(input); + } + + /** + * Write a byte to given output stream. + * + * @param output non-null output stream + * @param value byte + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt8(OutputStream output, byte value) throws IOException { + output.write(value); + } + + /** + * Write a byte to given output stream. + * + * @param output non-null output stream + * @param value byte + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt8(OutputStream output, int value) throws IOException { + output.write(ClickHouseChecker.between(value, ClickHouseValues.TYPE_INT, Byte.MIN_VALUE, Byte.MAX_VALUE)); + } + + /** + * Read an unsigned byte as short from given input stream. + * + * @param input non-null input stream + * @return unsigned byte + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static short readUnsignedInt8(InputStream input) throws IOException { + return (short) (readByte(input) & 0xFFL); + } + + /** + * Write an unsigned byte to given output stream. + * + * @param output non-null output stream + * @param value unsigned byte + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt8(OutputStream output, int value) throws IOException { + output.write((byte) (ClickHouseChecker.between(value, ClickHouseValues.TYPE_INT, 0, U_INT8_MAX) & 0xFFL)); + } + + /** + * Read a short value from given input stream. + * + * @param input non-null input stream + * @return short value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static short readInt16(InputStream input) throws IOException { + byte[] bytes = readBytes(input, 2); + return (short) ((0xFF & bytes[0]) | (bytes[1] << 8)); + } + + /** + * Write a short value to given output stream. + * + * @param output non-null output stream + * @param value short value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt16(OutputStream output, short value) throws IOException { + output.write(new byte[] { (byte) (0xFFL & value), (byte) (0xFFL & (value >> 8)) }); + } + + /** + * Write a short value to given output stream. + * + * @param output non-null output stream + * @param value short value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt16(OutputStream output, int value) throws IOException { + writeInt16(output, + (short) ClickHouseChecker.between(value, ClickHouseValues.TYPE_INT, Short.MIN_VALUE, Short.MAX_VALUE)); + } + + /** + * Read an unsigned short value from given input stream. + * + * @param input non-null input stream + * @return unsigned short value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static int readUnsignedInt16(InputStream input) throws IOException { + return (int) (readInt16(input) & 0xFFFFL); + } + + /** + * Write an unsigned short value to given output stream. + * + * @param output non-null output stream + * @param value unsigned short value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt16(OutputStream output, int value) throws IOException { + writeInt16(output, + (short) (ClickHouseChecker.between(value, ClickHouseValues.TYPE_INT, 0, U_INT16_MAX) & 0xFFFFL)); + } + + /** + * Read an integer from given input stream. + * + * @param input non-null input stream + * @return integer + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static int readInt32(InputStream input) throws IOException { + byte[] bytes = readBytes(input, 4); + + return (0xFF & bytes[0]) | ((0xFF & bytes[1]) << 8) | ((0xFF & bytes[2]) << 16) | (bytes[3] << 24); + } + + /** + * Write an integer to given output stream. + * + * @param output non-null output stream + * @param value integer + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt32(OutputStream output, int value) throws IOException { + output.write(new byte[] { (byte) (0xFFL & value), (byte) (0xFFL & (value >> 8)), (byte) (0xFFL & (value >> 16)), + (byte) (0xFFL & (value >> 24)) }); + } + + /** + * Read an unsigned integer from given input stream. + * + * @param input non-null input stream + * @return unsigned integer + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static long readUnsignedInt32(InputStream input) throws IOException { + return readInt32(input) & 0xFFFFFFFFL; + } + + /** + * Write an unsigned integer to given output stream. + * + * @param output non-null output stream + * @param value unsigned integer + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt32(OutputStream output, long value) throws IOException { + writeInt32(output, + (int) (ClickHouseChecker.between(value, ClickHouseValues.TYPE_LONG, 0, U_INT32_MAX) & 0xFFFFFFFFL)); + } + + /** + * Read a long value from given input stream. + * + * @param input non-null input stream + * @return long value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static long readInt64(InputStream input) throws IOException { + byte[] bytes = readBytes(input, 8); + + return (0xFFL & bytes[0]) | ((0xFFL & bytes[1]) << 8) | ((0xFFL & bytes[2]) << 16) | ((0xFFL & bytes[3]) << 24) + | ((0xFFL & bytes[4]) << 32) | ((0xFFL & bytes[5]) << 40) | ((0xFFL & bytes[6]) << 48) + | ((0xFFL & bytes[7]) << 56); + } + + /** + * Write a long value to given output stream. + * + * @param output non-null output stream + * @param value long value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt64(OutputStream output, long value) throws IOException { + value = Long.reverseBytes(value); + + byte[] bytes = new byte[8]; + for (int i = 7; i >= 0; i--) { + bytes[i] = (byte) (value & 0xFFL); + value >>= 8; + } + + output.write(bytes); + } + + /** + * Read an unsigned long value from given input stream. + * + * @param input non-null input stream + * @return unsigned long value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigInteger readUnsignedInt64(InputStream input) throws IOException { + return new BigInteger(1, reverse(readBytes(input, 8))); + } + + /** + * Write an unsigned long value to given output stream. + * + * @param output non-null output stream + * @param value unsigned long value, negative number will be treated as + * positive + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt64(OutputStream output, long value) throws IOException { + writeInt64(output, value); + } + + /** + * Write an unsigned long value to given output stream. Due to overhead of + * {@link java.math.BigInteger}, this method in general uses more memory and + * slower than {@link #writeUnsignedInt64(OutputStream, long)}. + * + * @param output non-null output stream + * @param value unsigned long value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt64(OutputStream output, BigInteger value) throws IOException { + writeInt64(output, ClickHouseChecker + .between(value, ClickHouseValues.TYPE_BIG_INTEGER, BigInteger.ZERO, U_INT64_MAX).longValue()); + } + + /** + * Read a big integer(16 bytes) from given input stream. + * + * @param input non-null input stream + * @return big integer + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigInteger readInt128(InputStream input) throws IOException { + return new BigInteger(reverse(readBytes(input, 16))); + } + + /** + * Write a big integer(16 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big integer + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt128(OutputStream output, BigInteger value) throws IOException { + writeBigInteger(output, value, 16); + } + + /** + * Read an unsigned big integer from given input stream. + * + * @param input non-null input stream + * @return unsigned big integer + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigInteger readUnsignedInt128(InputStream input) throws IOException { + return new BigInteger(1, reverse(readBytes(input, 16))); + } + + /** + * Write an unsigned big integer(16 bytes) to given output stream. + * + * @param output non-null output stream + * @param value unsigned big integer + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt128(OutputStream output, BigInteger value) throws IOException { + writeInt128(output, + ClickHouseChecker.between(value, ClickHouseValues.TYPE_BIG_INTEGER, BigInteger.ZERO, U_INT128_MAX)); + } + + /** + * Read a big integer(32 bytes) from given input stream. + * + * @param input non-null input stream + * @return big integer + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigInteger readInt256(InputStream input) throws IOException { + return new BigInteger(reverse(readBytes(input, 32))); + } + + /** + * Write a big integer(32 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big integer + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeInt256(OutputStream output, BigInteger value) throws IOException { + writeBigInteger(output, value, 32); + } + + /** + * Read an unsigned big integer(32 bytes) from given input stream. + * + * @param input non-null input stream + * @return big integer + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigInteger readUnsignedInt256(InputStream input) throws IOException { + return new BigInteger(1, reverse(readBytes(input, 32))); + } + + /** + * Write an unsigned big integer(32 bytes) to given output stream. + * + * @param output non-null output stream + * @param value unsigned big integer + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUnsignedInt256(OutputStream output, BigInteger value) throws IOException { + writeInt256(output, + ClickHouseChecker.between(value, ClickHouseValues.TYPE_BIG_INTEGER, BigInteger.ZERO, U_INT256_MAX)); + } + + /** + * Read a float value from given input stream. + * + * @param input non-null input stream + * @return float value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static float readFloat32(InputStream input) throws IOException { + return Float.intBitsToFloat(readInt32(input)); + } + + /** + * Write a float value to given output stream. + * + * @param output non-null output stream + * @param value float value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeFloat32(OutputStream output, float value) throws IOException { + writeInt32(output, Float.floatToIntBits(value)); + } + + /** + * Read a double value from given input stream. + * + * @param input non-null input stream + * @return double value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static double readFloat64(InputStream input) throws IOException { + return Double.longBitsToDouble(readInt64(input)); + } + + /** + * Write a double value to given output stream. + * + * @param output non-null output stream + * @param value double value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeFloat64(OutputStream output, double value) throws IOException { + writeInt64(output, Double.doubleToLongBits(value)); + } + + /** + * Read UUID from given input stream. + * + * @param input non-null input stream + * @return UUID + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static java.util.UUID readUuid(InputStream input) throws IOException { + return new java.util.UUID(readInt64(input), readInt64(input)); + } + + /** + * Write a UUID to given output stream. + * + * @param output non-null output stream + * @param value UUID + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeUuid(OutputStream output, java.util.UUID value) throws IOException { + writeInt64(output, value.getMostSignificantBits()); + writeInt64(output, value.getLeastSignificantBits()); + } + + /** + * Write a {@code length}-byte long big integer to given output stream. + * + * @param output non-null output stream + * @param value big integer + * @param length byte length of the value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeBigInteger(OutputStream output, BigInteger value, int length) throws IOException { + byte empty = value.signum() == -1 ? (byte) 0xFF : 0x00; + byte[] bytes = value.toByteArray(); + int endIndex = bytes.length == length + 1 && bytes[0] == (byte) 0 ? 1 : 0; + if (bytes.length - endIndex > length) { + throw new IllegalArgumentException( + ClickHouseUtils.format("Expected %d bytes but got %d from: %s", length, bytes.length, value)); + } + + for (int i = bytes.length - 1; i >= endIndex; i--) { + output.write(bytes[i]); + } + + for (int i = length - bytes.length; i > 0; i--) { + output.write(empty); + } + } + + /** + * Read big decimal(4 - 32 bytes) from given input stream. + * + * @param input non-null input stream + * @param precision precision of the decimal + * @param scale scale of the decimal + * @return big decimal + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigDecimal readDecimal(InputStream input, int precision, int scale) throws IOException { + BigDecimal v; + + if (precision <= 9) { + v = readDecimal32(input, scale); + } else if (precision <= 18) { + v = readDecimal64(input, scale); + } else if (precision <= 38) { + v = readDecimal128(input, scale); + } else { + v = readDecimal256(input, scale); + } + + return v; + } + + /** + * Write a big decimal(4 - 32 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big decimal + * @param precision precision of the decimal + * @param scale scale of the decimal, might be different from + * {@link java.math.BigDecimal#scale()} + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDecimal(OutputStream output, BigDecimal value, int precision, int scale) + throws IOException { + if (precision > 38) { + writeDecimal256(output, value, scale); + } else if (precision > 18) { + writeDecimal128(output, value, scale); + } else if (precision > 9) { + writeDecimal64(output, value, scale); + } else { + writeDecimal32(output, value, scale); + } + } + + /** + * Read big decimal(4 bytes) from given input stream. + * + * @param input non-null input stream + * @param scale scale of the decimal + * @return big decimal + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigDecimal readDecimal32(InputStream input, int scale) throws IOException { + return BigDecimal.valueOf(readInt32(input), + ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9)); + } + + /** + * Write a big decimal(4 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big decimal + * @param scale scale of the decimal, might be different from + * {@link java.math.BigDecimal#scale()} + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDecimal32(OutputStream output, BigDecimal value, int scale) throws IOException { + writeInt32(output, + ClickHouseChecker.between( + value.multiply(BigDecimal.TEN + .pow(ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9))), + ClickHouseValues.TYPE_BIG_DECIMAL, DECIMAL32_MIN, DECIMAL32_MAX).intValue()); + } + + /** + * Read big decimal(8 bytes) from gicen input stream. + * + * @param input non-null input stream + * @param scale scale of the decimal + * @return big decimal + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigDecimal readDecimal64(InputStream input, int scale) throws IOException { + return BigDecimal.valueOf(readInt64(input), + ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 18)); + } + + /** + * Write a big decimal(8 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big decimal + * @param scale scale of the decimal, might be different from + * {@link java.math.BigDecimal#scale()} + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDecimal64(OutputStream output, BigDecimal value, int scale) throws IOException { + writeInt64(output, + ClickHouseChecker.between( + ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 18) == 0 ? value + : value.multiply(BigDecimal.TEN.pow(scale)), + ClickHouseValues.TYPE_BIG_DECIMAL, DECIMAL64_MIN, DECIMAL64_MAX).longValue()); + } + + /** + * Read big decimal(16 bytes) from given input stream. + * + * @param input non-null input stream + * @param scale scale of the decimal + * @return big decimal + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigDecimal readDecimal128(InputStream input, int scale) throws IOException { + return new BigDecimal(readInt128(input), ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 38)); + } + + /** + * Write a big decimal(16 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big decimal + * @param scale scale of the decimal, might be different from + * {@link java.math.BigDecimal#scale()} + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDecimal128(OutputStream output, BigDecimal value, int scale) throws IOException { + writeInt128(output, + ClickHouseChecker.between( + ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 38) == 0 ? value + : value.multiply(BigDecimal.TEN.pow(scale)), + ClickHouseValues.TYPE_BIG_DECIMAL, DECIMAL128_MIN, DECIMAL128_MAX).toBigInteger()); + } + + /** + * Read big decimal from given input stream. + * + * @param input non-null input stream + * @param scale scale of the decimal + * @return big decimal + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static BigDecimal readDecimal256(InputStream input, int scale) throws IOException { + return new BigDecimal(readInt256(input), ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 76)); + } + + /** + * Write a big decimal(32 bytes) to given output stream. + * + * @param output non-null output stream + * @param value big decimal + * @param scale scale of the decimal, might be different from + * {@link java.math.BigDecimal#scale()} + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDecimal256(OutputStream output, BigDecimal value, int scale) throws IOException { + writeInt256(output, + ClickHouseChecker.between( + ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 76) == 0 ? value + : value.multiply(BigDecimal.TEN.pow(scale)), + ClickHouseValues.TYPE_BIG_DECIMAL, DECIMAL256_MIN, DECIMAL256_MAX).toBigInteger()); + } + + /** + * Read {@link java.time.LocalDate} from given input stream. + * + * @param input non-null input stream + * @return local date + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDate readDate(InputStream input) throws IOException { + return LocalDate.ofEpochDay(readUnsignedInt16(input)); + } + + /** + * Write a {@link java.time.LocalDate} to given output stream. + * + * @param output non-null output stream + * @param value local date + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDate(OutputStream output, LocalDate value) throws IOException { + int days = (int) value.toEpochDay(); + writeUnsignedInt16(output, ClickHouseChecker.between(days, ClickHouseValues.TYPE_DATE, 0, U_INT16_MAX)); + } + + /** + * Read {@link java.time.LocalDate} from given input stream. + * + * @param input non-null input stream + * @return local date + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDate readDate32(InputStream input) throws IOException { + return LocalDate.ofEpochDay(readInt32(input)); + } + + /** + * Write a {@link java.time.LocalDate} to given output stream. + * + * @param output non-null output stream + * @param value local date + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDate32(OutputStream output, LocalDate value) throws IOException { + writeInt32(output, ClickHouseChecker.between((int) value.toEpochDay(), ClickHouseValues.TYPE_DATE, DATE32_MIN, + DATE32_MAX)); + } + + /** + * Read {@link java.time.LocalDateTime} from given input stream. + * + * @param input non-null input stream + * @return local datetime + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDateTime readDateTime(InputStream input) throws IOException { + return readDateTime(input, 0); + } + + /** + * Read {@link java.time.LocalDateTime} from given input stream. + * + * @param input non-null input stream + * @param scale scale of the datetime, must between 0 and 9 inclusive + * @return local datetime + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDateTime readDateTime(InputStream input, int scale) throws IOException { + return ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9) == 0 ? readDateTime32(input) + : readDateTime64(input, scale); + } + + /** + * Write a {@link java.time.LocalDateTime} to given output stream. + * + * @param output non-null output stream + * @param value local datetime + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDateTime(OutputStream output, LocalDateTime value) throws IOException { + writeDateTime(output, value, 0); + } + + /** + * Write a {@link java.time.LocalDateTime} to given output stream. + * + * @param output non-null output stream + * @param value local datetime + * @param scale scale of the datetime, must between 0 and 9 inclusive + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDateTime(OutputStream output, LocalDateTime value, int scale) throws IOException { + if (ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9) == 0) { + writeDateTime32(output, value); + } else { + writeDateTime64(output, value, scale); + } + } + + /** + * Read {@link java.time.LocalDateTime} from given input stream. + * + * @param input non-null input stream + * @return local datetime + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDateTime readDateTime32(InputStream input) throws IOException { + long time = readUnsignedInt32(input); + + return LocalDateTime.ofEpochSecond(time < 0L ? 0L : time, 0, ZoneOffset.UTC); + } + + /** + * Write a {@link java.time.LocalDateTime} to given output stream. + * + * @param output non-null output stream + * @param value local datetime + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDateTime32(OutputStream output, LocalDateTime value) throws IOException { + long time = value.toEpochSecond(ZoneOffset.UTC); + + writeUnsignedInt32(output, ClickHouseChecker.between(time, ClickHouseValues.TYPE_DATE_TIME, 0L, DATETIME_MAX)); + } + + /** + * Read {@link java.time.LocalDateTime} from given input stream. Same as + * {@code readDateTime64(input, 3)}. + * + * @param input non-null input stream + * @return local datetime + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDateTime readDateTime64(InputStream input) throws IOException { + return readDateTime64(input, 3); + } + + /** + * Read {@link java.time.LocalDateTime} from given input stream. + * + * @param input non-null input stream + * @param scale scale of the datetime + * @return local datetime + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static LocalDateTime readDateTime64(InputStream input, int scale) throws IOException { + long value = readInt64(input); + int nanoSeconds = 0; + if (ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9) > 0) { + int factor = 1; + for (int i = 0; i < scale; i++) { + factor *= 10; + } + + nanoSeconds = (int) (value % factor); + value /= factor; + if (nanoSeconds < 0) { + nanoSeconds += factor; + value--; + } + if (nanoSeconds > 0L) { + for (int i = 9 - scale; i > 0; i--) { + nanoSeconds *= 10; + } + } + } + + return LocalDateTime.ofEpochSecond(value, nanoSeconds, ZoneOffset.UTC); + } + + /** + * Write a {@link java.time.LocalDateTime} to given output stream. Same as + * {@code writeDateTime64(output, value, 3)}. + * + * @param output non-null output stream + * @param value local datetime + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDateTime64(OutputStream output, LocalDateTime value) throws IOException { + writeDateTime64(output, value, 3); + } + + /** + * Write a {@link java.time.LocalDateTime} to given output stream. + * + * @param output non-null output stream + * @param value local datetime + * @param scale scale of the datetime, must between 0 and 9 inclusive + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeDateTime64(OutputStream output, LocalDateTime value, int scale) throws IOException { + long v = ClickHouseChecker.between(value.toEpochSecond(ZoneOffset.UTC), ClickHouseValues.TYPE_DATE_TIME, + DATETIME64_MIN, DATETIME64_MAX); + if (ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9) > 0) { + for (int i = 0; i < scale; i++) { + v *= 10; + } + int nanoSeconds = value.getNano(); + if (nanoSeconds > 0L) { + for (int i = 9 - scale; i > 0; i--) { + nanoSeconds /= 10; + } + v += nanoSeconds; + } + } + + writeInt64(output, v); + } + + /** + * Read string with fixed length from given input stream. + * + * @param input non-null input stream + * @param length byte length of the string + * @return string with fixed length + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static String readFixedString(InputStream input, int length) throws IOException { + return readFixedString(input, length, null); + } + + /** + * Read string with fixed length from given input stream. + * + * @param input non-null input stream + * @param length byte length of the string + * @param charset charset used to convert string to byte array, null means UTF-8 + * @return string with fixed length + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static String readFixedString(InputStream input, int length, Charset charset) throws IOException { + byte[] bytes = readBytes(input, length); + + return new String(bytes, charset == null ? StandardCharsets.UTF_8 : charset); + } + + /** + * Write a string with fixed length to given output stream. + * + * @param output non-null output stream + * @param value string + * @param length byte length of the string + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeFixedString(OutputStream output, String value, int length) throws IOException { + writeFixedString(output, value, length, null); + } + + /** + * Write a string with fixed length to given output stream. + * + * @param output non-null output stream + * @param value string + * @param length byte length of the string + * @param charset charset used to convert string to byte array, null means UTF-8 + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeFixedString(OutputStream output, String value, int length, Charset charset) + throws IOException { + byte[] src = ClickHouseChecker.notLongerThan(value.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + "value", length); + + byte[] bytes = new byte[length]; + System.arraycopy(src, 0, bytes, 0, src.length); + + output.write(bytes); + } + + /** + * Read string from given input stream. + * + * @param input non-null input stream + * @return string value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static String readString(InputStream input) throws IOException { + return readString(input, null); + } + + /** + * Read string from given input stream. + * + * @param input non-null input stream + * @param charset charset used to convert byte array to string, null means UTF-8 + * @return string value + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static String readString(InputStream input, Charset charset) throws IOException { + return new String(readBytes(input, readVarInt(input)), charset == null ? StandardCharsets.UTF_8 : charset); + } + + /** + * Write a string to given output stream. + * + * @param output non-null output stream + * @param value string + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeString(OutputStream output, String value) throws IOException { + writeString(output, value, null); + } + + /** + * Write a string to given output stream. + * + * @param output non-null output stream + * @param value string + * @param charset charset used to convert string to byte array, null means UTF-8 + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeString(OutputStream output, String value, Charset charset) throws IOException { + byte[] bytes = value.getBytes(charset == null ? StandardCharsets.UTF_8 : charset); + + writeVarInt(output, bytes.length); + output.write(bytes); + } + + /** + * Read varint from given input stream. + * + * @param input non-null input stream + * @return varint + * @throws IOException when failed to read value from input stream or reached + * end of the stream + */ + public static int readVarInt(InputStream input) throws IOException { + // /~https://github.com/ClickHouse/ClickHouse/blob/abe314feecd1647d7c2b952a25da7abf5c19f352/src/IO/VarInt.h#L126 + long result = 0L; + int shift = 0; + for (int i = 0; i < 9; i++) { + // gets 7 bits from next byte + int b = readUnsignedByte(input); + result |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + break; + } + shift += 7; + } + + return (int) result; + } + + /** + * Read varint from given byte buffer. + * + * @param buffer non-null byte buffer + * @return varint + */ + public static int readVarInt(ByteBuffer buffer) { + long result = 0L; + int shift = 0; + for (int i = 0; i < 9; i++) { + // gets 7 bits from next byte + int b = buffer.get(); + result |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + break; + } + shift += 7; + } + + return (int) result; + } + + /** + * Write varint to given output stream. + * + * @param output non-null output stream + * @param value long value + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeVarInt(OutputStream output, long value) throws IOException { + // /~https://github.com/ClickHouse/ClickHouse/blob/abe314feecd1647d7c2b952a25da7abf5c19f352/src/IO/VarInt.h#L187 + for (int i = 0; i < 9; i++) { + byte b = (byte) (value & 0x7F); + + if (value > 0x7F) { + b |= 0x80; + } + + value >>= 7; + output.write(b); + + if (value == 0) { + return; + } + } + } + + /** + * Write varint to given output stream. + * + * @param buffer non-null byte buffer + * @param value integer value + */ + public static void writeVarInt(ByteBuffer buffer, int value) { + while ((value & 0xFFFFFF80) != 0) { + buffer.put((byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + buffer.put((byte) (value & 0x7F)); + } + + private BinaryStreamUtils() { + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseArrayValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseArrayValue.java new file mode 100644 index 000000000..6973e023f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseArrayValue.java @@ -0,0 +1,516 @@ +package com.clickhouse.client.data; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wrapper class of Array. + */ +public class ClickHouseArrayValue extends ClickHouseObjectValue { + /** + * Creates an empty array. + * + * @param type of the array + * @return empty array + */ + @SuppressWarnings("unchecked") + public static ClickHouseArrayValue ofEmpty() { + return of((T[]) ClickHouseValues.EMPTY_ARRAY); + } + + /** + * Wrap the given value. + * + * @param type of element + * @param value value + * @return object representing the value + */ + public static ClickHouseArrayValue of(T[] value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param type of element + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + @SuppressWarnings("unchecked") + public static ClickHouseArrayValue of(ClickHouseValue ref, T[] value) { + return ref instanceof ClickHouseArrayValue + ? (ClickHouseArrayValue) ((ClickHouseArrayValue) ref).set(value) + : new ClickHouseArrayValue<>(value); + } + + protected ClickHouseArrayValue(T[] value) { + super(value); + } + + @Override + protected ClickHouseArrayValue set(T[] value) { + super.set(ClickHouseChecker.nonNull(value, ClickHouseValues.TYPE_ARRAY)); + return this; + } + + @Override + public Object[] asArray() { + return getValue(); + } + + @Override + @SuppressWarnings("unchecked") + public E[] asArray(Class clazz) { + T[] v = getValue(); + E[] array = (E[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), v.length); + int index = 0; + for (T o : v) { + array[index++] = clazz.cast(o); + } + return array; + } + + @Override + public Map asMap(Class keyClass, Class valueClass) { + if (keyClass == null || valueClass == null) { + throw new IllegalArgumentException("Non-null key and value classes are required"); + } + Map map = new LinkedHashMap<>(); + int index = 1; + for (Object o : getValue()) { + map.put(keyClass.cast(index++), valueClass.cast(o)); + } + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + @Override + public String asString(int length, Charset charset) { + String str = Arrays.deepToString(getValue()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseArrayValue copy(boolean deep) { + if (!deep) { + return new ClickHouseArrayValue<>(getValue()); + } + + T[] value = getValue(); + T[] newValue = Arrays.copyOf(value, value.length); // try harder + return new ClickHouseArrayValue<>(newValue); + } + + @Override + public boolean isNullOrEmpty() { + return getValue().length == 0; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue resetToNullOrEmpty() { + set((T[]) ClickHouseValues.EMPTY_ARRAY); + return this; + } + + @Override + public String toSqlExpression() { + T[] value = getValue(); + if (value == null || value.length == 0) { + return ClickHouseValues.EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder().append('['); + for (T v : value) { + builder.append(ClickHouseValues.convertToSqlExpression(v)); + } + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + return builder.append(']').toString(); + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(boolean[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Object[] v = new Object[len]; + int index = 0; + for (boolean b : value) { + v[index++] = b ? (byte) 1 : (byte) 0; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(char[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Object[] v = new Object[len]; + int index = 0; + for (char c : value) { + v[index++] = (int) c; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(byte value) { + set((T[]) new Byte[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(byte[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Byte[] v = new Byte[len]; + int index = 0; + for (byte b : value) { + v[index++] = b; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(short value) { + set((T[]) new Short[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(short[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Short[] v = new Short[len]; + int index = 0; + for (short s : value) { + v[index++] = s; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(int value) { + set((T[]) new Integer[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(int[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Integer[] v = new Integer[len]; + int index = 0; + for (int i : value) { + v[index++] = i; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(long value) { + set((T[]) new Long[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(long[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Long[] v = new Long[len]; + int index = 0; + for (long l : value) { + v[index++] = l; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(float value) { + set((T[]) new Float[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(float[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Float[] v = new Float[len]; + int index = 0; + for (float f : value) { + v[index++] = f; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(double value) { + set((T[]) new Double[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(double[] value) { + int len = value == null ? 0 : value.length; + if (len == 0) { + return resetToNullOrEmpty(); + } + + Double[] v = new Double[len]; + int index = 0; + for (double d : value) { + v[index++] = d; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(BigInteger value) { + set((T[]) new BigInteger[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(BigDecimal value) { + set((T[]) new BigDecimal[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Enum value) { + set((T[]) new Enum[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Inet4Address value) { + set((T[]) new Inet4Address[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Inet6Address value) { + set((T[]) new Inet6Address[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(LocalDate value) { + set((T[]) new LocalDate[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(LocalTime value) { + set((T[]) new LocalTime[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(LocalDateTime value) { + set((T[]) new LocalDateTime[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Collection value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + return resetToNullOrEmpty(); + } + + Object[] v = new Object[size]; + int index = 0; + for (Object o : value) { + v[index++] = o; + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Enumeration value) { + if (value == null) { + return resetToNullOrEmpty(); + } + + List v = new LinkedList<>(); + while (value.hasMoreElements()) { + v.add(value.nextElement()); + } + set((T[]) v.toArray(new Object[v.size()])); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Map value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + return resetToNullOrEmpty(); + } + + Object[] v = new Object[size]; + int index = 0; + for (Entry e : value.entrySet()) { + v[index++] = e.getValue(); + } + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(String value) { + set((T[]) new String[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(UUID value) { + set((T[]) new UUID[] { value }); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(ClickHouseValue value) { + if (value == null) { + return resetToNullOrEmpty(); + } else if (value instanceof ClickHouseArrayValue) { + set(((ClickHouseArrayValue) value).getValue()); + } else { + set(value.isNullOrEmpty() ? (T[]) ClickHouseValues.EMPTY_ARRAY : (T[]) value.asArray()); + } + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Object[] value) { + if (value == null || value.length == 0) { + return resetToNullOrEmpty(); + } + + set((T[]) value); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseValue updateUnknown(Object value) { + Object[] v = (Object[]) Array.newInstance(value == null ? Object.class : value.getClass(), 1); + v[0] = value; + set((T[]) v); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseArrayValue update(Object value) { + if (value instanceof Object[]) { + set((T[]) value); + } else { + super.update(value); + } + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + return Arrays.deepEquals(getValue(), ((ClickHouseArrayValue) obj).getValue()); + } + + @Override + public int hashCode() { + return Arrays.deepHashCode(getValue()); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBigDecimalValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBigDecimalValue.java new file mode 100644 index 000000000..d32b55ca3 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBigDecimalValue.java @@ -0,0 +1,300 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.UUID; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of BigDecimal. + */ +public class ClickHouseBigDecimalValue extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseBigDecimalValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseBigDecimalValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseBigDecimalValue + ? (ClickHouseBigDecimalValue) ((ClickHouseBigDecimalValue) ref).set(null) + : new ClickHouseBigDecimalValue(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseBigDecimalValue of(BigDecimal value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseBigDecimalValue of(ClickHouseValue ref, BigDecimal value) { + return ref instanceof ClickHouseBigDecimalValue + ? (ClickHouseBigDecimalValue) ((ClickHouseBigDecimalValue) ref).set(value) + : new ClickHouseBigDecimalValue(value); + } + + protected ClickHouseBigDecimalValue(BigDecimal value) { + super(value); + } + + @Override + public ClickHouseBigDecimalValue copy(boolean deep) { + return new ClickHouseBigDecimalValue(getValue()); + } + + @Override + public byte asByte() { + return isNullOrEmpty() ? (byte) 0 : getValue().byteValueExact(); + } + + @Override + public short asShort() { + return isNullOrEmpty() ? (short) 0 : getValue().shortValueExact(); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : getValue().intValueExact(); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : getValue().longValueExact(); + } + + @Override + public BigInteger asBigInteger() { + if (isNullOrEmpty()) { + return null; + } + + BigDecimal value = getValue(); + if (value.remainder(BigDecimal.ONE) != BigDecimal.ZERO) { + throw new IllegalArgumentException("Failed to convert BigDecimal to BigInteger: " + value); + } + + return value.toBigInteger(); + } + + @Override + public float asFloat() { + return isNullOrEmpty() ? 0F : getValue().floatValue(); + } + + @Override + public double asDouble() { + return isNullOrEmpty() ? 0D : getValue().doubleValue(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return getValue().setScale(scale); + } + + @Override + public Object asObject() { + return getValue(); + } + + public int getScale() { + return isNullOrEmpty() ? 0 : getValue().scale(); + } + + @Override + public String toSqlExpression() { + return isNullOrEmpty() ? ClickHouseValues.NULL_EXPR : String.valueOf(getValue()); + } + + @Override + public ClickHouseBigDecimalValue update(boolean value) { + set(value ? BigDecimal.ONE : BigDecimal.ZERO); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(char value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(byte value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(short value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(int value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(long value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(float value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(double value) { + set(BigDecimal.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(BigInteger value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(new BigDecimal(value)); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(BigDecimal value) { + set(value); + return this; + } + + @Override + public ClickHouseBigDecimalValue update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigDecimal.valueOf(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(Inet4Address value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(new BigDecimal(ClickHouseValues.convertToBigInteger(value))); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(Inet6Address value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(new BigDecimal(ClickHouseValues.convertToBigInteger(value))); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(LocalDate value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigDecimal.valueOf(value.toEpochDay())); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(LocalTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigDecimal.valueOf(value.toSecondOfDay())); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigDecimal.valueOf(value.toEpochSecond(ZoneOffset.UTC))); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(new BigDecimal(value)); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(UUID value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(new BigDecimal(ClickHouseValues.convertToBigInteger(value))); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asBigDecimal(getScale())); + } + return this; + } + + @Override + public ClickHouseBigDecimalValue update(Object value) { + if (value instanceof BigDecimal) { + set((BigDecimal) value); + return this; + } + + super.update(value); + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBigIntegerValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBigIntegerValue.java new file mode 100644 index 000000000..da78c661b --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBigIntegerValue.java @@ -0,0 +1,287 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.UUID; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of BigInteger. + */ +public class ClickHouseBigIntegerValue extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseBigIntegerValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseBigIntegerValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseBigIntegerValue + ? (ClickHouseBigIntegerValue) ((ClickHouseBigIntegerValue) ref).set(null) + : new ClickHouseBigIntegerValue(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseBigIntegerValue of(BigInteger value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseBigIntegerValue of(ClickHouseValue ref, BigInteger value) { + return ref instanceof ClickHouseBigIntegerValue + ? (ClickHouseBigIntegerValue) ((ClickHouseBigIntegerValue) ref).set(value) + : new ClickHouseBigIntegerValue(value); + } + + protected ClickHouseBigIntegerValue(BigInteger value) { + super(value); + } + + @Override + public ClickHouseBigIntegerValue copy(boolean deep) { + return new ClickHouseBigIntegerValue(getValue()); + } + + @Override + public byte asByte() { + return isNullOrEmpty() ? (byte) 0 : getValue().byteValueExact(); + } + + @Override + public short asShort() { + return isNullOrEmpty() ? (short) 0 : getValue().shortValueExact(); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : getValue().intValueExact(); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : getValue().longValueExact(); + } + + @Override + public BigInteger asBigInteger() { + return getValue(); + } + + @Override + public float asFloat() { + return isNullOrEmpty() ? 0F : getValue().floatValue(); + } + + @Override + public double asDouble() { + return isNullOrEmpty() ? 0D : getValue().doubleValue(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNullOrEmpty() ? null : new BigDecimal(getValue(), scale); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public String toSqlExpression() { + return isNullOrEmpty() ? ClickHouseValues.NULL_EXPR : String.valueOf(getValue()); + } + + @Override + public ClickHouseBigIntegerValue update(boolean value) { + set(value ? BigInteger.ONE : BigInteger.ZERO); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(char value) { + set(BigInteger.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(byte value) { + set(BigInteger.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(short value) { + set(BigInteger.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(int value) { + set(BigInteger.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(long value) { + set(BigInteger.valueOf(value)); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(float value) { + set(BigDecimal.valueOf(value).toBigInteger()); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(double value) { + set(BigDecimal.valueOf(value).toBigInteger()); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(BigInteger value) { + set(value); + return this; + } + + @Override + public ClickHouseBigIntegerValue update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.toBigIntegerExact()); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigInteger.valueOf(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(Inet4Address value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(ClickHouseValues.convertToBigInteger(value)); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(Inet6Address value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(ClickHouseValues.convertToBigInteger(value)); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(LocalDate value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigInteger.valueOf(value.toEpochDay())); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(LocalTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigInteger.valueOf(value.toSecondOfDay())); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(BigInteger.valueOf(value.toEpochSecond(ZoneOffset.UTC))); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(new BigInteger(value)); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(UUID value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(ClickHouseValues.convertToBigInteger(value)); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asBigInteger()); + } + return this; + } + + @Override + public ClickHouseBigIntegerValue update(Object value) { + if (value instanceof BigInteger) { + set((BigInteger) value); + return this; + } + + super.update(value); + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBitmap.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBitmap.java new file mode 100644 index 000000000..de0d46659 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBitmap.java @@ -0,0 +1,570 @@ +package com.clickhouse.client.data; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; +import org.roaringbitmap.RoaringBitmap; +import org.roaringbitmap.buffer.ImmutableRoaringBitmap; +import org.roaringbitmap.buffer.MutableRoaringBitmap; +import org.roaringbitmap.longlong.Roaring64Bitmap; +import org.roaringbitmap.longlong.Roaring64NavigableMap; +import com.clickhouse.client.ClickHouseDataType; + +public abstract class ClickHouseBitmap { + static class ClickHouseRoaringBitmap extends ClickHouseBitmap { + private final RoaringBitmap rb; + + protected ClickHouseRoaringBitmap(RoaringBitmap bitmap, ClickHouseDataType innerType) { + super(bitmap, innerType); + + this.rb = Objects.requireNonNull(bitmap); + } + + @Override + public int getCardinality() { + return rb.getCardinality(); + } + + @Override + public void serialize(ByteBuffer buffer) { + rb.serialize(buffer); + } + + @Override + public int serializedSizeInBytes() { + return rb.serializedSizeInBytes(); + } + + @Override + public int[] toIntArray() { + return rb.toArray(); + } + } + + static class ClickHouseImmutableRoaringBitmap extends ClickHouseBitmap { + private final ImmutableRoaringBitmap rb; + + protected ClickHouseImmutableRoaringBitmap(ImmutableRoaringBitmap rb, ClickHouseDataType innerType) { + super(rb, innerType); + + this.rb = Objects.requireNonNull(rb); + } + + @Override + public int getCardinality() { + return rb.getCardinality(); + } + + @Override + public void serialize(ByteBuffer buffer) { + rb.serialize(buffer); + } + + @Override + public int serializedSizeInBytes() { + return rb.serializedSizeInBytes(); + } + + @Override + public int[] toIntArray() { + return rb.toArray(); + } + } + + static class ClickHouseMutableRoaringBitmap extends ClickHouseBitmap { + private final MutableRoaringBitmap rb; + + protected ClickHouseMutableRoaringBitmap(MutableRoaringBitmap bitmap, ClickHouseDataType innerType) { + super(bitmap, innerType); + + this.rb = Objects.requireNonNull(bitmap); + } + + @Override + public int getCardinality() { + return rb.getCardinality(); + } + + @Override + public void serialize(ByteBuffer buffer) { + rb.serialize(buffer); + } + + @Override + public int serializedSizeInBytes() { + return rb.serializedSizeInBytes(); + } + + @Override + public int[] toIntArray() { + return rb.toArray(); + } + } + + static class ClickHouseRoaring64NavigableMap extends ClickHouseBitmap { + private final Roaring64NavigableMap rb; + + protected ClickHouseRoaring64NavigableMap(Roaring64NavigableMap bitmap, ClickHouseDataType innerType) { + super(bitmap, innerType); + + this.rb = Objects.requireNonNull(bitmap); + } + + @Override + public int getCardinality() { + return rb.getIntCardinality(); + } + + @Override + public long getLongCardinality() { + return rb.getLongCardinality(); + } + + @Override + public void serialize(ByteBuffer buffer) { + int size = serializedSizeInBytes(); + // TODO use custom data output so that we can handle large byte array + try (ByteArrayOutputStream bas = new ByteArrayOutputStream(size)) { + DataOutput out = new DataOutputStream(bas); + try { + // /~https://github.com/RoaringBitmap/RoaringBitmap/blob/0.9.9/RoaringBitmap/src/main/java/org/roaringbitmap/longlong/Roaring64NavigableMap.java#L1105 + rb.serialize(out); + } catch (IOException e) { + throw new IllegalArgumentException("Failed to serialize given bitmap", e); + } + + byte[] bytes = bas.toByteArray(); + for (int i = 4; i > 0; i--) { + buffer.put(bytes[i]); + } + buffer.putInt(0); + buffer.put(bytes, 5, size - 5); + } catch (IOException e) { + throw new IllegalStateException("Failed to serialize given bitmap", e); + } + } + + @Override + public int serializedSizeInBytes() { + return (int) rb.serializedSizeInBytes(); + } + + @Override + public long serializedSizeInBytesAsLong() { + return rb.serializedSizeInBytes(); + } + + @Override + public int[] toIntArray() { + long[] longs = toLongArray(); + int len = longs.length; + int[] ints = new int[len]; + for (int i = 0; i < len; i++) { + ints[i] = (int) longs[i]; + } + return ints; + } + + @Override + public long[] toLongArray() { + return rb.toArray(); + } + } + + public static ClickHouseBitmap wrap(byte... values) { + boolean isUnsigned = true; + int len = values.length; + int[] ints = new int[len]; + for (int i = 0; i < len; i++) { + byte v = values[i]; + ints[i] = v; + if (isUnsigned && v < 0) { + isUnsigned = false; + } + } + + return wrap(RoaringBitmap.bitmapOf(ints), isUnsigned ? ClickHouseDataType.UInt8 : ClickHouseDataType.Int8); + } + + public static ClickHouseBitmap wrap(short... values) { + boolean isUnsigned = true; + int len = values.length; + int[] ints = new int[len]; + for (int i = 0; i < len; i++) { + short v = values[i]; + ints[i] = v; + if (isUnsigned && v < 0) { + isUnsigned = false; + } + } + + return wrap(RoaringBitmap.bitmapOf(ints), isUnsigned ? ClickHouseDataType.UInt16 : ClickHouseDataType.Int16); + } + + public static ClickHouseBitmap wrap(int... values) { + boolean isUnsigned = true; + int len = values.length; + int[] ints = new int[len]; + for (int i = 0; i < len; i++) { + int v = values[i]; + ints[i] = v; + if (isUnsigned && v < 0) { + isUnsigned = false; + } + } + + return wrap(RoaringBitmap.bitmapOf(ints), isUnsigned ? ClickHouseDataType.UInt32 : ClickHouseDataType.Int32); + } + + public static ClickHouseBitmap wrap(long... values) { + boolean isUnsigned = true; + int len = values.length; + long[] longs = new long[len]; + for (int i = 0; i < len; i++) { + long v = values[i]; + longs[i] = v; + if (isUnsigned && v < 0) { + isUnsigned = false; + } + } + + return wrap(Roaring64NavigableMap.bitmapOf(longs), + isUnsigned ? ClickHouseDataType.UInt64 : ClickHouseDataType.Int64); + } + + public static ClickHouseBitmap wrap(Object bitmap, ClickHouseDataType innerType) { + final ClickHouseBitmap b; + if (bitmap instanceof RoaringBitmap) { + b = new ClickHouseRoaringBitmap((RoaringBitmap) bitmap, innerType); + } else if (bitmap instanceof MutableRoaringBitmap) { + b = new ClickHouseMutableRoaringBitmap((MutableRoaringBitmap) bitmap, innerType); + } else if (bitmap instanceof ImmutableRoaringBitmap) { + b = new ClickHouseImmutableRoaringBitmap((ImmutableRoaringBitmap) bitmap, innerType); + } else if (bitmap instanceof Roaring64Bitmap) { + b = new ClickHouseRoaring64NavigableMap( + Roaring64NavigableMap.bitmapOf(((Roaring64Bitmap) bitmap).toArray()), innerType); + } else if (bitmap instanceof Roaring64NavigableMap) { + b = new ClickHouseRoaring64NavigableMap((Roaring64NavigableMap) bitmap, innerType); + } else { + throw new IllegalArgumentException("Only RoaringBitmap is supported but got: " + bitmap); + } + + return b; + } + + public static ClickHouseBitmap deserialize(DataInputStream in, ClickHouseDataType innerType) throws IOException { + final ClickHouseBitmap rb; + + int byteLen = byteLength(innerType); + int flag = in.readUnsignedByte(); + if (flag == 0) { + byte cardinality = (byte) in.readUnsignedByte(); + byte[] bytes = new byte[2 + byteLen * cardinality]; + bytes[0] = (byte) flag; + bytes[1] = cardinality; + in.read(bytes, 2, bytes.length - 2); + + rb = ClickHouseBitmap.deserialize(bytes, innerType); + } else { + int len = BinaryStreamUtils.readVarInt(in); + byte[] bytes = new byte[len]; + + if (byteLen <= 4) { + in.readFully(bytes); + RoaringBitmap b = new RoaringBitmap(); + b.deserialize(flip(newBuffer(len).put(bytes))); + rb = ClickHouseBitmap.wrap(b, innerType); + } else { + // TODO implement a wrapper of DataInput to get rid of byte array here + bytes[0] = (byte) 0; // always unsigned + // read map size in big-endian byte order + for (int i = 4; i > 0; i--) { + bytes[i] = in.readByte(); + } + if (in.readByte() != 0 || in.readByte() != 0 || in.readByte() != 0 || in.readByte() != 0) { + throw new IllegalStateException( + "Not able to deserialize ClickHouseBitmap for too many bitmaps(>" + 0xFFFFFFFFL + ")!"); + } + // read the rest + in.readFully(bytes, 5, len - 5); + Roaring64NavigableMap b = new Roaring64NavigableMap(); + b.deserialize(new DataInputStream(new ByteArrayInputStream(bytes))); + rb = ClickHouseBitmap.wrap(b, innerType); + } + } + + return rb; + } + + public static ClickHouseBitmap deserialize(byte[] bytes, ClickHouseDataType innerType) throws IOException { + // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/AggregateFunctions/AggregateFunctionGroupBitmapData.h#L100 + ClickHouseBitmap rb = ClickHouseBitmap.wrap(); + + if (bytes == null || bytes.length == 0) { + return rb; + } + + int byteLen = byteLength(innerType); + ByteBuffer buffer = newBuffer(bytes.length); + buffer = (ByteBuffer) ((Buffer) buffer.put(bytes)).flip(); + + if (buffer.get() == (byte) 0) { // small set + int cardinality = buffer.get(); + if (byteLen == 1) { + byte[] values = new byte[cardinality]; + for (int i = 0; i < cardinality; i++) { + values[i] = buffer.get(); + } + rb = ClickHouseBitmap.wrap(values); + } else if (byteLen == 2) { + short[] values = new short[cardinality]; + for (int i = 0; i < cardinality; i++) { + values[i] = buffer.getShort(); + } + rb = ClickHouseBitmap.wrap(values); + } else if (byteLen == 4) { + int[] values = new int[cardinality]; + for (int i = 0; i < cardinality; i++) { + values[i] = buffer.getInt(); + } + rb = ClickHouseBitmap.wrap(values); + } else { + long[] values = new long[cardinality]; + for (int i = 0; i < cardinality; i++) { + values[i] = buffer.getLong(); + } + rb = ClickHouseBitmap.wrap(values); + } + } else { // serialized bitmap + int len = BinaryStreamUtils.readVarInt(buffer); + if (buffer.remaining() < len) { + throw new IllegalStateException( + "Need " + len + " bytes to deserialize ClickHouseBitmap but only got " + buffer.remaining()); + } + if (byteLen <= 4) { + RoaringBitmap b = new RoaringBitmap(); + b.deserialize(buffer); + rb = ClickHouseBitmap.wrap(b, innerType); + } else { + // consume map size(long in little-endian byte order) + byte[] bitmaps = new byte[4]; + buffer.get(bitmaps); + if (buffer.get() != 0 || buffer.get() != 0 || buffer.get() != 0 || buffer.get() != 0) { + throw new IllegalStateException( + "Not able to deserialize ClickHouseBitmap for too many bitmaps(>" + 0xFFFFFFFFL + ")!"); + } + // replace the last 5 bytes to flag(boolean for signed/unsigned) and map + // size(integer) + buffer.position(buffer.position() - 5); + // always unsigned due to limit of CRoaring + buffer.put((byte) 0); + // big-endian -> little-endian + for (int i = 3; i >= 0; i--) { + buffer.put(bitmaps[i]); + } + + buffer.position(buffer.position() - 5); + bitmaps = new byte[buffer.remaining()]; + buffer.get(bitmaps); + Roaring64NavigableMap b = new Roaring64NavigableMap(); + b.deserialize(new DataInputStream(new ByteArrayInputStream(bitmaps))); + rb = ClickHouseBitmap.wrap(b, innerType); + } + } + + return rb; + } + + private static ByteBuffer newBuffer(int capacity) { + ByteBuffer buffer = ByteBuffer.allocate(capacity); + if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { + buffer = buffer.slice().order(ByteOrder.LITTLE_ENDIAN); + } + + return buffer; + } + + private static ByteBuffer flip(ByteBuffer buffer) { + return (ByteBuffer) ((Buffer) buffer).flip(); + } + + private static int byteLength(ClickHouseDataType type) { + int byteLen = 0; + switch (Objects.requireNonNull(type)) { + case Int8: + case UInt8: + byteLen = 1; + break; + case Int16: + case UInt16: + byteLen = 2; + break; + case Int32: + case UInt32: + byteLen = 4; + break; + case Int64: + case UInt64: + byteLen = 8; + break; + default: + throw new IllegalArgumentException( + "Only native integer types are supported but we got: " + type.name()); + } + + return byteLen; + } + + protected final ClickHouseDataType innerType; + protected final int byteLen; + protected final Object reference; + + protected ClickHouseBitmap(Object bitmap, ClickHouseDataType innerType) { + this.innerType = innerType; + this.byteLen = byteLength(innerType); + this.reference = Objects.requireNonNull(bitmap); + } + + public abstract int getCardinality(); + + public long getLongCardinality() { + return getCardinality(); + } + + public abstract void serialize(ByteBuffer buffer); + + public abstract int serializedSizeInBytes(); + + public long serializedSizeInBytesAsLong() { + return serializedSizeInBytes(); + } + + public abstract int[] toIntArray(); + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseBitmap b = (ClickHouseBitmap) obj; + return Objects.equals(innerType, b.innerType) && Objects.equals(byteLen, b.byteLen) + && Objects.equals(reference, b.reference); + } + + @Override + public int hashCode() { + return Objects.hash(innerType, byteLen, reference); + } + + public long[] toLongArray() { + int[] ints = toIntArray(); + int len = ints.length; + long[] longs = new long[len]; + for (int i = 0; i < len; i++) { + longs[i] = ints[i]; + } + return longs; + } + + /** + * Serialize the bitmap into a flipped ByteBuffer. + * + * @return flipped byte buffer + */ + public ByteBuffer toByteBuffer() { + ByteBuffer buf; + + int cardinality = getCardinality(); + if (cardinality <= 32) { + buf = newBuffer(2 + byteLen * cardinality); + buf.put((byte) 0); + buf.put((byte) cardinality); + if (byteLen == 1) { + for (int v : toIntArray()) { + buf.put((byte) v); + } + } else if (byteLen == 2) { + for (int v : toIntArray()) { + buf.putShort((short) v); + } + } else if (byteLen == 4) { + for (int v : toIntArray()) { + buf.putInt(v); + } + } else { // 64 + for (long v : toLongArray()) { + buf.putLong(v); + } + } + } else if (byteLen <= 4) { + int size = serializedSizeInBytes(); + int varIntSize = BinaryStreamUtils.getVarIntSize(size); + + buf = newBuffer(1 + varIntSize + size); + buf.put((byte) 1); + BinaryStreamUtils.writeVarInt(buf, size); + serialize(buf); + } else { // 64 + // 1) deduct one to exclude the leading byte - boolean flag, see below: + // /~https://github.com/RoaringBitmap/RoaringBitmap/blob/0.9.9/RoaringBitmap/src/main/java/org/roaringbitmap/longlong/Roaring64NavigableMap.java#L1107 + // 2) add 4 bytes because CRoaring uses long to store count of 32-bit bitmaps, + // while Java uses int - see + // /~https://github.com/RoaringBitmap/CRoaring/blob/v0.2.66/cpp/roaring64map.hh#L597 + long size = serializedSizeInBytesAsLong() - 1 + 4; + int varIntSize = BinaryStreamUtils.getVarLongSize(size); + // TODO add serialize(DataOutput) to handle more + int intSize = (int) size; + buf = newBuffer(1 + varIntSize + intSize); + buf.put((byte) 1); + BinaryStreamUtils.writeVarInt(buf, intSize); + serialize(buf); + } + + return (ByteBuffer) ((Buffer) buf).flip(); + } + + public byte[] toBytes() { + ByteBuffer buffer = toByteBuffer(); + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + return bytes; + } + + public String toBitmapBuildExpression() { + StringBuilder sb = new StringBuilder(); + + if (byteLen <= 4) { + for (int v : toIntArray()) { + sb.append(',').append("to").append(innerType.name()).append('(').append(v).append(')'); + } + } else { + for (long v : toLongArray()) { + sb.append(',').append("to").append(innerType.name()).append('(').append(v).append(')'); + } + } + + if (sb.length() > 0) { + sb.deleteCharAt(0).insert(0, '[').append(']'); + } else { + sb.append("cast([] as Array(").append(innerType.name()).append(')').append(')'); + } + + return sb.insert(0, "bitmapBuild(").append(')').toString(); + } + + public Object unwrap() { + return this.reference; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBlockChecksum.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBlockChecksum.java new file mode 100644 index 000000000..00f31bdec --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseBlockChecksum.java @@ -0,0 +1,66 @@ +package com.clickhouse.client.data; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ClickHouseBlockChecksum { + private final long first; + private final long second; + + public ClickHouseBlockChecksum(long first, long second) { + this.first = first; + this.second = second; + } + + public static ClickHouseBlockChecksum fromBytes(byte[] checksum) { + ByteBuffer buffer = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN).put(checksum); + ((Buffer) buffer).flip(); + return new ClickHouseBlockChecksum(buffer.getLong(), buffer.getLong()); + } + + public static ClickHouseBlockChecksum calculateForBlock(byte magic, int compressedSizeWithHeader, + int uncompressedSize, byte[] data, int length) { + ByteBuffer buffer = ByteBuffer.allocate(compressedSizeWithHeader).order(ByteOrder.LITTLE_ENDIAN) + .put((byte) magic).putInt(compressedSizeWithHeader).putInt(uncompressedSize).put(data, 0, length); + ((Buffer) buffer).flip(); + return calculate(buffer.array()); + } + + public byte[] asBytes() { + ByteBuffer buffer = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN).putLong(first).putLong(second); + ((Buffer) buffer).flip(); + return buffer.array(); + } + + private static ClickHouseBlockChecksum calculate(byte[] data) { + long[] sum = ClickHouseCityHash.cityHash128(data, 0, data.length); + return new ClickHouseBlockChecksum(sum[0], sum[1]); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ClickHouseBlockChecksum that = (ClickHouseBlockChecksum) o; + + if (first != that.first) + return false; + return second == that.second; + } + + @Override + public int hashCode() { + int result = (int) (first ^ (first >>> 32)); + result = 31 * result + (int) (second ^ (second >>> 32)); + return result; + } + + @Override + public String toString() { + return "{" + first + ", " + second + '}'; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseByteValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseByteValue.java new file mode 100644 index 000000000..3e9717612 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseByteValue.java @@ -0,0 +1,275 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of byte. + */ +public class ClickHouseByteValue implements ClickHouseValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseByteValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseByteValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseByteValue ? ((ClickHouseByteValue) ref).set(true, (byte) 0) + : new ClickHouseByteValue(true, (byte) 0); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseByteValue of(byte value) { + return of(null, value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseByteValue of(int value) { + return of(null, (byte) value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseByteValue of(Number value) { + return value == null ? ofNull(null) : of(null, value.byteValue()); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseByteValue of(ClickHouseValue ref, byte value) { + return ref instanceof ClickHouseByteValue ? ((ClickHouseByteValue) ref).set(false, value) + : new ClickHouseByteValue(false, value); + } + + private boolean isNull; + private byte value; + + protected ClickHouseByteValue(boolean isNull, byte value) { + set(isNull, value); + } + + protected ClickHouseByteValue set(boolean isNull, byte value) { + this.isNull = isNull; + this.value = isNull ? 0 : value; + + return this; + } + + /** + * Gets value. + * + * @return value + */ + public byte getValue() { + return value; + } + + @Override + public ClickHouseByteValue copy(boolean deep) { + return new ClickHouseByteValue(isNull, value); + } + + @Override + public boolean isNullOrEmpty() { + return isNull; + } + + @Override + public byte asByte() { + return value; + } + + @Override + public short asShort() { + return value; + } + + @Override + public int asInteger() { + return value; + } + + @Override + public long asLong() { + return value; + } + + @Override + public BigInteger asBigInteger() { + return isNull ? null : BigInteger.valueOf(value); + } + + @Override + public float asFloat() { + return value; + } + + @Override + public double asDouble() { + return value; + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNull ? null : BigDecimal.valueOf(value, scale); + } + + @Override + public Object asObject() { + return isNull ? null : Byte.valueOf(value); + } + + @Override + public String asString(int length, Charset charset) { + if (isNull) { + return null; + } + + String str = String.valueOf(value); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseByteValue resetToNullOrEmpty() { + return set(true, (byte) 0); + } + + @Override + public String toSqlExpression() { + return isNull ? ClickHouseValues.NULL_EXPR : String.valueOf(value); + } + + @Override + public ClickHouseByteValue update(char value) { + return set(false, (byte) value); + } + + @Override + public ClickHouseByteValue update(byte value) { + return set(false, value); + } + + @Override + public ClickHouseByteValue update(short value) { + return set(false, (byte) value); + } + + @Override + public ClickHouseByteValue update(int value) { + return set(false, (byte) value); + } + + @Override + public ClickHouseByteValue update(long value) { + return set(false, (byte) value); + } + + @Override + public ClickHouseByteValue update(float value) { + return set(false, (byte) value); + } + + @Override + public ClickHouseByteValue update(double value) { + return set(false, (byte) value); + } + + @Override + public ClickHouseByteValue update(BigInteger value) { + return value == null ? resetToNullOrEmpty() : set(false, value.byteValueExact()); + } + + @Override + public ClickHouseByteValue update(BigDecimal value) { + return value == null ? resetToNullOrEmpty() : set(false, value.byteValueExact()); + } + + @Override + public ClickHouseByteValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : set(false, (byte) value.ordinal()); + } + + @Override + public ClickHouseByteValue update(String value) { + return value == null ? resetToNullOrEmpty() : set(false, Byte.parseByte(value)); + } + + @Override + public ClickHouseByteValue update(ClickHouseValue value) { + return value == null ? resetToNullOrEmpty() : set(false, value.asByte()); + } + + @Override + public ClickHouseByteValue update(Object value) { + if (value instanceof Number) { + return set(false, ((Number) value).byteValue()); + } else if (value instanceof ClickHouseValue) { + return set(false, ((ClickHouseValue) value).asByte()); + } + + ClickHouseValue.super.update(value); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseByteValue v = (ClickHouseByteValue) obj; + return isNull == v.isNull && value == v.value; + } + + @Override + public int hashCode() { + // not going to use Objects.hash(isNull, value) due to autoboxing + return (31 + (isNull ? 1231 : 1237)) * 31 + value; + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseCityHash.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseCityHash.java new file mode 100644 index 000000000..01d8c53ed --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseCityHash.java @@ -0,0 +1,257 @@ +/* + * Copyright 2017 YANDEX LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright (C) 2012 tamtam180 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.clickhouse.client.data; + +/** + * @author tamtam180 - kirscheless at gmail.com + * @see http://google-opensource.blogspot.jp/2011/04/introducing-cityhash.html + * @see http://code.google.com/p/cityhash/ + * + */ + +/** + * NOTE: The code is modified to be compatible with CityHash128 used in + * ClickHouse + */ +public class ClickHouseCityHash { + + private static final long k0 = 0xc3a5c85c97cb3127L; + private static final long k1 = 0xb492b66fbe98f273L; + private static final long k2 = 0x9ae16a3b2f90404fL; + private static final long k3 = 0xc949d7c7509e6557L; + + private static long toLongLE(byte[] b, int i) { + return 0xffffffffffffffffL & (((long) b[i + 7] << 56) + ((long) (b[i + 6] & 255) << 48) + + ((long) (b[i + 5] & 255) << 40) + ((long) (b[i + 4] & 255) << 32) + ((long) (b[i + 3] & 255) << 24) + + ((b[i + 2] & 255) << 16) + ((b[i + 1] & 255) << 8) + ((b[i + 0] & 255) << 0)); + } + + private static long toIntLE(byte[] b, int i) { + return 0xffffffffL & (((b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + ((b[i + 1] & 255) << 8) + + ((b[i + 0] & 255) << 0)); + } + + private static long fetch64(byte[] s, int pos) { + return toLongLE(s, pos); + } + + private static long fetch32(byte[] s, int pos) { + return toIntLE(s, pos); + } + + private static int staticCastToInt(byte b) { + return b & 0xFF; + } + + private static long rotate(long val, int shift) { + return shift == 0 ? val : (val >>> shift) | (val << (64 - shift)); + } + + private static long rotateByAtLeast1(long val, int shift) { + return (val >>> shift) | (val << (64 - shift)); + } + + private static long shiftMix(long val) { + return val ^ (val >>> 47); + } + + private static final long kMul = 0x9ddfea08eb382d69L; + + private static long hash128to64(long u, long v) { + long a = (u ^ v) * kMul; + a ^= (a >>> 47); + long b = (v ^ a) * kMul; + b ^= (b >>> 47); + b *= kMul; + return b; + } + + private static long hashLen16(long u, long v) { + return hash128to64(u, v); + } + + private static long hashLen0to16(byte[] s, int pos, int len) { + if (len > 8) { + long a = fetch64(s, pos + 0); + long b = fetch64(s, pos + len - 8); + return hashLen16(a, rotateByAtLeast1(b + len, len)) ^ b; + } + if (len >= 4) { + long a = fetch32(s, pos + 0); + return hashLen16((a << 3) + len, fetch32(s, pos + len - 4)); + } + if (len > 0) { + byte a = s[pos + 0]; + byte b = s[pos + (len >>> 1)]; + byte c = s[pos + len - 1]; + int y = staticCastToInt(a) + (staticCastToInt(b) << 8); + int z = len + (staticCastToInt(c) << 2); + return shiftMix(y * k2 ^ z * k3) * k2; + } + return k2; + } + + private static long[] weakHashLen32WithSeeds(long w, long x, long y, long z, long a, long b) { + + a += w; + b = rotate(b + a + z, 21); + long c = a; + a += x; + a += y; + b += rotate(a, 44); + return new long[] { a + z, b + c }; + } + + private static long[] weakHashLen32WithSeeds(byte[] s, int pos, long a, long b) { + return weakHashLen32WithSeeds(fetch64(s, pos + 0), fetch64(s, pos + 8), fetch64(s, pos + 16), + fetch64(s, pos + 24), a, b); + } + + private static long[] cityMurmur(byte[] s, int pos, int len, long seed0, long seed1) { + + long a = seed0; + long b = seed1; + long c = 0; + long d = 0; + + int l = len - 16; + if (l <= 0) { + a = shiftMix(a * k1) * k1; + c = b * k1 + hashLen0to16(s, pos, len); + d = shiftMix(a + (len >= 8 ? fetch64(s, pos + 0) : c)); + } else { + + c = hashLen16(fetch64(s, pos + len - 8) + k1, a); + d = hashLen16(b + len, c + fetch64(s, pos + len - 16)); + a += d; + + do { + a ^= shiftMix(fetch64(s, pos + 0) * k1) * k1; + a *= k1; + b ^= a; + c ^= shiftMix(fetch64(s, pos + 8) * k1) * k1; + c *= k1; + d ^= c; + pos += 16; + l -= 16; + } while (l > 0); + } + + a = hashLen16(a, c); + b = hashLen16(d, b); + + return new long[] { a ^ b, hashLen16(b, a) }; + } + + private static long[] cityHash128WithSeed(byte[] s, int pos, int len, long seed0, long seed1) { + if (len < 128) { + return cityMurmur(s, pos, len, seed0, seed1); + } + + long[] v = new long[2], w = new long[2]; + long x = seed0; + long y = seed1; + long z = k1 * len; + v[0] = rotate(y ^ k1, 49) * k1 + fetch64(s, pos); + v[1] = rotate(v[0], 42) * k1 + fetch64(s, pos + 8); + w[0] = rotate(y + z, 35) * k1 + x; + w[1] = rotate(x + fetch64(s, pos + 88), 53) * k1; + + // This is the same inner loop as CityHash64(), manually unrolled. + do { + x = rotate(x + y + v[0] + fetch64(s, pos + 16), 37) * k1; + y = rotate(y + v[1] + fetch64(s, pos + 48), 42) * k1; + + x ^= w[1]; + y ^= v[0]; + + z = rotate(z ^ w[0], 33); + v = weakHashLen32WithSeeds(s, pos, v[1] * k1, x + w[0]); + w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], y); + + { + long swap = z; + z = x; + x = swap; + } + pos += 64; + x = rotate(x + y + v[0] + fetch64(s, pos + 16), 37) * k1; + y = rotate(y + v[1] + fetch64(s, pos + 48), 42) * k1; + x ^= w[1]; + y ^= v[0]; + z = rotate(z ^ w[0], 33); + v = weakHashLen32WithSeeds(s, pos, v[1] * k1, x + w[0]); + w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], y); + { + long swap = z; + z = x; + x = swap; + } + pos += 64; + len -= 128; + } while (len >= 128); + + y += rotate(w[0], 37) * k0 + z; + x += rotate(v[0] + z, 49) * k0; + + // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. + for (int tail_done = 0; tail_done < len;) { + tail_done += 32; + y = rotate(y - x, 42) * k0 + v[1]; + w[0] += fetch64(s, pos + len - tail_done + 16); + x = rotate(x, 49) * k0 + w[0]; + w[0] += v[0]; + v = weakHashLen32WithSeeds(s, pos + len - tail_done, v[0], v[1]); + } + + // At this point our 48 bytes of state should contain more than + // enough information for a strong 128-bit hash. We use two + // different 48-byte-to-8-byte hashes to get a 16-byte final result. + + x = hashLen16(x, v[0]); + y = hashLen16(y, w[0]); + + return new long[] { hashLen16(x + v[1], w[1]) + y, hashLen16(x + w[1], y + v[1]) }; + } + + static long[] cityHash128(byte[] s, int pos, int len) { + + if (len >= 16) { + return cityHash128WithSeed(s, pos + 16, len - 16, fetch64(s, pos) ^ k3, fetch64(s, pos + 8)); + } else if (len >= 8) { + return cityHash128WithSeed(new byte[0], 0, 0, fetch64(s, pos) ^ (len * k0), fetch64(s, pos + len - 8) ^ k1); + } else { + return cityHash128WithSeed(s, pos, len, k0, k1); + } + } + +} \ No newline at end of file diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDateTimeValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDateTimeValue.java new file mode 100644 index 000000000..6909129c7 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDateTimeValue.java @@ -0,0 +1,335 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of LocalDateTime. + */ +public class ClickHouseDateTimeValue extends ClickHouseObjectValue { + private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * Create a new instance representing null getValue(). + * + * @param scale scale + * @return new instance representing null value + */ + public static ClickHouseDateTimeValue ofNull(int scale) { + return ofNull(null, scale); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @param scale scale, only used when {@code ref} is null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDateTimeValue ofNull(ClickHouseValue ref, int scale) { + return ref instanceof ClickHouseDateTimeValue + ? (ClickHouseDateTimeValue) ((ClickHouseDateTimeValue) ref).set(null) + : new ClickHouseDateTimeValue(null, scale); + } + + /** + * Wrap the given getValue(). + * + * @param value value + * @param scale scale + * @return object representing the value + */ + public static ClickHouseDateTimeValue of(LocalDateTime value, int scale) { + return of(null, value, scale); + } + + /** + * Wrap the given getValue(). + * + * @param value UTC date time in string + * @param scale scale + * @return object representing the value + */ + public static ClickHouseDateTimeValue of(String value, int scale) { + return of(null, value, scale); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @param scale scale, only used when {@code ref} is null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDateTimeValue of(ClickHouseValue ref, LocalDateTime value, int scale) { + return ref instanceof ClickHouseDateTimeValue + ? (ClickHouseDateTimeValue) ((ClickHouseDateTimeValue) ref).set(value) + : new ClickHouseDateTimeValue(value, scale); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value UTC date time in string + * @param scale scale + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDateTimeValue of(ClickHouseValue ref, String value, int scale) { + LocalDateTime dateTime = value == null || value.isEmpty() ? null + : LocalDateTime.parse(value, ClickHouseValues.DATETIME_FORMATTER); + return of(ref, dateTime, scale); + } + + private final int scale; + + protected ClickHouseDateTimeValue(LocalDateTime value, int scale) { + super(value); + this.scale = ClickHouseChecker.between(scale, ClickHouseValues.PARAM_SCALE, 0, 9); + } + + public int getScale() { + return scale; + } + + @Override + public ClickHouseDateTimeValue copy(boolean deep) { + return new ClickHouseDateTimeValue(getValue(), scale); + } + + @Override + public byte asByte() { + return isNullOrEmpty() ? (byte) 0 : (byte) getValue().toEpochSecond(ZoneOffset.UTC); + } + + @Override + public short asShort() { + return isNullOrEmpty() ? (short) 0 : (short) getValue().toEpochSecond(ZoneOffset.UTC); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : (int) getValue().toEpochSecond(ZoneOffset.UTC); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : getValue().toEpochSecond(ZoneOffset.UTC); + } + + @Override + public float asFloat() { + return isNullOrEmpty() ? 0F + : getValue().toEpochSecond(ZoneOffset.UTC) + getValue().getNano() / ClickHouseValues.NANOS.floatValue(); + } + + @Override + public double asDouble() { + return isNullOrEmpty() ? 0D + : getValue().toEpochSecond(ZoneOffset.UTC) + + getValue().getNano() / ClickHouseValues.NANOS.doubleValue(); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : BigInteger.valueOf(getValue().toEpochSecond(ZoneOffset.UTC)); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + LocalDateTime value = getValue(); + BigDecimal v = null; + if (value != null) { + int nanoSeconds = value.getNano(); + v = new BigDecimal(BigInteger.valueOf(value.toEpochSecond(ZoneOffset.UTC)), scale); + if (scale != 0 && nanoSeconds != 0) { + v = v.add(BigDecimal.valueOf(nanoSeconds).divide(ClickHouseValues.NANOS).setScale(scale, + RoundingMode.HALF_UP)); + } + } + return v; + } + + @Override + public LocalDate asDate() { + return isNullOrEmpty() ? null : getValue().toLocalDate(); + } + + @Override + public LocalTime asTime() { + return isNullOrEmpty() ? null : getValue().toLocalTime(); + } + + @Override + public LocalDateTime asDateTime(int scale) { + return getValue(); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + // different formatter for each scale? + String str = getValue().format(scale > 0 ? ClickHouseValues.DATETIME_FORMATTER : dateTimeFormatter); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + return new StringBuilder().append('\'') + .append(getValue().format(scale > 0 ? ClickHouseValues.DATETIME_FORMATTER : dateTimeFormatter)) + .append('\'').toString(); + } + + @Override + public ClickHouseDateTimeValue update(byte value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseDateTimeValue update(short value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseDateTimeValue update(int value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseDateTimeValue update(long value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseDateTimeValue update(float value) { + return update(BigDecimal.valueOf(value)); + } + + @Override + public ClickHouseDateTimeValue update(double value) { + return update(BigDecimal.valueOf(value)); + } + + @Override + public ClickHouseDateTimeValue update(BigInteger value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (scale == 0) { + set(ClickHouseValues.convertToDateTime(new BigDecimal(value, 0))); + } else { + set(ClickHouseValues.convertToDateTime(new BigDecimal(value, scale))); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + if (value.scale() != scale) { + value = value.setScale(scale, RoundingMode.HALF_UP); + } + set(ClickHouseValues.convertToDateTime(value)); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(LocalDate value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDateTime.of(value, LocalTime.MIN)); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(LocalTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDateTime.of(LocalDate.now(), value)); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(LocalDateTime value) { + set(value); + return this; + } + + @Override + public ClickHouseDateTimeValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDateTime.parse(value, ClickHouseValues.DATETIME_FORMATTER)); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asDateTime(scale)); + } + return this; + } + + @Override + public ClickHouseDateTimeValue update(Object value) { + if (value instanceof LocalDateTime) { + set((LocalDateTime) value); + } else if (value instanceof String) { + update((String) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDateValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDateValue.java new file mode 100644 index 000000000..3cc9e6551 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDateValue.java @@ -0,0 +1,276 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of LocalDate. + */ +public class ClickHouseDateValue extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseDateValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDateValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseDateValue ? (ClickHouseDateValue) ((ClickHouseDateValue) ref).set(null) + : new ClickHouseDateValue(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseDateValue of(LocalDate value) { + return of(null, value); + } + + public static ClickHouseDateValue of(long epochDay) { + return of(null, LocalDate.ofEpochDay(epochDay)); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDateValue of(ClickHouseValue ref, LocalDate value) { + return ref instanceof ClickHouseDateValue ? (ClickHouseDateValue) ((ClickHouseDateValue) ref).update(value) + : new ClickHouseDateValue(value); + } + + protected ClickHouseDateValue(LocalDate value) { + super(value); + } + + @Override + public ClickHouseDateValue copy(boolean deep) { + return new ClickHouseDateValue(getValue()); + } + + @Override + public byte asByte() { + return (byte) asLong(); + } + + @Override + public short asShort() { + return (short) asLong(); + } + + @Override + public int asInteger() { + return (int) asLong(); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : getValue().toEpochDay(); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : BigInteger.valueOf(getValue().toEpochDay()); + } + + @Override + public float asFloat() { + return asLong(); + } + + @Override + public double asDouble() { + return asLong(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNullOrEmpty() ? null : new BigDecimal(asBigInteger(), scale); + } + + @Override + public LocalDate asDate() { + return getValue(); + } + + @Override + public LocalTime asTime() { + return isNullOrEmpty() ? null : LocalTime.ofSecondOfDay(0L); + } + + @Override + public LocalDateTime asDateTime(int scale) { + if (isNullOrEmpty()) { + return null; + } + + return LocalDateTime.of(getValue(), ClickHouseValues.TIME_ZERO); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = getValue().format(ClickHouseValues.DATE_FORMATTER); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + return new StringBuilder().append('\'').append(getValue().format(ClickHouseValues.DATE_FORMATTER)).append('\'').toString(); + } + + @Override + public ClickHouseDateValue update(byte value) { + set(LocalDate.ofEpochDay(value)); + return this; + } + + @Override + public ClickHouseDateValue update(short value) { + set(LocalDate.ofEpochDay(value)); + return this; + } + + @Override + public ClickHouseDateValue update(int value) { + set(LocalDate.ofEpochDay(value)); + return this; + } + + @Override + public ClickHouseDateValue update(long value) { + set(LocalDate.ofEpochDay(value)); + return this; + } + + @Override + public ClickHouseDateValue update(float value) { + set(LocalDate.ofEpochDay((long) value)); + return this; + } + + @Override + public ClickHouseDateValue update(double value) { + set(LocalDate.ofEpochDay((long) value)); + return this; + } + + @Override + public ClickHouseDateValue update(BigInteger value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDate.ofEpochDay(value.longValueExact())); + } + return this; + } + + @Override + public ClickHouseDateValue update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDate.ofEpochDay(value.longValueExact())); + } + return this; + } + + @Override + public ClickHouseDateValue update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDate.ofEpochDay(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseDateValue update(LocalDate value) { + set(value); + return this; + } + + @Override + public ClickHouseDateValue update(LocalTime value) { + return this; + } + + @Override + public ClickHouseDateValue update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.toLocalDate()); + } + return this; + } + + @Override + public ClickHouseDateValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalDate.parse(value, ClickHouseValues.DATE_FORMATTER)); + } + return this; + } + + @Override + public ClickHouseDateValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asDate()); + } + return this; + } + + @Override + public ClickHouseDateValue update(Object value) { + if (value instanceof LocalDate) { + set((LocalDate) value); + } else if (value instanceof LocalDateTime) { + set(((LocalDateTime) value).toLocalDate()); + } else if (value instanceof LocalTime) { + set(LocalDate.now()); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDoubleValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDoubleValue.java new file mode 100644 index 000000000..98b2f4a9d --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseDoubleValue.java @@ -0,0 +1,286 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of double. + */ +public class ClickHouseDoubleValue implements ClickHouseValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseDoubleValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDoubleValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseDoubleValue ? ((ClickHouseDoubleValue) ref).set(true, 0D) + : new ClickHouseDoubleValue(true, 0D); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseDoubleValue of(double value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseDoubleValue of(ClickHouseValue ref, double value) { + return ref instanceof ClickHouseDoubleValue ? ((ClickHouseDoubleValue) ref).set(false, value) + : new ClickHouseDoubleValue(false, value); + } + + private boolean isNull; + private double value; + + protected ClickHouseDoubleValue(boolean isNull, double value) { + set(isNull, value); + } + + protected ClickHouseDoubleValue set(boolean isNull, double value) { + this.isNull = isNull; + this.value = isNull ? 0L : value; + + return this; + } + + /** + * Gets value. + * + * @return value + */ + public double getValue() { + return value; + } + + @Override + public ClickHouseDoubleValue copy(boolean deep) { + return new ClickHouseDoubleValue(isNull, value); + } + + @Override + public boolean isNullOrEmpty() { + return isNull; + } + + @Override + public byte asByte() { + return (byte) value; + } + + @Override + public short asShort() { + return (short) value; + } + + @Override + public int asInteger() { + return (int) value; + } + + @Override + public long asLong() { + return (long) value; + } + + @Override + public BigInteger asBigInteger() { + return isNull ? null : BigDecimal.valueOf(value).toBigInteger(); + } + + @Override + public float asFloat() { + return (float) value; + } + + @Override + public double asDouble() { + return value; + } + + @Override + public BigDecimal asBigDecimal(int scale) { + if (isNull) { + return null; + } + + BigDecimal dec = BigDecimal.valueOf(value); + if (value == 0F || isInfinity() || isNaN()) { + dec = dec.setScale(scale); + } else { + int diff = scale - dec.scale(); + if (diff > 0) { + dec = dec.divide(BigDecimal.TEN.pow(diff + 1)); + } else if (diff < 0) { + dec = dec.setScale(scale); + } + } + return dec; + } + + @Override + public Object asObject() { + return isNull ? null : getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNull) { + return null; + } + + String str = String.valueOf(value); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseDoubleValue resetToNullOrEmpty() { + return set(true, 0D); + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } else if (isNaN()) { + return ClickHouseValues.NAN_EXPR; + } else if (value == Float.POSITIVE_INFINITY) { + return ClickHouseValues.INF_EXPR; + } else if (value == Float.NEGATIVE_INFINITY) { + return ClickHouseValues.NINF_EXPR; + } + + return String.valueOf(value); + } + + @Override + public ClickHouseDoubleValue update(boolean value) { + return set(false, value ? 1 : 0); + } + + @Override + public ClickHouseDoubleValue update(char value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(byte value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(short value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(int value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(long value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(float value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(double value) { + return set(false, value); + } + + @Override + public ClickHouseDoubleValue update(BigInteger value) { + return value == null ? resetToNullOrEmpty() : set(false, value.doubleValue()); + } + + @Override + public ClickHouseDoubleValue update(BigDecimal value) { + return value == null ? resetToNullOrEmpty() : set(false, value.doubleValue()); + } + + @Override + public ClickHouseDoubleValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : set(false, value.ordinal()); + } + + @Override + public ClickHouseDoubleValue update(String value) { + return value == null ? resetToNullOrEmpty() : set(false, Double.parseDouble(value)); + } + + @Override + public ClickHouseDoubleValue update(ClickHouseValue value) { + return value == null ? resetToNullOrEmpty() : set(false, value.asDouble()); + } + + @Override + public ClickHouseDoubleValue update(Object value) { + if (value instanceof Number) { + return set(false, ((Number) value).doubleValue()); + } else if (value instanceof ClickHouseValue) { + return set(false, ((ClickHouseValue) value).asDouble()); + } + + ClickHouseValue.super.update(value); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseDoubleValue v = (ClickHouseDoubleValue) obj; + return isNull == v.isNull && value == v.value; + } + + @Override + public int hashCode() { + // not going to use Objects.hash(isNull, value) due to autoboxing + long l = Double.doubleToLongBits(value); + return (31 + (isNull ? 1231 : 1237)) * 31 + (int) (l ^ (l >>> 32)); + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseEnumValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseEnumValue.java new file mode 100644 index 000000000..0b32b93b9 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseEnumValue.java @@ -0,0 +1,269 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of enum. + */ +public class ClickHouseEnumValue> extends ClickHouseObjectValue { + static final String ERROR_NO_ENUM_TYPE = "Failed to convert value due to lack of enum type: "; + + /** + * Create a new instance representing null value. + * + * @param enum type + * @return new instance representing null value + */ + public static > ClickHouseEnumValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param enum type + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + @SuppressWarnings("unchecked") + public static > ClickHouseEnumValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseEnumValue ? (ClickHouseEnumValue) ((ClickHouseEnumValue) ref).set(null) + : new ClickHouseEnumValue<>(null); + } + + /** + * Wrap the given value. + * + * @param enum type + * @param value value + * @return object representing the value + */ + public static > ClickHouseEnumValue of(T value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param enum type + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + @SuppressWarnings("unchecked") + public static > ClickHouseEnumValue of(ClickHouseValue ref, T value) { + return ref instanceof ClickHouseEnumValue + ? (ClickHouseEnumValue) ((ClickHouseEnumValue) ref).update(value) + : new ClickHouseEnumValue<>(value); + } + + protected ClickHouseEnumValue(T value) { + super(value); + } + + @Override + public ClickHouseEnumValue copy(boolean deep) { + return new ClickHouseEnumValue<>(getValue()); + } + + @Override + public byte asByte() { + return isNullOrEmpty() ? (byte) 0 : (byte) getValue().ordinal(); + } + + @Override + public short asShort() { + return isNullOrEmpty() ? (short) 0 : (short) getValue().ordinal(); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : getValue().ordinal(); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : getValue().ordinal(); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : BigInteger.valueOf(getValue().ordinal()); + } + + @Override + public float asFloat() { + return isNullOrEmpty() ? 0F : getValue().ordinal(); + } + + @Override + public double asDouble() { + return isNullOrEmpty() ? 0D : getValue().ordinal(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNullOrEmpty() ? null : BigDecimal.valueOf(getValue().ordinal(), scale); + } + + @Override + @SuppressWarnings("unchecked") + public > E asEnum(Class enumType) { + return (E) getValue(); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = String.valueOf(getValue().name()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public String toSqlExpression() { + return ClickHouseValues.convertToQuotedString(asString(0, null)); + } + + @Override + public ClickHouseEnumValue update(boolean value) { + return update(value ? 1 : 0); + } + + @Override + public ClickHouseEnumValue update(char value) { + return update((int) value); + } + + @Override + public ClickHouseEnumValue update(byte value) { + return update((int) value); + } + + @Override + public ClickHouseEnumValue update(short value) { + return update((int) value); + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseEnumValue update(int value) { + if (isNullOrEmpty()) { + throw new IllegalArgumentException(ERROR_NO_ENUM_TYPE + value); + } + + Class clazz = (Class) getValue().getClass(); + for (T t : clazz.getEnumConstants()) { + if (t.ordinal() == value) { + return update(t); + } + } + + throw new IllegalArgumentException(); + } + + @Override + public ClickHouseEnumValue update(long value) { + return update((int) value); + } + + @Override + public ClickHouseEnumValue update(float value) { + return update((int) value); + } + + @Override + public ClickHouseEnumValue update(double value) { + return update((int) value); + } + + @Override + public ClickHouseEnumValue update(BigInteger value) { + if (value == null) { + resetToNullOrEmpty(); + return this; + } + + return update(value.intValueExact()); + } + + @Override + public ClickHouseEnumValue update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + return this; + } + + return update(value.intValueExact()); + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseEnumValue update(Enum value) { + set((T) value); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseEnumValue update(ClickHouseValue value) { + if (value == null || value.isNullOrEmpty()) { + resetToNullOrEmpty(); + } else if (value instanceof ClickHouseEnumValue) { + set(((ClickHouseEnumValue) value).getValue()); + } else if (isNullOrEmpty()) { + throw new IllegalArgumentException(ERROR_NO_ENUM_TYPE + value); + } else { + set(value.asEnum(isNullOrEmpty() ? null : (Class) getValue().getClass())); + } + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseEnumValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (isNullOrEmpty()) { + throw new IllegalArgumentException(ERROR_NO_ENUM_TYPE + value); + } else { + set((T) Enum.valueOf(getValue().getClass(), value)); + } + + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseEnumValue update(Object value) { + if (value instanceof Enum) { + set((T) value); + return this; + } else if (value instanceof ClickHouseEnumValue) { + set(((ClickHouseEnumValue) value).getValue()); + return this; + } + + super.update(value); + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseExternalTable.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseExternalTable.java new file mode 100644 index 000000000..52f45e5d3 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseExternalTable.java @@ -0,0 +1,132 @@ +package com.clickhouse.client.data; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseFormat; + +public class ClickHouseExternalTable { + public static class Builder { + private String name; + private InputStream content; + private ClickHouseFormat format; + private List columns; + + protected Builder() { + columns = new LinkedList<>(); + } + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withContent(InputStream content) { + this.content = content; + return this; + } + + public Builder withFormat(ClickHouseFormat format) { + this.format = format; + return this; + } + + public Builder addColumn(String name, String type) { + this.columns.add(ClickHouseColumn.of(name, type)); + return this; + } + + public Builder removeColumn(String name) { + Iterator iterator = columns.iterator(); + while (iterator.hasNext()) { + ClickHouseColumn c = iterator.next(); + if (c.getColumnName().equals(name)) { + iterator.remove(); + } + } + + return this; + } + + public Builder removeColumn(ClickHouseColumn column) { + this.columns.remove(column); + return this; + } + + public Builder withColumns(Collection columns) { + if (columns != null) { + for (ClickHouseColumn c : columns) { + this.columns.add(c); + } + } + return this; + } + + public ClickHouseExternalTable build() { + return new ClickHouseExternalTable(name, content, format, columns); + } + } + + public static Builder builder() { + return new Builder(); + } + + private final String name; + private final InputStream content; + private final ClickHouseFormat format; + private final List columns; + + private final String structure; + + protected ClickHouseExternalTable(String name, InputStream content, ClickHouseFormat format, + Collection columns) { + this.name = name == null ? "" : name.trim(); + this.content = ClickHouseChecker.nonNull(content, "content"); + this.format = format == null ? ClickHouseFormat.TabSeparated : format; + + int size = columns == null ? 0 : columns.size(); + if (size == 0) { + this.columns = Collections.emptyList(); + this.structure = ""; + } else { + StringBuilder builder = new StringBuilder(); + List list = new ArrayList<>(size); + for (ClickHouseColumn c : columns) { + list.add(c); + builder.append(c.getColumnName()).append(' ').append(c.getOriginalTypeName()).append(','); + } + this.columns = Collections.unmodifiableList(list); + this.structure = builder.deleteCharAt(builder.length() - 1).toString(); + } + } + + public boolean hasName() { + return !name.isEmpty(); + } + + public String getName() { + return name; + } + + public InputStream getContent() { + return content; + } + + public ClickHouseFormat getFormat() { + return format; + } + + public List getColumns() { + return columns; + } + + public String getStructure() { + return structure; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseFloatValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseFloatValue.java new file mode 100644 index 000000000..8bcd300d9 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseFloatValue.java @@ -0,0 +1,305 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of float. + */ +public class ClickHouseFloatValue implements ClickHouseValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseFloatValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseFloatValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseFloatValue ? ((ClickHouseFloatValue) ref).set(true, 0F) + : new ClickHouseFloatValue(true, 0F); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseFloatValue of(float value) { + return of(null, value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseFloatValue of(Number value) { + return value == null ? ofNull(null) : of(null, value.floatValue()); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseFloatValue of(ClickHouseValue ref, float value) { + return ref instanceof ClickHouseFloatValue ? ((ClickHouseFloatValue) ref).set(false, value) + : new ClickHouseFloatValue(false, value); + } + + private boolean isNull; + private float value; + + protected ClickHouseFloatValue(boolean isNull, float value) { + set(isNull, value); + } + + protected ClickHouseFloatValue set(boolean isNull, float value) { + this.isNull = isNull; + this.value = isNull ? 0F : value; + + return this; + } + + /** + * Gets value. + * + * @return value + */ + public float getValue() { + return value; + } + + @Override + public ClickHouseFloatValue copy(boolean deep) { + return new ClickHouseFloatValue(isNull, value); + } + + @Override + public boolean isInfinity() { + return value == Float.POSITIVE_INFINITY || value == Float.NEGATIVE_INFINITY; + } + + @Override + public boolean isNaN() { + return value != value; + } + + @Override + public boolean isNullOrEmpty() { + return isNull; + } + + @Override + public byte asByte() { + return (byte) value; + } + + @Override + public short asShort() { + return (short) value; + } + + @Override + public int asInteger() { + return (int) value; + } + + @Override + public long asLong() { + return (long) value; + } + + @Override + public BigInteger asBigInteger() { + return isNull ? null : BigDecimal.valueOf(value).toBigInteger(); + } + + @Override + public float asFloat() { + return value; + } + + @Override + public double asDouble() { + return value; + } + + @Override + public BigDecimal asBigDecimal(int scale) { + if (isNull) { + return null; + } + + BigDecimal dec = BigDecimal.valueOf(value); + if (value == 0F) { + dec = dec.setScale(scale); + } else { + int diff = scale - dec.scale(); + if (diff > 0) { + dec = dec.divide(BigDecimal.TEN.pow(diff + 1)); + } else if (diff < 0) { + dec = dec.setScale(scale); + } + } + return dec; + } + + @Override + public Object asObject() { + return isNull ? null : getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNull) { + return null; + } + + String str = String.valueOf(value); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseFloatValue resetToNullOrEmpty() { + return set(true, 0F); + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } else if (isNaN()) { + return ClickHouseValues.NAN_EXPR; + } else if (value == Float.POSITIVE_INFINITY) { + return ClickHouseValues.INF_EXPR; + } else if (value == Float.NEGATIVE_INFINITY) { + return ClickHouseValues.NINF_EXPR; + } + + return String.valueOf(value); + } + + @Override + public ClickHouseFloatValue update(boolean value) { + return set(false, value ? 1F : 0F); + } + + @Override + public ClickHouseFloatValue update(char value) { + return set(false, value); + } + + @Override + public ClickHouseFloatValue update(byte value) { + return set(false, value); + } + + @Override + public ClickHouseFloatValue update(short value) { + return set(false, value); + } + + @Override + public ClickHouseFloatValue update(int value) { + return set(false, value); + } + + @Override + public ClickHouseFloatValue update(long value) { + return set(false, value); + } + + @Override + public ClickHouseFloatValue update(float value) { + return set(false, value); + } + + @Override + public ClickHouseFloatValue update(double value) { + return set(false, (float) value); + } + + @Override + public ClickHouseFloatValue update(BigInteger value) { + return value == null ? resetToNullOrEmpty() : set(false, value.floatValue()); + } + + @Override + public ClickHouseFloatValue update(BigDecimal value) { + return value == null ? resetToNullOrEmpty() : set(false, value.floatValue()); + } + + @Override + public ClickHouseFloatValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : set(false, value.ordinal()); + } + + @Override + public ClickHouseFloatValue update(String value) { + return value == null ? resetToNullOrEmpty() : set(false, Float.parseFloat(value)); + } + + @Override + public ClickHouseFloatValue update(ClickHouseValue value) { + return value == null ? resetToNullOrEmpty() : set(false, value.asFloat()); + } + + @Override + public ClickHouseFloatValue update(Object value) { + if (value instanceof Number) { + return set(false, ((Number) value).floatValue()); + } else if (value instanceof ClickHouseValue) { + return set(false, ((ClickHouseValue) value).asFloat()); + } + + ClickHouseValue.super.update(value); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseFloatValue v = (ClickHouseFloatValue) obj; + return isNull == v.isNull && value == v.value; + } + + @Override + public int hashCode() { + // not going to use Objects.hash(isNull, value) due to autoboxing + return (31 + (isNull ? 1231 : 1237)) * 31 + Float.floatToIntBits(value); + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoMultiPolygonValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoMultiPolygonValue.java new file mode 100644 index 000000000..c48558736 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoMultiPolygonValue.java @@ -0,0 +1,429 @@ +package com.clickhouse.client.data; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of MultiPolygon. + */ +public class ClickHouseGeoMultiPolygonValue extends ClickHouseObjectValue { + static final double[][][][] EMPTY_VALUE = new double[0][][][]; + + /** + * Creates an empty multi-polygon. + * + * @return empty multi-polygon + */ + public static ClickHouseGeoMultiPolygonValue ofEmpty() { + return of(null, EMPTY_VALUE); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseGeoMultiPolygonValue of(double[][][][] value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseGeoMultiPolygonValue of(ClickHouseValue ref, double[][][][] value) { + return ref instanceof ClickHouseGeoMultiPolygonValue ? ((ClickHouseGeoMultiPolygonValue) ref).set(value) + : new ClickHouseGeoMultiPolygonValue(value); + } + + protected static double[][][][] check(double[][][][] value) { + for (int i = 0, len = ClickHouseChecker.nonNull(value, "multi-polygon").length; i < len; i++) { + ClickHouseGeoPolygonValue.check(value[i]); + } + + return value; + } + + protected static String convert(double[][][][] value, int length) { + StringBuilder builder = new StringBuilder().append('['); + for (int i = 0, len = value.length; i < len; i++) { + builder.append(ClickHouseGeoPolygonValue.convert(value[i], 0)).append(','); + } + + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + String str = builder.append(']').toString(); + return length > 0 ? ClickHouseChecker.notWithDifferentLength(str, length) : str; + } + + protected ClickHouseGeoMultiPolygonValue(double[][][][] value) { + super(value); + } + + @Override + protected ClickHouseGeoMultiPolygonValue set(double[][][][] value) { + return (ClickHouseGeoMultiPolygonValue) super.set(check(value)); + } + + @Override + public ClickHouseGeoMultiPolygonValue copy(boolean deep) { + if (!deep) { + return new ClickHouseGeoMultiPolygonValue(getValue()); + } + + double[][][][] value = getValue(); + double[][][][] newValue = new double[value.length][][][]; + int index = 0; + for (double[][][] v1 : value) { + double[][][] nv1 = new double[v1.length][][]; + int i = 0; + for (double[][] v2 : v1) { + double[][] nv2 = new double[v2.length][]; + int j = 0; + for (double[] v3 : v2) { + nv2[j++] = Arrays.copyOf(v3, v3.length); + } + nv1[i++] = nv2; + } + newValue[index++] = nv1; + } + return new ClickHouseGeoMultiPolygonValue(newValue); + } + + @Override + public Object[] asArray() { + return getValue(); + } + + @Override + @SuppressWarnings("unchecked") + public T[] asArray(Class clazz) { + double[][][][] v = getValue(); + T[] array = (T[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), v.length); + int index = 0; + for (double[][][] d : v) { + array[index++] = clazz.cast(d); + } + return array; + } + + @Override + public Map asMap(Class keyClass, Class valueClass) { + if (keyClass == null || valueClass == null) { + throw new IllegalArgumentException("Non-null key and value classes are required"); + } + Map map = new LinkedHashMap<>(); + int index = 1; + for (double[][][] d : getValue()) { + map.put(keyClass.cast(index++), valueClass.cast(d)); + } + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + @Override + public String asString(int length, Charset charset) { + return convert(getValue(), length); + } + + @Override + public boolean isNullOrEmpty() { + return getValue().length == 0; + } + + @Override + public ClickHouseGeoMultiPolygonValue resetToNullOrEmpty() { + set(EMPTY_VALUE); + return this; + } + + @Override + public String toSqlExpression() { + return convert(getValue(), 0); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(boolean value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BOOLEAN, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(boolean[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { + new double[][][] { new double[][] { new double[] { value[0] ? 1 : 0, value[1] ? 0 : 1 } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(char value) { + throw newUnsupportedException(ClickHouseValues.TYPE_CHAR, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(char[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { new double[] { value[0], value[1] } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(byte value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BYTE, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(byte[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { new double[] { value[0], value[1] } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(short value) { + throw newUnsupportedException(ClickHouseValues.TYPE_SHORT, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(short[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { new double[] { value[0], value[1] } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(int value) { + throw newUnsupportedException(ClickHouseValues.TYPE_INT, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(int[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { new double[] { value[0], value[1] } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(long value) { + throw newUnsupportedException(ClickHouseValues.TYPE_LONG, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(long[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { new double[] { value[0], value[1] } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(float value) { + throw newUnsupportedException(ClickHouseValues.TYPE_FLOAT, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(float[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { new double[] { value[0], value[1] } } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(double value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DOUBLE, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(double[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][][] { new double[][][] { new double[][] { value } } }); + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(BigInteger value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_INTEGER, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(BigDecimal value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_DECIMAL, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Enum value) { + throw newUnsupportedException(ClickHouseValues.TYPE_ENUM, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Inet4Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV4, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Inet6Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV6, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(LocalDate value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(LocalTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_TIME, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(LocalDateTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE_TIME, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Collection value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[][][][] { new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } } }); + } else { + set(new double[][][][] { new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } } }); + } + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Enumeration value) { + if (value == null || !value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value.nextElement(); + if (!value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v2 = value.nextElement(); + if (value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + + if (v1 instanceof Number) { + set(new double[][][][] { new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } } }); + } else { + set(new double[][][][] { new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } } }); + } + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Map value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.values().iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[][][][] { new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } } }); + } else { + set(new double[][][][] { new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } } }); + } + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(String value) { + throw newUnsupportedException(ClickHouseValues.TYPE_STRING, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(UUID value) { + throw newUnsupportedException(ClickHouseValues.TYPE_UUID, ClickHouseValues.TYPE_MULTI_POLYGON); + } + + @Override + public ClickHouseGeoMultiPolygonValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (value instanceof ClickHouseGeoMultiPolygonValue) { + set(((ClickHouseGeoMultiPolygonValue) value).getValue()); + } else { + update(value.asArray()); + } + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Object[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value[0]; + Object v2 = value[1]; + if (v1 instanceof Number) { + set(new double[][][][] { new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } } }); + } else { + set(new double[][][][] { new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } } }); + } + return this; + } + + @Override + public ClickHouseGeoMultiPolygonValue update(Object value) { + if (value instanceof double[][][][]) { + update((double[]) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoPointValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoPointValue.java new file mode 100644 index 000000000..52d039d16 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoPointValue.java @@ -0,0 +1,366 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of Point. + */ +public class ClickHouseGeoPointValue extends ClickHouseObjectValue { + /** + * Creats a point of origin. + * + * @return point of origin + */ + public static ClickHouseGeoPointValue ofOrigin() { + return of(null, new double[] { 0D, 0D }); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseGeoPointValue of(double[] value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseGeoPointValue of(ClickHouseValue ref, double[] value) { + return ref instanceof ClickHouseGeoPointValue ? ((ClickHouseGeoPointValue) ref).set(value) + : new ClickHouseGeoPointValue(value); + } + + protected static double[] check(double[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException("Non-null X and Y coordinates are required"); + } + + return value; + } + + protected static String convert(double[] value, int length) { + String str = new StringBuilder().append('(').append(value[0]).append(',').append(value[1]).append(')') + .toString(); + return length > 0 ? ClickHouseChecker.notWithDifferentLength(str, length) : str; + } + + protected ClickHouseGeoPointValue(double[] value) { + super(value); + } + + @Override + protected ClickHouseGeoPointValue set(double[] value) { + super.set(check(value)); + return this; + } + + @Override + public ClickHouseGeoPointValue copy(boolean deep) { + if (!deep) { + return new ClickHouseGeoPointValue(getValue()); + } + + double[] value = getValue(); + double[] newValue = new double[value.length]; + System.arraycopy(value, 0, newValue, 0, value.length); + return new ClickHouseGeoPointValue(newValue); + } + + @Override + public String asString(int length, Charset charset) { + return convert(getValue(), length); + } + + @Override + public boolean isNullOrEmpty() { + return false; + } + + @Override + public ClickHouseGeoPointValue resetToNullOrEmpty() { + set(new double[] { 0D, 0D }); + return this; + } + + @Override + public String toSqlExpression() { + return convert(getValue(), 0); + } + + @Override + public ClickHouseGeoPointValue update(boolean value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BOOLEAN, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(boolean[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0] ? 1 : 0, value[1] ? 0 : 1 }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(char value) { + throw newUnsupportedException(ClickHouseValues.TYPE_CHAR, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(char[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0], value[1] }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(byte value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BYTE, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(byte[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0], value[1] }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(short value) { + throw newUnsupportedException(ClickHouseValues.TYPE_SHORT, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(short[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0], value[1] }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(int value) { + throw newUnsupportedException(ClickHouseValues.TYPE_INT, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(int[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0], value[1] }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(long value) { + throw newUnsupportedException(ClickHouseValues.TYPE_LONG, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(long[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0], value[1] }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(float value) { + throw newUnsupportedException(ClickHouseValues.TYPE_FLOAT, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(float[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[] { value[0], value[1] }); + return this; + } + + @Override + public ClickHouseGeoPointValue update(double value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DOUBLE, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(double[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(value); + return this; + } + + @Override + public ClickHouseGeoPointValue update(BigInteger value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_INTEGER, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(BigDecimal value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_DECIMAL, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(Enum value) { + throw newUnsupportedException(ClickHouseValues.TYPE_ENUM, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(Inet4Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV4, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(Inet6Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV6, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(LocalDate value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(LocalTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_TIME, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(LocalDateTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE_TIME, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(Collection value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() }); + } else { + set(new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) }); + } + return this; + } + + @Override + public ClickHouseGeoPointValue update(Enumeration value) { + if (value == null || !value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value.nextElement(); + if (!value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v2 = value.nextElement(); + if (value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + + if (v1 instanceof Number) { + set(new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() }); + } else { + set(new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) }); + } + return this; + } + + @Override + public ClickHouseGeoPointValue update(Map value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.values().iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() }); + } else { + set(new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) }); + } + return this; + } + + @Override + public ClickHouseGeoPointValue update(String value) { + throw newUnsupportedException(ClickHouseValues.TYPE_STRING, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(UUID value) { + throw newUnsupportedException(ClickHouseValues.TYPE_UUID, ClickHouseValues.TYPE_POINT); + } + + @Override + public ClickHouseGeoPointValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (value instanceof ClickHouseGeoPointValue) { + set(((ClickHouseGeoPointValue) value).getValue()); + } else { + update(value.asArray()); + } + return this; + } + + @Override + public ClickHouseGeoPointValue update(Object[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value[0]; + Object v2 = value[1]; + if (v1 instanceof Number) { + set(new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() }); + } else { + set(new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) }); + } + return this; + } + + @Override + public ClickHouseGeoPointValue update(Object value) { + if (value instanceof double[]) { + update((double[]) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoPolygonValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoPolygonValue.java new file mode 100644 index 000000000..9b3e064fe --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoPolygonValue.java @@ -0,0 +1,423 @@ +package com.clickhouse.client.data; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of Polygon. + */ +public class ClickHouseGeoPolygonValue extends ClickHouseObjectValue { + static final double[][][] EMPTY_VALUE = new double[0][][]; + + /** + * Creates an empty polygon. + * + * @return empty polygon + */ + public static ClickHouseGeoPolygonValue ofEmpty() { + return of(null, EMPTY_VALUE); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseGeoPolygonValue of(double[][][] value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseGeoPolygonValue of(ClickHouseValue ref, double[][][] value) { + return ref instanceof ClickHouseGeoPolygonValue ? ((ClickHouseGeoPolygonValue) ref).set(value) + : new ClickHouseGeoPolygonValue(value); + } + + protected static double[][][] check(double[][][] value) { + for (int i = 0, len = ClickHouseChecker.nonNull(value, ClickHouseValues.TYPE_POLYGON).length; i < len; i++) { + ClickHouseGeoRingValue.check(value[i]); + } + + return value; + } + + protected static String convert(double[][][] value, int length) { + StringBuilder builder = new StringBuilder().append('['); + for (int i = 0, len = value.length; i < len; i++) { + builder.append(ClickHouseGeoRingValue.convert(value[i], 0)).append(','); + } + + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + String str = builder.append(']').toString(); + return length > 0 ? ClickHouseChecker.notWithDifferentLength(str, length) : str; + } + + protected ClickHouseGeoPolygonValue(double[][][] value) { + super(value); + } + + @Override + protected ClickHouseGeoPolygonValue set(double[][][] value) { + return (ClickHouseGeoPolygonValue) super.set(check(value)); + } + + @Override + public ClickHouseGeoPolygonValue copy(boolean deep) { + if (!deep) { + return new ClickHouseGeoPolygonValue(getValue()); + } + + double[][][] value = getValue(); + double[][][] newValue = new double[value.length][][]; + int index = 0; + for (double[][] v : value) { + double[][] nv = new double[v.length][]; + int i = 0; + for (double[] d : v) { + nv[i++] = Arrays.copyOf(d, d.length); + } + newValue[index++] = nv; + } + return new ClickHouseGeoPolygonValue(newValue); + } + + @Override + public Object[] asArray() { + return getValue(); + } + + @Override + @SuppressWarnings("unchecked") + public T[] asArray(Class clazz) { + double[][][] v = getValue(); + T[] array = (T[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), v.length); + int index = 0; + for (double[][] d : v) { + array[index++] = clazz.cast(d); + } + return array; + } + + @Override + public Map asMap(Class keyClass, Class valueClass) { + if (keyClass == null || valueClass == null) { + throw new IllegalArgumentException("Non-null key and value classes are required"); + } + Map map = new LinkedHashMap<>(); + int index = 1; + for (double[][] d : getValue()) { + map.put(keyClass.cast(index++), valueClass.cast(d)); + } + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + @Override + public String asString(int length, Charset charset) { + return convert(getValue(), length); + } + + @Override + public boolean isNullOrEmpty() { + return getValue().length == 0; + } + + @Override + public ClickHouseGeoPolygonValue resetToNullOrEmpty() { + set(EMPTY_VALUE); + return this; + } + + @Override + public String toSqlExpression() { + return convert(getValue(), 0); + } + + @Override + public ClickHouseGeoPolygonValue update(boolean value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BOOLEAN, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(boolean[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0] ? 1 : 0, value[1] ? 0 : 1 } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(char value) { + throw newUnsupportedException(ClickHouseValues.TYPE_CHAR, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(char[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0], value[1] } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(byte value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BYTE, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(byte[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0], value[1] } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(short value) { + throw newUnsupportedException(ClickHouseValues.TYPE_SHORT, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(short[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0], value[1] } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(int value) { + throw newUnsupportedException(ClickHouseValues.TYPE_INT, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(int[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0], value[1] } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(long value) { + throw newUnsupportedException(ClickHouseValues.TYPE_LONG, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(long[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0], value[1] } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(float value) { + throw newUnsupportedException(ClickHouseValues.TYPE_FLOAT, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(float[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { new double[] { value[0], value[1] } } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(double value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DOUBLE, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(double[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][][] { new double[][] { value } }); + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(BigInteger value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_INTEGER, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(BigDecimal value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_DECIMAL, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(Enum value) { + throw newUnsupportedException(ClickHouseValues.TYPE_ENUM, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(Inet4Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV4, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(Inet6Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV6, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(LocalDate value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(LocalTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_TIME, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(LocalDateTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE_TIME, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(Collection value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } }); + } else { + set(new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } }); + } + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(Enumeration value) { + if (value == null || !value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value.nextElement(); + if (!value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v2 = value.nextElement(); + if (value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + + if (v1 instanceof Number) { + set(new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } }); + } else { + set(new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } }); + } + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(Map value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.values().iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } }); + } else { + set(new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } }); + } + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(String value) { + throw newUnsupportedException(ClickHouseValues.TYPE_STRING, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(UUID value) { + throw newUnsupportedException(ClickHouseValues.TYPE_UUID, ClickHouseValues.TYPE_POLYGON); + } + + @Override + public ClickHouseGeoPolygonValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (value instanceof ClickHouseGeoPolygonValue) { + set(((ClickHouseGeoPolygonValue) value).getValue()); + } else { + update(value.asArray()); + } + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(Object[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value[0]; + Object v2 = value[1]; + if (v1 instanceof Number) { + set(new double[][][] { + new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } } }); + } else { + set(new double[][][] { new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } } }); + } + return this; + } + + @Override + public ClickHouseGeoPolygonValue update(Object value) { + if (value instanceof double[][][]) { + update((double[]) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoRingValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoRingValue.java new file mode 100644 index 000000000..5d325621e --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseGeoRingValue.java @@ -0,0 +1,416 @@ +package com.clickhouse.client.data; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of Ring. + */ +public class ClickHouseGeoRingValue extends ClickHouseObjectValue { + static final double[][] EMPTY_VALUE = new double[0][]; + + /** + * Creates an empty ring. + * + * @return empty ring + */ + public static ClickHouseGeoRingValue ofEmpty() { + return of(null, EMPTY_VALUE); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseGeoRingValue of(double[][] value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseGeoRingValue of(ClickHouseValue ref, double[][] value) { + return ref instanceof ClickHouseGeoRingValue ? ((ClickHouseGeoRingValue) ref).set(value) + : new ClickHouseGeoRingValue(value); + } + + protected static double[][] check(double[][] value) { + for (int i = 0, len = ClickHouseChecker.nonNull(value, ClickHouseValues.TYPE_RING).length; i < len; i++) { + ClickHouseGeoPointValue.check(value[i]); + } + + return value; + } + + protected static String convert(double[][] value, int length) { + StringBuilder builder = new StringBuilder().append('['); + for (int i = 0, len = value.length; i < len; i++) { + builder.append(ClickHouseGeoPointValue.convert(value[i], 0)).append(','); + } + + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + String str = builder.append(']').toString(); + return length > 0 ? ClickHouseChecker.notWithDifferentLength(str, length) : str; + } + + protected ClickHouseGeoRingValue(double[][] value) { + super(value); + } + + @Override + protected ClickHouseGeoRingValue set(double[][] value) { + super.set(check(value)); + return this; + } + + @Override + public ClickHouseGeoRingValue copy(boolean deep) { + if (!deep) { + return new ClickHouseGeoRingValue(getValue()); + } + + double[][] value = getValue(); + double[][] newValue = new double[value.length][]; + int index = 0; + for (double[] v : value) { + newValue[index++] = Arrays.copyOf(v, v.length); + } + return new ClickHouseGeoRingValue(newValue); + } + + @Override + public Object[] asArray() { + return getValue(); + } + + @Override + @SuppressWarnings("unchecked") + public T[] asArray(Class clazz) { + double[][] v = getValue(); + T[] array = (T[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), v.length); + int index = 0; + for (double[] d : v) { + array[index++] = clazz.cast(d); + } + return array; + } + + @Override + public Map asMap(Class keyClass, Class valueClass) { + if (keyClass == null || valueClass == null) { + throw new IllegalArgumentException("Non-null key and value classes are required"); + } + Map map = new LinkedHashMap<>(); + int index = 1; + for (double[] d : getValue()) { + map.put(keyClass.cast(index++), valueClass.cast(d)); + } + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + @Override + public String asString(int length, Charset charset) { + return convert(getValue(), length); + } + + @Override + public boolean isNullOrEmpty() { + double[][] value = getValue(); + return value == null || value.length == 0; + } + + @Override + public ClickHouseGeoRingValue resetToNullOrEmpty() { + set(EMPTY_VALUE); + return this; + } + + @Override + public String toSqlExpression() { + return convert(getValue(), 0); + } + + @Override + public ClickHouseGeoRingValue update(boolean value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BOOLEAN, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(boolean[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0] ? 1 : 0, value[1] ? 0 : 1 } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(char value) { + throw newUnsupportedException(ClickHouseValues.TYPE_CHAR, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(char[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0], value[1] } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(byte value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BYTE, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(byte[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0], value[1] } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(short value) { + throw newUnsupportedException(ClickHouseValues.TYPE_SHORT, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(short[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0], value[1] } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(int value) { + throw newUnsupportedException(ClickHouseValues.TYPE_INT, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(int[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0], value[1] } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(long value) { + throw newUnsupportedException(ClickHouseValues.TYPE_LONG, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(long[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0], value[1] } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(float value) { + throw newUnsupportedException(ClickHouseValues.TYPE_FLOAT, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(float[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { new double[] { value[0], value[1] } }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(double value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DOUBLE, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(double[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + Arrays.toString(value)); + } + set(new double[][] { value }); + return this; + } + + @Override + public ClickHouseGeoRingValue update(BigInteger value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_INTEGER, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(BigDecimal value) { + throw newUnsupportedException(ClickHouseValues.TYPE_BIG_DECIMAL, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(Enum value) { + throw newUnsupportedException(ClickHouseValues.TYPE_ENUM, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(Inet4Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV4, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(Inet6Address value) { + throw newUnsupportedException(ClickHouseValues.TYPE_IPV6, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(LocalDate value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(LocalTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_TIME, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(LocalDateTime value) { + throw newUnsupportedException(ClickHouseValues.TYPE_DATE_TIME, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(Collection value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } }); + } else { + set(new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } }); + } + return this; + } + + @Override + public ClickHouseGeoRingValue update(Enumeration value) { + if (value == null || !value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value.nextElement(); + if (!value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v2 = value.nextElement(); + if (value.hasMoreElements()) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + + if (v1 instanceof Number) { + set(new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } }); + } else { + set(new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } }); + } + return this; + } + + @Override + public ClickHouseGeoRingValue update(Map value) { + if (value == null || value.size() != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Iterator i = value.values().iterator(); + Object v1 = i.next(); + Object v2 = i.next(); + if (v1 instanceof Number) { + set(new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } }); + } else { + set(new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } }); + } + return this; + } + + @Override + public ClickHouseGeoRingValue update(String value) { + throw newUnsupportedException(ClickHouseValues.TYPE_STRING, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(UUID value) { + throw newUnsupportedException(ClickHouseValues.TYPE_UUID, ClickHouseValues.TYPE_RING); + } + + @Override + public ClickHouseGeoRingValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (value instanceof ClickHouseGeoRingValue) { + set(((ClickHouseGeoRingValue) value).getValue()); + } else { + update(value.asArray()); + } + return this; + } + + @Override + public ClickHouseGeoRingValue update(Object[] value) { + if (value == null || value.length != 2) { + throw new IllegalArgumentException(ClickHouseValues.ERROR_INVALID_POINT + value); + } + Object v1 = value[0]; + Object v2 = value[1]; + if (v1 instanceof Number) { + set(new double[][] { new double[] { ((Number) v1).doubleValue(), ((Number) v2).doubleValue() } }); + } else { + set(new double[][] { + new double[] { Double.parseDouble(v1.toString()), Double.parseDouble(v2.toString()) } }); + } + return this; + } + + @Override + public ClickHouseGeoRingValue update(Object value) { + if (value instanceof double[][]) { + set((double[][]) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIntegerValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIntegerValue.java new file mode 100644 index 000000000..61e89e943 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIntegerValue.java @@ -0,0 +1,270 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of int. + */ +public class ClickHouseIntegerValue implements ClickHouseValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseIntegerValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseIntegerValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseIntegerValue ? ((ClickHouseIntegerValue) ref).set(true, 0) + : new ClickHouseIntegerValue(true, 0); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseIntegerValue of(int value) { + return of(null, value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseIntegerValue of(Number value) { + return value == null ? ofNull(null) : of(null, value.intValue()); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseIntegerValue of(ClickHouseValue ref, int value) { + return ref instanceof ClickHouseIntegerValue ? ((ClickHouseIntegerValue) ref).set(false, value) + : new ClickHouseIntegerValue(false, value); + } + + private boolean isNull; + private int value; + + protected ClickHouseIntegerValue(boolean isNull, int value) { + set(isNull, value); + } + + protected ClickHouseIntegerValue set(boolean isNull, int value) { + this.isNull = isNull; + this.value = isNull ? 0 : value; + + return this; + } + + /** + * Gets value. + * + * @return value + */ + public int getValue() { + return value; + } + + @Override + public ClickHouseIntegerValue copy(boolean deep) { + return new ClickHouseIntegerValue(isNull, value); + } + + @Override + public boolean isNullOrEmpty() { + return isNull; + } + + @Override + public byte asByte() { + return (byte) value; + } + + @Override + public short asShort() { + return (short) value; + } + + @Override + public int asInteger() { + return value; + } + + @Override + public long asLong() { + return value; + } + + @Override + public BigInteger asBigInteger() { + return isNull ? null : BigInteger.valueOf(value); + } + + @Override + public float asFloat() { + return value; + } + + @Override + public double asDouble() { + return value; + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNull ? null : BigDecimal.valueOf(value, scale); + } + + @Override + public Object asObject() { + return isNull ? null : getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNull) { + return null; + } + + String str = String.valueOf(value); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseIntegerValue resetToNullOrEmpty() { + return set(true, 0); + } + + @Override + public String toSqlExpression() { + return isNullOrEmpty() ? ClickHouseValues.NULL_EXPR : String.valueOf(value); + } + + @Override + public ClickHouseIntegerValue update(boolean value) { + return set(false, value ? 1 : 0); + } + + @Override + public ClickHouseIntegerValue update(char value) { + return set(false, value); + } + + @Override + public ClickHouseIntegerValue update(byte value) { + return set(false, value); + } + + @Override + public ClickHouseIntegerValue update(short value) { + return set(false, value); + } + + @Override + public ClickHouseIntegerValue update(int value) { + return set(false, value); + } + + @Override + public ClickHouseIntegerValue update(long value) { + return set(false, (int) value); + } + + @Override + public ClickHouseIntegerValue update(float value) { + return set(false, (int) value); + } + + @Override + public ClickHouseIntegerValue update(double value) { + return set(false, (int) value); + } + + @Override + public ClickHouseIntegerValue update(BigInteger value) { + return value == null ? resetToNullOrEmpty() : set(false, value.intValueExact()); + } + + @Override + public ClickHouseIntegerValue update(BigDecimal value) { + return value == null ? resetToNullOrEmpty() : set(false, value.intValueExact()); + } + + @Override + public ClickHouseIntegerValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : set(false, value.ordinal()); + } + + @Override + public ClickHouseIntegerValue update(String value) { + return value == null ? resetToNullOrEmpty() : set(false, Integer.parseInt(value)); + } + + @Override + public ClickHouseIntegerValue update(ClickHouseValue value) { + return value == null ? resetToNullOrEmpty() : set(false, value.asInteger()); + } + + @Override + public ClickHouseIntegerValue update(Object value) { + if (value instanceof Number) { + return set(false, ((Number) value).intValue()); + } else if (value instanceof ClickHouseValue) { + return set(false, ((ClickHouseValue) value).asInteger()); + } + + ClickHouseValue.super.update(value); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseIntegerValue v = (ClickHouseIntegerValue) obj; + return isNull == v.isNull && value == v.value; + } + + @Override + public int hashCode() { + // not going to use Objects.hash(isNull, value) due to autoboxing + return (31 + (isNull ? 1231 : 1237)) * 31 + value; + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIpv4Value.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIpv4Value.java new file mode 100644 index 000000000..9897166ed --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIpv4Value.java @@ -0,0 +1,300 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of Inet4Address. + */ +public class ClickHouseIpv4Value extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseIpv4Value ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseIpv4Value ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseIpv4Value ? (ClickHouseIpv4Value) ((ClickHouseIpv4Value) ref).set(null) + : new ClickHouseIpv4Value(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseIpv4Value of(Inet4Address value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseIpv4Value of(ClickHouseValue ref, Inet4Address value) { + return ref instanceof ClickHouseIpv4Value ? (ClickHouseIpv4Value) ((ClickHouseIpv4Value) ref).set(value) + : new ClickHouseIpv4Value(value); + } + + protected ClickHouseIpv4Value(Inet4Address value) { + super(value); + } + + @Override + public ClickHouseIpv4Value copy(boolean deep) { + return new ClickHouseIpv4Value(getValue()); + } + + @Override + public byte asByte() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? (byte) 0 : bigInt.byteValueExact(); + } + + @Override + public short asShort() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? (short) 0 : bigInt.shortValueExact(); + } + + @Override + public int asInteger() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0 : bigInt.intValueExact(); + } + + @Override + public long asLong() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0L : bigInt.longValue(); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : new BigInteger(1, getValue().getAddress()); + } + + @Override + public float asFloat() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0F : bigInt.floatValue(); + } + + @Override + public double asDouble() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0D : bigInt.doubleValue(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? null : new BigDecimal(bigInt, scale); + } + + @Override + public Inet4Address asInet4Address() { + return getValue(); + } + + @Override + public Inet6Address asInet6Address() { + return ClickHouseValues.convertToIpv6(getValue()); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = String.valueOf(getValue().getHostAddress()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + return new StringBuilder().append('\'').append(getValue().getHostAddress()).append('\'').toString(); + } + + @Override + public ClickHouseIpv4Value update(byte value) { + return update((int) value); + } + + @Override + public ClickHouseIpv4Value update(short value) { + return update((int) value); + } + + @Override + public ClickHouseIpv4Value update(int value) { + set(ClickHouseValues.convertToIpv4(value)); + return this; + } + + @Override + public ClickHouseIpv4Value update(long value) { + return update((int) value); + } + + @Override + public ClickHouseIpv4Value update(float value) { + return update((int) value); + } + + @Override + public ClickHouseIpv4Value update(double value) { + return update((int) value); + } + + @Override + public ClickHouseIpv4Value update(BigInteger value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(value.intValueExact()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(value.intValueExact()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(value.ordinal()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(Inet4Address value) { + set(value); + return this; + } + + @Override + public ClickHouseIpv4Value update(Inet6Address value) { + set(ClickHouseValues.convertToIpv4(value)); + return this; + } + + @Override + public ClickHouseIpv4Value update(LocalDate value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update((int) value.toEpochDay()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(LocalTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(value.toSecondOfDay()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update((int) value.toEpochSecond(ZoneOffset.UTC)); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(String value) { + set(ClickHouseValues.convertToIpv4(value)); + return this; + } + + @Override + public ClickHouseIpv4Value update(UUID value) { + BigInteger v = ClickHouseValues.convertToBigInteger(value); + if (v == null) { + resetToNullOrEmpty(); + } else { + update(v.intValueExact()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asInet4Address()); + } + return this; + } + + @Override + public ClickHouseIpv4Value update(Object value) { + if (value instanceof Inet4Address) { + set((Inet4Address) value); + } else if (value instanceof Inet6Address) { + set(ClickHouseValues.convertToIpv4((Inet6Address) value)); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIpv6Value.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIpv6Value.java new file mode 100644 index 000000000..9fbf65be1 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseIpv6Value.java @@ -0,0 +1,295 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of Inet6Address. + */ +public class ClickHouseIpv6Value extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseIpv6Value ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseIpv6Value ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseIpv6Value ? (ClickHouseIpv6Value) ((ClickHouseIpv6Value) ref).set(null) + : new ClickHouseIpv6Value(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseIpv6Value of(Inet6Address value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseIpv6Value of(ClickHouseValue ref, Inet6Address value) { + return ref instanceof ClickHouseIpv6Value ? (ClickHouseIpv6Value) ((ClickHouseIpv6Value) ref).set(value) + : new ClickHouseIpv6Value(value); + } + + protected ClickHouseIpv6Value(Inet6Address value) { + super(value); + } + + @Override + public ClickHouseIpv6Value copy(boolean deep) { + return new ClickHouseIpv6Value(getValue()); + } + + @Override + public byte asByte() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? (byte) 0 : bigInt.byteValueExact(); + } + + @Override + public short asShort() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? (short) 0 : bigInt.shortValueExact(); + } + + @Override + public int asInteger() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0 : bigInt.intValueExact(); + } + + @Override + public long asLong() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0L : bigInt.longValueExact(); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : new BigInteger(1, getValue().getAddress()); + } + + @Override + public float asFloat() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0F : bigInt.floatValue(); + } + + @Override + public double asDouble() { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? 0D : bigInt.doubleValue(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + BigInteger bigInt = asBigInteger(); + return bigInt == null ? null : new BigDecimal(bigInt, scale); + } + + @Override + public Inet4Address asInet4Address() { + return ClickHouseValues.convertToIpv4(getValue()); + } + + @Override + public Inet6Address asInet6Address() { + return getValue(); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = String.valueOf(getValue().getHostAddress()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + return new StringBuilder().append('\'').append(getValue().getHostAddress()).append('\'').toString(); + } + + @Override + public ClickHouseIpv6Value update(byte value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseIpv6Value update(short value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseIpv6Value update(int value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseIpv6Value update(long value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseIpv6Value update(float value) { + return update(BigDecimal.valueOf(value).toBigIntegerExact()); + } + + @Override + public ClickHouseIpv6Value update(double value) { + return update(BigDecimal.valueOf(value).toBigIntegerExact()); + } + + @Override + public ClickHouseIpv6Value update(BigInteger value) { + set(ClickHouseValues.convertToIpv6(value)); + return this; + } + + @Override + public ClickHouseIpv6Value update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(value.toBigIntegerExact()); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(Inet4Address value) { + set(ClickHouseValues.convertToIpv6(value)); + return this; + } + + @Override + public ClickHouseIpv6Value update(Inet6Address value) { + set(value); + return this; + } + + @Override + public ClickHouseIpv6Value update(LocalDate value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.toEpochDay())); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(LocalTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.toSecondOfDay())); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.toEpochSecond(ZoneOffset.UTC))); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(String value) { + set(ClickHouseValues.convertToIpv6(value)); + return this; + } + + @Override + public ClickHouseIpv6Value update(UUID value) { + BigInteger v = ClickHouseValues.convertToBigInteger(value); + if (v == null) { + resetToNullOrEmpty(); + } else { + update(v); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asInet6Address()); + } + return this; + } + + @Override + public ClickHouseIpv6Value update(Object value) { + if (value instanceof Inet6Address) { + set((Inet6Address) value); + } else if (value instanceof Inet4Address) { + set(ClickHouseValues.convertToIpv6((Inet4Address) value)); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLZ4InputStream.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLZ4InputStream.java new file mode 100644 index 000000000..8435ea0be --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLZ4InputStream.java @@ -0,0 +1,123 @@ +package com.clickhouse.client.data; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; + +/** + * Reader from clickhouse in lz4. + */ +public class ClickHouseLZ4InputStream extends InputStream { + private static final LZ4Factory factory = LZ4Factory.fastestInstance(); + + static final int MAGIC = 0x82; + + private final InputStream stream; + private final DataInputStream dataWrapper; + + private byte[] currentBlock; + private int pointer; + + public ClickHouseLZ4InputStream(InputStream stream) { + this.stream = stream; + dataWrapper = new DataInputStream(stream); + } + + @Override + public int read() throws IOException { + if (!checkNext()) + return -1; + byte b = currentBlock[pointer]; + pointer += 1; + return b & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + if (!checkNext()) + return -1; + + int copied = 0; + int targetPointer = off; + while (copied != len) { + int toCopy = Math.min(currentBlock.length - pointer, len - copied); + System.arraycopy(currentBlock, pointer, b, targetPointer, toCopy); + targetPointer += toCopy; + pointer += toCopy; + copied += toCopy; + if (!checkNext()) { // finished + return copied; + } + } + return copied; + } + + @Override + public void close() throws IOException { + stream.close(); + } + + private boolean checkNext() throws IOException { + if (currentBlock == null || pointer == currentBlock.length) { + currentBlock = readNextBlock(); + pointer = 0; + } + return currentBlock != null; + } + + private int readInt() throws IOException { + byte b1 = (byte) dataWrapper.readUnsignedByte(); + byte b2 = (byte) dataWrapper.readUnsignedByte(); + byte b3 = (byte) dataWrapper.readUnsignedByte(); + byte b4 = (byte) dataWrapper.readUnsignedByte(); + + return b4 << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b1 & 0xFF); + } + + // every block is: + private byte[] readNextBlock() throws IOException { + int read = stream.read(); + if (read < 0) + return null; + + byte[] checksum = new byte[16]; + checksum[0] = (byte) read; + // checksum - 16 bytes. + dataWrapper.readFully(checksum, 1, 15); + ClickHouseBlockChecksum expected = ClickHouseBlockChecksum.fromBytes(checksum); + // header: + // 1 byte - 0x82 (shows this is LZ4) + int magic = dataWrapper.readUnsignedByte(); + if (magic != MAGIC) + throw new IOException("Magic is not correct: " + magic); + // 4 bytes - size of the compressed data including 9 bytes of the header + int compressedSizeWithHeader = readInt(); + // 4 bytes - size of uncompressed data + int uncompressedSize = readInt(); + int compressedSize = compressedSizeWithHeader - 9; // header + byte[] block = new byte[compressedSize]; + // compressed data: compressed_size - 9 байт. + dataWrapper.readFully(block); + + ClickHouseBlockChecksum real = ClickHouseBlockChecksum.calculateForBlock((byte) magic, compressedSizeWithHeader, + uncompressedSize, block, compressedSize); + if (!real.equals(expected)) { + throw new IllegalArgumentException("Checksum doesn't match: corrupted data."); + } + + byte[] decompressed = new byte[uncompressedSize]; + LZ4FastDecompressor decompressor = factory.fastDecompressor(); + decompressor.decompress(block, 0, decompressed, 0, uncompressedSize); + return decompressed; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLZ4OutputStream.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLZ4OutputStream.java new file mode 100644 index 000000000..c7da08d7a --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLZ4OutputStream.java @@ -0,0 +1,96 @@ +package com.clickhouse.client.data; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; + +public class ClickHouseLZ4OutputStream extends OutputStream { + private static final LZ4Factory factory = LZ4Factory.fastestInstance(); + private final DataOutputStream dataWrapper; + + private final LZ4Compressor compressor; + private final byte[] currentBlock; + private final byte[] compressedBlock; + + private int pointer; + + public ClickHouseLZ4OutputStream(OutputStream stream, int maxCompressBlockSize) { + dataWrapper = new DataOutputStream(stream); + compressor = factory.fastCompressor(); + currentBlock = new byte[maxCompressBlockSize]; + compressedBlock = new byte[compressor.maxCompressedLength(maxCompressBlockSize)]; + } + + /** + * @return Location of pointer in the byte buffer (bytes not yet flushed) + */ + public int position() { + return pointer; + } + + @Override + public void write(int b) throws IOException { + currentBlock[pointer] = (byte) b; + pointer++; + + if (pointer == currentBlock.length) { + writeBlock(); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + int blockSize = currentBlock.length; + int rest = blockSize - pointer; + while (len >= rest) { + System.arraycopy(b, off, currentBlock, pointer, rest); + pointer += rest; + writeBlock(); + off += rest; + len -= rest; + rest = blockSize; + } + + if (len > 0) { + System.arraycopy(b, off, currentBlock, pointer, len); + pointer += len; + } + } + + @Override + public void flush() throws IOException { + if (pointer != 0) { + writeBlock(); + } + dataWrapper.flush(); + } + + private void writeInt(int value) throws IOException { + dataWrapper.write(0xFF & value); + dataWrapper.write(0xFF & (value >> 8)); + dataWrapper.write(0xFF & (value >> 16)); + dataWrapper.write(0xFF & (value >> 24)); + } + + private void writeBlock() throws IOException { + int compressed = compressor.compress(currentBlock, 0, pointer, compressedBlock, 0); + ClickHouseBlockChecksum checksum = ClickHouseBlockChecksum.calculateForBlock( + (byte) ClickHouseLZ4InputStream.MAGIC, compressed + 9, pointer, compressedBlock, compressed); + dataWrapper.write(checksum.asBytes()); + dataWrapper.writeByte(ClickHouseLZ4InputStream.MAGIC); + writeInt(compressed + 9); // compressed size with header + writeInt(pointer); // uncompressed size + dataWrapper.write(compressedBlock, 0, compressed); + pointer = 0; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLongValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLongValue.java new file mode 100644 index 000000000..568ce0386 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseLongValue.java @@ -0,0 +1,301 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of long. + */ +public class ClickHouseLongValue implements ClickHouseValue { + /** + * Create a new instance representing null value of long. + * + * @param unsigned true if the long is unsigned; false otherwise + * @return new instance representing null value + */ + public static ClickHouseLongValue ofNull(boolean unsigned) { + return ofNull(null, unsigned); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @param unsigned true if the value is unsigned; false otherwise + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseLongValue ofNull(ClickHouseValue ref, boolean unsigned) { + return ref instanceof ClickHouseLongValue ? ((ClickHouseLongValue) ref).set(true, unsigned, 0L) + : new ClickHouseLongValue(true, unsigned, 0L); + } + + /** + * Wrap the given value. + * + * @param value value + * @param unsigned true if the value is unsigned; false otherwise + * @return object representing the value + */ + public static ClickHouseLongValue of(long value, boolean unsigned) { + return of(null, unsigned, value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseLongValue of(Number value) { + return value == null ? ofNull(null, false) : of(null, false, value.longValue()); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param unsigned true if the value is unsigned; false otherwise + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseLongValue of(ClickHouseValue ref, boolean unsigned, long value) { + return ref instanceof ClickHouseLongValue ? ((ClickHouseLongValue) ref).set(false, unsigned, value) + : new ClickHouseLongValue(false, unsigned, value); + } + + private boolean isNull; + // UInt64 is used in many places, so we prefer to use long instead of BigInteger + // for better performance and less memory footprints. We can still use + // asBigInteger() to avoid negative value. + private boolean unsigned; + private long value; + + protected ClickHouseLongValue(boolean isNull, boolean unsigned, long value) { + set(isNull, unsigned, value); + } + + protected ClickHouseLongValue set(boolean isNull, boolean unsigned, long value) { + this.isNull = isNull; + this.unsigned = unsigned; + this.value = isNull ? 0L : value; + + return this; + } + + /** + * Gets value. + * + * @return value + */ + public long getValue() { + return value; + } + + @Override + public ClickHouseLongValue copy(boolean deep) { + return new ClickHouseLongValue(isNull, unsigned, value); + } + + @Override + public boolean isNullOrEmpty() { + return isNull; + } + + public boolean isUnsigned() { + return unsigned; + } + + @Override + public byte asByte() { + return (byte) value; + } + + @Override + public short asShort() { + return (short) value; + } + + @Override + public int asInteger() { + return (int) value; + } + + @Override + public long asLong() { + return value; + } + + @Override + public BigInteger asBigInteger() { + if (isNull) { + return null; + } else if (!unsigned || value >= 0L) { + return BigInteger.valueOf(value); + } + + byte[] bytes = new byte[Long.BYTES]; + for (int i = 1; i <= Long.BYTES; i++) { + bytes[Long.BYTES - i] = (byte) ((value >>> (i * Long.BYTES)) & 0xFF); + } + return new BigInteger(1, bytes); + } + + @Override + public float asFloat() { + return value; + } + + @Override + public double asDouble() { + return value; + } + + @Override + public BigDecimal asBigDecimal(int scale) { + if (isNullOrEmpty()) { + return null; + } + + return unsigned && value < 0L ? new BigDecimal(asBigInteger(), scale) : BigDecimal.valueOf(value, scale); + } + + @Override + public Object asObject() { + return isNull ? null : getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNull) { + return null; + } + + String str = unsigned && value < 0L ? asBigInteger().toString() : String.valueOf(value); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseLongValue resetToNullOrEmpty() { + return set(true, false, 0L); + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + + return unsigned && value < 0L ? asBigInteger().toString() : String.valueOf(value); + } + + @Override + public ClickHouseLongValue update(boolean value) { + return set(false, unsigned, value ? 1L : 0L); + } + + @Override + public ClickHouseLongValue update(char value) { + return set(false, unsigned, value); + } + + @Override + public ClickHouseLongValue update(byte value) { + return set(false, unsigned, value); + } + + @Override + public ClickHouseLongValue update(short value) { + return set(false, unsigned, value); + } + + @Override + public ClickHouseLongValue update(int value) { + return set(false, unsigned, value); + } + + @Override + public ClickHouseLongValue update(long value) { + return set(false, unsigned, value); + } + + @Override + public ClickHouseLongValue update(float value) { + return set(false, unsigned, (long) value); + } + + @Override + public ClickHouseLongValue update(double value) { + return set(false, unsigned, (long) value); + } + + @Override + public ClickHouseLongValue update(BigInteger value) { + return value == null ? resetToNullOrEmpty() : set(false, unsigned, value.longValueExact()); + } + + @Override + public ClickHouseLongValue update(BigDecimal value) { + return value == null ? resetToNullOrEmpty() : set(false, unsigned, value.longValueExact()); + } + + @Override + public ClickHouseLongValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : set(false, unsigned, value.ordinal()); + } + + @Override + public ClickHouseLongValue update(String value) { + return value == null ? resetToNullOrEmpty() : set(false, unsigned, Long.parseLong(value)); + } + + @Override + public ClickHouseLongValue update(ClickHouseValue value) { + return value == null ? resetToNullOrEmpty() : set(false, unsigned, value.asLong()); + } + + @Override + public ClickHouseLongValue update(Object value) { + if (value instanceof Number) { + return set(false, unsigned, ((Number) value).longValue()); + } else if (value instanceof ClickHouseValue) { + return set(false, unsigned, ((ClickHouseValue) value).asLong()); + } + + ClickHouseValue.super.update(value); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseLongValue v = (ClickHouseLongValue) obj; + return isNull == v.isNull && unsigned == v.unsigned && value == v.value; + } + + @Override + public int hashCode() { + // not going to use Objects.hash(isNull, value) due to autoboxing + return ((31 + (isNull ? 1231 : 1237)) * 31 + (unsigned ? 1231 : 1237) * 31) + (int) (value ^ (value >>> 32)); + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseMapValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseMapValue.java new file mode 100644 index 000000000..7dad93f5f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseMapValue.java @@ -0,0 +1,327 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.function.Function; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +public class ClickHouseMapValue extends ClickHouseObjectValue> { + private static final String DEFAULT_STRING_KEY = "1"; + private static final String DEFAULT_UUID_KEY = "00000000-0000-0000-0000-000000000000"; + + /** + * Creates an empty map. + * + * @param keyType non-null class of key + * @param valueType non-null class of value + * @return empty map + */ + public static ClickHouseMapValue ofEmpty(Class keyType, Class valueType) { + return new ClickHouseMapValue(Collections.emptyMap(), keyType, valueType); + } + + /** + * Wrap the given value. + * + * @param value value + * @param keyType non-null class of key + * @param valueType non-null class of value + * @return object representing the value + */ + public static ClickHouseMapValue of(Map value, Class keyType, Class valueType) { + return of(null, value, keyType, valueType); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @param keyType non-null class of key + * @param valueType non-null class of value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseMapValue of(ClickHouseValue ref, Map value, Class keyType, Class valueType) { + return ref instanceof ClickHouseMapValue ? ((ClickHouseMapValue) ref).set(value) + : new ClickHouseMapValue(value, keyType, valueType); + } + + private final Class keyType; + private final Class valueType; + + protected ClickHouseMapValue(Map value, Class keyType, Class valueType) { + super(value); + + this.keyType = ClickHouseChecker.nonNull(keyType, "keyType"); + this.valueType = ClickHouseChecker.nonNull(valueType, "valueType"); + } + + protected Object getDefaultKey() { + Object key; + + if (keyType == String.class) { + key = DEFAULT_STRING_KEY; + } else if (keyType == UUID.class) { + key = DEFAULT_UUID_KEY; + } else if (keyType == Byte.class) { + key = Byte.valueOf((byte) 1); + } else if (keyType == Short.class) { + key = Short.valueOf((short) 1); + } else if (keyType == Integer.class) { + key = Integer.valueOf(1); + } else if (keyType == Long.class) { + key = Long.valueOf(1L); + } else if (keyType == Float.class) { + key = Float.valueOf(1F); + } else if (keyType == Double.class) { + key = Double.valueOf(1D); + } else if (keyType == BigInteger.class) { + key = BigInteger.ONE; + } else if (keyType == BigDecimal.class) { + key = BigDecimal.ONE; + } else { + throw new IllegalArgumentException("Unsupported key type: " + keyType); + } + + return key; + } + + @Override + protected ClickHouseMapValue set(Map value) { + super.set(ClickHouseChecker.nonNull(value, "value")); + return this; + } + + @Override + public ClickHouseMapValue copy(boolean deep) { + if (!deep) { + return new ClickHouseMapValue(getValue(), keyType, valueType); + } + + Map value = getValue(); + Map newValue = new LinkedHashMap<>(); + newValue.putAll(value); + return new ClickHouseMapValue(newValue, keyType, valueType); + } + + @Override + public String asString(int length, Charset charset) { + Map value = getValue(); + if (value == null || value.size() == 0) { + return "{}"; + } + StringBuilder builder = new StringBuilder().append('{'); + for (Entry e : value.entrySet()) { + builder.append(String.valueOf(e.getKey())).append(':').append(String.valueOf(e.getValue())).append(','); + } + builder.setLength(builder.length() - 1); + + String str = builder.append('}').toString(); + return length > 0 ? ClickHouseChecker.notWithDifferentLength(str, length) : str; + } + + @Override + public boolean isNullOrEmpty() { + return getValue().isEmpty(); + } + + @Override + public ClickHouseMapValue resetToNullOrEmpty() { + set(Collections.emptyMap()); + return this; + } + + @Override + public String toSqlExpression() { + Map value = getValue(); + if (value == null || value.size() == 0) { + return "{}"; + } + + StringBuilder builder = new StringBuilder().append('{'); + // non-null number, string or uuid + Function keySerializer = String.class == keyType || UUID.class == keyType + ? ClickHouseValues::convertToQuotedString + : ClickHouseValues::convertToString; + // any value which may or may not be null + Function valueSerializer = ClickHouseValues::convertToSqlExpression; + + for (Entry e : value.entrySet()) { + builder.append(keySerializer.apply(e.getKey())).append(" : ").append(valueSerializer.apply(e.getValue())) + .append(','); + } + builder.setLength(builder.length() - 1); + + return builder.append('}').toString(); + } + + @Override + public ClickHouseMapValue update(byte value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(short value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(int value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(long value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(float value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(double value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(BigInteger value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(BigDecimal value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(String value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(Enum value) { + Object v; + if (value == null) { + v = null; + } else if (valueType == value.getClass()) { + v = value; + } else if (valueType == String.class) { + v = value.name(); + } else if (keyType == Byte.class) { + v = Byte.valueOf((byte) value.ordinal()); + } else if (keyType == Short.class) { + v = Short.valueOf((short) value.ordinal()); + } else if (keyType == Integer.class) { + v = Integer.valueOf(value.ordinal()); + } else if (keyType == Long.class) { + v = Long.valueOf(value.ordinal()); + } else if (keyType == Float.class) { + v = Float.valueOf(value.ordinal()); + } else if (keyType == Double.class) { + v = Double.valueOf(value.ordinal()); + } else if (keyType == BigInteger.class) { + v = BigInteger.valueOf(value.ordinal()); + } else if (keyType == BigDecimal.class) { + v = BigDecimal.valueOf(value.ordinal()); + } else { + throw newUnsupportedException(value.getClass().getName(), valueType.getName()); + } + set(Collections.singletonMap(getDefaultKey(), v)); + return this; + } + + @Override + public ClickHouseMapValue update(Inet4Address value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(Inet6Address value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(LocalDate value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(LocalTime value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(LocalDateTime value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseMapValue update(Map value) { + set(value == null ? Collections.emptyMap() : value); + return this; + } + + @Override + public ClickHouseMapValue update(UUID value) { + set(Collections.singletonMap(getDefaultKey(), valueType.cast(value))); + return this; + } + + @Override + public ClickHouseValue updateUnknown(Object value) { + throw new IllegalArgumentException("Unknown value: " + value); + } + + @Override + public ClickHouseMapValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else if (value instanceof ClickHouseMapValue) { + set(((ClickHouseMapValue) value).getValue()); + } else { + set(value.asMap()); + } + + return this; + } + + @Override + public ClickHouseValue update(Object value) { + if (value instanceof Map) { + set((Map) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseNestedValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseNestedValue.java new file mode 100644 index 000000000..83105ebc7 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseNestedValue.java @@ -0,0 +1,548 @@ +package com.clickhouse.client.data; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wrapper class of Nested. + */ +public class ClickHouseNestedValue extends ClickHouseObjectValue { + /** + * Creates an empty nested value. + * + * @param columns non-null columns + * @return empty nested value + */ + public static ClickHouseNestedValue ofEmpty(List columns) { + return of(null, columns, new Object[0][]); + } + + /** + * Wrap the given value. + * + * @param columns columns + * @param values values + * @return object representing the value + */ + public static ClickHouseNestedValue of(List columns, Object[][] values) { + return of(null, columns, values); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param columns columns + * @param values values + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseNestedValue of(ClickHouseValue ref, List columns, Object[][] values) { + return ref instanceof ClickHouseNestedValue + ? (ClickHouseNestedValue) ((ClickHouseNestedValue) ref).update(values) + : new ClickHouseNestedValue(columns, values); + } + + protected static Object[][] check(List columns, Object[][] value) { + if (columns == null || value == null) { + throw new IllegalArgumentException("Non-null columns and value are required"); + } + + if (columns.isEmpty()) { + throw new IllegalArgumentException("At least one column must be specified for nested type"); + } + + if (value.length != 0 && value.length != columns.size()) { + throw new IllegalArgumentException("Columns and values should have same length"); + } + + return value; + } + + private final List columns; + + protected ClickHouseNestedValue(List columns, Object[][] values) { + super(check(columns, values)); + this.columns = columns; + } + + protected Object getSingleValue() { + Object[][] value = getValue(); + + if (value == null || value.length != 1 || value[0] == null || value[0].length != 1) { + throw new UnsupportedOperationException( + "Only nested object containing only one value(one column and one row) supports type conversion"); + } + + return value[0][0]; + } + + @Override + protected ClickHouseNestedValue set(Object[][] value) { + if (columns == null && getValue() == null) { // must be called from constructor + super.set(value); + } else { + super.set(check(columns, value)); + } + return this; + } + + /** + * Gets immutable list of nested columns. + * + * @return immutable list of columns + */ + public List getColumns() { + return Collections.unmodifiableList(columns); + } + + @Override + public ClickHouseNestedValue copy(boolean deep) { + if (!deep) { + return new ClickHouseNestedValue(columns, getValue()); + } + + Object[][] value = getValue(); + int len = value.length; + Object[][] newValue = new Object[len][]; + for (int i = 0; i < len; i++) { + Object[] v = value[i]; + newValue[i] = Arrays.copyOf(v, v.length); + } + return new ClickHouseNestedValue(columns, newValue); + } + + @Override + public Object[] asArray() { + return getValue(); + } + + @Override + @SuppressWarnings("unchecked") + public T[] asArray(Class clazz) { + Object[][] v = getValue(); + T[] array = (T[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), v.length); + int index = 0; + for (Object[] o : v) { + array[index++] = clazz.cast(o); + } + return array; + } + + @Override + public Map asMap() { + Map map = new LinkedHashMap<>(); + int index = 0; + for (Object[] o : getValue()) { + map.put(columns.get(index++).getColumnName(), o); + } + + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + @Override + public Map asMap(Class keyClass, Class valueClass) { + if (keyClass == null || valueClass == null) { + throw new IllegalArgumentException("Non-null key and value classes are required"); + } + Map map = new LinkedHashMap<>(); + int index = 0; + for (Object[] o : getValue()) { + map.put(keyClass.cast(columns.get(index++).getColumnName()), valueClass.cast(o)); + } + // why not use Collections.unmodifiableMap(map) here? + return map; + } + + @Override + public String asString(int length, Charset charset) { + String str = Arrays.deepToString(getValue()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public boolean isNullOrEmpty() { + Object[][] value = getValue(); + return value == null || value.length == 0; + } + + @Override + public ClickHouseNestedValue resetToNullOrEmpty() { + set(new Object[0][]); + return this; + } + + @Override + public String toSqlExpression() { + Object[][] value = getValue(); + if (value == null || value.length == 0) { + return ClickHouseValues.EMPTY_ARRAY_EXPR; + } + + StringBuilder builder = new StringBuilder(); + for (Object[] v : value) { + if (v == null || v.length == 0) { + builder.append(ClickHouseValues.EMPTY_ARRAY_EXPR); + } else { + builder.append('['); + for (Object o : v) { + builder.append(ClickHouseValues.convertToSqlExpression(o)).append(','); + } + builder.setLength(builder.length() - 1); + builder.append(']'); + } + builder.append(','); + } + builder.setLength(builder.length() - 1); + return builder.toString(); + } + + @Override + public ClickHouseNestedValue update(boolean value) { + set(new Object[][] { new Byte[] { value ? (byte) 1 : (byte) 0 } }); + return this; + } + + @Override + public ClickHouseNestedValue update(boolean[] value) { + int len = value == null ? 0 : value.length; + Byte[] v = new Byte[len]; + if (len > 0) { + int index = 0; + for (boolean b : value) { + v[index++] = b ? (byte) 1 : (byte) 0; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(char value) { + set(new Object[][] { new Integer[] { (int) value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(char[] value) { + int len = value == null ? 0 : value.length; + Integer[] v = new Integer[len]; + if (len > 0) { + int index = 0; + for (char c : value) { + v[index++] = (int) c; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(byte value) { + set(new Object[][] { new Object[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(byte[] value) { + int len = value == null ? 0 : value.length; + Byte[] v = new Byte[len]; + if (len > 0) { + int index = 0; + for (byte b : value) { + v[index++] = b; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(short value) { + set(new Object[][] { new Short[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(short[] value) { + int len = value == null ? 0 : value.length; + Short[] v = new Short[len]; + if (len > 0) { + int index = 0; + for (short s : value) { + v[index++] = s; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(int value) { + set(new Object[][] { new Integer[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(int[] value) { + int len = value == null ? 0 : value.length; + Integer[] v = new Integer[len]; + if (len > 0) { + int index = 0; + for (int i : value) { + v[index++] = i; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(long value) { + set(new Object[][] { new Long[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(long[] value) { + int len = value == null ? 0 : value.length; + Long[] v = new Long[len]; + if (len > 0) { + int index = 0; + for (long l : value) { + v[index++] = l; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(float value) { + set(new Object[][] { new Float[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(float[] value) { + int len = value == null ? 0 : value.length; + Float[] v = new Float[len]; + if (len > 0) { + int index = 0; + for (float f : value) { + v[index++] = f; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(double value) { + set(new Object[][] { new Double[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(double[] value) { + int len = value == null ? 0 : value.length; + Double[] v = new Double[len]; + if (len > 0) { + int index = 0; + for (double d : value) { + v[index++] = d; + } + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(BigInteger value) { + set(new Object[][] { new BigInteger[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(BigDecimal value) { + set(new Object[][] { new BigDecimal[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(Enum value) { + set(new Object[][] { new Enum[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(Inet4Address value) { + set(new Object[][] { new Inet4Address[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(Inet6Address value) { + set(new Object[][] { new Inet6Address[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(LocalDate value) { + set(new Object[][] { new LocalDate[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(LocalTime value) { + set(new Object[][] { new LocalTime[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(LocalDateTime value) { + set(new Object[][] { new LocalDateTime[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(Collection value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + return resetToNullOrEmpty(); + } + + Object[] v = new Object[size]; + int index = 0; + for (Object o : value) { + v[index++] = o; + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(Enumeration value) { + if (value == null) { + return resetToNullOrEmpty(); + } + + List v = new LinkedList<>(); + while (value.hasMoreElements()) { + v.add(value.nextElement()); + } + set(new Object[][] { v.toArray(new Object[v.size()]) }); + return this; + } + + @Override + public ClickHouseNestedValue update(Map value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + return resetToNullOrEmpty(); + } + + Object[] v = new Object[size]; + int index = 0; + for (Object o : value.values()) { + v[index++] = o; + } + set(new Object[][] { v }); + return this; + } + + @Override + public ClickHouseNestedValue update(String value) { + set(new Object[][] { new String[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(UUID value) { + set(new Object[][] { new UUID[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(ClickHouseValue value) { + if (value == null) { + return resetToNullOrEmpty(); + } else if (value instanceof ClickHouseNestedValue) { + set(((ClickHouseNestedValue) value).getValue()); + } else { + set(new Object[][] { value.asArray() }); + } + return this; + } + + @Override + public ClickHouseNestedValue update(Object[] value) { + if (value == null) { + return resetToNullOrEmpty(); + } else if (value instanceof Object[][]) { + set((Object[][]) value); + } else { + set(new Object[][] { value }); + } + return this; + } + + @Override + public ClickHouseValue updateUnknown(Object value) { + set(new Object[][] { new Object[] { value } }); + return this; + } + + @Override + public ClickHouseNestedValue update(Object value) { + if (value instanceof Object[][]) { + set((Object[][]) value); + } else { + super.update(value); + } + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + return Arrays.deepEquals(getValue(), ((ClickHouseArrayValue) obj).getValue()); + } + + @Override + public int hashCode() { + return Arrays.deepHashCode(getValue()); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseObjectValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseObjectValue.java new file mode 100644 index 000000000..8fd94c8ff --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseObjectValue.java @@ -0,0 +1,173 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +public abstract class ClickHouseObjectValue implements ClickHouseValue { + // a nested structure like Map might not be always serializable + @SuppressWarnings("squid:S1948") + private T value; + + protected ClickHouseObjectValue(T value) { + set(value); + } + + protected ClickHouseObjectValue set(T value) { + this.value = value; + return this; + } + + /** + * Gets value. + * + * @return value + */ + public T getValue() { + return value; + } + + @Override + public boolean isNullOrEmpty() { + return value == null; + } + + @Override + public byte asByte() { + if (isNullOrEmpty()) { + return (byte) 0; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_BYTE); + } + + @Override + public short asShort() { + if (isNullOrEmpty()) { + return (short) 0; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_SHORT); + } + + @Override + public int asInteger() { + if (isNullOrEmpty()) { + return 0; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_INT); + } + + @Override + public long asLong() { + if (isNullOrEmpty()) { + return 0L; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_LONG); + } + + @Override + public BigInteger asBigInteger() { + if (isNullOrEmpty()) { + return null; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_BIG_INTEGER); + } + + @Override + public float asFloat() { + if (isNullOrEmpty()) { + return 0F; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_FLOAT); + } + + @Override + public double asDouble() { + if (isNullOrEmpty()) { + return 0D; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_DOUBLE); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + if (isNullOrEmpty()) { + return null; + } + + throw newUnsupportedException(ClickHouseValues.TYPE_OBJECT, ClickHouseValues.TYPE_BIG_DECIMAL); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public E asObject(Class clazz) { + return ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS).cast(getValue()); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = value.toString(); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseObjectValue resetToNullOrEmpty() { + return set(null); + } + + @Override + public String toSqlExpression() { + return isNullOrEmpty() ? ClickHouseValues.NULL_EXPR : asString(); + } + + @Override + public ClickHouseValue update(Object value) { + return ClickHouseValue.super.update(value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseObjectValue v = (ClickHouseObjectValue) obj; + return value == v.value || (value != null && value.equals(v.value)); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHousePipedStream.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHousePipedStream.java new file mode 100644 index 000000000..366bb5d12 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHousePipedStream.java @@ -0,0 +1,279 @@ +package com.clickhouse.client.data; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import com.clickhouse.client.ClickHouseUtils; + +/** + * A combination of {@link java.io.PipedOutputStream} and + * {@link java.io.PipedInputStream} for streaming data between server and + * client. To avoid dead lock and high memory usage, please make sure writer and + * reader are on two separate threads. + */ +public class ClickHousePipedStream extends OutputStream { + protected static final ByteBuffer EMPTY = ByteBuffer.wrap(new byte[0]); + + static class Input extends InputStream { + private final BlockingQueue queue; + private final int timeout; + + // too much to maintain a 2-level buffer for reading? + private ByteBuffer buffer; + private boolean closed; + + Input(BlockingQueue queue, int timeout) { + this.queue = queue; + this.timeout = timeout; + + this.buffer = null; + this.closed = false; + } + + private void ensureOpen() throws IOException { + if (this.closed) { + throw new IOException("Stream has been closed"); + } + + if (this.buffer == null) { + updateBuffer(); + } + } + + @SuppressWarnings("squid:S2142") + private int updateBuffer() throws IOException { + try { + if (timeout > 0) { + buffer = queue.poll(timeout, TimeUnit.MILLISECONDS); + if (buffer == null) { + throw new IOException(ClickHouseUtils.format("Read timed out after %d ms", timeout)); + } + } else { + buffer = queue.take(); + } + + return buffer.remaining(); + } catch (InterruptedException e) { + throw new IOException("Thread was interrupted when getting next buffer from queue", e); + } + } + + @Override + public int available() throws IOException { + ensureOpen(); + + if (buffer == EMPTY || buffer.limit() == 0) { + return 0; + } + + int available = buffer.remaining(); + if (available == 0) { + available = updateBuffer(); + } + + return available; + } + + @Override + public void close() throws IOException { + // it's caller's responsiblity to consume all data in the queue, which will + // unblock writer + this.closed = true; + this.buffer = null; + } + + @Override + public int read() throws IOException { + ensureOpen(); + + if (buffer == EMPTY || buffer.limit() == 0) { + return -1; + } + + if (buffer.hasRemaining()) { + return 0xFF & buffer.get(); + } else { + updateBuffer(); + + return read(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + ensureOpen(); + + if (buffer == EMPTY || buffer.limit() == 0) { + return -1; + } + + int counter = 0; + while (len > 0) { + if (buffer == EMPTY || buffer.limit() == 0) { + return counter; + } + + int remain = buffer.remaining(); + if (remain > 0) { + if (remain >= len) { + buffer.get(b, off, len); + counter += len; + len = 0; + } else { + buffer.get(b, off, remain); + counter += remain; + off += remain; + len -= remain; + + updateBuffer(); + } + } else { + updateBuffer(); + } + } + + return counter; + } + + @Override + public long skip(long n) throws IOException { + ensureOpen(); + + // peforms better but this is a bit tricky + if (n == Long.MAX_VALUE) { + long counter = buffer.remaining(); + while (buffer != EMPTY && buffer.limit() > 0) { + counter += buffer.limit(); + updateBuffer(); + } + + return counter; + } + + return super.skip(n); + } + } + + protected final BlockingQueue queue; + + private final int bufferSize; + private final int timeout; + + private ByteBuffer buffer; + private boolean closed; + + public ClickHousePipedStream(int bufferSize, int queueLength, int timeout) { + // DisruptorBlockingQueue? Did not see much difference here... + this.queue = queueLength <= 0 ? new LinkedBlockingDeque<>() : new ArrayBlockingQueue<>(queueLength); + + // may need an initialBufferSize and a monitor to update bufferSize in runtime + this.bufferSize = bufferSize <= 0 ? 8192 : bufferSize; + this.timeout = timeout; + + this.buffer = ByteBuffer.allocate(this.bufferSize); + this.closed = false; + } + + private void ensureOpen() throws IOException { + if (this.closed) { + throw new IOException("Stream has been closed"); + } + } + + @SuppressWarnings("squid:S2142") + private void updateBuffer() throws IOException { + if (buffer.position() > 0) { + if (buffer.hasRemaining()) { + ((Buffer) buffer).limit(buffer.position()); + } + ((Buffer) buffer).rewind(); + + try { + if (timeout > 0) { + if (!queue.offer(buffer, timeout, TimeUnit.MILLISECONDS)) { + throw new IOException(ClickHouseUtils.format("Write timed out after %d ms", timeout)); + } + } else { + queue.put(buffer); + } + } catch (InterruptedException e) { + throw new IOException("Thread was interrupted when putting buffer into queue", e); + } + + buffer = ByteBuffer.allocate(bufferSize); + } + } + + public InputStream getInput() { + return new Input(queue, timeout); + } + + @Override + @SuppressWarnings("squid:S2142") + public void close() throws IOException { + if (this.closed) { + return; + } + + flush(); + + buffer = EMPTY; + try { + if (timeout > 0) { + if (!queue.offer(buffer, timeout, TimeUnit.MILLISECONDS)) { + throw new IOException(ClickHouseUtils.format("Close stream timed out after %d ms", timeout)); + } + } else { + queue.put(buffer); + } + } catch (InterruptedException e) { + throw new IOException("Thread was interrupted when putting EMPTY buffer into queue", e); + } + this.closed = true; + } + + @Override + public void flush() throws IOException { + updateBuffer(); + } + + @Override + public void write(int b) throws IOException { + ensureOpen(); + + if (!buffer.hasRemaining()) { + updateBuffer(); + } + + buffer.put((byte) (0xFF & b)); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + ensureOpen(); + + while (len > 0) { + int remain = buffer.remaining(); + if (remain > 0) { + if (remain >= len) { + buffer.put(b, off, len); + len = 0; + } else { + buffer.put(b, off, remain); + off += remain; + len -= remain; + + updateBuffer(); + } + } else { + updateBuffer(); + } + } + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java new file mode 100644 index 000000000..70da0895d --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java @@ -0,0 +1,386 @@ +package com.clickhouse.client.data; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseDataProcessor; +import com.clickhouse.client.ClickHouseDataType; +import com.clickhouse.client.ClickHouseDeserializer; +import com.clickhouse.client.ClickHouseFormat; +import com.clickhouse.client.ClickHouseRecord; +import com.clickhouse.client.ClickHouseSerializer; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Data processor for handling {@link ClickHouseFormat#RowBinary} and + * {@link ClickHouseFormat#RowBinaryWithNamesAndTypes} two formats. + */ +public class ClickHouseRowBinaryProcessor extends ClickHouseDataProcessor { + public static class MappedFunctions { + private static final MappedFunctions instance = new MappedFunctions(); + + private final Map> deserializers; + private final Map> serializers; + + private MappedFunctions() { + deserializers = new EnumMap<>(ClickHouseDataType.class); + serializers = new EnumMap<>(ClickHouseDataType.class); + + // enum and numbers + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseByteValue.of(r, BinaryStreamUtils.readInt8(i)), + (v, c, o) -> BinaryStreamUtils.writeInt8(o, v.asByte()), ClickHouseDataType.Enum, + ClickHouseDataType.Enum8, ClickHouseDataType.Int8); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseShortValue.of(r, BinaryStreamUtils.readUnsignedInt8(i)), + (v, c, o) -> BinaryStreamUtils.writeUnsignedInt8(o, v.asInteger()), ClickHouseDataType.UInt8); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseShortValue.of(r, BinaryStreamUtils.readInt16(i)), + (v, c, o) -> BinaryStreamUtils.writeInt16(o, v.asShort()), ClickHouseDataType.Enum16, + ClickHouseDataType.Int16); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseIntegerValue.of(r, BinaryStreamUtils.readUnsignedInt16(i)), + (v, c, o) -> BinaryStreamUtils.writeUnsignedInt16(o, v.asInteger()), ClickHouseDataType.UInt16); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseIntegerValue.of(r, BinaryStreamUtils.readInt32(i)), + (v, c, o) -> BinaryStreamUtils.writeInt32(o, v.asInteger()), ClickHouseDataType.Int32); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseLongValue.of(r, false, BinaryStreamUtils.readUnsignedInt32(i)), + (v, c, o) -> BinaryStreamUtils.writeUnsignedInt32(o, v.asLong()), ClickHouseDataType.UInt32); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseLongValue.of(r, false, BinaryStreamUtils.readInt64(i)), + (v, c, o) -> BinaryStreamUtils.writeInt64(o, v.asLong()), ClickHouseDataType.IntervalYear, + ClickHouseDataType.IntervalQuarter, ClickHouseDataType.IntervalMonth, + ClickHouseDataType.IntervalWeek, ClickHouseDataType.IntervalDay, ClickHouseDataType.IntervalHour, + ClickHouseDataType.IntervalMinute, ClickHouseDataType.IntervalSecond, ClickHouseDataType.Int64); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseLongValue.of(r, true, BinaryStreamUtils.readInt64(i)), + (v, c, o) -> BinaryStreamUtils.writeInt64(o, v.asLong()), ClickHouseDataType.UInt64); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigIntegerValue.of(r, BinaryStreamUtils.readInt128(i)), + (v, c, o) -> BinaryStreamUtils.writeInt128(o, v.asBigInteger()), ClickHouseDataType.Int128); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigIntegerValue.of(r, BinaryStreamUtils.readUnsignedInt128(i)), + (v, c, o) -> BinaryStreamUtils.writeUnsignedInt128(o, v.asBigInteger()), + ClickHouseDataType.UInt128); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigIntegerValue.of(r, BinaryStreamUtils.readInt256(i)), + (v, c, o) -> BinaryStreamUtils.writeInt256(o, v.asBigInteger()), ClickHouseDataType.Int256); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigIntegerValue.of(r, BinaryStreamUtils.readUnsignedInt256(i)), + (v, c, o) -> BinaryStreamUtils.writeUnsignedInt256(o, v.asBigInteger()), + ClickHouseDataType.UInt256); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseFloatValue.of(r, BinaryStreamUtils.readFloat32(i)), + (v, c, o) -> BinaryStreamUtils.writeFloat32(o, v.asFloat()), ClickHouseDataType.Float32); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseDoubleValue.of(r, BinaryStreamUtils.readFloat64(i)), + (v, c, o) -> BinaryStreamUtils.writeFloat64(o, v.asDouble()), ClickHouseDataType.Float64); + + // decimals + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigDecimalValue.of(r, + BinaryStreamUtils.readDecimal(i, c.getPrecision(), c.getScale())), + (v, c, o) -> BinaryStreamUtils.writeDecimal(o, v.asBigDecimal(c.getScale()), c.getPrecision(), + c.getScale()), + ClickHouseDataType.Decimal); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigDecimalValue.of(r, BinaryStreamUtils.readDecimal32(i, c.getScale())), + (v, c, o) -> BinaryStreamUtils.writeDecimal32(o, v.asBigDecimal(c.getScale()), c.getScale()), + ClickHouseDataType.Decimal32); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigDecimalValue.of(r, BinaryStreamUtils.readDecimal64(i, c.getScale())), + (v, c, o) -> BinaryStreamUtils.writeDecimal64(o, v.asBigDecimal(c.getScale()), c.getScale()), + ClickHouseDataType.Decimal64); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigDecimalValue.of(r, BinaryStreamUtils.readDecimal128(i, c.getScale())), + (v, c, o) -> BinaryStreamUtils.writeDecimal128(o, v.asBigDecimal(c.getScale()), c.getScale()), + ClickHouseDataType.Decimal128); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseBigDecimalValue.of(r, BinaryStreamUtils.readDecimal256(i, c.getScale())), + (v, c, o) -> BinaryStreamUtils.writeDecimal256(o, v.asBigDecimal(c.getScale()), c.getScale()), + ClickHouseDataType.Decimal256); + + // date, time, datetime and IPs + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseDateValue.of(r, BinaryStreamUtils.readDate(i)), + (v, c, o) -> BinaryStreamUtils.writeDate(o, v.asDate()), ClickHouseDataType.Date); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseDateValue.of(r, BinaryStreamUtils.readDate32(i)), + (v, c, o) -> BinaryStreamUtils.writeDate(o, v.asDate()), ClickHouseDataType.Date32); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseDateTimeValue.of(r, + (c.getScale() > 0 ? BinaryStreamUtils.readDateTime64(i, c.getScale()) + : BinaryStreamUtils.readDateTime(i)), + c.getScale()), + (v, c, o) -> BinaryStreamUtils.writeDateTime(o, v.asDateTime(), c.getScale()), + ClickHouseDataType.DateTime); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseDateTimeValue.of(r, BinaryStreamUtils.readDateTime(i), 0), + (v, c, o) -> BinaryStreamUtils.writeDateTime32(o, v.asDateTime()), ClickHouseDataType.DateTime32); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseDateTimeValue.of(r, BinaryStreamUtils.readDateTime64(i, c.getScale()), + c.getScale()), + (v, c, o) -> BinaryStreamUtils.writeDateTime64(o, v.asDateTime(), c.getScale()), + ClickHouseDataType.DateTime64); + + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseIpv4Value.of(r, BinaryStreamUtils.readInet4Address(i)), + (v, c, o) -> BinaryStreamUtils.writeInet4Address(o, v.asInet4Address()), ClickHouseDataType.IPv4); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseIpv6Value.of(r, BinaryStreamUtils.readInet6Address(i)), + (v, c, o) -> BinaryStreamUtils.writeInet6Address(o, v.asInet6Address()), ClickHouseDataType.IPv6); + + // string and uuid + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseStringValue.of(r, BinaryStreamUtils.readFixedString(i, c.getPrecision())), + (v, c, o) -> BinaryStreamUtils.writeFixedString(o, v.asString(c.getPrecision()), c.getPrecision()), + ClickHouseDataType.FixedString); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseStringValue.of(r, BinaryStreamUtils.readString(i)), + (v, c, o) -> BinaryStreamUtils.writeString(o, v.asString()), ClickHouseDataType.String); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseUuidValue.of(r, BinaryStreamUtils.readUuid(i)), + (v, c, o) -> BinaryStreamUtils.writeUuid(o, v.asUuid()), ClickHouseDataType.UUID); + + // geo types + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseGeoPointValue.of(r, BinaryStreamUtils.readGeoPoint(i)), (v, c, o) -> { + }, ClickHouseDataType.Point); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseGeoRingValue.of(r, BinaryStreamUtils.readGeoRing(i)), (v, c, o) -> { + }, ClickHouseDataType.Ring); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseGeoPolygonValue.of(r, BinaryStreamUtils.readGeoPolygon(i)), (v, c, o) -> { + }, ClickHouseDataType.Polygon); + buildMappings(deserializers, serializers, + (r, c, i) -> ClickHouseGeoMultiPolygonValue.of(r, BinaryStreamUtils.readGeoMultiPolygon(i)), + (v, c, o) -> { + }, ClickHouseDataType.MultiPolygon); + + // advanced types + buildMappings(deserializers, serializers, (r, c, i) -> { + int size = BinaryStreamUtils.readVarInt(i); + ClickHouseColumn nestedColumn = c.getNestedColumns().get(0); + // TODO optimize primitive array + Object[] arr = (Object[]) Array.newInstance(nestedColumn.getDataType().getJavaClass(), size); + for (int k = 0; k < size; k++) { + arr[k] = deserialize(nestedColumn, null, i).asObject(); + } + return ClickHouseArrayValue.of(r, arr); + }, (v, c, o) -> { + }, ClickHouseDataType.Array); + buildMappings(deserializers, serializers, (r, c, i) -> { + Map map = new LinkedHashMap<>(); + ClickHouseColumn keyCol = c.getKeyInfo(); + ClickHouseColumn valCol = c.getValueInfo(); + for (int k = 0, len = BinaryStreamUtils.readVarInt(i); k < len; k++) { + map.put(deserialize(keyCol, null, i).asObject(), deserialize(valCol, null, i).asObject()); + } + return ClickHouseMapValue.of(map, valCol.getDataType().getJavaClass(), + valCol.getDataType().getJavaClass()); + }, (v, c, o) -> { + }, ClickHouseDataType.Map); + buildMappings(deserializers, serializers, (r, c, i) -> { + int count = c.getNestedColumns().size(); + String[] names = new String[count]; + Object[][] values = new Object[count][]; + int l = 0; + for (ClickHouseColumn col : c.getNestedColumns()) { + names[l] = col.getColumnName(); + int k = BinaryStreamUtils.readVarInt(i); + Object[] nvalues = new Object[k]; + for (int j = 0; j < k; j++) { + nvalues[j] = deserialize(col, null, i).asObject(); + } + values[l++] = nvalues; + } + return ClickHouseNestedValue.of(r, c.getNestedColumns(), values); + }, (v, c, o) -> { + }, ClickHouseDataType.Nested); + buildMappings(deserializers, serializers, (r, c, i) -> { + List tupleValues = new ArrayList<>(c.getNestedColumns().size()); + for (ClickHouseColumn col : c.getNestedColumns()) { + tupleValues.add(deserialize(col, null, i).asObject()); + } + return ClickHouseTupleValue.of(r, tupleValues); + }, (v, c, o) -> { + }, ClickHouseDataType.Tuple); + } + + @SuppressWarnings("unchecked") + public ClickHouseValue deserialize(ClickHouseColumn column, ClickHouseValue ref, InputStream input) + throws IOException { + if (column.isNullable() && BinaryStreamUtils.readNull(input)) { + return ref == null ? ClickHouseValues.newValue(column) : ref.resetToNullOrEmpty(); + } + + ClickHouseDeserializer func = (ClickHouseDeserializer) deserializers + .get(column.getDataType()); + if (func == null) { + throw new IllegalArgumentException(ERROR_UNKNOWN_DATA_TYPE + column.getDataType().name()); + } + return func.deserialize(ref, column, input); + } + + @SuppressWarnings("unchecked") + public void serialize(ClickHouseColumn column, ClickHouseValue value, OutputStream output) throws IOException { + if (column.isNullable()) { // always false for geo types, and Array, Nested, Map and Tuple etc. + if (value.isNullOrEmpty()) { + BinaryStreamUtils.writeNull(output); + return; + } else { + BinaryStreamUtils.writeNonNull(output); + } + } + + ClickHouseSerializer func = (ClickHouseSerializer) serializers + .get(column.getDataType()); + if (func == null) { + throw new IllegalArgumentException(ERROR_UNKNOWN_DATA_TYPE + column.getDataType().name()); + } + func.serialize(value, column, output); + } + } + + public static MappedFunctions getMappedFunctions() { + return MappedFunctions.instance; + } + + private class Records implements Iterator { + private final Supplier factory; + private ClickHouseValue[] values; + + Records() { + int size = columns.size(); + if (config.isReuseValueWrapper()) { + values = new ClickHouseValue[size]; + factory = () -> values; + } else { + factory = () -> new ClickHouseValue[size]; + } + } + + void readNextRow() { + int index = 0; + int size = columns.size(); + values = factory.get(); + ClickHouseColumn column = null; + try { + MappedFunctions m = getMappedFunctions(); + for (; index < size; index++) { + column = columns.get(index); + values[index] = m.deserialize(column, values[index], input); + } + } catch (EOFException e) { + if (index == 0) { // end of the stream, which is fine + values = null; + } else { + throw new IllegalStateException( + ClickHouseUtils.format("Reached end of the stream when reading column #%d(total %d): %s", + index + 1, size, column), + e); + } + } catch (IOException e) { + throw new IllegalStateException( + ClickHouseUtils.format("Failed to read column #%d(total %d): %s", index + 1, size, column), e); + } + } + + @Override + public boolean hasNext() { + try { + return input.available() > 0; + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public ClickHouseRecord next() { + readNextRow(); + + return new ClickHouseRecord() { + @Override + public int size() { + return values.length; + } + + @Override + public ClickHouseValue getValue(int index) throws IOException { + return values[index]; + } + + @Override + public ClickHouseValue getValue(String columnName) throws IOException { + int index = 0; + for (ClickHouseColumn c : columns) { + if (c.getColumnName().equals(columnName)) { + getValue(index); + } + index++; + } + + throw new IllegalArgumentException("Not able to find a column named: " + columnName); + } + }; + } + } + + @Override + protected List readColumns() throws IOException { + if (!config.getFormat().hasHeader()) { + return Collections.emptyList(); + } + + int size = 0; + try { + size = BinaryStreamUtils.readVarInt(input); + } catch (EOFException e) { + // no result returned + return Collections.emptyList(); + } + + String[] names = new String[ClickHouseChecker.between(size, "size", 0, Integer.MAX_VALUE)]; + for (int i = 0; i < size; i++) { + names[i] = BinaryStreamUtils.readString(input); + } + + List columns = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + columns.add(ClickHouseColumn.of(names[i], BinaryStreamUtils.readString(input))); + } + + return columns; + } + + public ClickHouseRowBinaryProcessor(ClickHouseConfig config, InputStream input, OutputStream output, + List columns, Map settings) throws IOException { + super(config, input, output, columns, settings); + } + + @Override + public Iterable records() { + return columns.isEmpty() ? Collections.emptyList() : new Iterable() { + @Override + public Iterator iterator() { + return new Records(); + } + }; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseShortValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseShortValue.java new file mode 100644 index 000000000..5a8fe9f2a --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseShortValue.java @@ -0,0 +1,280 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of short. + */ +public class ClickHouseShortValue implements ClickHouseValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseShortValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseShortValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseShortValue ? ((ClickHouseShortValue) ref).set(true, (short) 0) + : new ClickHouseShortValue(true, (short) 0); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseShortValue of(short value) { + return of(null, value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseShortValue of(int value) { + return of(null, (short) value); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseShortValue of(Number value) { + return value == null ? ofNull(null) : of(null, value.shortValue()); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseShortValue of(ClickHouseValue ref, short value) { + return ref instanceof ClickHouseShortValue ? ((ClickHouseShortValue) ref).set(false, value) + : new ClickHouseShortValue(false, value); + } + + private boolean isNull; + private short value; + + protected ClickHouseShortValue(boolean isNull, short value) { + set(isNull, value); + } + + protected ClickHouseShortValue set(boolean isNull, short value) { + this.isNull = isNull; + this.value = isNull ? 0 : value; + + return this; + } + + /** + * Gets value. + * + * @return value + */ + public short getValue() { + return value; + } + + @Override + public ClickHouseShortValue copy(boolean deep) { + return new ClickHouseShortValue(isNull, value); + } + + @Override + public boolean isNullOrEmpty() { + return isNull; + } + + @Override + public byte asByte() { + return (byte) value; + } + + @Override + public short asShort() { + return value; + } + + @Override + public int asInteger() { + return value; + } + + @Override + public long asLong() { + return value; + } + + @Override + public BigInteger asBigInteger() { + return isNull ? null : BigInteger.valueOf(value); + } + + @Override + public float asFloat() { + return value; + } + + @Override + public double asDouble() { + return value; + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNull ? null : BigDecimal.valueOf(value, scale); + } + + @Override + public Object asObject() { + return isNull ? null : getValue(); + } + + @Override + public String asString(int length, Charset charset) { + if (isNull) { + return null; + } + + String str = String.valueOf(value); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public ClickHouseShortValue resetToNullOrEmpty() { + return set(true, (short) 0); + } + + @Override + public String toSqlExpression() { + return isNullOrEmpty() ? ClickHouseValues.NULL_EXPR : String.valueOf(value); + } + + @Override + public ClickHouseShortValue update(boolean value) { + return set(false, value ? (short) 1 : (short) 0); + } + + @Override + public ClickHouseShortValue update(char value) { + return set(false, (short) value); + } + + @Override + public ClickHouseShortValue update(byte value) { + return set(false, value); + } + + @Override + public ClickHouseShortValue update(short value) { + return set(false, value); + } + + @Override + public ClickHouseShortValue update(int value) { + return set(false, (short) value); + } + + @Override + public ClickHouseShortValue update(long value) { + return set(false, (short) value); + } + + @Override + public ClickHouseShortValue update(float value) { + return set(false, (short) value); + } + + @Override + public ClickHouseShortValue update(double value) { + return set(false, (short) value); + } + + @Override + public ClickHouseShortValue update(BigInteger value) { + return value == null ? resetToNullOrEmpty() : set(false, value.shortValueExact()); + } + + @Override + public ClickHouseShortValue update(BigDecimal value) { + return value == null ? resetToNullOrEmpty() : set(false, value.shortValueExact()); + } + + @Override + public ClickHouseShortValue update(Enum value) { + return value == null ? resetToNullOrEmpty() : set(false, (short) value.ordinal()); + } + + @Override + public ClickHouseShortValue update(String value) { + return value == null ? resetToNullOrEmpty() : set(false, Short.parseShort(value)); + } + + @Override + public ClickHouseShortValue update(ClickHouseValue value) { + return value == null ? resetToNullOrEmpty() : set(false, value.asShort()); + } + + @Override + public ClickHouseShortValue update(Object value) { + if (value instanceof Number) { + return set(false, ((Number) value).shortValue()); + } else if (value instanceof ClickHouseValue) { + return set(false, ((ClickHouseValue) value).asShort()); + } + + ClickHouseValue.super.update(value); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseShortValue v = (ClickHouseShortValue) obj; + return isNull == v.isNull && value == v.value; + } + + @Override + public int hashCode() { + // not going to use Objects.hash(isNull, value) due to autoboxing + return (31 + (isNull ? 1231 : 1237)) * 31 + value; + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java new file mode 100644 index 000000000..bcd082b49 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java @@ -0,0 +1,323 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Objects; +import java.util.UUID; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of string. + */ +public class ClickHouseStringValue implements ClickHouseValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseStringValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseStringValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseStringValue ? ((ClickHouseStringValue) ref).set(null) + : new ClickHouseStringValue(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseStringValue of(String value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseStringValue of(ClickHouseValue ref, String value) { + return ref instanceof ClickHouseStringValue ? ((ClickHouseStringValue) ref).set(value) + : new ClickHouseStringValue(value); + } + + private String value; + + protected ClickHouseStringValue(String value) { + update(value); + } + + protected ClickHouseStringValue set(String value) { + this.value = value; + return this; + } + + /** + * Gets value. + * + * @return value + */ + public String getValue() { + return value; + } + + @Override + public ClickHouseStringValue copy(boolean deep) { + return new ClickHouseStringValue(value); + } + + @Override + public boolean isNullOrEmpty() { + return value == null; + } + + @Override + public boolean asBoolean() { + // what about Y/N, Yes/No, enabled/disabled? + return !isNullOrEmpty() && Boolean.parseBoolean(value); + } + + @Override + public byte asByte() { + return isNullOrEmpty() ? (byte) 0 : Byte.parseByte(value); + } + + @Override + public short asShort() { + return isNullOrEmpty() ? (short) 0 : Short.parseShort(value); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : Integer.parseInt(value); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : Long.parseLong(value); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : new BigInteger(value); + } + + @Override + public float asFloat() { + return isNullOrEmpty() ? 0F : Float.parseFloat(value); + } + + @Override + public double asDouble() { + return isNullOrEmpty() ? 0D : Double.parseDouble(value); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNullOrEmpty() ? null : new BigDecimal(asBigInteger(), scale); + } + + @Override + public LocalDate asDate() { + return isNullOrEmpty() ? null : LocalDate.parse(value, ClickHouseValues.DATE_FORMATTER); + } + + @Override + public LocalTime asTime() { + return isNullOrEmpty() ? null : LocalTime.parse(value, ClickHouseValues.TIME_FORMATTER); + } + + @Override + public LocalDateTime asDateTime(int scale) { + return isNullOrEmpty() ? null : LocalDateTime.parse(value, ClickHouseValues.DATETIME_FORMATTER); + } + + @Override + public > T asEnum(Class enumType) { + return isNullOrEmpty() ? null : Enum.valueOf(enumType, value); + } + + @Override + public Inet4Address asInet4Address() { + return ClickHouseValues.convertToIpv4(getValue()); + } + + @Override + public Inet6Address asInet6Address() { + return ClickHouseValues.convertToIpv6(getValue()); + } + + @Override + public Object asObject() { + return value; + } + + @Override + public String asString(int length, Charset charset) { + if (value != null && length > 0) { + ClickHouseChecker.notWithDifferentLength(value.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return value; + } + + @Override + public UUID asUuid() { + return isNullOrEmpty() ? null : UUID.fromString(value); + } + + @Override + public ClickHouseStringValue resetToNullOrEmpty() { + return set(null); + } + + @Override + public String toSqlExpression() { + return ClickHouseValues.convertToQuotedString(value); + } + + @Override + public ClickHouseStringValue update(boolean value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(char value) { + // consistent with asCharacter() + return set(String.valueOf((int) value)); + } + + @Override + public ClickHouseStringValue update(byte value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(short value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(int value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(long value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(float value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(double value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseValue update(BigInteger value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseValue update(BigDecimal value) { + return set(String.valueOf(value)); + } + + @Override + public ClickHouseStringValue update(Enum value) { + return set(value == null ? null : value.name()); + } + + @Override + public ClickHouseStringValue update(Inet4Address value) { + return set(value == null ? null : value.getHostAddress()); + } + + @Override + public ClickHouseStringValue update(Inet6Address value) { + return set(value == null ? null : value.getHostAddress()); + } + + @Override + public ClickHouseStringValue update(LocalDate value) { + return set(value == null ? null : value.format(ClickHouseValues.DATE_FORMATTER)); + } + + @Override + public ClickHouseStringValue update(LocalTime value) { + return set(value == null ? null : value.format(ClickHouseValues.TIME_FORMATTER)); + } + + @Override + public ClickHouseStringValue update(LocalDateTime value) { + return set(value == null ? null : value.format(ClickHouseValues.DATETIME_FORMATTER)); + } + + @Override + public ClickHouseStringValue update(String value) { + return set(value); + } + + @Override + public ClickHouseStringValue update(UUID value) { + return set(value == null ? null : value.toString()); + } + + @Override + public ClickHouseStringValue update(ClickHouseValue value) { + return set(value == null ? null : value.asString()); + } + + @Override + public ClickHouseStringValue update(Object value) { + return update(value == null ? null : value.toString()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseStringValue v = (ClickHouseStringValue) obj; + return value == v.value || (value != null && value.equals(v.value)); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return ClickHouseValues.convertToString(this); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTabSeparatedProcessor.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTabSeparatedProcessor.java new file mode 100644 index 000000000..d480d4980 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTabSeparatedProcessor.java @@ -0,0 +1,148 @@ +package com.clickhouse.client.data; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseDataProcessor; +import com.clickhouse.client.ClickHouseFormat; +import com.clickhouse.client.ClickHouseRecord; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.data.tsv.ByteFragment; +import com.clickhouse.client.data.tsv.StreamSplitter; + +public class ClickHouseTabSeparatedProcessor extends ClickHouseDataProcessor { + private static String[] toStringArray(ByteFragment headerFragment) { + ByteFragment[] split = headerFragment.split((byte) 0x09); + String[] array = new String[split.length]; + for (int i = 0; i < split.length; i++) { + array[i] = split[i].asString(true); + } + return array; + } + + private class Records implements Iterator { + private ByteFragment currentRow; + + Records() { + if (!columns.isEmpty()) { + readNextRow(); + } + } + + void readNextRow() { + try { + currentRow = splitter.next(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public boolean hasNext() { + return currentRow != null; + } + + @Override + public ClickHouseRecord next() { + if (!hasNext()) { + throw new NoSuchElementException("No more record"); + } + + ByteFragment[] currentCols = currentRow.split((byte) 0x09); + readNextRow(); + + return new ClickHouseRecord() { + @Override + public int size() { + return currentCols.length; + } + + @Override + public ClickHouseValue getValue(int index) { + return ClickHouseStringValue.of(null, currentCols[index].asString(true)); + } + + @Override + public ClickHouseValue getValue(String columnName) throws IOException { + int index = 0; + for (ClickHouseColumn c : columns) { + if (c.getColumnName().equals(columnName)) { + getValue(index); + } + index++; + } + + throw new IllegalArgumentException("Not able to find a column named: " + columnName); + } + }; + } + } + + private StreamSplitter splitter; + + @Override + public List readColumns() throws IOException { + if (input == null) { + return Collections.emptyList(); + } else if (!config.getFormat().hasHeader()) { + return DEFAULT_COLUMNS; + } + + this.splitter = new StreamSplitter(input, (byte) 0x0A, config.getMaxBufferSize()); + + ByteFragment headerFragment = this.splitter.next(); + if (headerFragment == null) { + throw new IllegalArgumentException("ClickHouse response without column names"); + } + String header = headerFragment.asString(true); + if (header.startsWith("Code: ") && !header.contains("\t")) { + input.close(); + throw new IllegalArgumentException("ClickHouse error: " + header); + } + String[] cols = toStringArray(headerFragment); + String[] types = null; + if (ClickHouseFormat.TabSeparatedWithNamesAndTypes == config.getFormat()) { + ByteFragment typesFragment = splitter.next(); + if (typesFragment == null) { + throw new IllegalArgumentException("ClickHouse response without column types"); + } + + types = toStringArray(typesFragment); + } + List list = new ArrayList<>(cols.length); + + for (int i = 0; i < cols.length; i++) { + list.add(ClickHouseColumn.of(cols[i], types == null ? "Nullable(String)" : types[i])); + } + + return list; + } + + public ClickHouseTabSeparatedProcessor(ClickHouseConfig config, InputStream input, OutputStream output, + List columns, Map settings) throws IOException { + super(config, input, output, columns, settings); + + if (this.splitter == null && input != null) { + this.splitter = new StreamSplitter(input, (byte) 0x0A, config.getMaxBufferSize()); + } + } + + @Override + public Iterable records() { + return new Iterable() { + @Override + public Iterator iterator() { + return new Records(); + } + }; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTimeValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTimeValue.java new file mode 100644 index 000000000..4a135ef08 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTimeValue.java @@ -0,0 +1,280 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of LocalTime. + */ +public class ClickHouseTimeValue extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseTimeValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseTimeValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseTimeValue ? (ClickHouseTimeValue) ((ClickHouseTimeValue) ref).set(null) + : new ClickHouseTimeValue(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseTimeValue of(LocalTime value) { + return of(null, value); + } + + /** + * Wrap the given value. + * + * @param secondOfDay second of day + * @return object representing the value + */ + public static ClickHouseTimeValue of(long secondOfDay) { + // what about nano second? + return of(null, LocalTime.ofSecondOfDay(secondOfDay)); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseTimeValue of(ClickHouseValue ref, LocalTime value) { + return ref instanceof ClickHouseTimeValue ? (ClickHouseTimeValue) ((ClickHouseTimeValue) ref).update(value) + : new ClickHouseTimeValue(value); + } + + protected ClickHouseTimeValue(LocalTime value) { + super(value); + } + + @Override + public ClickHouseTimeValue copy(boolean deep) { + return new ClickHouseTimeValue(getValue()); + } + + @Override + public byte asByte() { + return (byte) asInteger(); + } + + @Override + public short asShort() { + return (short) asInteger(); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : getValue().toSecondOfDay(); + } + + @Override + public long asLong() { + return asInteger(); + } + + @Override + public BigInteger asBigInteger() { + return isNullOrEmpty() ? null : BigInteger.valueOf(getValue().toSecondOfDay()); + } + + @Override + public float asFloat() { + return asInteger(); + } + + @Override + public double asDouble() { + return asInteger(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNullOrEmpty() ? null : new BigDecimal(BigInteger.valueOf(getValue().toSecondOfDay()), scale); + } + + @Override + public LocalDate asDate() { + return isNullOrEmpty() ? null : ClickHouseValues.DATE_ZERO; + } + + @Override + public LocalTime asTime() { + return getValue(); + } + + @Override + public LocalDateTime asDateTime(int scale) { + return isNullOrEmpty() ? null : LocalDateTime.of(ClickHouseValues.DATE_ZERO, getValue()); + } + + @Override + public String asString(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + String str = getValue().format(ClickHouseValues.TIME_FORMATTER); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + return new StringBuilder().append('\'').append(getValue().format(ClickHouseValues.TIME_FORMATTER)).append('\'') + .toString(); + } + + @Override + public ClickHouseTimeValue update(byte value) { + set(LocalTime.ofSecondOfDay(value)); + return this; + } + + @Override + public ClickHouseTimeValue update(short value) { + set(LocalTime.ofSecondOfDay(value)); + return this; + } + + @Override + public ClickHouseTimeValue update(int value) { + set(LocalTime.ofSecondOfDay(value)); + return this; + } + + @Override + public ClickHouseTimeValue update(long value) { + set(LocalTime.ofSecondOfDay(value)); + return this; + } + + @Override + public ClickHouseTimeValue update(float value) { + set(LocalTime.ofSecondOfDay((long) value)); + return this; + } + + @Override + public ClickHouseTimeValue update(double value) { + set(LocalTime.ofSecondOfDay((long) value)); + return this; + } + + @Override + public ClickHouseTimeValue update(BigInteger value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalTime.ofSecondOfDay(value.longValueExact())); + } + return this; + } + + @Override + public ClickHouseTimeValue update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalTime.ofSecondOfDay(value.longValueExact())); + } + return this; + } + + @Override + public ClickHouseTimeValue update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalTime.ofSecondOfDay(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseTimeValue update(LocalDate value) { + return this; + } + + @Override + public ClickHouseTimeValue update(LocalTime value) { + set(value); + return this; + } + + @Override + public ClickHouseTimeValue update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.toLocalTime()); + } + return this; + } + + @Override + public ClickHouseTimeValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(LocalTime.parse(value, ClickHouseValues.TIME_FORMATTER)); + } + return this; + } + + @Override + public ClickHouseTimeValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asTime()); + } + return this; + } + + @Override + public ClickHouseTimeValue update(Object value) { + if (value instanceof LocalTime) { + set((LocalTime) value); + } else if (value instanceof LocalDateTime) { + set(((LocalDateTime) value).toLocalTime()); + } else if (value instanceof LocalDate) { + set(LocalTime.MIN); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTupleValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTupleValue.java new file mode 100644 index 000000000..1e176451b --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseTupleValue.java @@ -0,0 +1,493 @@ +package com.clickhouse.client.data; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wrapper class of Tuple. + */ +public class ClickHouseTupleValue extends ClickHouseObjectValue> { + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseTupleValue of(Object... value) { + return of(null, Arrays.asList(value)); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseTupleValue of(ClickHouseValue ref, List value) { + return ref instanceof ClickHouseTupleValue ? (ClickHouseTupleValue) ((ClickHouseTupleValue) ref).set(value) + : new ClickHouseTupleValue(value); + } + + protected ClickHouseTupleValue(List value) { + super(value); + } + + protected Object getSingleValue() { + List value = getValue(); + + if (value == null || value.size() != 1) { + throw new UnsupportedOperationException("Only singleton tuple supports type conversion"); + } + + return value.iterator().next(); + } + + @Override + public ClickHouseTupleValue copy(boolean deep) { + if (!deep || isNullOrEmpty()) { + return new ClickHouseTupleValue(getValue()); + } + + return new ClickHouseTupleValue(new ArrayList<>(getValue())); + } + + @Override + public Object[] asArray() { + if (isNullOrEmpty()) { + return ClickHouseValues.EMPTY_ARRAY; + } + + List value = getValue(); + return value.toArray(new Object[value.size()]); + } + + @Override + @SuppressWarnings("unchecked") + public T[] asArray(Class clazz) { + if (isNullOrEmpty()) { + return (T[]) ClickHouseValues.EMPTY_ARRAY; + } + + List value = getValue(); + T[] array = (T[]) Array.newInstance(ClickHouseChecker.nonNull(clazz, ClickHouseValues.TYPE_CLASS), + value.size()); + int index = 0; + for (Object v : value) { + array[index++] = clazz.cast(v); + } + return array; + } + + @Override + public Map asMap() { + if (isNullOrEmpty()) { + return Collections.emptyMap(); + } + + Map map = new LinkedHashMap<>(); + int index = 1; + for (Object v : getValue()) { + map.put(index++, v); + } + return map; + } + + @Override + @SuppressWarnings("unchecked") + public Map asMap(Class keyClass, Class valueClass) { + if (isNullOrEmpty()) { + return Collections.emptyMap(); + } + + // Class.cast() cannot convert Integer to Byte or String, so we're stuck with + // Integer... + if (Integer.class != keyClass || valueClass == null) { + throw new IllegalArgumentException("Key class must be Integer and value class cannot be null"); + } + + Map map = new LinkedHashMap<>(); + int index = 1; + for (Object v : getValue()) { + map.put(index++, valueClass.cast(v)); + } + return (Map) map; + } + + @Override + public String asString(int length, Charset charset) { + String str = Arrays.deepToString(asArray()); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(str.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), + length); + } + + return str; + } + + @Override + public List asTuple() { + return getValue(); + } + + @Override + public boolean isNullOrEmpty() { + return getValue().isEmpty(); + } + + @Override + public ClickHouseTupleValue resetToNullOrEmpty() { + set(Collections.emptyList()); + return this; + } + + @Override + public String toSqlExpression() { + StringBuilder builder = new StringBuilder().append('('); + for (Object v : getValue()) { + builder.append(ClickHouseValues.convertToSqlExpression(v)).append(','); + } + if (builder.length() > 1) { + builder.setLength(builder.length() - 1); + } + return builder.append(')').toString(); + } + + @Override + public ClickHouseTupleValue update(boolean[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (boolean b : value) { + v.add(b ? (byte) 1 : (byte) 0); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(char[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (char c : value) { + v.add((int) c); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(byte value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(byte[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (byte b : value) { + v.add(b); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(short value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(short[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (short s : value) { + v.add(s); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(int value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(int[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (int i : value) { + v.add(i); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(long value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(long[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (long l : value) { + v.add(l); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(float value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(float[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (float f : value) { + v.add(f); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(double value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(double[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(value.length); + for (double d : value) { + v.add(d); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(BigInteger value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(BigDecimal value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(Enum value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(Inet4Address value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(Inet6Address value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(LocalDate value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(LocalTime value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(LocalDateTime value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(Collection value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(size); + for (Object o : value) { + v.add(o); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(Enumeration value) { + if (value == null) { + set(Collections.emptyList()); + return this; + } + + List v = new LinkedList<>(); + while (value.hasMoreElements()) { + v.add(value.nextElement()); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(Map value) { + int size = value == null ? 0 : value.size(); + if (size == 0) { + set(Collections.emptyList()); + return this; + } + + List v = new ArrayList<>(size); + for (Entry e : value.entrySet()) { + v.add(e.getValue()); + } + set(v); + return this; + } + + @Override + public ClickHouseTupleValue update(String value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(UUID value) { + set(Collections.singletonList(value)); + return this; + } + + @Override + public ClickHouseTupleValue update(ClickHouseValue value) { + if (value == null) { + set(Collections.emptyList()); + return this; + } + + set(value.asTuple()); + return this; + } + + @Override + public ClickHouseTupleValue update(Object[] value) { + if (value == null || value.length == 0) { + set(Collections.emptyList()); + return this; + } + + set(Arrays.asList(value)); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ClickHouseTupleValue update(Object value) { + if (value instanceof List) { + set((List) value); + } else { + super.update(value); + } + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { // too bad this is a mutable class :< + return true; + } else if (obj == null || getClass() != obj.getClass()) { + return false; + } + + List v1 = getValue(); + List v2 = ((ClickHouseTupleValue) obj).getValue(); + return v1 == v2 || (v1 != null && v1.equals(v2)); // deep equal? + } + + @Override + public int hashCode() { + List v = getValue(); + return Arrays.deepHashCode(v == null ? null : v.toArray(new Object[v.size()])); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseUuidValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseUuidValue.java new file mode 100644 index 000000000..7fe3955c8 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseUuidValue.java @@ -0,0 +1,260 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.UUID; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +/** + * Wraper class of string. + */ +public class ClickHouseUuidValue extends ClickHouseObjectValue { + /** + * Create a new instance representing null value. + * + * @return new instance representing null value + */ + public static ClickHouseUuidValue ofNull() { + return ofNull(null); + } + + /** + * Update given value to null or create a new instance if {@code ref} is null. + * + * @param ref object to update, could be null + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseUuidValue ofNull(ClickHouseValue ref) { + return ref instanceof ClickHouseUuidValue ? (ClickHouseUuidValue) ((ClickHouseUuidValue) ref).set(null) + : new ClickHouseUuidValue(null); + } + + /** + * Wrap the given value. + * + * @param value value + * @return object representing the value + */ + public static ClickHouseUuidValue of(UUID value) { + return of(null, value); + } + + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param value value + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseUuidValue of(ClickHouseValue ref, UUID value) { + return ref instanceof ClickHouseUuidValue ? (ClickHouseUuidValue) ((ClickHouseUuidValue) ref).set(value) + : new ClickHouseUuidValue(value); + } + + protected ClickHouseUuidValue(UUID value) { + super(value); + } + + @Override + public ClickHouseUuidValue copy(boolean deep) { + return new ClickHouseUuidValue(getValue()); + } + + @Override + public byte asByte() { + return isNullOrEmpty() ? (byte) 0 : asBigInteger().byteValueExact(); + } + + @Override + public short asShort() { + return isNullOrEmpty() ? (short) 0 : asBigInteger().shortValueExact(); + } + + @Override + public int asInteger() { + return isNullOrEmpty() ? 0 : asBigInteger().intValueExact(); + } + + @Override + public long asLong() { + return isNullOrEmpty() ? 0L : asBigInteger().longValueExact(); + } + + @Override + public BigInteger asBigInteger() { + return ClickHouseValues.convertToBigInteger(getValue()); + } + + @Override + public float asFloat() { + return isNullOrEmpty() ? 0F : asBigInteger().floatValue(); + } + + @Override + public double asDouble() { + return isNullOrEmpty() ? 0D : asBigInteger().doubleValue(); + } + + @Override + public BigDecimal asBigDecimal(int scale) { + return isNullOrEmpty() ? null : new BigDecimal(asBigInteger(), scale); + } + + @Override + public Object asObject() { + return getValue(); + } + + @Override + public UUID asUuid() { + return getValue(); + } + + @Override + public String toSqlExpression() { + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } + return new StringBuilder().append('\'').append(getValue().toString()).append('\'').toString(); + } + + @Override + public ClickHouseUuidValue update(byte value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseUuidValue update(short value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseUuidValue update(int value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseUuidValue update(long value) { + return update(BigInteger.valueOf(value)); + } + + @Override + public ClickHouseUuidValue update(float value) { + return update(BigDecimal.valueOf(value).toBigIntegerExact()); + } + + @Override + public ClickHouseUuidValue update(double value) { + return update(BigDecimal.valueOf(value).toBigIntegerExact()); + } + + @Override + public ClickHouseUuidValue update(BigInteger value) { + set(ClickHouseValues.convertToUuid(value)); + return this; + } + + @Override + public ClickHouseUuidValue update(BigDecimal value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(value.toBigIntegerExact()); + } + return this; + } + + @Override + public ClickHouseUuidValue update(Enum value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.ordinal())); + } + return this; + } + + @Override + public ClickHouseUuidValue update(Inet4Address value) { + return update(ClickHouseValues.convertToBigInteger(value)); + } + + @Override + public ClickHouseUuidValue update(Inet6Address value) { + return update(ClickHouseValues.convertToBigInteger(value)); + } + + @Override + public ClickHouseUuidValue update(LocalDate value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.toEpochDay())); + } + return this; + } + + @Override + public ClickHouseUuidValue update(LocalTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.toSecondOfDay())); + } + return this; + } + + @Override + public ClickHouseUuidValue update(LocalDateTime value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + update(BigInteger.valueOf(value.toEpochSecond(ZoneOffset.UTC))); + } + return this; + } + + @Override + public ClickHouseUuidValue update(String value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(UUID.fromString(value)); + } + return this; + } + + @Override + public ClickHouseUuidValue update(UUID value) { + set(value); + return this; + } + + @Override + public ClickHouseUuidValue update(ClickHouseValue value) { + if (value == null) { + resetToNullOrEmpty(); + } else { + set(value.asUuid()); + } + return this; + } + + @Override + public ClickHouseUuidValue update(Object value) { + if (value instanceof UUID) { + set((UUID) value); + } else { + super.update(value); + } + return this; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/package-info.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/package-info.java new file mode 100644 index 000000000..1621d43a4 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides necessary classes to handle different format or type of data. + */ +package com.clickhouse.client.data; diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/ArrayByteFragment.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/ArrayByteFragment.java new file mode 100644 index 000000000..6cea30656 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/ArrayByteFragment.java @@ -0,0 +1,18 @@ +package com.clickhouse.client.data.tsv; + +public final class ArrayByteFragment extends ByteFragment { + + private ArrayByteFragment(byte[] buf, int start, int len) { + super(buf, start, len); + } + + public static ArrayByteFragment wrap(ByteFragment fragment) { + return new ArrayByteFragment(fragment.buf, fragment.start, fragment.len); + } + + @Override + public boolean isNull() { + // NULL + return len == 4 && buf[start] == 'N' && buf[start + 1] == 'U' && buf[start + 2] == 'L' && buf[start + 3] == 'L'; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/ByteFragment.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/ByteFragment.java new file mode 100644 index 000000000..989929305 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/ByteFragment.java @@ -0,0 +1,245 @@ +package com.clickhouse.client.data.tsv; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class ByteFragment { + + protected final byte[] buf; + protected final int start; + protected final int len; + private static final ByteFragment EMPTY = new ByteFragment(new byte[0], 0, 0); + + public ByteFragment(byte[] buf, int start, int len) { + this.buf = buf; + this.start = start; + this.len = len; + } + + public static ByteFragment fromString(String str) { + // https://bugs.openjdk.java.net/browse/JDK-6219899 + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return new ByteFragment(bytes, 0, bytes.length); + } + + public String asString() { + return new String(buf, start, len, StandardCharsets.UTF_8); + } + + public String asString(boolean unescape) { + if (unescape) { + if (isNull()) { + return null; + } + return new String(unescape(), StandardCharsets.UTF_8); + } else { + return asString(); + } + } + + public boolean isNull() { + // \N + return len == 2 && buf[start] == '\\' && buf[start + 1] == 'N'; + } + + public boolean isEmpty() { + return len == 0; + } + + public boolean isNaN() { + // nan + return len == 3 && buf[start] == 'n' && buf[start + 1] == 'a' && buf[start + 2] == 'n'; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("ByteFragment{["); + for (byte b1 : buf) { + if (b1 == '\t') { + b.append(""); + } else { + b.append((char) b1); + } + } + b.append(']'); + b.append(", start=" + start + ", len=" + len + '}'); + return b.toString(); + } + + public ByteFragment[] split(byte sep) { + StreamSplitter ss = new StreamSplitter(this, sep); + int c = count(sep) + 1; + ByteFragment[] res = new ByteFragment[c]; + try { + int i = 0; + ByteFragment next = null; + while ((next = ss.next()) != null) { + res[i++] = next; + } + } catch (IOException ignore) { + } + if (res[c - 1] == null) { + res[c - 1] = ByteFragment.EMPTY; + } + return res; + } + + // [45, 49, 57, 52, 49, 51, 56, 48, 57, 49, 52, 9, 9, 50, 48, 49, 50, 45, 48, + // 55, 45, 49, 55, 32, 49, 51, 58, 49, 50, 58, 50, 49, 9, 49, 50, 49, 50, 55, + // 53, 53, 9, 50, 57, 57, 57, 55, 55, 57, 57, 55, 56, 9, 48, 9, 52, 48, 57, 49, + // 57, 55, 52, 49, 49, 51, 50, 56, 53, 53, 50, 54, 57, 51, 9, 51, 9, 54, 9, 50, + // 48, 9, 48, 92, 48, 9, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, + // 118, 105, 116, 111, 46, 114, 117, 47, 99, 97, 116, 97, 108, 111, 103, 47, + // 103, 97, 114, 97, 122, 104, 105, 95, 105, 95, 109, 97, 115, 104, 105, 110, + // 111, 109, 101, 115, 116, 97, 45, 56, 53, 47, 116, 97, 116, 97, 114, 115, 116, + // 97, 110, 45, 54, 53, 48, 49, 51, 48, 47, 112, 97, 103, 101, 56, 9, 104, 116, + // 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 118, 105, 116, 111, 46, 114, + // 117, 47, 99, 97, 116, 97, 108, 111, 103, 47, 103, 97, 114, 97, 122, 104, 105, + // 95, 105, 95, 109, 97, 115, 104, 105, 110, 111, 109, 101, 115, 116, 97, 45, + // 56, 53, 47, 116, 97, 116, 97, 114, 115, 116, 97, 110, 45, 54, 53, 48, 49, 51, + // 48, 47, 112, 97, 103, 101, 55, 9, 48, 9, 48, 9, 50, 56, 53, 55, 48, 56, 48, + // 9, 45, 49, 9, 48, 9, 9, 48, 9, 48, 9, 48, 9, 45, 49, 9, 48, 48, 48, 48, 45, + // 48, 48, 45, 48, 48, 32, 48, 48, 58, 48, 48, 58, 48, 48, 9, 9, 48, 9, 48, 9, + // 103, 9, 45, 49, 9, 45, 49, 9, 45, 49, 9] + public ByteArrayInputStream asStream() { + return new ByteArrayInputStream(buf, start, len); + } + + private int count(byte sep) { + int res = 0; + for (int i = start; i < start + len; i++) { + if (buf[i] == sep) { + res++; + } + } + return res; + } + + public int getLen() { + return len; + } + + // "\0" => 0 + // "\r" => 13 + // "\n" => 10 + // "\\" => 92 + // "\'" => 39 + // "\b" => 8 + // "\f" => 12 + // "\t" => 9 + // null + // "\N" => 0 + private static final byte[] convert = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0.. 9 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10..19 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20..29 + -1, -1, -1, -1, -1, -1, -1, -1, -1, 39, // 30..39 + -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, // 40..49 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 50..59 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 60..69 + -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, // 70..79 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80..89 + -1, -1, 92, -1, -1, -1, -1, -1, 8, -1, // 90..99 + -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, // 100..109 + 10, -1, -1, -1, 13, -1, 9, -1, -1, -1, // 110..119 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 120..129 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, }; + // [0xb6][0xfe][0x7][0x1][0xd8][0xd6][0x94][0x80][0x5]\0 html. + // [-74,-2,7,1,-40,-42,-108,-128,5,0] real value + // [-74,-2,7,1,-40,-42,-108,-128,5,92,48] parsed value + + public byte[] unescape() { + int resLen = 0; + { + boolean prevSlash = false; + for (int i = start; i < start + len; i++) { + if (prevSlash) { + resLen++; + prevSlash = false; + } else { + if (buf[i] == 92) { // slash character + prevSlash = true; + } else { + resLen++; + } + } + } + } + if (resLen == len) { + return getBytesCopy(); + } + byte[] res = new byte[resLen]; + int index = 0; + { + boolean prevSlash = false; + for (int i = start; i < start + len; i++) { + if (prevSlash) { + prevSlash = false; + res[index++] = convert[buf[i]]; + + } else { + if (buf[i] == 92) { // slash character + prevSlash = true; + } else { + res[index++] = buf[i]; + } + } + + } + } + return res; + } + + final static byte[] reverse; + static { + reverse = new byte[convert.length]; + for (int i = 0; i < convert.length; i++) { + reverse[i] = -1; + byte c = convert[i]; + if (c != -1) { + reverse[c] = (byte) i; + } + } + } + + public static void escape(byte[] bytes, OutputStream stream) throws IOException { + for (byte b : bytes) { + if (b < 0 || b >= reverse.length) { + stream.write(b); + } else { + byte converted = reverse[b]; + if (converted != -1) { + stream.write(92); + stream.write(converted); + } else { + stream.write(b); + } + } + } + } + + private byte[] getBytesCopy() { + byte[] bytes = new byte[len]; + System.arraycopy(buf, start, bytes, 0, len); + return bytes; + } + + public int length() { + return len; + } + + public int charAt(int i) { + return buf[start + i]; + } + + public ByteFragment subseq(int start, int len) { + if (start < 0 || start + len > this.len) { + throw new IllegalArgumentException( + "arg start,len=" + (start + "," + len) + " while this start,len=" + (this.start + "," + this.len)); + } + return new ByteFragment(buf, this.start + start, len); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/FastByteArrayInputStream.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/FastByteArrayInputStream.java new file mode 100644 index 000000000..bdbee4527 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/FastByteArrayInputStream.java @@ -0,0 +1,113 @@ +package com.clickhouse.client.data.tsv; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Not synchronized quick version of {@link java.io.ByteArrayInputStream} + */ +public final class FastByteArrayInputStream extends InputStream { + private final byte[] buf; + + private int pos; + + private final int count; + + public FastByteArrayInputStream(byte[] buf) { + this.buf = buf; + pos = 0; + count = buf.length; + } + + /** + * Special constructor fo creating InputStream over not fully filled array + * + * @param buf byte array + * @param count number of filled elements + */ + public FastByteArrayInputStream(byte[] buf, int count) { + this.buf = buf; + pos = 0; + this.count = count; + } + + @Override + public int read() { + return pos < count ? buf[pos++] & 0xff : -1; + } + + @Override + public int read(byte[] b, int off, int len) { + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + if (pos >= count) { + return -1; + } + if (pos + len > count) { + // noinspection AssignmentToMethodParameter + len = count - pos; + } + if (len <= 0) { + return 0; + } + System.arraycopy(buf, pos, b, off, len); + pos += len; + return len; + } + + @Override + public long skip(long n) { + if (pos + n > count) { + // noinspection AssignmentToMethodParameter + n = count - pos; + } + if (n < 0) { + return 0; + } + pos += (int) n; + return n; + } + + @Override + public int available() { + return count - pos; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public void close() throws IOException { + } + + public int getPos() { + return pos; + } + + public int getCount() { + return count; + } + + public byte[] getBuf() { + return buf; + } + + public byte[] getData() { + if (buf.length > count) { + byte[] data = new byte[count]; + System.arraycopy(buf, 0, data, 0, count); + return data; + } else { + return buf; + } + } + + @Override + public void reset() { + pos = 0; + } + +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/FastByteArrayOutputStream.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/FastByteArrayOutputStream.java new file mode 100644 index 000000000..a55654871 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/FastByteArrayOutputStream.java @@ -0,0 +1,167 @@ +package com.clickhouse.client.data.tsv; + +import java.io.DataOutput; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * Not synchronized quick version of {@link java.io.ByteArrayOutputStream} + */ + +public final class FastByteArrayOutputStream extends OutputStream { + + /** + * The buffer where data is stored. + */ + private byte[] buf; + + /** + * The number of valid bytes in the buffer. + */ + private int count; + + /** + * Creates a new byte array output stream. The buffer capacity is initially 32 + * bytes, though its size increases if necessary. + */ + public FastByteArrayOutputStream() { + this(1024); + } + + /** + * Creates a new byte array output stream, with a buffer capacity of the + * specified size, in bytes. + * + * @param size the initial size. + * @exception IllegalArgumentException if size is negative. + */ + public FastByteArrayOutputStream(int size) { + super(); + if (size < 0) { + throw new IllegalArgumentException("Negative initial size: " + size); + } + buf = new byte[size]; + } + + private int ensureCapacity(int datalen) { + int newcount = count + datalen; + if (newcount > buf.length) { + buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount)); + } + return newcount; + } + + /** + * Writes the specified byte to this byte array output stream. + * + * @param b the byte to be written. + */ + @Override + public void write(int b) { + int newcount = ensureCapacity(1); + buf[count] = (byte) b; + count = newcount; + } + + /** + * Writes len bytes from the specified byte array starting at + * offset off to this byte array output stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + */ + @Override + public void write(byte[] b, int off, int len) { + if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + int newcount = ensureCapacity(len); + System.arraycopy(b, off, buf, count, len); + count = newcount; + } + + /** + * returns inner array + * + * @return the current contents of this output stream, as a byte array. + */ + public byte[] toByteArray() { + byte[] result = new byte[count]; + System.arraycopy(buf, 0, result, 0, count); + return result; + } + + public void writeTo(OutputStream output) throws IOException { + output.write(buf, 0, count); + } + + /** + * Returns the current size of the buffer. + * + * @return the value of the count field, which is the number of + * valid bytes in this output stream. + */ + public int size() { + return count; + } + + /** + * Closing a {@code ByteArrayOutputStream} has no effect. The methods in this + * class can be called after the stream has been closed without generating an + * {@code IOException}. + */ + @Override + public void close() throws IOException { + } + + /** + * Copies data from input stream + * + * @param source source stream + * @param offset offset in the source + * @param count number of bytes to copy + */ + public void copyFrom(FastByteArrayInputStream source, int offset, int count) { + if (offset + count > source.getCount()) { + throw new IndexOutOfBoundsException("Trying to copy data past the end of source" + ", source.size=" + + source.getCount() + ", offset=" + offset + ", count=" + count); + } + byte[] srcBuf = source.getBuf(); + write(srcBuf, offset, count); + } + + public void copyTo(OutputStream dest) throws IOException { + dest.write(buf, 0, count); + } + + public void copyTo(DataOutput dest) throws IOException { + dest.write(buf, 0, count); + } + + /** + * Creates InputStream using the same data that is written into this stream with + * no copying in memory + * + * @return a input stream contained all bytes recorded in a current stream + */ + public FastByteArrayInputStream convertToInputStream() { + return new FastByteArrayInputStream(buf, count); + } + + public ByteBuffer toByteBuffer() { + return ByteBuffer.wrap(buf, 0, count); + } + + public byte[] getBuffer() { + return buf; + } + + public void reset() { + count = 0; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/StreamSplitter.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/StreamSplitter.java new file mode 100644 index 000000000..71ee17deb --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/StreamSplitter.java @@ -0,0 +1,156 @@ +package com.clickhouse.client.data.tsv; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * We have a stream of bytes and a separator as an input. We split the stream by + * the separator and pass the byte arrays to output. + */ +public class StreamSplitter { + private static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + private static final int buflen = 65536; + + // initial parameters + private final InputStream delegate; + private final byte sep; + + private byte[] buf; + // position until which the buf is filled with values read from delegate + private int posRead; + // position until which the values from buf already passed out through next() + private int posNext; + + private int markedRead; + private int markedNext; + + private boolean readOnce; + + private boolean closed; + + public StreamSplitter(ByteFragment bf, byte sep) { + this.delegate = bf.asStream(); + this.sep = sep; + buf = new byte[bf.getLen()]; + readOnce = true; + } + + public StreamSplitter(InputStream delegate, byte sep, int buflen) { + this.delegate = delegate; + this.sep = sep; + buf = new byte[buflen]; + } + + public StreamSplitter(InputStream delegate, byte sep) { + this(delegate, sep, buflen); + } + + public ByteFragment next() throws IOException { + // if sent out all that have read + if (posNext >= posRead) { + // need to read more from the stream + int readBytes = readFromStream(); + if (readBytes <= 0) { + // if everything was sent out and there is nothing left in the stream + return null; + } + } + // looking for the separator + int positionSep; + while ((positionSep = indexOf(buf, sep, posNext, posRead)) < posNext) { + // read from stream till we find the separator + int readBytes = readFromStream(); + if (readBytes <= 0) { + // if there is nothing to read, return everything left as a result + positionSep = posRead; + break; + } + } + // if the separator is found, return the fragment + int fragmentStart = posNext; + posNext = positionSep + 1; + return new ByteFragment(buf, fragmentStart, positionSep - fragmentStart); + } + + // if there is no separator in read but not sent fragment - read more data + protected int readFromStream() throws IOException { + if (readOnce) { + if (posRead >= buf.length) { + return -1; + } else { + int read = delegate.read(buf, posRead, buf.length - posRead); + if (read > 0) { + posRead += read; + } + return read; + } + } else { + if (posRead >= buf.length) { // buffer is filled + shiftOrResize(); + } + int read = delegate.read(buf, posRead, buf.length - posRead); + if (read > 0) { + posRead += read; + } + return read; + } + } + + // if we have read till the end of buffer, we have to create a new buffer + // and move data by posNext (already send data position) + // if there is no sent data and buffer is still full - expand the buffer + private void shiftOrResize() { + if (posNext > 0) { + byte[] oldBuf = buf; + buf = new byte[buf.length]; + System.arraycopy(oldBuf, posNext, buf, 0, oldBuf.length - posNext); + posRead -= posNext; + posNext = 0; + } else { + byte[] oldBuf = buf; + int len = buf.length * 2; + if (len > MAX_ARRAY_LENGTH) { + len = MAX_ARRAY_LENGTH; + } + buf = new byte[len]; + System.arraycopy(oldBuf, 0, buf, 0, oldBuf.length); + } + } + + private static int indexOf(byte[] array, byte target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + public void close() throws IOException { + closed = true; + delegate.close(); + } + + public boolean isClosed() throws IOException { + return closed; + } + + @Override + public String toString() { + String bufStr = new String(buf, StandardCharsets.UTF_8).trim(); + + return "StreamSplitter{" + "delegate=" + delegate + ", sep=" + sep + ", buf=" + bufStr + ", posRead=" + posRead + + ", posNext=" + posNext + ", readOnce=" + readOnce + '}'; + } + + public void mark() { + markedRead = posRead; + markedNext = posNext; + } + + public void reset() { + posRead = markedRead; + posNext = markedNext; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/package-info.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/package-info.java new file mode 100644 index 000000000..fcf3366a0 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/tsv/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides necessary classes to handle TSV format. + */ +package com.clickhouse.client.data.tsv; diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseErrorCode.java b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseErrorCode.java new file mode 100644 index 000000000..82f1bdb8f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseErrorCode.java @@ -0,0 +1,618 @@ +package com.clickhouse.client.exception; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * All supported error codes defined in ClickHouse. + */ +public enum ClickHouseErrorCode { + // grep -n '^[[:space:]]*M(' | sed -e + // 's|^\([0-9]*\):[[:space:]]*M(\([0-9]*\),[[:space:]]*\([0-9A-Z_]*\)).*|\3(\2), + // // + // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L\1|g' + OK(0), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L17 + UNSUPPORTED_METHOD(1), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L18 + UNSUPPORTED_PARAMETER(2), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L19 + UNEXPECTED_END_OF_FILE(3), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L20 + EXPECTED_END_OF_FILE(4), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L21 + CANNOT_PARSE_TEXT(6), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L22 + INCORRECT_NUMBER_OF_COLUMNS(7), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L23 + THERE_IS_NO_COLUMN(8), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L24 + SIZES_OF_COLUMNS_DOESNT_MATCH(9), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L25 + NOT_FOUND_COLUMN_IN_BLOCK(10), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L26 + POSITION_OUT_OF_BOUND(11), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L27 + PARAMETER_OUT_OF_BOUND(12), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L28 + SIZES_OF_COLUMNS_IN_TUPLE_DOESNT_MATCH(13), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L29 + DUPLICATE_COLUMN(15), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L30 + NO_SUCH_COLUMN_IN_TABLE(16), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L31 + DELIMITER_IN_STRING_LITERAL_DOESNT_MATCH(17), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L32 + CANNOT_INSERT_ELEMENT_INTO_CONSTANT_COLUMN(18), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L33 + SIZE_OF_FIXED_STRING_DOESNT_MATCH(19), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L34 + NUMBER_OF_COLUMNS_DOESNT_MATCH(20), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L35 + CANNOT_READ_ALL_DATA_FROM_TAB_SEPARATED_INPUT(21), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L36 + CANNOT_PARSE_ALL_VALUE_FROM_TAB_SEPARATED_INPUT(22), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L37 + CANNOT_READ_FROM_ISTREAM(23), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L38 + CANNOT_WRITE_TO_OSTREAM(24), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L39 + CANNOT_PARSE_ESCAPE_SEQUENCE(25), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L40 + CANNOT_PARSE_QUOTED_STRING(26), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L41 + CANNOT_PARSE_INPUT_ASSERTION_FAILED(27), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L42 + CANNOT_PRINT_FLOAT_OR_DOUBLE_NUMBER(28), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L43 + CANNOT_PRINT_INTEGER(29), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L44 + CANNOT_READ_SIZE_OF_COMPRESSED_CHUNK(30), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L45 + CANNOT_READ_COMPRESSED_CHUNK(31), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L46 + ATTEMPT_TO_READ_AFTER_EOF(32), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L47 + CANNOT_READ_ALL_DATA(33), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L48 + TOO_MANY_ARGUMENTS_FOR_FUNCTION(34), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L49 + TOO_FEW_ARGUMENTS_FOR_FUNCTION(35), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L50 + BAD_ARGUMENTS(36), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L51 + UNKNOWN_ELEMENT_IN_AST(37), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L52 + CANNOT_PARSE_DATE(38), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L53 + TOO_LARGE_SIZE_COMPRESSED(39), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L54 + CHECKSUM_DOESNT_MATCH(40), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L55 + CANNOT_PARSE_DATETIME(41), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L56 + NUMBER_OF_ARGUMENTS_DOESNT_MATCH(42), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L57 + ILLEGAL_TYPE_OF_ARGUMENT(43), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L58 + ILLEGAL_COLUMN(44), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L59 + ILLEGAL_NUMBER_OF_RESULT_COLUMNS(45), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L60 + UNKNOWN_FUNCTION(46), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L61 + UNKNOWN_IDENTIFIER(47), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L62 + NOT_IMPLEMENTED(48), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L63 + LOGICAL_ERROR(49), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L64 + UNKNOWN_TYPE(50), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L65 + EMPTY_LIST_OF_COLUMNS_QUERIED(51), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L66 + COLUMN_QUERIED_MORE_THAN_ONCE(52), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L67 + TYPE_MISMATCH(53), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L68 + STORAGE_DOESNT_ALLOW_PARAMETERS(54), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L69 + STORAGE_REQUIRES_PARAMETER(55), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L70 + UNKNOWN_STORAGE(56), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L71 + TABLE_ALREADY_EXISTS(57), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L72 + TABLE_METADATA_ALREADY_EXISTS(58), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L73 + ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER(59), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L74 + UNKNOWN_TABLE(60), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L75 + ONLY_FILTER_COLUMN_IN_BLOCK(61), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L76 + SYNTAX_ERROR(62), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L77 + UNKNOWN_AGGREGATE_FUNCTION(63), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L78 + CANNOT_READ_AGGREGATE_FUNCTION_FROM_TEXT(64), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L79 + CANNOT_WRITE_AGGREGATE_FUNCTION_AS_TEXT(65), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L80 + NOT_A_COLUMN(66), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L81 + ILLEGAL_KEY_OF_AGGREGATION(67), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L82 + CANNOT_GET_SIZE_OF_FIELD(68), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L83 + ARGUMENT_OUT_OF_BOUND(69), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L84 + CANNOT_CONVERT_TYPE(70), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L85 + CANNOT_WRITE_AFTER_END_OF_BUFFER(71), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L86 + CANNOT_PARSE_NUMBER(72), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L87 + UNKNOWN_FORMAT(73), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L88 + CANNOT_READ_FROM_FILE_DESCRIPTOR(74), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L89 + CANNOT_WRITE_TO_FILE_DESCRIPTOR(75), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L90 + CANNOT_OPEN_FILE(76), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L91 + CANNOT_CLOSE_FILE(77), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L92 + UNKNOWN_TYPE_OF_QUERY(78), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L93 + INCORRECT_FILE_NAME(79), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L94 + INCORRECT_QUERY(80), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L95 + UNKNOWN_DATABASE(81), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L96 + DATABASE_ALREADY_EXISTS(82), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L97 + DIRECTORY_DOESNT_EXIST(83), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L98 + DIRECTORY_ALREADY_EXISTS(84), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L99 + FORMAT_IS_NOT_SUITABLE_FOR_INPUT(85), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L100 + RECEIVED_ERROR_FROM_REMOTE_IO_SERVER(86), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L101 + CANNOT_SEEK_THROUGH_FILE(87), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L102 + CANNOT_TRUNCATE_FILE(88), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L103 + UNKNOWN_COMPRESSION_METHOD(89), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L104 + EMPTY_LIST_OF_COLUMNS_PASSED(90), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L105 + SIZES_OF_MARKS_FILES_ARE_INCONSISTENT(91), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L106 + EMPTY_DATA_PASSED(92), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L107 + UNKNOWN_AGGREGATED_DATA_VARIANT(93), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L108 + CANNOT_MERGE_DIFFERENT_AGGREGATED_DATA_VARIANTS(94), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L109 + CANNOT_READ_FROM_SOCKET(95), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L110 + CANNOT_WRITE_TO_SOCKET(96), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L111 + CANNOT_READ_ALL_DATA_FROM_CHUNKED_INPUT(97), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L112 + CANNOT_WRITE_TO_EMPTY_BLOCK_OUTPUT_STREAM(98), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L113 + UNKNOWN_PACKET_FROM_CLIENT(99), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L114 + UNKNOWN_PACKET_FROM_SERVER(100), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L115 + UNEXPECTED_PACKET_FROM_CLIENT(101), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L116 + UNEXPECTED_PACKET_FROM_SERVER(102), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L117 + RECEIVED_DATA_FOR_WRONG_QUERY_ID(103), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L118 + TOO_SMALL_BUFFER_SIZE(104), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L119 + CANNOT_READ_HISTORY(105), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L120 + CANNOT_APPEND_HISTORY(106), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L121 + FILE_DOESNT_EXIST(107), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L122 + NO_DATA_TO_INSERT(108), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L123 + CANNOT_BLOCK_SIGNAL(109), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L124 + CANNOT_UNBLOCK_SIGNAL(110), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L125 + CANNOT_MANIPULATE_SIGSET(111), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L126 + CANNOT_WAIT_FOR_SIGNAL(112), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L127 + THERE_IS_NO_SESSION(113), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L128 + CANNOT_CLOCK_GETTIME(114), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L129 + UNKNOWN_SETTING(115), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L130 + THERE_IS_NO_DEFAULT_VALUE(116), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L131 + INCORRECT_DATA(117), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L132 + ENGINE_REQUIRED(119), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L133 + CANNOT_INSERT_VALUE_OF_DIFFERENT_SIZE_INTO_TUPLE(120), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L134 + UNSUPPORTED_JOIN_KEYS(121), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L135 + INCOMPATIBLE_COLUMNS(122), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L136 + UNKNOWN_TYPE_OF_AST_NODE(123), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L137 + INCORRECT_ELEMENT_OF_SET(124), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L138 + INCORRECT_RESULT_OF_SCALAR_SUBQUERY(125), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L139 + CANNOT_GET_RETURN_TYPE(126), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L140 + ILLEGAL_INDEX(127), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L141 + TOO_LARGE_ARRAY_SIZE(128), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L142 + FUNCTION_IS_SPECIAL(129), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L143 + CANNOT_READ_ARRAY_FROM_TEXT(130), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L144 + TOO_LARGE_STRING_SIZE(131), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L145 + AGGREGATE_FUNCTION_DOESNT_ALLOW_PARAMETERS(133), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L146 + PARAMETERS_TO_AGGREGATE_FUNCTIONS_MUST_BE_LITERALS(134), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L147 + ZERO_ARRAY_OR_TUPLE_INDEX(135), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L148 + UNKNOWN_ELEMENT_IN_CONFIG(137), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L149 + EXCESSIVE_ELEMENT_IN_CONFIG(138), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L150 + NO_ELEMENTS_IN_CONFIG(139), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L151 + ALL_REQUESTED_COLUMNS_ARE_MISSING(140), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L152 + SAMPLING_NOT_SUPPORTED(141), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L153 + NOT_FOUND_NODE(142), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L154 + FOUND_MORE_THAN_ONE_NODE(143), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L155 + FIRST_DATE_IS_BIGGER_THAN_LAST_DATE(144), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L156 + UNKNOWN_OVERFLOW_MODE(145), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L157 + QUERY_SECTION_DOESNT_MAKE_SENSE(146), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L158 + NOT_FOUND_FUNCTION_ELEMENT_FOR_AGGREGATE(147), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L159 + NOT_FOUND_RELATION_ELEMENT_FOR_CONDITION(148), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L160 + NOT_FOUND_RHS_ELEMENT_FOR_CONDITION(149), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L161 + EMPTY_LIST_OF_ATTRIBUTES_PASSED(150), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L162 + INDEX_OF_COLUMN_IN_SORT_CLAUSE_IS_OUT_OF_RANGE(151), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L163 + UNKNOWN_DIRECTION_OF_SORTING(152), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L164 + ILLEGAL_DIVISION(153), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L165 + AGGREGATE_FUNCTION_NOT_APPLICABLE(154), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L166 + UNKNOWN_RELATION(155), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L167 + DICTIONARIES_WAS_NOT_LOADED(156), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L168 + ILLEGAL_OVERFLOW_MODE(157), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L169 + TOO_MANY_ROWS(158), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L170 + TIMEOUT_EXCEEDED(159), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L171 + TOO_SLOW(160), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L172 + TOO_MANY_COLUMNS(161), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L173 + TOO_DEEP_SUBQUERIES(162), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L174 + TOO_DEEP_PIPELINE(163), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L175 + READONLY(164), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L176 + TOO_MANY_TEMPORARY_COLUMNS(165), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L177 + TOO_MANY_TEMPORARY_NON_CONST_COLUMNS(166), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L178 + TOO_DEEP_AST(167), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L179 + TOO_BIG_AST(168), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L180 + BAD_TYPE_OF_FIELD(169), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L181 + BAD_GET(170), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L182 + CANNOT_CREATE_DIRECTORY(172), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L183 + CANNOT_ALLOCATE_MEMORY(173), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L184 + CYCLIC_ALIASES(174), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L185 + CHUNK_NOT_FOUND(176), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L186 + DUPLICATE_CHUNK_NAME(177), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L187 + MULTIPLE_ALIASES_FOR_EXPRESSION(178), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L188 + MULTIPLE_EXPRESSIONS_FOR_ALIAS(179), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L189 + THERE_IS_NO_PROFILE(180), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L190 + ILLEGAL_FINAL(181), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L191 + ILLEGAL_PREWHERE(182), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L192 + UNEXPECTED_EXPRESSION(183), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L193 + ILLEGAL_AGGREGATION(184), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L194 + UNSUPPORTED_MYISAM_BLOCK_TYPE(185), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L195 + UNSUPPORTED_COLLATION_LOCALE(186), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L196 + COLLATION_COMPARISON_FAILED(187), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L197 + UNKNOWN_ACTION(188), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L198 + TABLE_MUST_NOT_BE_CREATED_MANUALLY(189), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L199 + SIZES_OF_ARRAYS_DOESNT_MATCH(190), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L200 + SET_SIZE_LIMIT_EXCEEDED(191), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L201 + UNKNOWN_USER(192), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L202 + WRONG_PASSWORD(193), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L203 + REQUIRED_PASSWORD(194), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L204 + IP_ADDRESS_NOT_ALLOWED(195), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L205 + UNKNOWN_ADDRESS_PATTERN_TYPE(196), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L206 + SERVER_REVISION_IS_TOO_OLD(197), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L207 + DNS_ERROR(198), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L208 + UNKNOWN_QUOTA(199), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L209 + QUOTA_DOESNT_ALLOW_KEYS(200), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L210 + QUOTA_EXPIRED(201), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L211 + TOO_MANY_SIMULTANEOUS_QUERIES(202), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L212 + NO_FREE_CONNECTION(203), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L213 + CANNOT_FSYNC(204), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L214 + NESTED_TYPE_TOO_DEEP(205), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L215 + ALIAS_REQUIRED(206), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L216 + AMBIGUOUS_IDENTIFIER(207), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L217 + EMPTY_NESTED_TABLE(208), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L218 + SOCKET_TIMEOUT(209), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L219 + NETWORK_ERROR(210), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L220 + EMPTY_QUERY(211), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L221 + UNKNOWN_LOAD_BALANCING(212), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L222 + UNKNOWN_TOTALS_MODE(213), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L223 + CANNOT_STATVFS(214), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L224 + NOT_AN_AGGREGATE(215), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L225 + QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING(216), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L226 + CLIENT_HAS_CONNECTED_TO_WRONG_PORT(217), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L227 + TABLE_IS_DROPPED(218), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L228 + DATABASE_NOT_EMPTY(219), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L229 + DUPLICATE_INTERSERVER_IO_ENDPOINT(220), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L230 + NO_SUCH_INTERSERVER_IO_ENDPOINT(221), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L231 + ADDING_REPLICA_TO_NON_EMPTY_TABLE(222), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L232 + UNEXPECTED_AST_STRUCTURE(223), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L233 + REPLICA_IS_ALREADY_ACTIVE(224), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L234 + NO_ZOOKEEPER(225), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L235 + NO_FILE_IN_DATA_PART(226), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L236 + UNEXPECTED_FILE_IN_DATA_PART(227), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L237 + BAD_SIZE_OF_FILE_IN_DATA_PART(228), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L238 + QUERY_IS_TOO_LARGE(229), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L239 + NOT_FOUND_EXPECTED_DATA_PART(230), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L240 + TOO_MANY_UNEXPECTED_DATA_PARTS(231), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L241 + NO_SUCH_DATA_PART(232), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L242 + BAD_DATA_PART_NAME(233), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L243 + NO_REPLICA_HAS_PART(234), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L244 + DUPLICATE_DATA_PART(235), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L245 + ABORTED(236), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L246 + NO_REPLICA_NAME_GIVEN(237), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L247 + FORMAT_VERSION_TOO_OLD(238), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L248 + CANNOT_MUNMAP(239), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L249 + CANNOT_MREMAP(240), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L250 + MEMORY_LIMIT_EXCEEDED(241), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L251 + TABLE_IS_READ_ONLY(242), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L252 + NOT_ENOUGH_SPACE(243), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L253 + UNEXPECTED_ZOOKEEPER_ERROR(244), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L254 + CORRUPTED_DATA(246), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L255 + INCORRECT_MARK(247), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L256 + INVALID_PARTITION_VALUE(248), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L257 + NOT_ENOUGH_BLOCK_NUMBERS(250), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L258 + NO_SUCH_REPLICA(251), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L259 + TOO_MANY_PARTS(252), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L260 + REPLICA_IS_ALREADY_EXIST(253), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L261 + NO_ACTIVE_REPLICAS(254), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L262 + TOO_MANY_RETRIES_TO_FETCH_PARTS(255), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L263 + PARTITION_ALREADY_EXISTS(256), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L264 + PARTITION_DOESNT_EXIST(257), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L265 + UNION_ALL_RESULT_STRUCTURES_MISMATCH(258), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L266 + CLIENT_OUTPUT_FORMAT_SPECIFIED(260), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L267 + UNKNOWN_BLOCK_INFO_FIELD(261), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L268 + BAD_COLLATION(262), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L269 + CANNOT_COMPILE_CODE(263), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L270 + INCOMPATIBLE_TYPE_OF_JOIN(264), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L271 + NO_AVAILABLE_REPLICA(265), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L272 + MISMATCH_REPLICAS_DATA_SOURCES(266), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L273 + STORAGE_DOESNT_SUPPORT_PARALLEL_REPLICAS(267), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L274 + CPUID_ERROR(268), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L275 + INFINITE_LOOP(269), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L276 + CANNOT_COMPRESS(270), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L277 + CANNOT_DECOMPRESS(271), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L278 + CANNOT_IO_SUBMIT(272), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L279 + CANNOT_IO_GETEVENTS(273), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L280 + AIO_READ_ERROR(274), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L281 + AIO_WRITE_ERROR(275), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L282 + INDEX_NOT_USED(277), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L283 + ALL_CONNECTION_TRIES_FAILED(279), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L284 + NO_AVAILABLE_DATA(280), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L285 + DICTIONARY_IS_EMPTY(281), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L286 + INCORRECT_INDEX(282), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L287 + UNKNOWN_DISTRIBUTED_PRODUCT_MODE(283), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L288 + WRONG_GLOBAL_SUBQUERY(284), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L289 + TOO_FEW_LIVE_REPLICAS(285), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L290 + UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE(286), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L291 + UNKNOWN_FORMAT_VERSION(287), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L292 + DISTRIBUTED_IN_JOIN_SUBQUERY_DENIED(288), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L293 + REPLICA_IS_NOT_IN_QUORUM(289), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L294 + LIMIT_EXCEEDED(290), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L295 + DATABASE_ACCESS_DENIED(291), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L296 + MONGODB_CANNOT_AUTHENTICATE(293), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L297 + INVALID_BLOCK_EXTRA_INFO(294), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L298 + RECEIVED_EMPTY_DATA(295), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L299 + NO_REMOTE_SHARD_FOUND(296), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L300 + SHARD_HAS_NO_CONNECTIONS(297), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L301 + CANNOT_PIPE(298), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L302 + CANNOT_FORK(299), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L303 + CANNOT_DLSYM(300), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L304 + CANNOT_CREATE_CHILD_PROCESS(301), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L305 + CHILD_WAS_NOT_EXITED_NORMALLY(302), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L306 + CANNOT_SELECT(303), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L307 + CANNOT_WAITPID(304), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L308 + TABLE_WAS_NOT_DROPPED(305), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L309 + TOO_DEEP_RECURSION(306), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L310 + TOO_MANY_BYTES(307), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L311 + UNEXPECTED_NODE_IN_ZOOKEEPER(308), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L312 + FUNCTION_CANNOT_HAVE_PARAMETERS(309), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L313 + INVALID_SHARD_WEIGHT(317), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L314 + INVALID_CONFIG_PARAMETER(318), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L315 + UNKNOWN_STATUS_OF_INSERT(319), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L316 + VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE(321), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L317 + BARRIER_TIMEOUT(335), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L318 + UNKNOWN_DATABASE_ENGINE(336), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L319 + DDL_GUARD_IS_ACTIVE(337), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L320 + UNFINISHED(341), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L321 + METADATA_MISMATCH(342), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L322 + SUPPORT_IS_DISABLED(344), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L323 + TABLE_DIFFERS_TOO_MUCH(345), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L324 + CANNOT_CONVERT_CHARSET(346), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L325 + CANNOT_LOAD_CONFIG(347), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L326 + CANNOT_INSERT_NULL_IN_ORDINARY_COLUMN(349), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L327 + INCOMPATIBLE_SOURCE_TABLES(350), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L328 + AMBIGUOUS_TABLE_NAME(351), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L329 + AMBIGUOUS_COLUMN_NAME(352), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L330 + INDEX_OF_POSITIONAL_ARGUMENT_IS_OUT_OF_RANGE(353), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L331 + ZLIB_INFLATE_FAILED(354), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L332 + ZLIB_DEFLATE_FAILED(355), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L333 + BAD_LAMBDA(356), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L334 + RESERVED_IDENTIFIER_NAME(357), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L335 + INTO_OUTFILE_NOT_ALLOWED(358), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L336 + TABLE_SIZE_EXCEEDS_MAX_DROP_SIZE_LIMIT(359), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L337 + CANNOT_CREATE_CHARSET_CONVERTER(360), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L338 + SEEK_POSITION_OUT_OF_BOUND(361), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L339 + CURRENT_WRITE_BUFFER_IS_EXHAUSTED(362), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L340 + CANNOT_CREATE_IO_BUFFER(363), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L341 + RECEIVED_ERROR_TOO_MANY_REQUESTS(364), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L342 + SIZES_OF_NESTED_COLUMNS_ARE_INCONSISTENT(366), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L343 + TOO_MANY_FETCHES(367), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L344 + ALL_REPLICAS_ARE_STALE(369), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L345 + DATA_TYPE_CANNOT_BE_USED_IN_TABLES(370), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L346 + INCONSISTENT_CLUSTER_DEFINITION(371), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L347 + SESSION_NOT_FOUND(372), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L348 + SESSION_IS_LOCKED(373), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L349 + INVALID_SESSION_TIMEOUT(374), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L350 + CANNOT_DLOPEN(375), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L351 + CANNOT_PARSE_UUID(376), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L352 + ILLEGAL_SYNTAX_FOR_DATA_TYPE(377), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L353 + DATA_TYPE_CANNOT_HAVE_ARGUMENTS(378), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L354 + UNKNOWN_STATUS_OF_DISTRIBUTED_DDL_TASK(379), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L355 + CANNOT_KILL(380), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L356 + HTTP_LENGTH_REQUIRED(381), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L357 + CANNOT_LOAD_CATBOOST_MODEL(382), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L358 + CANNOT_APPLY_CATBOOST_MODEL(383), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L359 + PART_IS_TEMPORARILY_LOCKED(384), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L360 + MULTIPLE_STREAMS_REQUIRED(385), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L361 + NO_COMMON_TYPE(386), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L362 + DICTIONARY_ALREADY_EXISTS(387), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L363 + CANNOT_ASSIGN_OPTIMIZE(388), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L364 + INSERT_WAS_DEDUPLICATED(389), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L365 + CANNOT_GET_CREATE_TABLE_QUERY(390), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L366 + EXTERNAL_LIBRARY_ERROR(391), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L367 + QUERY_IS_PROHIBITED(392), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L368 + THERE_IS_NO_QUERY(393), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L369 + QUERY_WAS_CANCELLED(394), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L370 + FUNCTION_THROW_IF_VALUE_IS_NON_ZERO(395), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L371 + TOO_MANY_ROWS_OR_BYTES(396), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L372 + QUERY_IS_NOT_SUPPORTED_IN_MATERIALIZED_VIEW(397), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L373 + UNKNOWN_MUTATION_COMMAND(398), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L374 + FORMAT_IS_NOT_SUITABLE_FOR_OUTPUT(399), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L375 + CANNOT_STAT(400), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L376 + FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME(401), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L377 + CANNOT_IOSETUP(402), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L378 + INVALID_JOIN_ON_EXPRESSION(403), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L379 + BAD_ODBC_CONNECTION_STRING(404), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L380 + PARTITION_SIZE_EXCEEDS_MAX_DROP_SIZE_LIMIT(405), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L381 + TOP_AND_LIMIT_TOGETHER(406), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L382 + DECIMAL_OVERFLOW(407), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L383 + BAD_REQUEST_PARAMETER(408), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L384 + EXTERNAL_EXECUTABLE_NOT_FOUND(409), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L385 + EXTERNAL_SERVER_IS_NOT_RESPONDING(410), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L386 + PTHREAD_ERROR(411), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L387 + NETLINK_ERROR(412), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L388 + CANNOT_SET_SIGNAL_HANDLER(413), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L389 + ALL_REPLICAS_LOST(415), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L390 + REPLICA_STATUS_CHANGED(416), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L391 + EXPECTED_ALL_OR_ANY(417), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L392 + UNKNOWN_JOIN(418), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L393 + MULTIPLE_ASSIGNMENTS_TO_COLUMN(419), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L394 + CANNOT_UPDATE_COLUMN(420), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L395 + CANNOT_ADD_DIFFERENT_AGGREGATE_STATES(421), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L396 + UNSUPPORTED_URI_SCHEME(422), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L397 + CANNOT_GETTIMEOFDAY(423), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L398 + CANNOT_LINK(424), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L399 + SYSTEM_ERROR(425), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L400 + CANNOT_COMPILE_REGEXP(427), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L401 + UNKNOWN_LOG_LEVEL(428), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L402 + FAILED_TO_GETPWUID(429), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L403 + MISMATCHING_USERS_FOR_PROCESS_AND_DATA(430), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L404 + ILLEGAL_SYNTAX_FOR_CODEC_TYPE(431), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L405 + UNKNOWN_CODEC(432), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L406 + ILLEGAL_CODEC_PARAMETER(433), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L407 + CANNOT_PARSE_PROTOBUF_SCHEMA(434), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L408 + NO_COLUMN_SERIALIZED_TO_REQUIRED_PROTOBUF_FIELD(435), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L409 + PROTOBUF_BAD_CAST(436), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L410 + PROTOBUF_FIELD_NOT_REPEATED(437), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L411 + DATA_TYPE_CANNOT_BE_PROMOTED(438), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L412 + CANNOT_SCHEDULE_TASK(439), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L413 + INVALID_LIMIT_EXPRESSION(440), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L414 + CANNOT_PARSE_DOMAIN_VALUE_FROM_STRING(441), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L415 + BAD_DATABASE_FOR_TEMPORARY_TABLE(442), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L416 + NO_COLUMNS_SERIALIZED_TO_PROTOBUF_FIELDS(443), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L417 + UNKNOWN_PROTOBUF_FORMAT(444), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L418 + CANNOT_MPROTECT(445), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L419 + FUNCTION_NOT_ALLOWED(446), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L420 + HYPERSCAN_CANNOT_SCAN_TEXT(447), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L421 + BROTLI_READ_FAILED(448), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L422 + BROTLI_WRITE_FAILED(449), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L423 + BAD_TTL_EXPRESSION(450), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L424 + BAD_TTL_FILE(451), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L425 + SETTING_CONSTRAINT_VIOLATION(452), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L426 + MYSQL_CLIENT_INSUFFICIENT_CAPABILITIES(453), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L427 + OPENSSL_ERROR(454), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L428 + SUSPICIOUS_TYPE_FOR_LOW_CARDINALITY(455), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L429 + UNKNOWN_QUERY_PARAMETER(456), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L430 + BAD_QUERY_PARAMETER(457), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L431 + CANNOT_UNLINK(458), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L432 + CANNOT_SET_THREAD_PRIORITY(459), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L433 + CANNOT_CREATE_TIMER(460), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L434 + CANNOT_SET_TIMER_PERIOD(461), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L435 + CANNOT_DELETE_TIMER(462), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L436 + CANNOT_FCNTL(463), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L437 + CANNOT_PARSE_ELF(464), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L438 + CANNOT_PARSE_DWARF(465), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L439 + INSECURE_PATH(466), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L440 + CANNOT_PARSE_BOOL(467), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L441 + CANNOT_PTHREAD_ATTR(468), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L442 + VIOLATED_CONSTRAINT(469), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L443 + QUERY_IS_NOT_SUPPORTED_IN_LIVE_VIEW(470), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L444 + INVALID_SETTING_VALUE(471), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L445 + READONLY_SETTING(472), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L446 + DEADLOCK_AVOIDED(473), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L447 + INVALID_TEMPLATE_FORMAT(474), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L448 + INVALID_WITH_FILL_EXPRESSION(475), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L449 + WITH_TIES_WITHOUT_ORDER_BY(476), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L450 + INVALID_USAGE_OF_INPUT(477), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L451 + UNKNOWN_POLICY(478), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L452 + UNKNOWN_DISK(479), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L453 + UNKNOWN_PROTOCOL(480), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L454 + PATH_ACCESS_DENIED(481), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L455 + DICTIONARY_ACCESS_DENIED(482), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L456 + TOO_MANY_REDIRECTS(483), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L457 + INTERNAL_REDIS_ERROR(484), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L458 + SCALAR_ALREADY_EXISTS(485), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L459 + CANNOT_GET_CREATE_DICTIONARY_QUERY(487), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L460 + UNKNOWN_DICTIONARY(488), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L461 + INCORRECT_DICTIONARY_DEFINITION(489), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L462 + CANNOT_FORMAT_DATETIME(490), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L463 + UNACCEPTABLE_URL(491), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L464 + ACCESS_ENTITY_NOT_FOUND(492), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L465 + ACCESS_ENTITY_ALREADY_EXISTS(493), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L466 + ACCESS_ENTITY_FOUND_DUPLICATES(494), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L467 + ACCESS_STORAGE_READONLY(495), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L468 + QUOTA_REQUIRES_CLIENT_KEY(496), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L469 + ACCESS_DENIED(497), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L470 + LIMIT_BY_WITH_TIES_IS_NOT_SUPPORTED(498), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L471 + S3_ERROR(499), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L472 + CANNOT_CREATE_DATABASE(501), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L473 + CANNOT_SIGQUEUE(502), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L474 + AGGREGATE_FUNCTION_THROW(503), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L475 + FILE_ALREADY_EXISTS(504), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L476 + CANNOT_DELETE_DIRECTORY(505), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L477 + UNEXPECTED_ERROR_CODE(506), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L478 + UNABLE_TO_SKIP_UNUSED_SHARDS(507), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L479 + UNKNOWN_ACCESS_TYPE(508), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L480 + INVALID_GRANT(509), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L481 + CACHE_DICTIONARY_UPDATE_FAIL(510), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L482 + UNKNOWN_ROLE(511), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L483 + SET_NON_GRANTED_ROLE(512), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L484 + UNKNOWN_PART_TYPE(513), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L485 + ACCESS_STORAGE_FOR_INSERTION_NOT_FOUND(514), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L486 + INCORRECT_ACCESS_ENTITY_DEFINITION(515), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L487 + AUTHENTICATION_FAILED(516), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L488 + CANNOT_ASSIGN_ALTER(517), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L489 + CANNOT_COMMIT_OFFSET(518), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L490 + NO_REMOTE_SHARD_AVAILABLE(519), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L491 + CANNOT_DETACH_DICTIONARY_AS_TABLE(520), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L492 + ATOMIC_RENAME_FAIL(521), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L493 + UNKNOWN_ROW_POLICY(523), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L494 + ALTER_OF_COLUMN_IS_FORBIDDEN(524), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L495 + INCORRECT_DISK_INDEX(525), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L496 + NO_SUITABLE_FUNCTION_IMPLEMENTATION(527), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L497 + CASSANDRA_INTERNAL_ERROR(528), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L498 + NOT_A_LEADER(529), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L499 + CANNOT_CONNECT_RABBITMQ(530), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L500 + CANNOT_FSTAT(531), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L501 + LDAP_ERROR(532), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L502 + INCONSISTENT_RESERVATIONS(533), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L503 + NO_RESERVATIONS_PROVIDED(534), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L504 + UNKNOWN_RAID_TYPE(535), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L505 + CANNOT_RESTORE_FROM_FIELD_DUMP(536), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L506 + ILLEGAL_MYSQL_VARIABLE(537), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L507 + MYSQL_SYNTAX_ERROR(538), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L508 + CANNOT_BIND_RABBITMQ_EXCHANGE(539), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L509 + CANNOT_DECLARE_RABBITMQ_EXCHANGE(540), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L510 + CANNOT_CREATE_RABBITMQ_QUEUE_BINDING(541), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L511 + CANNOT_REMOVE_RABBITMQ_EXCHANGE(542), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L512 + UNKNOWN_MYSQL_DATATYPES_SUPPORT_LEVEL(543), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L513 + ROW_AND_ROWS_TOGETHER(544), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L514 + FIRST_AND_NEXT_TOGETHER(545), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L515 + NO_ROW_DELIMITER(546), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L516 + INVALID_RAID_TYPE(547), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L517 + UNKNOWN_VOLUME(548), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L518 + DATA_TYPE_CANNOT_BE_USED_IN_KEY(549), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L519 + CONDITIONAL_TREE_PARENT_NOT_FOUND(550), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L520 + ILLEGAL_PROJECTION_MANIPULATOR(551), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L521 + UNRECOGNIZED_ARGUMENTS(552), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L522 + LZMA_STREAM_ENCODER_FAILED(553), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L523 + LZMA_STREAM_DECODER_FAILED(554), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L524 + ROCKSDB_ERROR(555), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L525 + SYNC_MYSQL_USER_ACCESS_ERROR(556), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L526 + UNKNOWN_UNION(557), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L527 + EXPECTED_ALL_OR_DISTINCT(558), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L528 + INVALID_GRPC_QUERY_INFO(559), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L529 + ZSTD_ENCODER_FAILED(560), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L530 + ZSTD_DECODER_FAILED(561), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L531 + TLD_LIST_NOT_FOUND(562), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L532 + CANNOT_READ_MAP_FROM_TEXT(563), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L533 + INTERSERVER_SCHEME_DOESNT_MATCH(564), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L534 + TOO_MANY_PARTITIONS(565), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L535 + CANNOT_RMDIR(566), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L536 + DUPLICATED_PART_UUIDS(567), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L537 + RAFT_ERROR(568), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L538 + MULTIPLE_COLUMNS_SERIALIZED_TO_SAME_PROTOBUF_FIELD(569), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L539 + DATA_TYPE_INCOMPATIBLE_WITH_PROTOBUF_FIELD(570), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L540 + DATABASE_REPLICATION_FAILED(571), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L541 + TOO_MANY_QUERY_PLAN_OPTIMIZATIONS(572), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L542 + EPOLL_ERROR(573), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L543 + DISTRIBUTED_TOO_MANY_PENDING_BYTES(574), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L544 + UNKNOWN_SNAPSHOT(575), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L545 + KERBEROS_ERROR(576), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L546 + INVALID_SHARD_ID(577), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L547 + INVALID_FORMAT_INSERT_QUERY_WITH_DATA(578), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L548 + INCORRECT_PART_TYPE(579), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L549 + CANNOT_SET_ROUNDING_MODE(580), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L550 + TOO_LARGE_DISTRIBUTED_DEPTH(581), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L551 + NO_SUCH_PROJECTION_IN_TABLE(582), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L552 + ILLEGAL_PROJECTION(583), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L553 + PROJECTION_NOT_USED(584), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L554 + CANNOT_PARSE_YAML(585), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L555 + CANNOT_CREATE_FILE(586), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L556 + CONCURRENT_ACCESS_NOT_SUPPORTED(587), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L557 + DISTRIBUTED_BROKEN_BATCH_INFO(588), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L558 + DISTRIBUTED_BROKEN_BATCH_FILES(589), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L559 + CANNOT_SYSCONF(590), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L560 + SQLITE_ENGINE_ERROR(591), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L561 + DATA_ENCRYPTION_ERROR(592), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L562 + ZERO_COPY_REPLICATION_ERROR(593), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L563 + BZIP2_STREAM_DECODER_FAILED(594), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L564 + BZIP2_STREAM_ENCODER_FAILED(595), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L565 + INTERSECT_OR_EXCEPT_RESULT_STRUCTURES_MISMATCH(596), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L566 + NO_SUCH_ERROR_CODE(597), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L567 + BACKUP_ALREADY_EXISTS(598), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L568 + BACKUP_NOT_FOUND(599), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L569 + BACKUP_VERSION_NOT_SUPPORTED(600), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L570 + BACKUP_DAMAGED(601), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L571 + NO_BASE_BACKUP(602), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L572 + WRONG_BASE_BACKUP(603), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L573 + BACKUP_ENTRY_ALREADY_EXISTS(604), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L574 + BACKUP_ENTRY_NOT_FOUND(605), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L575 + BACKUP_IS_EMPTY(606), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L576 + BACKUP_ELEMENT_DUPLICATE(607), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L577 + CANNOT_RESTORE_TABLE(608), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L578 + FUNCTION_ALREADY_EXISTS(609), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L579 + CANNOT_DROP_SYSTEM_FUNCTION(610), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L580 + CANNOT_CREATE_RECURSIVE_FUNCTION(611), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L581 + OBJECT_ALREADY_STORED_ON_DISK(612), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L582 + OBJECT_WAS_NOT_STORED_ON_DISK(613), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L583 + POSTGRESQL_CONNECTION_FAILURE(614), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L584 + CANNOT_ADVISE(615), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L585 + UNKNOWN_READ_METHOD(616), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L586 + LZ4_ENCODER_FAILED(617), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L587 + LZ4_DECODER_FAILED(618), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L588 + POSTGRESQL_REPLICATION_INTERNAL_ERROR(619), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L589 + QUERY_NOT_ALLOWED(620), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L590 + KEEPER_EXCEPTION(999), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L592 + POCO_EXCEPTION(1000), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L593 + STD_EXCEPTION(1001), // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L594 + UNKNOWN_EXCEPTION(1002); // /~https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp#L595 + + private static final Map byCodes; + + static { + Map map = new HashMap<>(); + for (ClickHouseErrorCode errorCode : values()) { + map.put(errorCode.code, errorCode); + } + byCodes = Collections.unmodifiableMap(map); + } + + public final Integer code; + + ClickHouseErrorCode(Integer code) { + this.code = code; + } + + public static ClickHouseErrorCode fromCode(Integer code) { + return byCodes.get(code); + } + + @Override + public String toString() { + return name() + " (code " + code + ')'; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseException.java b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseException.java new file mode 100644 index 000000000..e42836ef8 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseException.java @@ -0,0 +1,54 @@ +package com.clickhouse.client.exception; + +public class ClickHouseException extends Exception { + /** + * Generated ID. + */ + private static final long serialVersionUID = -2417038200885554382L; + + private final int errorCode; + + private static String buildErrorMessage(String message, int code, String host, int port, Throwable cause) { + StringBuilder builder = new StringBuilder(); + + builder.append("ClickHouse exception, "); + if (message != null) { + builder.append(" message: ").append(message); + } else { + builder.append(" code: ").append(code); + } + + if (host != null) { + builder.append(", host: ").append(host).append(", port: ").append(port); + } + + builder.append(';'); + if (cause != null) { + builder.append(' ').append(cause.getMessage()); + } + + return builder.toString(); + } + + public ClickHouseException(int code, Throwable cause, String host, int port) { + super(buildErrorMessage(null, code, host, port, cause), cause); + + errorCode = code; + } + + public ClickHouseException(int code, String message, Throwable cause, String host, int port) { + super(buildErrorMessage(message, code, host, port, cause), cause); + + errorCode = code; + } + + public ClickHouseException(int code, String message, Throwable cause) { + super(buildErrorMessage(message, code, null, 0, cause), cause); + + errorCode = code; + } + + public int getErrorCode() { + return errorCode; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseExceptionSpecifier.java b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseExceptionSpecifier.java new file mode 100644 index 000000000..f50ac4818 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseExceptionSpecifier.java @@ -0,0 +1,95 @@ +package com.clickhouse.client.exception; + +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; + +/** + * Specify ClickHouse exception to ClickHouseException and fill it with a vendor + * code. + */ +public final class ClickHouseExceptionSpecifier { + + private static final Logger log = LoggerFactory.getLogger(ClickHouseExceptionSpecifier.class); + + private ClickHouseExceptionSpecifier() { + } + + public static ClickHouseException specify(Throwable cause, String host, int port) { + return specify(cause != null ? cause.getMessage() : null, cause, host, port); + } + + public static ClickHouseException specify(String clickHouseMessage, String host, int port) { + return specify(clickHouseMessage, null, host, port); + } + + public static ClickHouseException specify(String clickHouseMessage) { + return specify(clickHouseMessage, "unknown", -1); + } + + /** + * Here we expect the ClickHouse error message to be of the following format: + * "Code: 10, e.displayText() = DB::Exception: ...". + */ + private static ClickHouseException specify(String clickHouseMessage, Throwable cause, String host, int port) { + if (ClickHouseChecker.isNullOrEmpty(clickHouseMessage) && cause != null) { + return getException(cause, host, port); + } + + try { + int code; + if (clickHouseMessage.startsWith("Poco::Exception. Code: 1000, ")) { + code = 1000; + } else { + // Code: 175, e.displayText() = DB::Exception: + code = getErrorCode(clickHouseMessage); + } + // ошибку в изначальном виде все-таки укажем + Throwable messageHolder = cause != null ? cause : new Throwable(clickHouseMessage); + if (code == -1) { + return getException(messageHolder, host, port); + } + + return new ClickHouseException(code, messageHolder, host, port); + } catch (Exception e) { + log.error( + "Unsupported ClickHouse error format, please fix ClickHouseExceptionSpecifier, message: {}, error: {}", + clickHouseMessage, e.getMessage()); + return new ClickHouseUnknownException(clickHouseMessage, cause, host, port); + } + } + + private static int getErrorCode(String errorMessage) { + int startIndex = errorMessage.indexOf(' '); + int endIndex = startIndex == -1 ? -1 : errorMessage.indexOf(',', startIndex); + + if (startIndex == -1 || endIndex == -1) { + return -1; + } + + try { + return Integer.parseInt(errorMessage.substring(startIndex + 1, endIndex)); + } catch (NumberFormatException e) { + return -1; + } + } + + private static ClickHouseException getException(Throwable cause, String host, int port) { + if (cause instanceof SocketTimeoutException) + // if we've got SocketTimeoutException, we'll say that the query is not good. + // This is not the same as SOCKET_TIMEOUT of clickhouse + // but it actually could be a failing ClickHouse + { + return new ClickHouseException(ClickHouseErrorCode.TIMEOUT_EXCEEDED.code, cause, host, port); + } else if (cause instanceof ConnectException) + // couldn't connect to ClickHouse during connectTimeout + { + return new ClickHouseException(ClickHouseErrorCode.NETWORK_ERROR.code, cause, host, port); + } else { + return new ClickHouseUnknownException(cause, host, port); + } + } + +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseUnknownException.java b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseUnknownException.java new file mode 100644 index 000000000..21e548227 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/exception/ClickHouseUnknownException.java @@ -0,0 +1,24 @@ +package com.clickhouse.client.exception; + +public class ClickHouseUnknownException extends ClickHouseException { + /** + * Generated ID. + */ + private static final long serialVersionUID = -1724790228244438601L; + + public ClickHouseUnknownException(Throwable cause, String host, int port) { + super(ClickHouseErrorCode.UNKNOWN_EXCEPTION.code, cause, host, port); + } + + public ClickHouseUnknownException(String message, Throwable cause, String host, int port) { + super(ClickHouseErrorCode.UNKNOWN_EXCEPTION.code, message, cause, host, port); + } + + public ClickHouseUnknownException(String message, Throwable cause) { + super(ClickHouseErrorCode.UNKNOWN_EXCEPTION.code, message, cause); + } + + public ClickHouseUnknownException(Integer code, Throwable cause, String host, int port) { + super(code, cause, host, port); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/exception/package-info.java b/clickhouse-client/src/main/java/com/clickhouse/client/exception/package-info.java new file mode 100644 index 000000000..e1cb83105 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/exception/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides shared exception classes. + */ +package com.clickhouse.client.exception; diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/JdkLogger.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/JdkLogger.java new file mode 100644 index 000000000..45ff38a58 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/JdkLogger.java @@ -0,0 +1,141 @@ +package com.clickhouse.client.logging; + +import java.util.ResourceBundle; +import java.util.logging.Level; +import com.clickhouse.client.ClickHouseChecker; + +/** + * Adaptor for JDK logger. + */ +public class JdkLogger implements Logger { + private final java.util.logging.Logger logger; + + protected void log(Level level, LogMessage msg) { + if (msg.hasThrowable()) { + logger.logrb(level, (String) null, (String) null, (ResourceBundle) null, msg.getMessage(), + msg.getThrowable()); + } else { + logger.logrb(level, (String) null, (String) null, (ResourceBundle) null, msg.getMessage()); + } + } + + /** + * Default constructor. + * + * @param logger non-null JDK logger + */ + public JdkLogger(java.util.logging.Logger logger) { + this.logger = ClickHouseChecker.nonNull(logger, "logger"); + } + + @Override + public void debug(Runnable function) { + if (function != null && logger.isLoggable(Level.FINE)) { + function.run(); + } + } + + @Override + public void debug(Object format, Object... arguments) { + if (logger.isLoggable(Level.FINE)) { + log(Level.FINE, LogMessage.of(format, arguments)); + } + } + + @Override + public void debug(Object message, Throwable t) { + if (logger.isLoggable(Level.FINE)) { + logger.logrb(Level.FINE, (String) null, (String) null, (ResourceBundle) null, String.valueOf(message), t); + } + } + + @Override + public void error(Runnable function) { + if (function != null && logger.isLoggable(Level.SEVERE)) { + function.run(); + } + } + + @Override + public void error(Object format, Object... arguments) { + if (logger.isLoggable(Level.SEVERE)) { + log(Level.SEVERE, LogMessage.of(format, arguments)); + } + } + + @Override + public void error(Object message, Throwable t) { + if (logger.isLoggable(Level.SEVERE)) { + logger.logrb(Level.SEVERE, (String) null, (String) null, (ResourceBundle) null, String.valueOf(message), t); + } + } + + @Override + public void info(Runnable function) { + if (function != null && logger.isLoggable(Level.INFO)) { + function.run(); + } + } + + @Override + public void info(Object format, Object... arguments) { + if (logger.isLoggable(Level.INFO)) { + log(Level.INFO, LogMessage.of(format, arguments)); + } + } + + @Override + public void info(Object message, Throwable t) { + if (logger.isLoggable(Level.INFO)) { + logger.logrb(Level.INFO, (String) null, (String) null, (ResourceBundle) null, String.valueOf(message), t); + } + } + + @Override + public void trace(Runnable function) { + if (function != null && logger.isLoggable(Level.FINEST)) { + function.run(); + } + } + + @Override + public void trace(Object format, Object... arguments) { + if (logger.isLoggable(Level.FINEST)) { + log(Level.FINEST, LogMessage.of(format, arguments)); + } + } + + @Override + public void trace(Object message, Throwable t) { + if (logger.isLoggable(Level.FINEST)) { + logger.logrb(Level.FINEST, (String) null, (String) null, (ResourceBundle) null, String.valueOf(message), t); + } + } + + @Override + public void warn(Runnable function) { + if (function != null && logger.isLoggable(Level.WARNING)) { + function.run(); + } + } + + @Override + public void warn(Object format, Object... arguments) { + if (logger.isLoggable(Level.WARNING)) { + log(Level.WARNING, LogMessage.of(format, arguments)); + } + } + + @Override + public void warn(Object message, Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + logger.logrb(Level.WARNING, (String) null, (String) null, (ResourceBundle) null, String.valueOf(message), + t); + } + } + + @Override + public Object unwrap() { + return logger; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/JdkLoggerFactory.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/JdkLoggerFactory.java new file mode 100644 index 000000000..715685e16 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/JdkLoggerFactory.java @@ -0,0 +1,11 @@ +package com.clickhouse.client.logging; + +/** + * Adaptor of JDK logger factory. + */ +public class JdkLoggerFactory extends LoggerFactory { + @Override + public Logger get(String name) { + return new JdkLogger(java.util.logging.Logger.getLogger(name)); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/LogMessage.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/LogMessage.java new file mode 100644 index 000000000..36057fd0f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/LogMessage.java @@ -0,0 +1,81 @@ +package com.clickhouse.client.logging; + +import java.util.Locale; + +/** + * Log message with arguments and/or error. + */ +public final class LogMessage { + /** + * Creates a log message with arguments. The latest argument could be a + * {@code java.lang.Throwable} providing details like stack trace of an error. + * + * @param format Object format, could be null + * @param arguments arguments, could be null or empty + * @return log message + */ + public static LogMessage of(Object format, Object... arguments) { + String message = String.valueOf(format); + Throwable t = null; + + int len = arguments != null ? arguments.length : 0; + if (len > 0) { + Object lastArg = arguments[len - 1]; + if (lastArg instanceof Throwable) { + t = (Throwable) lastArg; + if (--len > 0) { + Object[] args = new Object[len]; + System.arraycopy(arguments, 0, args, 0, len); + arguments = args; + } + } + + if (len > 0) { + message = String.format(Locale.ROOT, message, arguments); + } + } + + return new LogMessage(message, t); + } + + private final String message; + private final Throwable throwable; + + /** + * Default constructor. + * + * @param message non-null message + * @param t throwable + */ + private LogMessage(String message, Throwable t) { + this.message = message; + this.throwable = t; + } + + /** + * Gets log message. + * + * @return non-null log message + */ + public String getMessage() { + return this.message; + } + + /** + * Gets error which may or may not be null. + * + * @return error, could be null + */ + public Throwable getThrowable() { + return this.throwable; + } + + /** + * Checks if error is available or not. + * + * @return true if there's error; false otherwise + */ + public boolean hasThrowable() { + return this.throwable != null; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/Logger.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/Logger.java new file mode 100644 index 000000000..67661eadf --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/Logger.java @@ -0,0 +1,149 @@ +package com.clickhouse.client.logging; + +/** + * Unified logger. Pay attention that the {@code format} follows standard + * {@link java.util.Formatter}. + */ +public interface Logger { + /** + * Logs output of a custom function at the DEBUG level. The function will only + * run when log level is DEBUG or lower. + * + * @param function custom function to run + */ + void debug(Runnable function); + + /** + * Logs a message at the DEBUG level according to the specified format and + * arguments. + * + * @param format the format string + * @param arguments a list of arguments, the last one could be a + * {@link java.lang.Throwable} + */ + void debug(Object format, Object... arguments); + + /** + * Logs an error (see {@link java.lang.Throwable}) at the DEBUG level with an + * accompanying message. + * + * @param message the message accompanying the error + * @param t the error to log + */ + void debug(Object message, Throwable t); + + /** + * Logs output of a custom function at the ERROR level. The function will only + * run when log level is ERROR or lower. + * + * @param function custom function to run + */ + void error(Runnable function); + + /** + * Logs a message at the ERROR level according to the specified format and + * arguments. + * + * @param format the format string + * @param arguments a list of arguments, the last one could be a + * {@link java.lang.Throwable} + */ + void error(Object format, Object... arguments); + + /** + * Logs an error (see {@link java.lang.Throwable}) at the ERROR level with an + * accompanying message. + * + * @param message the message accompanying the error + * @param t the error to log + */ + void error(Object message, Throwable t); + + /** + * Logs output of a custom function at the INFO level. The function will only + * run when log level is INFO or lower. + * + * @param function custom function to run + */ + void info(Runnable function); + + /** + * Logs a message at the INFO level according to the specified format and + * arguments. + * + * @param format the format string + * @param arguments a list of arguments, the last one could be a + * {@link java.lang.Throwable} + */ + void info(Object format, Object... arguments); + + /** + * Logs an error (see {@link java.lang.Throwable}) at the INFO level with an + * accompanying message. + * + * @param message the message accompanying the error + * @param t the error to log + */ + void info(Object message, Throwable t); + + /** + * Logs output of a custom function at the TRACE level. The function will only + * run when log level is TRACE. + * + * @param function custom function to run + */ + void trace(Runnable function); + + /** + * Logs a message at the TRACE level according to the specified format and + * arguments. + * + * @param format the format string + * @param arguments a list of arguments, the last one could be a + * {@link java.lang.Throwable} + */ + void trace(Object format, Object... arguments); + + /** + * Logs an error (see {@link java.lang.Throwable}) at the TRACE level with an + * accompanying message. + * + * @param message the message accompanying the error + * @param t the error to log + */ + void trace(Object message, Throwable t); + + /** + * Logs output of a custom function at the WARN level. The function will only + * run when log level is WARN or lower. + * + * @param function custom function to run + */ + void warn(Runnable function); + + /** + * Logs a message at the WARN level according to the specified format and + * arguments. + * + * @param format the format string + * @param arguments a list of arguments, the last one could be a + * {@link java.lang.Throwable} + */ + void warn(Object format, Object... arguments); + + /** + * Logs an error (see {@link java.lang.Throwable}) at the WRAN level with an + * accompanying message. + * + * @param message the message accompanying the error + * @param t the error to log + */ + void warn(Object message, Throwable t); + + /** + * Return logger implementation. + * + * @return implementation + */ + Object unwrap(); +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/LoggerFactory.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/LoggerFactory.java new file mode 100644 index 000000000..21781a8ea --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/LoggerFactory.java @@ -0,0 +1,75 @@ +package com.clickhouse.client.logging; + +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseUtils; + +/** + * Unified factory class to get logger. + */ +@SuppressWarnings("squid:S1181") +public abstract class LoggerFactory { + private static final LoggerFactory instance; + + static { + instance = ClickHouseUtils.getService(LoggerFactory.class, () -> { + LoggerFactory factory = null; + + try { + if (org.slf4j.LoggerFactory.getILoggerFactory() != null) { + factory = new Slf4jLoggerFactory(); + } + } catch (Throwable ignore) { // fall back to JDK + factory = new JdkLoggerFactory(); + } + + return ClickHouseChecker.nonNull(factory, "factory"); + }); + } + + /** + * Gets logger for the given class. Same as {@code getInstance().get(clazz)}. + * + * @param clazz class + * @return logger for the given class + */ + public static Logger getLogger(Class clazz) { + return instance.get(clazz); + } + + /** + * Gets logger for the given name. Same as {@code getInstance().get(name)}. + * + * @param name name + * @return logger for the given name + */ + public static Logger getLogger(String name) { + return instance.get(name); + } + + /** + * Gets instance of the factory for creating logger. + * + * @return factory for creating logger + */ + public static LoggerFactory getInstance() { + return instance; + } + + /** + * Gets logger for the given class. + * + * @param clazz class + * @return logger for the given class + */ + public Logger get(Class clazz) { + return get(ClickHouseChecker.nonNull(clazz, "Class").getName()); + } + + /** + * Gets logger for the given name. + * + * @param name name + * @return logger for the given name + */ + public abstract Logger get(String name); +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/Slf4jLogger.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/Slf4jLogger.java new file mode 100644 index 000000000..04bf1cda6 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/Slf4jLogger.java @@ -0,0 +1,154 @@ +package com.clickhouse.client.logging; + +import com.clickhouse.client.ClickHouseChecker; + +/** + * Adaptor for slf4j logger. + */ +public class Slf4jLogger implements Logger { + private final org.slf4j.Logger logger; + + /** + * Default constructor. + * + * @param logger non-null SLF4J logger + */ + public Slf4jLogger(org.slf4j.Logger logger) { + this.logger = ClickHouseChecker.nonNull(logger, "logger"); + } + + @Override + public void debug(Runnable function) { + if (function != null && logger.isDebugEnabled()) { + function.run(); + } + } + + @Override + public void debug(Object format, Object... arguments) { + if (logger.isDebugEnabled()) { + LogMessage msg = LogMessage.of(format, arguments); + if (msg.hasThrowable()) { + logger.debug(msg.getMessage(), msg.getThrowable()); + } else { + logger.debug(msg.getMessage()); + } + } + } + + @Override + public void debug(Object message, Throwable t) { + if (logger.isDebugEnabled()) { + logger.debug(String.valueOf(message), t); + } + } + + @Override + public void error(Runnable function) { + if (function != null && logger.isErrorEnabled()) { + function.run(); + } + } + + @Override + public void error(Object format, Object... arguments) { + if (logger.isErrorEnabled()) { + LogMessage msg = LogMessage.of(format, arguments); + if (msg.hasThrowable()) { + logger.error(msg.getMessage(), msg.getThrowable()); + } else { + logger.error(msg.getMessage()); + } + } + } + + @Override + public void error(Object message, Throwable t) { + if (logger.isErrorEnabled()) { + logger.error(String.valueOf(message), t); + } + } + + @Override + public void info(Runnable function) { + if (function != null && logger.isInfoEnabled()) { + function.run(); + } + } + + @Override + public void info(Object format, Object... arguments) { + if (logger.isInfoEnabled()) { + LogMessage msg = LogMessage.of(format, arguments); + if (msg.hasThrowable()) { + logger.info(msg.getMessage(), msg.getThrowable()); + } else { + logger.info(msg.getMessage()); + } + } + } + + @Override + public void info(Object message, Throwable t) { + if (logger.isInfoEnabled()) { + logger.info(String.valueOf(message), t); + } + } + + @Override + public void trace(Runnable function) { + if (function != null && logger.isTraceEnabled()) { + function.run(); + } + } + + @Override + public void trace(Object format, Object... arguments) { + if (logger.isTraceEnabled()) { + LogMessage msg = LogMessage.of(format, arguments); + if (msg.hasThrowable()) { + logger.trace(msg.getMessage(), msg.getThrowable()); + } else { + logger.trace(msg.getMessage()); + } + } + } + + @Override + public void trace(Object message, Throwable t) { + if (logger.isTraceEnabled()) { + logger.trace(String.valueOf(message), t); + } + } + + @Override + public void warn(Runnable function) { + if (function != null && logger.isWarnEnabled()) { + function.run(); + } + } + + @Override + public void warn(Object format, Object... arguments) { + if (logger.isWarnEnabled()) { + LogMessage msg = LogMessage.of(format, arguments); + if (msg.hasThrowable()) { + logger.warn(msg.getMessage(), msg.getThrowable()); + } else { + logger.warn(msg.getMessage()); + } + } + } + + @Override + public void warn(Object message, Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn(String.valueOf(message), t); + } + } + + @Override + public Object unwrap() { + return logger; + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/Slf4jLoggerFactory.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/Slf4jLoggerFactory.java new file mode 100644 index 000000000..20cccda7f --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/Slf4jLoggerFactory.java @@ -0,0 +1,16 @@ +package com.clickhouse.client.logging; + +/** + * Adaptor of slf4j logger factory. + */ +public class Slf4jLoggerFactory extends LoggerFactory { + @Override + public Logger get(Class clazz) { + return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(clazz)); + } + + @Override + public Logger get(String name) { + return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name)); + } +} diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/logging/package-info.java b/clickhouse-client/src/main/java/com/clickhouse/client/logging/package-info.java new file mode 100644 index 000000000..20cb23a07 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/logging/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides logging classes. + */ +package com.clickhouse.client.logging; diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/package-info.java b/clickhouse-client/src/main/java/com/clickhouse/client/package-info.java new file mode 100644 index 000000000..06985f7d3 --- /dev/null +++ b/clickhouse-client/src/main/java/com/clickhouse/client/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides necessary classes to communicate with ClickHouse server. + */ +package com.clickhouse.client; diff --git a/clickhouse-client/src/main/java9/module-info.java b/clickhouse-client/src/main/java9/module-info.java new file mode 100644 index 000000000..faef20d66 --- /dev/null +++ b/clickhouse-client/src/main/java9/module-info.java @@ -0,0 +1,32 @@ +/** + * Declares com.clickhouse.client module. + */ +module com.clickhouse.client { + exports com.clickhouse.client; + exports com.clickhouse.client.config; + exports com.clickhouse.client.data; + exports com.clickhouse.client.exception; + + exports com.clickhouse.client.logging to + com.clickhouse.client.http, + com.clickhouse.client.grpc, + com.clickhouse.client.mysql, + com.clickhouse.client.postgresql, + // native is a reserved keyword :< + com.clickhouse.client.tcp, + com.clickhouse.jdbc; + + requires java.base; + + requires static java.logging; + requires static org.dnsjava; + requires static org.lz4.java; + requires static org.slf4j; + requires static org.roaringbitmap; + + uses com.clickhouse.client.ClickHouseClient; + uses com.clickhouse.client.ClickHouseDataStreamFactory; + uses com.clickhouse.client.ClickHouseDnsResolver; + uses com.clickhouse.client.ClickHouseSslContextProvider; + uses com.clickhouse.client.logging.LoggerFactory; +} diff --git a/clickhouse-client/src/main/resources/META-INF/services/com.clickhouse.client.ClickHouseSslContextProvider b/clickhouse-client/src/main/resources/META-INF/services/com.clickhouse.client.ClickHouseSslContextProvider new file mode 100644 index 000000000..d0918520b --- /dev/null +++ b/clickhouse-client/src/main/resources/META-INF/services/com.clickhouse.client.ClickHouseSslContextProvider @@ -0,0 +1 @@ +com.clickhouse.client.config.ClickHouseDefaultSslContextProvider diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/BaseClickHouseValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/BaseClickHouseValueTest.java new file mode 100644 index 000000000..efc2d0f73 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/BaseClickHouseValueTest.java @@ -0,0 +1,203 @@ +package com.clickhouse.client; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.testng.Assert; + +public abstract class BaseClickHouseValueTest { + protected Map buildMap(K[] keys, V[] values) { + Map map = new LinkedHashMap<>(); + int len = keys == null ? 0 : keys.length; + if (values == null || values.length != len) { + throw new IllegalArgumentException("Keys and values should have same length"); + } + for (int i = 0; i < len; i++) { + map.put(keys[i], values[i]); + } + return map; + } + + @SuppressWarnings("unchecked") + protected void checkValueOrException(Supplier actual, Object expected) { + if (expected == null) { + Assert.assertNull(actual.get()); + } else if (expected instanceof Class && Throwable.class.isAssignableFrom((Class) expected)) { + Assert.assertThrows((Class) expected, () -> actual.get()); + } else if (expected instanceof String) { + Assert.assertEquals(String.valueOf(actual.get()), (String) expected); + } else if (expected instanceof List) { + List l1 = (List) actual.get(); + List l2 = (List) expected; + // unfortunately TestNG 6.x didn't work + Assert.assertTrue(Arrays.deepEquals(l1.toArray(new Object[l1.size()]), l2.toArray(new Object[l2.size()]))); + // Assert.assertEquals(l1.toArray(new Object[l1.size()]), l2.toArray(new + // Object[l2.size()])); + } else if (expected instanceof Map) { + Assert.assertEqualsDeep((Map) actual.get(), (Map) expected); + } else { + Assert.assertEquals(actual.get(), expected); + } + } + + protected void checkNull(ClickHouseValue v) { + checkNull(v, true, 3, 9); + } + + protected void checkNull(ClickHouseValue v, boolean nullable, int bigDecimalScale, int dateTimeScale) { + Assert.assertFalse(v.isInfinity()); + Assert.assertFalse(v.isNaN()); + Assert.assertTrue(v.isNullOrEmpty()); + + Assert.assertEquals(v.asBoolean(), false); + Assert.assertEquals(v.asCharacter(), '\0'); + Assert.assertEquals(v.asByte(), (byte) 0); + Assert.assertEquals(v.asShort(), (short) 0); + Assert.assertEquals(v.asInteger(), 0); + Assert.assertEquals(v.asLong(), 0L); + Assert.assertEquals(v.asFloat(), 0F); + Assert.assertEquals(v.asDouble(), 0D); + + Assert.assertNull(v.asBigDecimal()); + Assert.assertNull(v.asBigDecimal(bigDecimalScale)); + Assert.assertNull(v.asBigInteger()); + Assert.assertNull(v.asEnum(ClickHouseDataType.class)); + Assert.assertNull(v.asDate()); + Assert.assertNull(v.asDateTime()); + Assert.assertNull(v.asTime()); + Assert.assertNull(v.asDateTime(dateTimeScale)); + Assert.assertNull(v.asInet4Address()); + Assert.assertNull(v.asInet6Address()); + Assert.assertNull(v.asUuid()); + if (nullable) { + Assert.assertNull(v.asObject()); + Assert.assertNull(v.asObject(Object.class)); + Assert.assertNull(v.asString()); + Assert.assertNull(v.asString(StandardCharsets.UTF_16)); + Assert.assertNull(v.asString(1)); + Assert.assertNull(v.asString(1, StandardCharsets.UTF_16)); + Assert.assertEquals(v.toSqlExpression(), ClickHouseValues.NULL_EXPR); + } else { + Assert.assertNotNull(v.asObject()); + Assert.assertNotNull(v.asObject(Object.class)); + Assert.assertNotNull(v.asString()); + Assert.assertNotNull(v.asString(StandardCharsets.UTF_16)); + Assert.assertNotNull(v.asString(v.asString().length())); + Assert.assertNotNull( + v.asString(v.asString(StandardCharsets.UTF_16).getBytes(StandardCharsets.UTF_16).length, + StandardCharsets.UTF_16)); + Assert.assertNotNull(v.toSqlExpression()); + Assert.assertNotEquals(v.toSqlExpression(), ClickHouseValues.NULL_EXPR); + } + + Assert.assertEquals(v.asArray(), new Object[0]); + Assert.assertEquals(v.asArray(ClickHouseDataType.class), new Object[0]); + Assert.assertNotNull(v.asMap()); + Assert.assertEquals(v.asMap().size(), 0); + Assert.assertEquals(v.asMap(Integer.class, ClickHouseDataType.class).size(), 0); + Assert.assertEquals(v.asTuple(), Collections.emptyList()); + } + + protected void checkValue(ClickHouseValue v, Object... expected) { + checkValue(v, 3, 9, expected); + } + + protected void checkValue(ClickHouseValue v, int bigDecimalScale, int dateTimeScale, Object... expected) { + int i = 0; + checkValueOrException(v::isInfinity, expected[i++]); + checkValueOrException(v::isNaN, expected[i++]); + checkValueOrException(v::isNullOrEmpty, expected[i++]); + + checkValueOrException(v::asBoolean, expected[i++]); + checkValueOrException(v::asByte, expected[i++]); + checkValueOrException(v::asShort, expected[i++]); + checkValueOrException(v::asInteger, expected[i++]); + checkValueOrException(v::asLong, expected[i++]); + Object nanValue = expected[i++]; + if (nanValue != null) { // skip NaN + checkValueOrException(v::asFloat, nanValue); + } + nanValue = expected[i++]; + if (nanValue != null) { // skip NaN + checkValueOrException(v::asDouble, nanValue); + } + + checkValueOrException(v::asBigDecimal, expected[i++]); + checkValueOrException(() -> v.asBigDecimal(bigDecimalScale), expected[i++]); + checkValueOrException(v::asBigInteger, expected[i++]); + checkValueOrException(() -> v.asEnum(ClickHouseDataType.class), expected[i++]); + checkValueOrException(v::asObject, expected[i++]); + checkValueOrException(v::asDate, expected[i++]); + checkValueOrException(v::asDateTime, expected[i++]); + checkValueOrException(() -> v.asDateTime(dateTimeScale), expected[i++]); + checkValueOrException(v::asInet4Address, expected[i++]); + checkValueOrException(v::asInet6Address, expected[i++]); + checkValueOrException(v::asString, expected[i++]); + checkValueOrException(v::toSqlExpression, expected[i++]); + checkValueOrException(v::asTime, expected[i++]); + checkValueOrException(v::asUuid, expected[i++]); + + Class keyClass = (Class) expected[i++]; + Class valueClass = (Class) expected[i++]; + checkValueOrException(v::asArray, expected[i++]); + checkValueOrException(() -> v.asArray(valueClass), expected[i++]); + checkValueOrException(v::asMap, expected[i++]); + checkValueOrException(() -> v.asMap(keyClass, valueClass), expected[i++]); + checkValueOrException(v::asTuple, expected[i++]); + } + + protected Object getReturnValue(Supplier func) { + try { + return func.get(); + } catch (Throwable t) { + return t.getClass().getName() + ':' + t.getMessage(); + } + } + + protected > void sameValue(ClickHouseValue v1, ClickHouseValue v2, int bigDecimalScale, + int dateTimeScale, Class arrayType, Class enumType, Class keyType, Class valueType) { + // you're supposed to pass two different values for comparison + // Assert.assertFalse(v1 == v2); + + Assert.assertEquals(getReturnValue(v1::isInfinity), getReturnValue(v2::isInfinity)); + Assert.assertEquals(getReturnValue(v1::isNaN), getReturnValue(v2::isNaN)); + Assert.assertEquals(getReturnValue(v1::isNullOrEmpty), getReturnValue(v2::isNullOrEmpty)); + + Assert.assertEquals(getReturnValue(v1::asBoolean), getReturnValue(v2::asBoolean)); + Assert.assertEquals(getReturnValue(v1::asCharacter), getReturnValue(v2::asCharacter)); + Assert.assertEquals(getReturnValue(v1::asByte), getReturnValue(v2::asByte)); + Assert.assertEquals(getReturnValue(v1::asShort), getReturnValue(v2::asShort)); + Assert.assertEquals(getReturnValue(v1::asInteger), getReturnValue(v2::asInteger)); + Assert.assertEquals(getReturnValue(v1::asLong), getReturnValue(v2::asLong)); + Assert.assertEquals(getReturnValue(v1::asFloat), getReturnValue(v2::asFloat)); + Assert.assertEquals(getReturnValue(v1::asDouble), getReturnValue(v2::asDouble)); + Assert.assertEquals(getReturnValue(v1::asBigDecimal), getReturnValue(v2::asBigDecimal)); + Assert.assertEquals(getReturnValue(() -> v1.asBigDecimal(bigDecimalScale)), + getReturnValue(() -> v2.asBigDecimal(bigDecimalScale))); + Assert.assertEquals(getReturnValue(v1::asBigInteger), getReturnValue(v2::asBigInteger)); + + Assert.assertEquals(getReturnValue(v1::asDate), getReturnValue(v2::asDate)); + Assert.assertEquals(getReturnValue(v1::asDateTime), getReturnValue(v2::asDateTime)); + Assert.assertEquals(getReturnValue(() -> v1.asDateTime(dateTimeScale)), + getReturnValue(() -> v2.asDateTime(dateTimeScale))); + Assert.assertEquals(getReturnValue(v1::asTime), getReturnValue(v2::asTime)); + Assert.assertEquals(getReturnValue(() -> v1.asEnum(enumType)), getReturnValue(() -> v2.asEnum(enumType))); + Assert.assertEquals(getReturnValue(v1::asInet4Address), getReturnValue(v2::asInet4Address)); + Assert.assertEquals(getReturnValue(v1::asInet6Address), getReturnValue(v2::asInet6Address)); + Assert.assertEquals(getReturnValue(v1::asString), getReturnValue(v2::asString)); + Assert.assertEquals(getReturnValue(v1::toSqlExpression), getReturnValue(v2::toSqlExpression)); + Assert.assertEquals(getReturnValue(v1::asUuid), getReturnValue(v2::asUuid)); + + Assert.assertEquals(getReturnValue(v1::asArray), getReturnValue(v2::asArray)); + Assert.assertEquals(getReturnValue(() -> v1.asArray(arrayType)), getReturnValue(() -> v2.asArray(arrayType))); + Assert.assertEquals(getReturnValue(v1::asMap), getReturnValue(v2::asMap)); + Assert.assertEquals(getReturnValue(() -> v1.asMap(keyType, valueType)), + getReturnValue(() -> v2.asMap(keyType, valueType))); + Assert.assertEquals(getReturnValue(v1::asTuple), getReturnValue(v2::asTuple)); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java new file mode 100644 index 000000000..c273ee536 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java @@ -0,0 +1,25 @@ +package com.clickhouse.client; + +import org.testng.annotations.BeforeTest; + +/** + * Base class for all integration tests. + */ +public abstract class BaseIntegrationTest { + @BeforeTest(groups = { "integration" }) + public static void setupClickHouseContainer() { + ClickHouseServerForTest.beforeSuite(); + } + + protected ClickHouseNode getServer(ClickHouseProtocol protocol) { + return ClickHouseServerForTest.getClickHouseNode(protocol, ClickHouseNode.builder().build()); + } + + protected ClickHouseNode getServer(ClickHouseProtocol protocol, ClickHouseNode base) { + return ClickHouseServerForTest.getClickHouseNode(protocol, base); + } + + protected ClickHouseNode getServer(ClickHouseProtocol protocol, int port) { + return ClickHouseServerForTest.getClickHouseNode(protocol, port); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java new file mode 100644 index 000000000..a431bcbb7 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java @@ -0,0 +1,106 @@ +package com.clickhouse.client; + +import java.math.BigInteger; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseCheckerTest { + @Test(groups = { "unit" }) + public void testBetween() { + // int + Assert.assertEquals(ClickHouseChecker.between(0, "value", 0, 0), 0); + Assert.assertEquals(ClickHouseChecker.between(0, "value", -1, 1), 0); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.between(1, "value", 2, 3)); + + // long + Assert.assertEquals(ClickHouseChecker.between(0L, "value", 0L, 0L), 0L); + Assert.assertEquals(ClickHouseChecker.between(0L, "value", -1L, 1L), 0L); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.between(1L, "value", 2L, 3L)); + + // bigint + Assert.assertEquals(ClickHouseChecker.between(BigInteger.ZERO, "value", BigInteger.ZERO, BigInteger.ZERO), + BigInteger.ZERO); + Assert.assertEquals( + ClickHouseChecker.between(BigInteger.ZERO, "value", BigInteger.valueOf(-1L), BigInteger.ONE), + BigInteger.ZERO); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.between(BigInteger.ONE, "value", + BigInteger.valueOf(2), BigInteger.valueOf(3L))); + } + + @Test(groups = { "unit" }) + public void testIsNullOrEmpty() { + Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(null)); + Assert.assertTrue(ClickHouseChecker.isNullOrEmpty("")); + Assert.assertFalse(ClickHouseChecker.isNullOrEmpty(" ")); + } + + @Test(groups = { "unit" }) + public void testNonEmpty() { + Assert.assertEquals(ClickHouseChecker.nonEmpty(" ", "value"), " "); + + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.nonEmpty(null, null)); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.nonEmpty("", "")); + } + + @Test(groups = { "unit" }) + public void testNonNull() { + Object obj; + Assert.assertEquals(ClickHouseChecker.nonNull(obj = new Object(), "value"), obj); + Assert.assertEquals(ClickHouseChecker.nonNull(obj = 1, "value"), obj); + + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.nonNull(null, null)); + } + + @Test(groups = { "unit" }) + public void testNotLessThan() { + // int + Assert.assertEquals(ClickHouseChecker.notLessThan(1, "value", 0), 1); + Assert.assertEquals(ClickHouseChecker.notLessThan(0, "value", 0), 0); + Assert.assertEquals(ClickHouseChecker.notLessThan(0, "value", -1), 0); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.notLessThan(0, "value", 1)); + + // long + Assert.assertEquals(ClickHouseChecker.notLessThan(1L, "value", 0L), 1L); + Assert.assertEquals(ClickHouseChecker.notLessThan(0L, "value", 0L), 0L); + Assert.assertEquals(ClickHouseChecker.notLessThan(0L, "value", -1L), 0L); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.notLessThan(0L, "value", 1L)); + + // bigint + Assert.assertEquals(ClickHouseChecker.notLessThan(BigInteger.ONE, "value", BigInteger.ZERO), BigInteger.ONE); + Assert.assertEquals(ClickHouseChecker.notLessThan(BigInteger.ZERO, "value", BigInteger.ZERO), BigInteger.ZERO); + Assert.assertEquals(ClickHouseChecker.notLessThan(BigInteger.ZERO, "value", BigInteger.valueOf(-1L)), + BigInteger.ZERO); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseChecker.notLessThan(BigInteger.ZERO, "value", BigInteger.ONE)); + } + + @Test(groups = { "unit" }) + public void testNotLongerThan() { + byte[] bytes; + Assert.assertEquals(ClickHouseChecker.notLongerThan(bytes = null, "value", 0), bytes); + Assert.assertEquals(ClickHouseChecker.notLongerThan(bytes = new byte[0], "value", 0), bytes); + Assert.assertEquals(ClickHouseChecker.notLongerThan(bytes = new byte[1], "value", 1), bytes); + + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseChecker.notLongerThan(null, null, -1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseChecker.notLongerThan(new byte[0], null, -1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseChecker.notLongerThan(new byte[2], null, 1)); + } + + @Test(groups = { "unit" }) + public void testNotWithDifferentLength() { + byte[] bytes; + Assert.assertEquals(ClickHouseChecker.notWithDifferentLength(bytes = null, "value", 0), bytes); + Assert.assertEquals(ClickHouseChecker.notWithDifferentLength(bytes = new byte[0], "value", 0), bytes); + Assert.assertEquals(ClickHouseChecker.notWithDifferentLength(bytes = new byte[1], "value", 1), bytes); + + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseChecker.notWithDifferentLength((byte[]) null, null, -1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseChecker.notWithDifferentLength(new byte[0], null, -1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseChecker.notWithDifferentLength(new byte[2], null, 1)); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClientBuilderTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClientBuilderTest.java new file mode 100644 index 000000000..96980fb03 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClientBuilderTest.java @@ -0,0 +1,33 @@ +package com.clickhouse.client; + +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.config.ClickHouseClientOption; + +public class ClickHouseClientBuilderTest { + @Test(groups = { "unit" }) + public void testBuildClient() { + ClickHouseClientBuilder builder = new ClickHouseClientBuilder(); + ClickHouseClient client = builder.build(); + Assert.assertTrue(client instanceof ClickHouseTestClient); + Assert.assertNotEquals(builder.build(), client); + + ClickHouseTestClient testClient = (ClickHouseTestClient) client; + Assert.assertTrue(testClient.getConfig() == builder.getConfig()); + } + + @Test(groups = { "unit" }) + public void testBuildConfig() { + ClickHouseClientBuilder builder = new ClickHouseClientBuilder(); + ClickHouseConfig config = builder.getConfig(); + Assert.assertNotNull(config); + Assert.assertEquals(builder.getConfig(), config); + + String clientName = "test client"; + builder.addOption(ClickHouseClientOption.CLIENT_NAME, clientName); + Assert.assertNotEquals(builder.getConfig(), config); + config = builder.getConfig(); + Assert.assertEquals(config.getClientName(), clientName); + Assert.assertEquals(config.getOption(ClickHouseClientOption.CLIENT_NAME), clientName); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClientTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClientTest.java new file mode 100644 index 000000000..2dd8c22a2 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClientTest.java @@ -0,0 +1,37 @@ +package com.clickhouse.client; + +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.ClickHouseRequest.Mutation; + +public class ClickHouseClientTest { + @Test(groups = { "unit" }) + public void testQuery() throws Exception { + ClickHouseClient client = ClickHouseClient.builder().build(); + Assert.assertNotNull(client); + ClickHouseRequest req = client.connect(ClickHouseNode.builder().build()); + Assert.assertNotNull(req); + Assert.assertNull(req.config); + Assert.assertNotNull(req.getConfig()); + Assert.assertNotNull(req.config); + Assert.assertEquals(req.getClient(), client); + Assert.assertEquals(req.getFormat(), client.getConfig().getFormat()); + Assert.assertNull(req.sql); + Assert.assertNull(req.query("select 1").execute().get()); + } + + @Test(groups = { "unit" }) + public void testMutation() throws Exception { + ClickHouseClient client = ClickHouseClient.builder().build(); + Assert.assertNotNull(client); + Mutation req = client.connect(ClickHouseNode.builder().build()).write(); + Assert.assertNotNull(req); + Assert.assertNull(req.config); + Assert.assertNotNull(req.getConfig()); + Assert.assertNotNull(req.config); + Assert.assertEquals(req.getClient(), client); + Assert.assertEquals(req.getFormat(), client.getConfig().getFormat()); + Assert.assertNull(req.sql); + Assert.assertNull(req.table("my_table").format(ClickHouseFormat.RowBinary).execute().get()); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClusterTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClusterTest.java new file mode 100644 index 000000000..bda5c6f45 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseClusterTest.java @@ -0,0 +1,116 @@ +package com.clickhouse.client; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import com.clickhouse.client.ClickHouseNode.Status; + +public class ClickHouseClusterTest extends BaseIntegrationTest { + private ClickHouseCluster createCluster(int size) { + ClickHouseNode template = ClickHouseNode.builder().host("test.host").build(); + + ClickHouseNode[] nodes = new ClickHouseNode[size]; + for (int i = 0; i < size; i++) { + nodes[i] = ClickHouseNode.builder(template).port(ClickHouseProtocol.HTTP, i + 1) + .tags(String.valueOf(i % size)).build(); + } + + return ClickHouseCluster.of(nodes); + } + + @DataProvider(name = "nodeSelectorProvider") + private Object[][] getNodeSelectors() { + return new Object[][] { { null }, { ClickHouseNodeSelector.EMPTY }, + { ClickHouseNodeSelector.of(Collections.emptyList(), Collections.singleton("3")) } }; + } + + @Test(dataProvider = "nodeSelectorProvider", groups = { "unit" }) + public void testGetNode(ClickHouseNodeSelector nodeSelector) throws Exception { + int size = 5; + int requests = 500; + int len = size * requests; + int tag = nodeSelector != null && nodeSelector.getPreferredTags().size() > 0 + ? Integer.parseInt(nodeSelector.getPreferredTags().iterator().next()) + : -1; + + ClickHouseCluster cluster = createCluster(size); + + // single thread + int[] counters = new int[size]; + for (int i = 0; i < len; i++) { + ClickHouseNode node = cluster.apply(nodeSelector); + counters[node.getPort() - 1] += 1; + } + + for (int i = 0; i < size; i++) { + if (tag == -1) { + Assert.assertEquals(counters[i], requests); + } else if (i == tag) { + Assert.assertEquals(counters[i], len); + } else { + Assert.assertEquals(counters[i], 0); + } + } + + // multi-thread + CountDownLatch latch = new CountDownLatch(len); + List results = Collections.synchronizedList(new ArrayList<>(len)); + ExecutorService executor = Executors.newFixedThreadPool(3); + for (int i = 0; i < len; i++) { + executor.execute(() -> { + ClickHouseNode node = cluster.apply(nodeSelector); + results.add(node); + latch.countDown(); + }); + } + + latch.await(15, TimeUnit.SECONDS); + counters = new int[size]; + for (int i = 0; i < len; i++) { + counters[results.get(i).getPort() - 1] += 1; + } + for (int i = 0; i < size; i++) { + if (tag == -1) { + Assert.assertEquals(counters[i], requests); + } else if (i == tag) { + Assert.assertEquals(counters[i], len); + } else { + Assert.assertEquals(counters[i], 0); + } + } + } + + @Test(dataProvider = "nodeSelectorProvider", groups = { "unit" }) + public void testCheck(ClickHouseNodeSelector nodeSelector) { + int size = 5000; + int len = nodeSelector != null && nodeSelector.getPreferredTags().size() > 0 ? 1 : size; + + ClickHouseCluster cluster = createCluster(size); + for (int i = 0; i < len; i++) { + ClickHouseNode node = cluster.apply(nodeSelector); + node.updateStatus(Status.UNHEALTHY); + Assert.assertTrue(cluster.getAvailableNodes().size() >= size - i - 1); + } + } + + @Test(groups = { "integration" }) + public void testProbe() { + // FIXME does not support ClickHouseProtocol.POSTGRESQL for now + ClickHouseProtocol[] protocols = new ClickHouseProtocol[] { ClickHouseProtocol.GRPC, ClickHouseProtocol.HTTP, + ClickHouseProtocol.MYSQL, ClickHouseProtocol.NATIVE }; + + for (ClickHouseProtocol p : protocols) { + ClickHouseNode node = getServer(ClickHouseProtocol.ANY, p.getDefaultPort()); + ClickHouseNode probedNode = ClickHouseCluster.probe(node); + Assert.assertNotEquals(probedNode, node); + Assert.assertEquals(probedNode.getProtocol(), p); + } + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseColumnTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseColumnTest.java new file mode 100644 index 000000000..3ccc288af --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseColumnTest.java @@ -0,0 +1,110 @@ +package com.clickhouse.client; + +import java.util.LinkedList; +import java.util.List; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseColumnTest { + + @Test(groups = { "unit" }) + public void testReadColumn() { + String args = "AggregateFunction(max, UInt64), cc LowCardinality(Nullable(String)), a UInt8 null"; + List list = new LinkedList<>(); + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.indexOf("cc") - 2); + Assert.assertEquals(list.size(), 1); + list.clear(); + Assert.assertEquals(ClickHouseColumn.readColumn(args, args.indexOf("cc") + 3, args.length(), null, list), + args.lastIndexOf(',')); + list.clear(); + Assert.assertEquals(ClickHouseColumn.readColumn(args, args.lastIndexOf('U'), args.length(), null, list), + args.length() - 1); + Assert.assertEquals(list.size(), 1); + ClickHouseColumn column = list.get(0); + Assert.assertNotNull(column); + Assert.assertFalse(column.isLowCardinality()); + Assert.assertTrue(column.isNullable()); + Assert.assertEquals(column.getDataType(), ClickHouseDataType.UInt8); + list.clear(); + + args = "INT1 unsigned not null, b DateTime64(3) NULL"; + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.indexOf(',')); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertFalse(column.isNullable()); + Assert.assertEquals(column.getDataType(), ClickHouseDataType.UInt8); + list.clear(); + + Assert.assertEquals(ClickHouseColumn.readColumn(args, args.indexOf('D'), args.length(), null, list), + args.length() - 1); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertTrue(column.isNullable()); + Assert.assertEquals(column.getDataType(), ClickHouseDataType.DateTime64); + } + + @Test(groups = { "unit" }) + public void testReadNestedColumn() { + String args = "Array(Array(Nullable(UInt8)))"; + List list = new LinkedList<>(); + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.length()); + Assert.assertEquals(list.size(), 1); + ClickHouseColumn column = list.get(0); + Assert.assertEquals(column.getNestedColumns().size(), 1); + Assert.assertEquals(column.getNestedColumns().get(0).getNestedColumns().size(), 1); + list.clear(); + + args = " Tuple(Nullable(FixedString(3)), Array(UInt8),String not null) "; + Assert.assertEquals(ClickHouseColumn.readColumn(args, 1, args.length(), null, list), args.length() - 2); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertEquals(column.getOriginalTypeName(), args.trim()); + list.clear(); + + args = "Map(UInt8 , UInt8)"; + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.length()); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertEquals(column.getOriginalTypeName(), args); + list.clear(); + + args = "Map(String, FixedString(233))"; + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.length()); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertEquals(column.getOriginalTypeName(), args); + list.clear(); + + args = "Map(String, Tuple(UInt8, Nullable(String), UInt16 null))"; + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.length()); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertEquals(column.getOriginalTypeName(), args); + Assert.assertEquals(column.getNestedColumns().size(), 2); + Assert.assertEquals(column.getKeyInfo().getOriginalTypeName(), "String"); + Assert.assertEquals(column.getValueInfo().getOriginalTypeName(), "Tuple(UInt8, Nullable(String), UInt16 null)"); + Assert.assertEquals(column.getValueInfo().getNestedColumns().size(), 3); + list.clear(); + + args = "Nested(\na Array(Nullable(UInt8)), `b b` LowCardinality(Nullable(DateTime64(3))))"; + Assert.assertEquals(ClickHouseColumn.readColumn(args, 0, args.length(), null, list), args.length()); + Assert.assertEquals(list.size(), 1); + column = list.get(0); + Assert.assertEquals(column.getOriginalTypeName(), args); + } + + @Test(groups = { "unit" }) + public void testParse() throws Exception { + ClickHouseColumn column = ClickHouseColumn.of("arr", "Nullable(Array(Nullable(UInt8))"); + Assert.assertNotNull(column); + + List list = ClickHouseColumn.parse("a String not null, b String null"); + Assert.assertEquals(list.size(), 2); + list = ClickHouseColumn.parse("a String not null, b Int8"); + Assert.assertEquals(list.size(), 2); + list = ClickHouseColumn.parse("a String, b String null"); + Assert.assertEquals(list.size(), 2); + list = ClickHouseColumn.parse("a String default 'cc', b String null"); + Assert.assertEquals(list.size(), 2); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseConfigTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseConfigTest.java new file mode 100644 index 000000000..54e983c1a --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseConfigTest.java @@ -0,0 +1,75 @@ +package com.clickhouse.client; + +import java.util.HashMap; +import java.util.Map; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.config.ClickHouseConfigOption; +import com.clickhouse.client.config.ClickHouseDefaults; + +public class ClickHouseConfigTest { + @Test(groups = { "unit" }) + public void testDefaultValues() { + ClickHouseConfig config = new ClickHouseConfig(null, null, null, null, null); + Assert.assertEquals(config.getClientName(), + ClickHouseClientOption.CLIENT_NAME.getEffectiveDefaultValue()); + Assert.assertEquals(config.getDatabase(), ClickHouseDefaults.DATABASE.getEffectiveDefaultValue()); + + Assert.assertEquals(config.getOption(ClickHouseDefaults.CLUSTER), + ClickHouseDefaults.CLUSTER.getEffectiveDefaultValue()); + Assert.assertEquals(config.getOption(ClickHouseDefaults.HOST), + ClickHouseDefaults.HOST.getEffectiveDefaultValue()); + Assert.assertEquals(config.getOption(ClickHouseDefaults.PORT), + ClickHouseDefaults.PORT.getEffectiveDefaultValue()); + Assert.assertEquals(config.getOption(ClickHouseDefaults.WEIGHT), + ClickHouseDefaults.WEIGHT.getEffectiveDefaultValue()); + ClickHouseCredentials credentials = config.getDefaultCredentials(); + Assert.assertEquals(credentials.useAccessToken(), false); + Assert.assertEquals(credentials.getUserName(), ClickHouseDefaults.USER.getEffectiveDefaultValue()); + Assert.assertEquals(credentials.getPassword(), ClickHouseDefaults.PASSWORD.getEffectiveDefaultValue()); + Assert.assertEquals(config.getFormat().name(), ClickHouseDefaults.FORMAT.getEffectiveDefaultValue()); + Assert.assertFalse(config.getMetricRegistry().isPresent()); + } + + @Test(groups = { "unit" }) + public void testCustomValues() throws Exception { + String clientName = "test client"; + String cluster = "test cluster"; + String database = "test_database"; + String host = "test.host"; + Integer port = 12345; + Integer weight = -99; + String user = "sa"; + String password = "welcome"; + + Map options = new HashMap<>(); + options.put(ClickHouseClientOption.CLIENT_NAME, clientName); + options.put(ClickHouseDefaults.CLUSTER, cluster); + options.put(ClickHouseClientOption.DATABASE, database); + options.put(ClickHouseDefaults.HOST, host); + options.put(ClickHouseDefaults.PORT, port); + options.put(ClickHouseDefaults.WEIGHT, weight); + options.put(ClickHouseDefaults.USER, "useless"); + options.put(ClickHouseDefaults.PASSWORD, "useless"); + + Object metricRegistry = new Object(); + + ClickHouseConfig config = new ClickHouseConfig(options, + ClickHouseCredentials.fromUserAndPassword(user, password), null, metricRegistry); + Assert.assertEquals(config.getClientName(), clientName); + Assert.assertEquals(config.getDatabase(), database); + Assert.assertEquals(config.getOption(ClickHouseDefaults.CLUSTER), cluster); + Assert.assertEquals(config.getOption(ClickHouseDefaults.HOST), host); + Assert.assertEquals(config.getOption(ClickHouseDefaults.PORT), port); + Assert.assertEquals(config.getOption(ClickHouseDefaults.WEIGHT), weight); + + ClickHouseCredentials credentials = config.getDefaultCredentials(); + Assert.assertEquals(credentials.useAccessToken(), false); + Assert.assertEquals(credentials.getUserName(), user); + Assert.assertEquals(credentials.getPassword(), password); + Assert.assertEquals(config.getPreferredProtocols().size(), 0); + Assert.assertEquals(config.getPreferredTags().size(), 0); + Assert.assertEquals(config.getMetricRegistry().get(), metricRegistry); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseDataStreamFactoryTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseDataStreamFactoryTest.java new file mode 100644 index 000000000..0b00a5c38 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseDataStreamFactoryTest.java @@ -0,0 +1,11 @@ +package com.clickhouse.client; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseDataStreamFactoryTest { + @Test(groups = { "unit" }) + public void testGetInstance() throws Exception { + Assert.assertNotNull(ClickHouseDataStreamFactory.getInstance()); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseDataTypeTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseDataTypeTest.java new file mode 100644 index 000000000..1cdf1d5b4 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseDataTypeTest.java @@ -0,0 +1,48 @@ +package com.clickhouse.client; + +import java.util.List; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseDataTypeTest { + @Test(groups = { "unit" }) + public void testAlias() { + for (String alias : ClickHouseDataType.allAliases) { + Assert.assertTrue(ClickHouseDataType.isAlias(alias)); + } + + for (ClickHouseDataType t : ClickHouseDataType.values()) { + Assert.assertFalse(ClickHouseDataType.isAlias(t.name())); + } + } + + @Test(groups = { "unit" }) + public void testMapping() { + for (ClickHouseDataType t : ClickHouseDataType.values()) { + Assert.assertEquals(ClickHouseDataType.of(t.name()), t); + if (!t.isCaseSensitive()) { + Assert.assertEquals(ClickHouseDataType.of(t.name().toLowerCase()), t); + Assert.assertEquals(ClickHouseDataType.of(t.name().toUpperCase()), t); + } + + for (String alias : t.getAliases()) { + Assert.assertEquals(ClickHouseDataType.of(alias), t); + Assert.assertEquals(ClickHouseDataType.of(alias.toLowerCase()), t); + Assert.assertEquals(ClickHouseDataType.of(alias.toUpperCase()), t); + } + } + } + + @Test(groups = { "unit" }) + public void testMatch() { + List matched = ClickHouseDataType.match("INT1"); + Assert.assertEquals(matched.size(), 3); + Assert.assertEquals(matched.get(0), "INT1"); + Assert.assertEquals(matched.get(1), "INT1 SIGNED"); + Assert.assertEquals(matched.get(2), "INT1 UNSIGNED"); + + matched = ClickHouseDataType.match("UInt32"); + Assert.assertEquals(matched.size(), 1); + Assert.assertEquals(matched.get(0), "UInt32"); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseNodeSelectorTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseNodeSelectorTest.java new file mode 100644 index 000000000..4902eb023 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseNodeSelectorTest.java @@ -0,0 +1,126 @@ +package com.clickhouse.client; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseNodeSelectorTest { + @SuppressWarnings({ "unchecked", "varargs" }) + private List listOf(T... values) { + List list = new ArrayList<>(values.length); + for (T value : values) { + list.add(value); + } + return Collections.unmodifiableList(list); + } + + @SuppressWarnings({ "unchecked", "varargs" }) + private Set setOf(T... values) { + Set set = new HashSet<>(); + for (T value : values) { + set.add(value); + } + return Collections.unmodifiableSet(set); + } + + @Test(groups = { "unit" }) + public void testBuilder() { + Assert.assertTrue(ClickHouseNodeSelector.of((Collection) null, + (Collection) null) == ClickHouseNodeSelector.EMPTY); + Assert.assertTrue( + ClickHouseNodeSelector.of(new ArrayList(), null) == ClickHouseNodeSelector.EMPTY); + Assert.assertTrue(ClickHouseNodeSelector.of(null, new ArrayList()) == ClickHouseNodeSelector.EMPTY); + Assert.assertTrue(ClickHouseNodeSelector.of(new ArrayList(), + new ArrayList()) == ClickHouseNodeSelector.EMPTY); + } + + @Test(groups = { "unit" }) + public void testGetPreferredProtocols() { + Assert.assertEquals(ClickHouseNodeSelector.of(listOf((ClickHouseProtocol) null), null).getPreferredProtocols(), + Collections.emptyList()); + Assert.assertEquals(ClickHouseNodeSelector.of(listOf(ClickHouseProtocol.ANY), null).getPreferredProtocols(), + Collections.emptyList()); + Assert.assertEquals(ClickHouseNodeSelector.of(listOf(ClickHouseProtocol.HTTP, ClickHouseProtocol.ANY), null) + .getPreferredProtocols(), Collections.emptyList()); + + Assert.assertEquals(ClickHouseNodeSelector + .of(listOf(ClickHouseProtocol.HTTP, ClickHouseProtocol.GRPC, ClickHouseProtocol.HTTP), null) + .getPreferredProtocols(), listOf(ClickHouseProtocol.HTTP, ClickHouseProtocol.GRPC)); + } + + @Test(groups = { "unit" }) + public void testGetPreferredTags() { + Assert.assertEquals(ClickHouseNodeSelector.of(null, listOf((String) null)).getPreferredTags(), + Collections.emptySet()); + Assert.assertEquals(ClickHouseNodeSelector.of(null, listOf("")).getPreferredTags(), Collections.emptySet()); + + Assert.assertEquals(ClickHouseNodeSelector.of(null, listOf("A", "C", "D", "C", "B")).getPreferredTags(), + setOf("A", "C", "D", "B")); + Assert.assertEquals(ClickHouseNodeSelector.of(null, setOf("A", "C", "D", "C", "B")).getPreferredTags(), + setOf("A", "C", "D", "B")); + } + + @Test(groups = { "unit" }) + public void testMatchAnyOfPreferredProtocols() { + ClickHouseNodeSelector selector = ClickHouseNodeSelector.of(listOf((ClickHouseProtocol) null), null); + for (ClickHouseProtocol p : ClickHouseProtocol.values()) { + Assert.assertTrue(selector.matchAnyOfPreferredProtocols(p)); + } + + selector = ClickHouseNodeSelector.of(listOf(ClickHouseProtocol.ANY), null); + for (ClickHouseProtocol p : ClickHouseProtocol.values()) { + Assert.assertTrue(selector.matchAnyOfPreferredProtocols(p)); + } + + for (ClickHouseProtocol protocol : ClickHouseProtocol.values()) { + if (protocol == ClickHouseProtocol.ANY) { + continue; + } + + selector = ClickHouseNodeSelector.of(listOf(protocol), null); + for (ClickHouseProtocol p : ClickHouseProtocol.values()) { + if (p == ClickHouseProtocol.ANY || p == protocol) { + Assert.assertTrue(selector.matchAnyOfPreferredProtocols(p)); + } else { + Assert.assertFalse(selector.matchAnyOfPreferredProtocols(p)); + } + } + } + } + + @Test(groups = { "unit" }) + public void testMatchAllPreferredTags() { + List tags = listOf((String) null); + ClickHouseNodeSelector selector = ClickHouseNodeSelector.of(null, tags); + Assert.assertTrue(selector.matchAllPreferredTags(Collections.emptyList())); + + selector = ClickHouseNodeSelector.of(null, tags = listOf((String) null, "")); + Assert.assertTrue(selector.matchAllPreferredTags(Collections.emptyList())); + + selector = ClickHouseNodeSelector.of(null, tags = listOf("1", "3", "2")); + Assert.assertTrue(selector.matchAllPreferredTags(tags)); + Assert.assertTrue(selector.matchAllPreferredTags(listOf("2", "2", "1", "3", "3"))); + } + + @Test(groups = { "unit" }) + public void testMatchAnyOfPreferredTags() { + List tags = listOf((String) null); + ClickHouseNodeSelector selector = ClickHouseNodeSelector.of(null, tags); + Assert.assertTrue(selector.matchAnyOfPreferredTags(Collections.emptyList())); + + selector = ClickHouseNodeSelector.of(null, tags = listOf((String) null, "")); + Assert.assertTrue(selector.matchAnyOfPreferredTags(Collections.emptyList())); + + selector = ClickHouseNodeSelector.of(null, tags = listOf("1", "3", "2")); + for (String tag : tags) { + Assert.assertTrue(selector.matchAnyOfPreferredTags(Collections.singleton(tag))); + } + Assert.assertTrue(selector.matchAnyOfPreferredTags(listOf("v", "3", "5"))); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseNodeTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseNodeTest.java new file mode 100644 index 000000000..c6fea3f2d --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseNodeTest.java @@ -0,0 +1,99 @@ +package com.clickhouse.client; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.Arrays; + +import com.clickhouse.client.config.ClickHouseDefaults; + +public class ClickHouseNodeTest { + private void checkDefaultValues(ClickHouseNode node) { + Assert.assertNotNull(node); + Assert.assertEquals(node.getCluster(), ClickHouseDefaults.CLUSTER.getEffectiveDefaultValue()); + Assert.assertEquals(node.getDatabase(), ClickHouseDefaults.DATABASE.getEffectiveDefaultValue()); + Assert.assertEquals(node.getProtocol().name(), ClickHouseDefaults.PROTOCOL.getEffectiveDefaultValue()); + Assert.assertFalse(node.getCredentials().isPresent()); + Assert.assertTrue(node.getTags().isEmpty()); + Assert.assertNotNull(node.getAddress()); + Assert.assertEquals(node.getHost(), ClickHouseDefaults.HOST.getEffectiveDefaultValue()); + Assert.assertEquals(node.getPort(), ClickHouseDefaults.PORT.getEffectiveDefaultValue()); + Assert.assertEquals(node.getWeight(), ClickHouseDefaults.WEIGHT.getEffectiveDefaultValue()); + } + + private void checkCustomValues(ClickHouseNode node, String cluster, String host, int port, int weight, + ClickHouseProtocol protocol, String database, ClickHouseCredentials credentials, String[] tags) { + Assert.assertNotNull(node); + Assert.assertEquals(node.getCluster(), cluster); + Assert.assertNotNull(node.getAddress()); + Assert.assertEquals(node.getHost(), host); + Assert.assertEquals(node.getPort(), port); + Assert.assertEquals(node.getWeight(), weight); + Assert.assertEquals(node.getProtocol(), protocol); + Assert.assertEquals(node.getDatabase(), database); + Assert.assertEquals(node.getCredentials().orElse(null), credentials); + Assert.assertEquals(node.getTags().size(), tags.length); + for (String t : tags) { + Assert.assertTrue(node.getTags().contains(t)); + } + } + + @Test(groups = { "unit" }) + public void testDefaultNode() { + checkDefaultValues(ClickHouseNode.builder().build()); + } + + @Test(groups = { "unit" }) + public void testCustomNode() { + String cluster = "my_cluster"; + String database = "my_db"; + String host = "non-existing.host"; + int port = 38123; + int weight = -100; + ClickHouseProtocol protocol = ClickHouseProtocol.HTTP; + ClickHouseCredentials credentials = ClickHouseCredentials.fromUserAndPassword("user", "passwd"); + String[] tags = new String[] { "dc1", "rack1", "server1", "id1" }; + + ClickHouseNode node = ClickHouseNode.builder().cluster(cluster).host(host).port(protocol, port).weight(weight) + .database(database).credentials(credentials).tags(Arrays.asList(tags)).build(); + checkCustomValues(node, cluster, host, port, weight, protocol, database, credentials, tags); + } + + @Test(groups = { "unit" }) + public void testBuildWithNode() { + String cluster = "my_cluster"; + String database = "my_db"; + String host = "non-existing.host"; + int port = 38123; + int weight = -100; + ClickHouseProtocol protocol = ClickHouseProtocol.HTTP; + ClickHouseCredentials credentials = ClickHouseCredentials.fromUserAndPassword("user", "passwd"); + String[] tags = new String[] { "dc1", "rack1", "server1", "id1" }; + + ClickHouseNode base = ClickHouseNode.builder().cluster(cluster).host(host).port(protocol, port).weight(weight) + .database(database).credentials(credentials).tags(null, tags).build(); + ClickHouseNode node = ClickHouseNode.builder(base).build(); + checkCustomValues(node, cluster, host, port, weight, protocol, database, credentials, tags); + + node = ClickHouseNode.builder(base).cluster(null).host(null).port(null, null).weight(null).database(null) + .credentials(null).tags(null, (String[]) null).build(); + checkDefaultValues(node); + } + + @Test(groups = { "unit" }) + public void testBuildInOneGo() { + String host = "non-existing.host"; + String database = "my_db"; + ClickHouseProtocol protocol = ClickHouseProtocol.NATIVE; + int port = 19000; + ClickHouseNode node = ClickHouseNode.of(host, protocol, port, database); + checkCustomValues(node, (String) ClickHouseDefaults.CLUSTER.getEffectiveDefaultValue(), host, port, + (int) ClickHouseDefaults.WEIGHT.getEffectiveDefaultValue(), protocol, database, null, new String[0]); + + protocol = ClickHouseProtocol.GRPC; + node = ClickHouseNode.of(host, protocol, port, database, "read-only", "primary"); + checkCustomValues(node, (String) ClickHouseDefaults.CLUSTER.getEffectiveDefaultValue(), host, port, + (int) ClickHouseDefaults.WEIGHT.getEffectiveDefaultValue(), protocol, database, null, + new String[] { "read-only", "primary" }); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseParameterizedQueryTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseParameterizedQueryTest.java new file mode 100644 index 000000000..1288495ca --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseParameterizedQueryTest.java @@ -0,0 +1,185 @@ +package com.clickhouse.client; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +import com.clickhouse.client.data.ClickHouseDateTimeValue; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class ClickHouseParameterizedQueryTest { + @DataProvider(name = "queryWithoutParameterProvider") + private Object[][] getQueriesWithoutAnyParameter() { + return new Object[][] { { "1" }, { "select 1" }, { "select 1::Float32" } }; + } + + @Test(groups = { "unit" }) + public void testApplyCollection() { + String query = "select :param1::String"; + ClickHouseParameterizedQuery q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((Collection) null), "select NULL::String"); + Assert.assertEquals(q.apply(Collections.emptyList()), "select NULL::String"); + Assert.assertEquals(q.apply(Collections.emptySet()), "select NULL::String"); + Assert.assertEquals(q.apply(Arrays.asList(new String[] { "first", "last" })), "select first::String"); + + query = "select :param1::String,:param2 + 1 as result"; + q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((Collection) null), "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Collections.emptyList()), "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Collections.emptySet()), "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Arrays.asList(new String[] { "first" })), + "select first::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Arrays.asList(new String[] { "first", "last" })), + "select first::String,last + 1 as result"); + Assert.assertEquals(q.apply(Arrays.asList(new String[] { "first", "last", "more" })), + "select first::String,last + 1 as result"); + } + + @Test(groups = { "unit" }) + public void testApplyObjects() { + String query = "select :param1::String"; + ClickHouseParameterizedQuery q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((Object) null), "select NULL::String"); + Assert.assertEquals(q.apply((Object) null, (Object) null), "select NULL::String"); + Assert.assertEquals(q.apply('a'), "select 97::String"); + Assert.assertEquals(q.apply(1, (Object) null), "select 1::String"); + Assert.assertEquals(q.apply(Collections.singletonList('a')), "select (97)::String"); + Assert.assertEquals(q.apply(Arrays.asList(1, null)), "select (1,NULL)::String"); + + query = "select :param1::String,:param2 + 1 as result"; + q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((Object) null), "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply((Object) null, (Object) null), "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply('a'), "select 97::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(1, (Object) null), "select 1::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(ClickHouseDateTimeValue.ofNull(3).update(1), (Object) null), + "select '1970-01-01 00:00:00.001'::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Collections.singletonList('a')), "select (97)::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Arrays.asList(1, null)), "select (1,NULL)::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Arrays.asList(ClickHouseDateTimeValue.ofNull(3).update(1), null)), + "select ('1970-01-01 00:00:00.001',NULL)::String,NULL + 1 as result"); + } + + @Test(groups = { "unit" }) + public void testApplyMap() { + String query = "select :param1::String"; + ClickHouseParameterizedQuery q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((Map) null), "select NULL::String"); + Assert.assertEquals(q.apply(Collections.emptyMap()), "select NULL::String"); + Assert.assertEquals(q.apply(Collections.singletonMap("key", "value")), "select NULL::String"); + Assert.assertEquals(q.apply(Collections.singletonMap("param1", "value")), "select value::String"); + + query = "select :param1::String,:param2 + 1 as result"; + q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((Map) null), "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Collections.emptyMap()), "select NULL::String,NULL + 1 as result"); + Map map = new HashMap<>(); + map.put("param2", "v2"); + map.put("param1", "v1"); + map.put("param3", "v3"); + Assert.assertEquals(q.apply(Collections.singletonMap("key", "value")), + "select NULL::String,NULL + 1 as result"); + Assert.assertEquals(q.apply(Collections.singletonMap("param2", "value")), + "select NULL::String,value + 1 as result"); + Assert.assertEquals(q.apply(map), "select v1::String,v2 + 1 as result"); + } + + @Test(groups = { "unit" }) + public void testApplyStrings() { + String query = "select :param1::String"; + ClickHouseParameterizedQuery q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((String) null), "select null::String"); + Assert.assertEquals(q.apply((String) null, (String) null), "select null::String"); + Assert.assertEquals(q.apply("'a'"), "select 'a'::String"); + Assert.assertEquals(q.apply("1", (String) null), "select 1::String"); + + query = "select :param1::String,:param2 + 1 as result"; + q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertEquals(q.apply((String) null), "select null::String,NULL + 1 as result"); + Assert.assertEquals(q.apply((String) null, (String) null), "select null::String,null + 1 as result"); + Assert.assertEquals(q.apply("'a'"), "select 'a'::String,NULL + 1 as result"); + Assert.assertEquals(q.apply("1", (String) null), "select 1::String,null + 1 as result"); + } + + @Test(groups = { "unit" }) + public void testInvalidQuery() { + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseParameterizedQuery.of(null)); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseParameterizedQuery.of("")); + } + + @Test(dataProvider = "queryWithoutParameterProvider", groups = { "unit" }) + public void testQueryWithoutAnyParameter(String query) { + ClickHouseParameterizedQuery q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertTrue(q.apply((String) null) == query); + Assert.assertTrue(q.apply((Object) null) == query); + Assert.assertTrue(q.apply((Collection) null) == query); + Assert.assertTrue(q.apply(Collections.emptyList()) == query); + Assert.assertTrue(q.apply(Collections.emptySet()) == query); + Assert.assertTrue(q.apply((Enumeration) null) == query); + Assert.assertTrue(q.apply(new Enumeration() { + @Override + public boolean hasMoreElements() { + return false; + } + + @Override + public String nextElement() { + throw new NoSuchElementException(); + } + }) == query); + Assert.assertTrue(q.apply((Map) null) == query); + Assert.assertTrue(q.apply(Collections.emptyMap()) == query); + Assert.assertTrue(q.apply("test") == query); + Assert.assertTrue(q.apply("test1", "test2") == query); + Assert.assertFalse(q.hasParameter()); + Assert.assertTrue((Object) q.getNamedParameters() == Collections.emptyList()); + Assert.assertEquals(q.getQueryParts().toArray(new String[0][]), + new String[][] { new String[] { query, null } }); + } + + @Test(groups = { "unit" }) + public void testQueryWithParameters() { + String query = "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (:no ) and value = :v(String)"; + ClickHouseParameterizedQuery q = ClickHouseParameterizedQuery.of(query); + Assert.assertTrue(q.getOriginalQuery() == query); + Assert.assertTrue(q.hasParameter()); + Assert.assertEquals(q.getQueryParts().toArray(new String[0][]), new String[][] { new String[] { + "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (", + "no" }, new String[] { " ) and value = ", "v" } }); + Assert.assertEquals(q.apply((String) null), + "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (null ) and value = NULL"); + Assert.assertEquals(q.apply((String) null, (String) null, (String) null), + "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (null ) and value = null"); + Assert.assertEquals(q.apply("1", "2", "3"), + "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (1 ) and value = 2"); + Assert.assertEquals(q.apply("''", "'\\''", "233"), + "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in ('' ) and value = '\\''"); + } + + @Test(groups = { "unit" }) + public void testApplyNamedParameters() { + String sql = "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (:no ) and value = :v(String)"; + Map params = new HashMap<>(); + params.put("no", "1,2,3"); + params.put("v", "'s t r'"); + + Assert.assertEquals(ClickHouseParameterizedQuery.apply(sql, params), + "select 2>1?3:2, name, value, value::Decimal64(3) from my_table where value != ':ccc' and num in (1,2,3 ) and value = 's t r'"); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseRequestTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseRequestTest.java new file mode 100644 index 000000000..09ec92388 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseRequestTest.java @@ -0,0 +1,179 @@ +package com.clickhouse.client; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.ClickHouseRequest.Mutation; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.config.ClickHouseDefaults; +import com.clickhouse.client.data.ClickHouseBigIntegerValue; +import com.clickhouse.client.data.ClickHouseByteValue; +import com.clickhouse.client.data.ClickHouseDateTimeValue; +import com.clickhouse.client.data.ClickHouseFloatValue; +import com.clickhouse.client.data.ClickHouseIntegerValue; +import com.clickhouse.client.data.ClickHouseStringValue; + +public class ClickHouseRequestTest { + @Test(groups = { "unit" }) + public void testBuild() { + ClickHouseRequest request = ClickHouseClient.newInstance().connect(ClickHouseNode.builder().build()); + Assert.assertNotNull(request); + + ClickHouseConfig config = request.getConfig(); + List stmts = request.getStatements(); + Assert.assertEquals(config, request.getConfig()); + Assert.assertEquals(stmts, request.getStatements()); + Assert.assertEquals(stmts.size(), 0); + + String db = "db"; + String table = "test"; + String sql = "select 1"; + + request.table(table); + // Assert.assertNotEquals(config, request.getConfig()); + Assert.assertNotEquals(stmts, request.getStatements()); + Assert.assertEquals(request.getStatements().size(), 1); + Assert.assertEquals(request.getStatements().get(0), "SELECT * FROM " + table); + + request.query(sql); + Assert.assertEquals(request.getStatements().get(0), sql); + + request.use(db); + Assert.assertEquals(request.getConfig().getDatabase(), db); + Assert.assertEquals(request.getStatements().size(), 1); + Assert.assertEquals(request.getStatements().get(0), sql); + + Mutation m = request.write(); + Assert.assertEquals(m.getConfig().getDatabase(), db); + Assert.assertEquals(m.getStatements().size(), 0); + + m.removeOption(ClickHouseClientOption.DATABASE).table(table); + Assert.assertEquals(m.getStatements().size(), 1); + Assert.assertEquals(m.getStatements().get(0), "INSERT INTO " + table); + + m.query(sql = "delete from test where id = 1"); + Assert.assertEquals(m.getStatements().size(), 1); + Assert.assertEquals(m.getStatements().get(0), sql); + } + + @Test(groups = { "unit" }) + public void testFormat() { + ClickHouseRequest request = ClickHouseClient.newInstance().connect(ClickHouseNode.builder().build()); + Assert.assertEquals(request.getFormat(), + ClickHouseFormat.valueOf((String) ClickHouseDefaults.FORMAT.getEffectiveDefaultValue())); + Assert.assertThrows(IllegalArgumentException.class, () -> request.format(null)); + Assert.assertEquals(request.getFormat(), + ClickHouseFormat.valueOf((String) ClickHouseDefaults.FORMAT.getEffectiveDefaultValue())); + request.format(ClickHouseFormat.ArrowStream); + Assert.assertEquals(request.getFormat(), ClickHouseFormat.ArrowStream); + request.format(ClickHouseFormat.Arrow); + Assert.assertEquals(request.getFormat(), ClickHouseFormat.Arrow); + } + + @Test(groups = { "unit" }) + public void testParams() { + String sql = "select :one as one, :two as two, * from my_table where key=:key and arr[:idx] in numbers(:range)"; + ClickHouseRequest request = ClickHouseClient.newInstance().connect(ClickHouseNode.builder().build()) + .query(sql); + Assert.assertEquals(request.getQuery(), sql); + request.params(ClickHouseByteValue.of(Byte.MIN_VALUE)); + Assert.assertEquals(request.getQuery(), sql); + Assert.assertEquals(request.getStatements(false).size(), 1); + Assert.assertEquals(request.getStatements(false).get(0), + "select -128 as one, NULL as two, * from my_table where key=NULL and arr[NULL] in numbers(NULL)"); + + request.params(ClickHouseStringValue.of(""), ClickHouseDateTimeValue.of("2012-12-12 12:23:34.56789", 2), + ClickHouseStringValue.of("key"), ClickHouseIntegerValue.of(1), + ClickHouseBigIntegerValue.of(BigInteger.TEN)); + Assert.assertEquals(request.getQuery(), sql); + Assert.assertEquals(request.getStatements(false).size(), 1); + Assert.assertEquals(request.getStatements(false).get(0), + "select '' as one, '2012-12-12 12:23:34.56789' as two, * from my_table where key='key' and arr[1] in numbers(10)"); + + Map params = new HashMap<>(); + params.put("one", ClickHouseFloatValue.of(1.0F).toSqlExpression()); + request.params(params); + Assert.assertEquals(request.getQuery(), sql); + Assert.assertEquals(request.getStatements(false).size(), 1); + Assert.assertEquals(request.getStatements(false).get(0), + "select 1.0 as one, NULL as two, * from my_table where key=NULL and arr[NULL] in numbers(NULL)"); + + params.put("one", ClickHouseStringValue.of("").toSqlExpression()); + params.put("two", ClickHouseDateTimeValue.of("2012-12-12 12:23:34.56789", 2).toSqlExpression()); + params.put("key", ClickHouseStringValue.of("key").toSqlExpression()); + params.put("some", ClickHouseBigIntegerValue.of(BigInteger.ONE).toSqlExpression()); + params.put("idx", ClickHouseIntegerValue.of(1).toSqlExpression()); + params.put("range", ClickHouseBigIntegerValue.of(BigInteger.TEN).toSqlExpression()); + request.params(params); + Assert.assertEquals(request.getQuery(), sql); + Assert.assertEquals(request.getStatements(false).size(), 1); + Assert.assertEquals(request.getStatements(false).get(0), + "select '' as one, '2012-12-12 12:23:34.56789' as two, * from my_table where key='key' and arr[1] in numbers(10)"); + } + + @Test(groups = { "unit" }) + public void testSession() { + String sessionId = UUID.randomUUID().toString(); + ClickHouseRequest request = ClickHouseClient.newInstance().connect(ClickHouseNode.builder().build()); + Assert.assertEquals(request.getSessionId().isPresent(), false); + Assert.assertEquals(request.sessionId, null); + Assert.assertEquals(request.getConfig().isSessionCheck(), false); + Assert.assertEquals(request.getConfig().getSessionTimeout(), 0); + + request.session(sessionId, true, 10); + Assert.assertEquals(request.getSessionId().get(), sessionId); + Assert.assertEquals(request.getConfig().isSessionCheck(), true); + Assert.assertEquals(request.getConfig().getSessionTimeout(), 10); + + ClickHouseRequest sealedRequest = request.query("select 1").seal(); + Assert.assertEquals(sealedRequest.getSessionId().get(), sessionId); + Assert.assertEquals(sealedRequest.getConfig().isSessionCheck(), true); + Assert.assertEquals(sealedRequest.getConfig().getSessionTimeout(), 10); + + sealedRequest = request.query("select 2").seal(); + Assert.assertEquals(sealedRequest.getSessionId().get(), sessionId); + Assert.assertEquals(sealedRequest.getConfig().isSessionCheck(), true); + Assert.assertEquals(sealedRequest.getConfig().getSessionTimeout(), 10); + + request.query("select 3").clearSession(); + Assert.assertEquals(sealedRequest.getSessionId().get(), sessionId); + Assert.assertEquals(sealedRequest.getConfig().isSessionCheck(), true); + Assert.assertEquals(sealedRequest.getConfig().getSessionTimeout(), 10); + Assert.assertEquals(request.getSessionId().isPresent(), false); + Assert.assertEquals(request.sessionId, null); + Assert.assertEquals(request.getConfig().isSessionCheck(), false); + Assert.assertEquals(request.getConfig().getSessionTimeout(), 0); + } + + @Test(groups = { "unit" }) + public void testSettings() { + ClickHouseRequest request = ClickHouseClient.newInstance().connect(ClickHouseNode.builder().build()); + Assert.assertEquals(request.getStatements().size(), 0); + request.set("enable_optimize_predicate_expression", 1); + Assert.assertEquals(request.getStatements().size(), 1); + Assert.assertEquals(request.getStatements().get(0), "SET enable_optimize_predicate_expression=1"); + request.set("log_queries_min_type", "EXCEPTION_WHILE_PROCESSING"); + Assert.assertEquals(request.getStatements().size(), 2); + Assert.assertEquals(request.getStatements().get(1), "SET log_queries_min_type='EXCEPTION_WHILE_PROCESSING'"); + } + + @Test(groups = { "unit" }) + public void testMutation() { + Mutation request = ClickHouseClient.newInstance().connect(ClickHouseNode.builder().build()).write(); + request.table("test_table").format(ClickHouseFormat.Arrow).data(new ByteArrayInputStream(new byte[0])); + + String expectedSql = "INSERT INTO test_table FORMAT Arrow"; + Assert.assertEquals(request.getQuery(), expectedSql); + Assert.assertEquals(request.getStatements().get(0), expectedSql); + + request = request.seal(); + Assert.assertEquals(request.getQuery(), expectedSql); + Assert.assertEquals(request.getStatements().get(0), expectedSql); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java new file mode 100644 index 000000000..89a4a9c6c --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java @@ -0,0 +1,186 @@ +package com.clickhouse.client; + +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.Properties; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testng.annotations.AfterSuite; +import org.testng.annotations.BeforeSuite; + +/** + * Adaptive ClickHouse server environment for integration test. Two modes are + * supported: 1) existing server(when system property {@code clickhouseServer} + * is defined); and 2) test container. + * + *

+ * Below system properties can be used for customization: + *

    + *
  • clickhouseServer - host of clickhouse server
  • + *
  • clickhouse<Protocol>Port - port of specific protocol, for example: + * {@code clickhouseGRPCPort}
  • + *
  • clickhouseImage - custom docker image, with or without tag and/or image + * digest
  • + *
  • clickhouseVersion - version of clickhouse, could be replaced by the one + * used in {@code clickhouseImage}
  • + *
  • clickhouseTimezone - server timezone
  • + *
  • additionalPackages - additional system packages should be installed in + * container mode
  • + *
+ */ +public class ClickHouseServerForTest { + private static final Properties properties; + + private static final String clickhouseServer; + private static final String clickhouseVersion; + private static final GenericContainer clickhouseContainer; + + static { + properties = new Properties(); + try (InputStream in = ClickHouseUtils.getFileInputStream("test.properties")) { + properties.load(in); + } catch (Exception e) { + // ignore + } + + String host = ClickHouseUtils.getProperty("clickhouseServer", properties); + clickhouseServer = ClickHouseChecker.isNullOrEmpty(host) ? null : host; + + String imageTag = ClickHouseUtils.getProperty("clickhouseVersion", properties); + + if (clickhouseServer != null) { // use external server + clickhouseVersion = ClickHouseChecker.isNullOrEmpty(imageTag) + || ClickHouseVersion.of(imageTag).getYear() == 0 ? "" : imageTag; + clickhouseContainer = null; + } else { // use test container + String timezone = ClickHouseUtils.getProperty("clickhouseTimezone", properties); + if (ClickHouseChecker.isNullOrEmpty(timezone)) { + timezone = "UTC"; + } + + String imageName = ClickHouseUtils.getProperty("clickhouseImage", properties); + if (ClickHouseChecker.isNullOrEmpty(imageName)) { + imageName = "clickhouse/clickhouse-server"; + } + + int tagIndex = imageName.indexOf(':'); + int digestIndex = imageName.indexOf('@'); + if (tagIndex > 0) { + imageTag = ""; + clickhouseVersion = digestIndex > 0 ? imageName.substring(tagIndex + 1, digestIndex) + : imageName.substring(tagIndex + 1); + } else if (digestIndex > 0 || ClickHouseChecker.isNullOrEmpty(imageTag)) { + clickhouseVersion = imageTag = ""; + } else { + if (ClickHouseVersion.of(imageTag).getYear() == 0) { + clickhouseVersion = ""; + } else { + clickhouseVersion = imageTag; + } + imageTag = ":" + imageTag; + } + + String imageNameWithTag = imageName + imageTag; + String additionalPackages = ClickHouseUtils.getProperty("additionalPackages", properties); + + clickhouseContainer = (ClickHouseChecker.isNullOrEmpty(additionalPackages) + ? new GenericContainer<>(imageNameWithTag) + : new GenericContainer<>(new ImageFromDockerfile().withDockerfileFromBuilder(builder -> builder + .from(imageNameWithTag).run("apt-get update && apt-get install " + additionalPackages)))) + .withEnv("TZ", timezone) + .withExposedPorts(ClickHouseProtocol.GRPC.getDefaultPort(), + ClickHouseProtocol.HTTP.getDefaultPort(), + ClickHouseProtocol.MYSQL.getDefaultPort(), + ClickHouseProtocol.NATIVE.getDefaultPort(), + ClickHouseProtocol.POSTGRESQL.getDefaultPort()) + .withClasspathResourceMapping("containers/clickhouse-server/config.d", + "/etc/clickhouse-server/config.d", BindMode.READ_ONLY) + .withClasspathResourceMapping("containers/clickhouse-server/users.d", + "/etc/clickhouse-server/users.d", BindMode.READ_ONLY) + .waitingFor(Wait.forHttp("/ping").forPort(ClickHouseProtocol.HTTP.getDefaultPort()) + .forStatusCode(200).withStartupTimeout(Duration.of(60, SECONDS))); + } + } + + public static String getClickHouseVersion() { + return clickhouseVersion; + } + + public static GenericContainer getClickHouseContainer() { + return clickhouseContainer; + } + + public static String getClickHouseAddress() { + return getClickHouseAddress(ClickHouseProtocol.ANY, false); + } + + public static String getClickHouseAddress(ClickHouseProtocol protocol, boolean useIPaddress) { + StringBuilder builder = new StringBuilder(); + + if (clickhouseContainer != null) { + builder.append(useIPaddress ? clickhouseContainer.getContainerIpAddress() : clickhouseContainer.getHost()) + .append(':').append(clickhouseContainer.getMappedPort(protocol.getDefaultPort())); + } else { + String port = ClickHouseUtils + .getProperty(ClickHouseUtils.format("clickhouse%SPort", protocol.name(), properties)); + if (ClickHouseChecker.isNullOrEmpty(port)) { + port = String.valueOf(protocol.getDefaultPort()); + } + builder.append(clickhouseServer).append(':').append(port); + } + + return builder.toString(); + } + + public static ClickHouseNode getClickHouseNode(ClickHouseProtocol protocol, ClickHouseNode template) { + String host = clickhouseServer; + int port = protocol.getDefaultPort(); + + if (clickhouseContainer != null) { + host = clickhouseContainer.getContainerIpAddress(); + port = clickhouseContainer.getMappedPort(port); + } else { + String config = ClickHouseUtils + .getProperty(ClickHouseUtils.format("clickhouse%SPort", protocol.name(), properties)); + if (config != null && !config.isEmpty()) { + port = Integer.parseInt(config); + } + } + + return ClickHouseNode.builder(template).address(protocol, new InetSocketAddress(host, port)).build(); + } + + public static ClickHouseNode getClickHouseNode(ClickHouseProtocol protocol, int port) { + String host = clickhouseServer; + + if (clickhouseContainer != null) { + host = clickhouseContainer.getContainerIpAddress(); + port = clickhouseContainer.getMappedPort(port); + } + + return ClickHouseNode.builder().address(protocol, new InetSocketAddress(host, port)).build(); + } + + @BeforeSuite(groups = { "integration" }) + public static void beforeSuite() { + if (clickhouseContainer != null) { + if (clickhouseContainer.isRunning()) { + return; + } + + clickhouseContainer.start(); + } + } + + @AfterSuite(groups = { "integration" }) + public static void afterSuite() { + if (clickhouseContainer != null) { + clickhouseContainer.stop(); + } + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseTestClient.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseTestClient.java new file mode 100644 index 000000000..bc003df86 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseTestClient.java @@ -0,0 +1,35 @@ +package com.clickhouse.client; + +import java.util.concurrent.CompletableFuture; +import com.clickhouse.client.exception.ClickHouseException; + +public class ClickHouseTestClient implements ClickHouseClient { + private ClickHouseConfig clientConfig; + + @Override + public boolean accept(ClickHouseProtocol protocol) { + return true; + } + + @Override + public CompletableFuture execute(ClickHouseRequest request) throws ClickHouseException { + return CompletableFuture.supplyAsync(() -> null); + } + + @Override + public ClickHouseConfig getConfig() { + return this.clientConfig; + } + + @Override + public void init(ClickHouseConfig config) { + ClickHouseClient.super.init(config); + + this.clientConfig = config; + } + + @Override + public void close() { + this.clientConfig = null; + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseUtilsTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseUtilsTest.java new file mode 100644 index 000000000..42463fd63 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseUtilsTest.java @@ -0,0 +1,204 @@ +package com.clickhouse.client; + +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseUtilsTest { + @Test(groups = { "unit" }) + public void testEscape() { + Assert.assertEquals(ClickHouseUtils.escape(null, '\0'), null); + Assert.assertEquals(ClickHouseUtils.escape("", '\''), ""); + Assert.assertEquals(ClickHouseUtils.escape("'", '\''), "\\'"); + Assert.assertEquals(ClickHouseUtils.escape("\\", '\0'), "\\\\"); + Assert.assertEquals(ClickHouseUtils.escape("\\'", '\0'), "\\\\'"); + Assert.assertEquals(ClickHouseUtils.escape("\\'", '\''), "\\\\\\'"); + } + + @Test(groups = { "unit" }) + public void testGetService() { + ClickHouseClient client = null; + try { + client = ClickHouseUtils.getService(ClickHouseClient.class, null); + } catch (Exception e) { + Assert.fail("Should not fail"); + } + + Assert.assertNotNull(client); + + ClickHouseUtilsTest me = null; + try { + me = ClickHouseUtils.getService(ClickHouseUtilsTest.class, null); + Assert.fail("Should fail"); + } catch (Exception e) { + Assert.assertNotNull(e); + } + Assert.assertNull(me); + + try { + me = ClickHouseUtils.getService(ClickHouseUtilsTest.class, new ClickHouseUtilsTest()); + } catch (Exception e) { + Assert.fail("Should not fail"); + } + Assert.assertNotNull(me); + + me = null; + try { + me = ClickHouseUtils.getService(ClickHouseUtilsTest.class, () -> new ClickHouseUtilsTest()); + } catch (Exception e) { + Assert.fail("Should not fail"); + } + Assert.assertNotNull(me); + } + + @Test(groups = { "unit" }) + public void testSkipBrackets() { + final String args = "select * except(`a({[]})a`('bbb')[1]) from table"; + Assert.assertEquals(ClickHouseUtils.skipBrackets(args, args.indexOf('('), args.length(), '('), + args.lastIndexOf(')') + 1); + Assert.assertEquals(ClickHouseUtils.skipBrackets(args, args.indexOf('('), args.length(), '('), + args.lastIndexOf(')') + 1); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipBrackets(args, 0, args.length(), '(')); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipBrackets(args, 0, args.length(), '[')); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipBrackets(args, 0, args.length(), '{')); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipBrackets(args, 0, args.length(), '/')); + + String newArgs = ")"; + Assert.assertEquals(ClickHouseUtils.skipBrackets(newArgs, 0, newArgs.length(), '('), newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipBrackets(newArgs = "))", 0, newArgs.length(), '('), + newArgs.lastIndexOf(')')); + Assert.assertEquals(ClickHouseUtils.skipBrackets(newArgs = ")]", 0, newArgs.length(), '['), newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipBrackets(newArgs = "{}", 0, newArgs.length(), '{'), newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipBrackets(newArgs = "{ '''\\'}'}", 0, newArgs.length(), '{'), + newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipBrackets(newArgs = "{ -- }\n/*/*}*/*/}", 0, newArgs.length(), '{'), + newArgs.length()); + } + + @Test(groups = { "unit" }) + public void testSkipQuotedString() { + final String args = "1'2'"; + Assert.assertEquals(ClickHouseUtils.skipQuotedString(args, 0, args.length(), '\''), 2); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(args, 2, args.length(), '\''), args.length()); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipQuotedString(args, 0, args.length(), '`')); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipQuotedString(args, 0, args.length(), '"')); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(args, 2, args.length(), '\''), args.length()); + + String newArgs = "''''"; + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs, 0, newArgs.length(), '\''), newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs = "''''''", 0, newArgs.length(), '\''), + newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs = "'\\''''", 0, newArgs.length(), '\''), + newArgs.length()); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseUtils.skipQuotedString("", 0, 0, '\'')); + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseUtils.skipQuotedString("'", 0, 1, '\'')); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs = "''", 0, newArgs.length(), '\''), + newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs = " '''\\'}'", 0, newArgs.length(), '\''), + newArgs.indexOf('\\')); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs, 1, newArgs.length(), '\''), newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs, 1, newArgs.length(), '\''), newArgs.length()); + Assert.assertEquals(ClickHouseUtils.skipQuotedString(newArgs = "'\\\\'''", 0, newArgs.length(), '\''), + newArgs.length()); + } + + @Test(groups = { "unit" }) + public void testSkipSingleLineComment() { + String args = "select 1 -- select one\n union all select 2 -- select two--"; + Assert.assertEquals(ClickHouseUtils.skipSingleLineComment(args, 11, args.length()), args.indexOf('\n') + 1); + Assert.assertEquals(ClickHouseUtils.skipSingleLineComment(args, args.indexOf("--", 11), args.length()), + args.length()); + } + + @Test(groups = { "unit" }) + public void testSkipMultipleLineComment() { + String args = "select 1 /* select 1/*one*/ -- a */, 2"; + Assert.assertEquals(ClickHouseUtils.skipMultiLineComment(args, 11, args.length()), args.lastIndexOf("*/") + 2); + Assert.assertEquals(ClickHouseUtils.skipMultiLineComment(args, 21, args.length()), args.indexOf("*/", 21) + 2); + Assert.assertThrows(IllegalArgumentException.class, + () -> ClickHouseUtils.skipMultiLineComment(args, args.lastIndexOf("*/") + 1, args.length())); + } + + @Test(groups = { "unit" }) + public void testSkipContentsUntil() { + String args = "select 'a' as `--b`,1/*('1(/*'*/(*/ from number(10)"; + Assert.assertEquals(ClickHouseUtils.skipContentsUntil(args, 0, args.length(), '('), args.lastIndexOf('(') + 1); + + args = "column1 AggregateFunction(quantiles(0.5, 0.9), UInt64),\ncolumn2 UInt8 not null"; + Assert.assertEquals(ClickHouseUtils.skipContentsUntil(args, 0, args.length(), ','), args.lastIndexOf(',') + 1); + Assert.assertEquals(ClickHouseUtils.skipContentsUntil(args, 0, args.length(), '@'), args.length()); + } + + @Test(groups = { "unit" }) + public void testReadNameOrQuotedString() { + String args = "123"; + StringBuilder builder = new StringBuilder(); + Assert.assertEquals(ClickHouseUtils.readNameOrQuotedString(args, 0, args.length(), builder), args.length()); + Assert.assertEquals(builder.toString(), args); + + builder.setLength(0); + Assert.assertEquals(ClickHouseUtils.readNameOrQuotedString(args = " 123", 1, args.length(), builder), + args.length()); + Assert.assertEquals(builder.toString(), "123"); + + builder.setLength(0); + Assert.assertEquals(ClickHouseUtils.readNameOrQuotedString(args = " `1\"'2``3` ", 1, args.length(), builder), + args.lastIndexOf('`') + 1); + Assert.assertEquals(builder.toString(), "1\"'2`3"); + } + + @Test(groups = { "unit" }) + public void testReadEnumValues() { + String args = "Enum( ' `''1\" ' = 1, '\\''=2 )"; + Map values = new HashMap<>(); + Assert.assertEquals(ClickHouseUtils.readEnumValues(args, 5, args.length(), values), args.lastIndexOf(')') + 1); + Assert.assertEquals(values.size(), 2); + Assert.assertEquals(values.get(" `'1\" "), (Integer) 1); + Assert.assertEquals(values.get("'"), (Integer) 2); + + values.clear(); + Assert.assertThrows(IllegalArgumentException.class, () -> { + String columns = "Enum () "; + ClickHouseUtils.readEnumValues(columns, 6, columns.length(), values); + }); + + values.clear(); + Assert.assertThrows(IllegalArgumentException.class, () -> { + String columns = "Enum ( 1 = '3' )"; + ClickHouseUtils.readEnumValues(columns, 6, columns.length(), values); + }); + + args = "a Enum('1)'=2), b UInt8"; + values.clear(); + Assert.assertEquals(ClickHouseUtils.readEnumValues(args, 7, args.length(), values), args.lastIndexOf(',')); + Assert.assertEquals(values.size(), 1); + Assert.assertEquals(values.get("1)"), (Integer) 2); + } + + @Test(groups = { "unit" }) + public void testReadParameters() { + String args = "column1 AggregateFunction( quantiles(0.5, 'c \\'''([1],2) d',0.9) , UInt64),\ncolumn2 UInt8 not null"; + List params = new LinkedList<>(); + Assert.assertEquals(ClickHouseUtils.readParameters(args, args.indexOf('('), args.length(), params), + args.lastIndexOf(')') + 1); + Assert.assertEquals(params.size(), 2); + } + + @Test(groups = { "unit" }) + public void testFileInputStream() { + Assert.assertThrows(FileNotFoundException.class, + () -> ClickHouseUtils.getFileInputStream(UUID.randomUUID().toString())); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseValuesTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseValuesTest.java new file mode 100644 index 000000000..1c9606e82 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseValuesTest.java @@ -0,0 +1,146 @@ +package com.clickhouse.client; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.UUID; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseValuesTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testConvertToDateTime() { + Assert.assertEquals(ClickHouseValues.convertToDateTime(null), null); + Assert.assertEquals(ClickHouseValues.convertToDateTime(BigDecimal.valueOf(0L)), + LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC)); + Assert.assertEquals(ClickHouseValues.convertToDateTime(BigDecimal.valueOf(1L)), + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC)); + for (int i = 1; i < 9; i++) { + BigDecimal d = BigDecimal.TEN.pow(i); + Assert.assertEquals( + ClickHouseValues.convertToDateTime(BigDecimal.valueOf(1L).add(BigDecimal.valueOf(1L).divide(d))), + LocalDateTime.ofEpochSecond(1L, BigDecimal.TEN.pow(9 - i).intValueExact(), ZoneOffset.UTC)); + } + + Assert.assertEquals(ClickHouseValues.convertToDateTime(BigDecimal.valueOf(-1L)), + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC)); + for (int i = 1; i < 9; i++) { + BigDecimal d = BigDecimal.TEN.pow(i); + Assert.assertEquals( + ClickHouseValues.convertToDateTime(BigDecimal.valueOf(-1L).add(BigDecimal.valueOf(-1L).divide(d))), + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC) + .minus(BigDecimal.TEN.pow(9 - i).longValueExact(), ChronoUnit.NANOS)); + } + } + + @Test(groups = { "unit" }) + public void testConvertToSqlExpression() throws UnknownHostException { + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(null), ClickHouseValues.NULL_EXPR); + + // primitives + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(true), String.valueOf(1)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(false), String.valueOf(0)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression('\0'), String.valueOf(0)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression('a'), String.valueOf(97)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Byte.MAX_VALUE), String.valueOf(Byte.MAX_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Byte.MIN_VALUE), String.valueOf(Byte.MIN_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Short.MAX_VALUE), String.valueOf(Short.MAX_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Short.MIN_VALUE), String.valueOf(Short.MIN_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Integer.MAX_VALUE), + String.valueOf(Integer.MAX_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Integer.MIN_VALUE), + String.valueOf(Integer.MIN_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Long.MAX_VALUE), String.valueOf(Long.MAX_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Long.MIN_VALUE), String.valueOf(Long.MIN_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Float.MAX_VALUE), String.valueOf(Float.MAX_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Float.MIN_VALUE), String.valueOf(Float.MIN_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Double.MAX_VALUE), + String.valueOf(Double.MAX_VALUE)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Double.MIN_VALUE), + String.valueOf(Double.MIN_VALUE)); + + // stringlike types + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(""), String.valueOf("''")); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression("萌萌哒"), String.valueOf("'萌萌哒'")); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression("'萌\\'\\\'萌哒'"), + String.valueOf("'\\'萌\\\\\\'\\\\\\'萌哒\\''")); + Assert.assertEquals( + ClickHouseValues.convertToSqlExpression(UUID.fromString("00000000-0000-0000-0000-000000000000")), + "'00000000-0000-0000-0000-000000000000'"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(InetAddress.getByName("localhost")), "'127.0.0.1'"); + Assert.assertEquals( + ClickHouseValues.convertToSqlExpression((Inet4Address) InetAddress.getAllByName("127.0.0.1")[0]), + "'127.0.0.1'"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression((Inet6Address) InetAddress.getAllByName("::1")[0]), + "'0:0:0:0:0:0:0:1'"); + + // enum, big integer and decimals + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(ClickHouseDataType.DateTime64), + String.valueOf(ClickHouseDataType.DateTime64.ordinal())); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(BigInteger.valueOf(1L)), String.valueOf(1)); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(BigDecimal.valueOf(123456789.123456789D)), + "123456789.12345679"); + + // date, time and date time + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(LocalDate.of(2021, 11, 12)), "'2021-11-12'"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(LocalTime.of(11, 12, 13, 123456789)), + "'11:12:13.123456789'"); + Assert.assertEquals( + ClickHouseValues.convertToSqlExpression( + LocalDateTime.of(LocalDate.of(2021, 11, 12), LocalTime.of(11, 12, 13, 123456789))), + "'2021-11-12 11:12:13.123456789'"); + + // arrays + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new Object[0]), "[]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new boolean[] { true, false, true }), "[1,0,1]"); + Assert.assertEquals( + ClickHouseValues.convertToSqlExpression(new boolean[][] { new boolean[] { true, false, true } }), + "[[1,0,1]]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new char[] { 'a', '\0', '囧' }), "[97,0,22247]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new byte[] { (byte) 11, (byte) -12, (byte) 127 }), + "[11,-12,127]"); + Assert.assertEquals( + ClickHouseValues.convertToSqlExpression(new short[] { (short) 11, (short) -22247, (short) 25534 }), + "[11,-22247,25534]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new int[] { 233, -122247, 165535 }), + "[233,-122247,165535]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new long[] { 233333333L, -122247L, 165535L }), + "[233333333,-122247,165535]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new float[] { 2.33F, -1.22247F, 165535F }), + "[2.33,-1.22247,165535.0]"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new double[] { 2.33333D, -1.22247D, 165535D }), + "[2.33333,-1.22247,165535.0]"); + + // tuple + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new ArrayList<>(0)), "()"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(Arrays.asList('a', true, "'x'", 233)), + "(97,1,'\\'x\\'',233)"); + + // map + Assert.assertEquals(ClickHouseValues.convertToSqlExpression(new HashMap<>()), "{}"); + Assert.assertEquals(ClickHouseValues.convertToSqlExpression( + buildMap(new Integer[] { 2, 3 }, new String[] { "two", "three" })), "{2 : 'two',3 : 'three'}"); + + // mixed + Assert.assertEquals( + ClickHouseValues.convertToSqlExpression(new Object[] { true, 'a', (byte) 1, (short) 2, 3, 4L, 5.555F, + 6.666666D, "'x'", UUID.fromString("00000000-0000-0000-0000-000000000002"), + InetAddress.getByName("127.0.0.1"), InetAddress.getByName("::1"), ClickHouseDataType.Decimal256, + BigInteger.valueOf(123456789L), BigDecimal.valueOf(1.23456789D), null, + LocalDate.of(2021, 11, 12), LocalTime.of(11, 12, 13, 123456789), + LocalDateTime.of(LocalDate.of(2021, 11, 12), LocalTime.of(11, 12, 13, 123456789)), + new boolean[] { false, true } }), + "[1,97,1,2,3,4,5.555,6.666666,'\\'x\\'','00000000-0000-0000-0000-000000000002','127.0.0.1','0:0:0:0:0:0:0:1',29,123456789,1.23456789,NULL,'2021-11-12','11:12:13.123456789','2021-11-12 11:12:13.123456789',[0,1]]"); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseVersionTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseVersionTest.java new file mode 100644 index 000000000..c57c2c249 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseVersionTest.java @@ -0,0 +1,53 @@ +package com.clickhouse.client; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseVersionTest { + private void check(ClickHouseVersion v, int year, int major, int minor, int internal) { + Assert.assertNotNull(v); + Assert.assertEquals(v.getYear(), year); + Assert.assertEquals(v.getMajor(), major); + Assert.assertEquals(v.getMinor(), minor); + Assert.assertEquals(v.getInternal(), internal); + } + + @Test(groups = { "unit" }) + public void testConstructor() { + check(new ClickHouseVersion(-1, -2, -3, -4), 0, 0, 0, 0); + check(new ClickHouseVersion(0, 0, 0, 0), 0, 0, 0, 0); + check(new ClickHouseVersion(5, 4, 3, 2), 5, 4, 3, 2); + } + + @Test(groups = { "unit" }) + public void testParser() { + check(ClickHouseVersion.of(null), 0, 0, 0, 0); + check(ClickHouseVersion.of(""), 0, 0, 0, 0); + check(ClickHouseVersion.of("twenty-one.three"), 0, 0, 0, 0); + + check(ClickHouseVersion.of("a1b2"), 0, 0, 0, 0); + check(ClickHouseVersion.of("a1b 2abc"), 0, 0, 0, 0); + check(ClickHouseVersion.of("a1.2.3.4"), 0, 0, 0, 0); + check(ClickHouseVersion.of("a1b 2"), 2, 0, 0, 0); + check(ClickHouseVersion.of("a1b 2 aaa"), 2, 0, 0, 0); + check(ClickHouseVersion.of("1.2.3.4"), 1, 2, 3, 4); + check(ClickHouseVersion.of("1.2.3.4.6"), 1, 2, 3, 4); + check(ClickHouseVersion.of(" 1 . 2 . 3 . 4 . 6 "), 1, 2, 3, 4); + check(ClickHouseVersion.of("upgrade from 021.03.00.01 to 21.7.8.9"), 21, 3, 0, 1); + check(ClickHouseVersion.of("21.7..9 is supported"), 21, 7, 0, 0); + + check(ClickHouseVersion.of( + "100000000000000000.10000000000000000000000000.100000000000000000000000000000.10000000000000000000000"), + 0, 0, 0, 0); + } + + @Test(groups = { "unit" }) + public void testCompare() { + Assert.assertTrue(ClickHouseVersion.of("1.1.12345").compareTo(ClickHouseVersion.of("21.3")) < 0); + Assert.assertTrue(ClickHouseVersion.of("21.9").compareTo(ClickHouseVersion.of("19.16")) > 0); + Assert.assertTrue(ClickHouseVersion.of("021.03").compareTo(ClickHouseVersion.of("21.3.0.0")) == 0); + Assert.assertTrue(ClickHouseVersion.of(null).compareTo(ClickHouseVersion.of(" ")) == 0); + + Assert.assertThrows(NullPointerException.class, () -> ClickHouseVersion.of(null).compareTo(null)); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/config/ClickHouseConfigOptionTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/config/ClickHouseConfigOptionTest.java new file mode 100644 index 000000000..c40415554 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/config/ClickHouseConfigOptionTest.java @@ -0,0 +1,84 @@ +package com.clickhouse.client.config; + +import java.util.Optional; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.ClickHouseChecker; + +public class ClickHouseConfigOptionTest { + static enum ClickHouseTestOption implements ClickHouseConfigOption { + STR("string_option", "string", "string option"), + STR0("string_option0", "string0", "string option without environment variable support"), + STR1("string_option0", "string1", "string option without environment variable and system property support"), + INT("integer_option", 2333, "integer option"), + INT0("integer_option0", 23330, "integer option without environment variable support"), + INT1("integer_option1", 23331, "integer option without environment variable and system property support"), + BOOL("boolean_option", false, "boolean option"), + BOOL0("boolean_option0", true, "boolean option without environment variable support"), + BOOL1("boolean_option1", false, "boolean option without environment variable and system property support"); + + private final String key; + private final Object defaultValue; + private final Class clazz; + private final String description; + + ClickHouseTestOption(String key, T defaultValue, String description) { + this.key = ClickHouseChecker.nonNull(key, "key"); + this.defaultValue = Optional.of(defaultValue); + this.clazz = defaultValue.getClass(); + this.description = ClickHouseChecker.nonNull(description, "description"); + } + + @Override + public String getKey() { + return key; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public Class getValueType() { + return clazz; + } + + @Override + public String getDescription() { + return description; + } + } + + @Test(groups = { "unit" }) + public void testGetEffectiveDefaultValue() { + // environment variables are set in pom.xml + Assert.assertEquals(ClickHouseTestOption.STR.getEffectiveDefaultValue(), + ClickHouseTestOption.STR.getDefaultValueFromEnvVar().get()); + Assert.assertEquals(ClickHouseTestOption.INT.getEffectiveDefaultValue(), + Integer.parseInt(ClickHouseTestOption.INT.getDefaultValueFromEnvVar().get())); + Assert.assertEquals(ClickHouseTestOption.BOOL.getEffectiveDefaultValue(), + Boolean.valueOf(ClickHouseTestOption.BOOL.getDefaultValueFromEnvVar().get())); + + String sv = "system.property"; + int iv = 12345; + boolean bv = true; + System.setProperty(ClickHouseTestOption.STR0.getPrefix().toLowerCase() + "_" + + ClickHouseTestOption.STR0.name().toLowerCase(), sv); + System.setProperty(ClickHouseTestOption.INT0.getPrefix().toLowerCase() + "_" + + ClickHouseTestOption.INT0.name().toLowerCase(), String.valueOf(iv)); + System.setProperty(ClickHouseTestOption.BOOL0.getPrefix().toLowerCase() + "_" + + ClickHouseTestOption.BOOL0.name().toLowerCase(), String.valueOf(bv)); + + Assert.assertEquals(ClickHouseTestOption.STR0.getEffectiveDefaultValue(), sv); + Assert.assertEquals(ClickHouseTestOption.INT0.getEffectiveDefaultValue(), iv); + Assert.assertEquals(ClickHouseTestOption.BOOL0.getEffectiveDefaultValue(), bv); + + Assert.assertEquals(ClickHouseTestOption.STR1.getEffectiveDefaultValue(), + ClickHouseTestOption.STR1.getDefaultValue()); + Assert.assertEquals(ClickHouseTestOption.INT1.getEffectiveDefaultValue(), + ClickHouseTestOption.INT1.getDefaultValue()); + Assert.assertEquals(ClickHouseTestOption.BOOL1.getEffectiveDefaultValue(), + ClickHouseTestOption.BOOL1.getDefaultValue()); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/BinaryStreamUtilsTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/BinaryStreamUtilsTest.java new file mode 100644 index 000000000..4f3b87c42 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/BinaryStreamUtilsTest.java @@ -0,0 +1,1244 @@ +package com.clickhouse.client.data; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.UUID; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class BinaryStreamUtilsTest { + protected static byte[] generateBytes(int... ints) { + byte[] bytes = new byte[ints.length]; + for (int i = 0; i < ints.length; i++) { + bytes[i] = (byte) (0xFF & ints[i]); + } + return bytes; + } + + protected static InputStream generateInput(byte... values) { + return new ByteArrayInputStream(values); + } + + protected static InputStream generateInput(int... values) { + return new ByteArrayInputStream(generateBytes(values)); + } + + protected static byte[][] generateBytes(int byteLength, boolean unsigned) { + if (byteLength < 1) { + throw new IllegalArgumentException("byteLength must be greater than zero"); + } + + byte[][] bytes = new byte[byteLength + 1 + (unsigned ? 0 : 2)][byteLength]; + for (int i = 0; i <= byteLength; i++) { + byte[] arr = new byte[byteLength]; + if (i == 0) { + for (int j = 0; j < byteLength; j++) { + arr[j] = 0; + } + continue; + } + + for (int j = 0; j < i; j++) { + arr[j] = (byte) 0xFF; + } + + for (int j = i; j < byteLength; j++) { + arr[j] = 0; + } + + bytes[i] = arr; + } + + if (!unsigned) { + // min/max values + byte[] values = new byte[] { (byte) 0x80, (byte) 0x7F }; + for (int i = 0; i < values.length; i++) { + byte[] arr = new byte[byteLength]; + for (int j = 0; j < byteLength - 1; j++) { + arr[j] = (byte) 0xFF; + } + arr[byteLength - 1] = values[i]; + bytes[bytes.length - 1 - i] = arr; + } + } + + return bytes; + } + + protected static byte[] getWrittenBytes(WriterFunction writter) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + writter.write(output); + return output.toByteArray(); + } + } + + @Test(groups = { "unit" }) + public void testReverse() { + byte[] bytes = null; + Assert.assertEquals(BinaryStreamUtils.reverse(bytes), bytes); + Assert.assertEquals(BinaryStreamUtils.reverse(bytes = new byte[0]), bytes); + Assert.assertEquals(BinaryStreamUtils.reverse(bytes = new byte[] { 1 }), bytes); + Assert.assertEquals(BinaryStreamUtils.reverse(bytes = new byte[] { 1, 2 }), new byte[] { 2, 1 }); + Assert.assertEquals(BinaryStreamUtils.reverse(bytes = new byte[] { 1, 2, 3 }), new byte[] { 3, 2, 1 }); + } + + @Test(groups = { "unit" }) + public void testReadBoolean() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readBoolean(generateInput(1)), true); + Assert.assertEquals(BinaryStreamUtils.readBoolean(generateInput(0)), false); + + Assert.assertThrows(IllegalArgumentException.class, () -> BinaryStreamUtils.readBoolean(generateInput(2))); + } + + @Test(groups = { "unit" }) + public void testWriteBoolean() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeBoolean(o, true)), generateBytes(1)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeBoolean(o, false)), generateBytes(0)); + } + + @Test(groups = { "unit" }) + public void testReadByte() throws IOException { + for (int b = Byte.MIN_VALUE; b <= Byte.MAX_VALUE; b++) { + Assert.assertEquals(BinaryStreamUtils.readByte(generateInput(b)), b); + Assert.assertEquals(BinaryStreamUtils.readInt8(generateInput(b)), b); + } + } + + @Test(groups = { "unit" }) + public void testWriteByte() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, (byte) 0)), generateBytes(0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, (byte) 1)), generateBytes(1)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, (byte) -1)), generateBytes(-1)); + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, 0)), generateBytes(0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, 1)), generateBytes(1)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, -1)), generateBytes(-1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, Byte.MAX_VALUE + 1))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeInt8(o, Byte.MIN_VALUE - 1))); + } + + @Test(groups = { "unit" }) + public void testReadUnsignedByte() throws IOException { + for (int i = 0; i < 0xFF; i++) { + Assert.assertEquals(BinaryStreamUtils.readUnsignedByte(generateInput(i)), i); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt8(generateInput(i)), (short) i); + } + } + + @Test(groups = { "unit" }) + public void testWriteUnsignedByte() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt8(o, 0)), generateBytes(0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt8(o, 1)), generateBytes(1)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt8(o, 255)), generateBytes(-1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt8(o, 256))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt8(o, -1))); + } + + @Test(groups = { "unit" }) + public void testReadNull() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readNull(generateInput(1)), true); + Assert.assertEquals(BinaryStreamUtils.readNull(generateInput(0)), false); + + Assert.assertThrows(IllegalArgumentException.class, () -> BinaryStreamUtils.readNull(generateInput(2))); + } + + @Test(groups = { "unit" }) + public void testWriteNull() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeNull(o)), generateBytes(1)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeNonNull(o)), generateBytes(0)); + } + + @Test(groups = { "unit" }) + public void testReadInt16() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readInt16(generateInput(0, 0x80)), Short.MIN_VALUE); + Assert.assertEquals(BinaryStreamUtils.readInt16(generateInput(0xFF, 0x7F)), Short.MAX_VALUE); + Assert.assertEquals(BinaryStreamUtils.readInt16(generateInput(0xFF, 0xFF)), Short.parseShort("-1")); + Assert.assertEquals(BinaryStreamUtils.readInt16(generateInput(0, 0)), Short.parseShort("0")); + } + + @Test(groups = { "unit" }) + public void testWriteInt16() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, (short) 0)), generateBytes(0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, (short) 1)), generateBytes(1, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, (short) -1)), + generateBytes(0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, Short.MIN_VALUE)), + generateBytes(0, 0x80)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, Short.MAX_VALUE)), + generateBytes(0xFF, 0x7F)); + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, 0)), generateBytes(0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, 1)), generateBytes(1, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, -1)), generateBytes(0xFF, 0xFF)); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, Short.MIN_VALUE - 1))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeInt16(o, Short.MAX_VALUE + 1))); + } + + @Test(groups = { "unit" }) + public void testReadUnsignedInt16() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt16(generateInput(0, 0)), 0); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt16(generateInput(0xFF, 0)), 0xFF); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt16(generateInput(0, 0xFF)), 0xFF00); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt16(generateInput(0xFF, 0xFF)), 0xFFFF); + } + + @Test(groups = { "unit" }) + public void testWriteUnsignedInt16() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt16(o, 0)), generateBytes(0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt16(o, 1)), generateBytes(1, 0)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt16(o, -1))); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt16(o, 0xFF)), + generateBytes(0xFF, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt16(o, 0xFFFF)), + generateBytes(0xFF, 0xFF)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt16(o, 0xFFFF + 1))); + } + + @Test(groups = { "unit" }) + public void testReadInt32() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readInt32(generateInput(0, 0, 0, 0x80)), Integer.MIN_VALUE); + Assert.assertEquals(BinaryStreamUtils.readInt32(generateInput(0, 0, 0, 0)), 0); + Assert.assertEquals(BinaryStreamUtils.readInt32(generateInput(0xFF, 0, 0, 0)), 0xFF); + Assert.assertEquals(BinaryStreamUtils.readInt32(generateInput(0xFF, 0xFF, 0, 0)), 0xFFFF); + Assert.assertEquals(BinaryStreamUtils.readInt32(generateInput(0xFF, 0xFF, 0xFF, 0)), 0xFFFFFF); + Assert.assertEquals(BinaryStreamUtils.readInt32(generateInput(0xFF, 0xFF, 0xFF, 0x7F)), Integer.MAX_VALUE); + } + + @Test(groups = { "unit" }) + public void testWriteInt32() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, 0)), generateBytes(0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, 1)), generateBytes(1, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, 0xFF)), generateBytes(0xFF, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, 0xFFFF)), + generateBytes(0xFF, 0xFF, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, 0xFFFFFF)), + generateBytes(0xFF, 0xFF, 0xFF, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, 0xFFFFFFFF)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, Integer.MIN_VALUE)), + generateBytes(0, 0, 0, 0x80)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt32(o, Integer.MAX_VALUE)), + generateBytes(0xFF, 0xFF, 0xFF, 0x7F)); + } + + @Test(groups = { "unit" }) + public void testReadUnsignedInt32() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt32(generateInput(0, 0, 0, 0)), 0L); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt32(generateInput(0xFF, 0, 0, 0)), 0xFFL); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt32(generateInput(0xFF, 0xFF, 0, 0)), 0xFFFFL); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt32(generateInput(0xFF, 0xFF, 0xFF, 0)), 0xFFFFFFL); + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt32(generateInput(0xFF, 0xFF, 0xFF, 0xFF)), 0xFFFFFFFFL); + } + + @Test(groups = { "unit" }) + public void testWriteUnsignedInt32() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 0L)), + generateBytes(0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 1L)), + generateBytes(1, 0, 0, 0)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, -1L))); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 0xFFL)), + generateBytes(0xFF, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 0xFFFFL)), + generateBytes(0xFF, 0xFF, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 0xFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 0xFFFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt32(o, 0xFFFFFFFFL + 1L))); + } + + @Test(groups = { "unit" }) + public void testReadInt64() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0, 0, 0, 0, 0, 0, 0, 0x80)), Long.MIN_VALUE); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0, 0, 0, 0, 0, 0, 0, 0)), 0L); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0, 0, 0, 0, 0, 0, 0)), 0xFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0, 0, 0, 0, 0, 0)), 0xFFFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0)), 0xFFFFFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0)), + 0xFFFFFFFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0)), + 0xFFFFFFFFFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0)), + 0xFFFFFFFFFFFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0)), + 0xFFFFFFFFFFFFFFL); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)), + -1L); + Assert.assertEquals(BinaryStreamUtils.readInt64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F)), + Long.MAX_VALUE); + } + + @Test(groups = { "unit" }) + public void testWriteInt64() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, Long.MIN_VALUE)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0x80)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0L)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFL)), + generateBytes(0xFF, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFL)), + generateBytes(0xFF, 0xFF, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFFFFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFFFFFFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, 0xFFFFFFFFFFFFFFFFL)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt64(o, Long.MAX_VALUE)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F)); + } + + @Test(groups = { "unit" }) + public void testReadUnsignedInt64() throws IOException { + byte[] bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt64(generateInput(bytes)), BigInteger.ZERO); + Assert.assertEquals( + BinaryStreamUtils.readUnsignedInt64(generateInput(bytes = generateBytes(0xFF, 0, 0, 0, 0, 0, 0, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + + Assert.assertEquals( + BinaryStreamUtils.readUnsignedInt64(generateInput(bytes = generateBytes(0xFF, 0xFF, 0, 0, 0, 0, 0, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + Assert.assertEquals( + BinaryStreamUtils + .readUnsignedInt64(generateInput(bytes = generateBytes(0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + Assert.assertEquals( + BinaryStreamUtils + .readUnsignedInt64(generateInput(bytes = generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + Assert.assertEquals( + BinaryStreamUtils + .readUnsignedInt64(generateInput(bytes = generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + Assert.assertEquals( + BinaryStreamUtils.readUnsignedInt64( + generateInput(bytes = generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + Assert.assertEquals( + BinaryStreamUtils.readUnsignedInt64( + generateInput(bytes = generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0))), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + Assert.assertEquals( + BinaryStreamUtils.readUnsignedInt64( + generateInput(bytes = generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF))), + new BigInteger(1, BinaryStreamUtils.reverse(bytes))); + + } + + @Test(groups = { "unit" }) + public void testWriteUnsignedInt64() throws IOException { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, BigInteger.valueOf(-1L)))); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, BigInteger.ZERO)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, BigInteger.ONE)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0, 0, 0, 0, 0, 0, 0xFF)))), + generateBytes(0xFF, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0, 0, 0, 0, 0, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt64(o, + new BigInteger(1, generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)) + .add(BigInteger.ONE)))); + } + + @Test(groups = { "unit" }) + public void testReadInt128() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 2, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(BinaryStreamUtils.readInt128(generateInput(bytes)), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + } + + Assert.assertThrows(IOException.class, () -> BinaryStreamUtils.readInt128(generateInput(1))); + } + + @Test(groups = { "unit" }) + public void testWriteInt128() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt128(o, BigInteger.ZERO)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt128(o, BigInteger.ONE)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt128(o, BigInteger.valueOf(-1L))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF)); + + byte[][] arr = generateBytes(Long.BYTES * 2, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt128(o, new BigInteger(bytes))), + BinaryStreamUtils.reverse(bytes)); + } + } + + @Test(groups = { "unit" }) + public void testReadUnsignedInt128() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 2, true); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt128(generateInput(bytes)), + new BigInteger(1, BinaryStreamUtils.reverse(bytes))); + } + + Assert.assertThrows(IOException.class, () -> BinaryStreamUtils.readUnsignedInt128(generateInput(1))); + } + + @Test(groups = { "unit" }) + public void testWriteUnsignedInt128() throws IOException { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt128(o, BigInteger.valueOf(-1L)))); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt128(o, BigInteger.ZERO)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt128(o, BigInteger.ONE)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + byte[][] arr = generateBytes(Long.BYTES * 2, true); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt128(o, new BigInteger(1, bytes))), + BinaryStreamUtils.reverse(bytes)); + } + } + + @Test(groups = { "unit" }) + public void testReadInt256() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 4, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(BinaryStreamUtils.readInt256(generateInput(bytes)), + new BigInteger(BinaryStreamUtils.reverse(bytes))); + } + + Assert.assertThrows(IOException.class, () -> BinaryStreamUtils.readInt256(generateInput(1))); + } + + @Test(groups = { "unit" }) + public void testWriteInt256() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt256(o, BigInteger.ZERO)), generateBytes(0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt256(o, BigInteger.ONE)), generateBytes(1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt256(o, BigInteger.valueOf(-1L))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF)); + + byte[][] arr = generateBytes(Long.BYTES * 4, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeInt256(o, new BigInteger(bytes))), + BinaryStreamUtils.reverse(bytes)); + } + } + + @Test(groups = { "unit" }) + public void testReadUnsignedInt256() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 4, true); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(BinaryStreamUtils.readUnsignedInt256(generateInput(bytes)), + new BigInteger(1, BinaryStreamUtils.reverse(bytes))); + } + + Assert.assertThrows(IOException.class, () -> BinaryStreamUtils.readUnsignedInt256(generateInput(1))); + } + + @Test(groups = { "unit" }) + public void testWriteUnsignedInt256() throws IOException { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt256(o, BigInteger.valueOf(-1L)))); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt256(o, BigInteger.ZERO)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt256(o, BigInteger.ONE)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0)); + + byte[][] arr = generateBytes(Long.BYTES * 4, true); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeUnsignedInt256(o, new BigInteger(1, bytes))), + BinaryStreamUtils.reverse(bytes)); + } + } + + @Test(groups = { "unit" }) + public void testReadDate() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(-1, 0)), LocalDate.ofEpochDay(255)); + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(0, 0x80)), LocalDate.ofEpochDay(0x8000)); + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(0, 0)), LocalDate.ofEpochDay(0)); + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(1, 0)), LocalDate.ofEpochDay(1)); + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(0xFF, 0x7F)), + LocalDate.ofEpochDay(Short.MAX_VALUE)); + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(0xFF, 0xFF)), LocalDate.ofEpochDay(0xFFFF)); + + Assert.assertEquals(BinaryStreamUtils.readDate(generateInput(0x9E, 0x49)), LocalDate.of(2021, 8, 7)); + } + + @Test(groups = { "unit" }) + public void testWriteDate() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.ofEpochDay(255))), + generateBytes(-1, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.ofEpochDay(0x8000))), + generateBytes(0, 0x80)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.ofEpochDay(0))), + generateBytes(0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.ofEpochDay(1))), + generateBytes(1, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.ofEpochDay(Short.MAX_VALUE))), + generateBytes(0xFF, 0x7F)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.ofEpochDay(0xFFFF))), + generateBytes(0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate(o, LocalDate.of(2021, 8, 7))), + generateBytes(0x9E, 0x49)); + } + + @Test(groups = { "unit" }) + public void testReadDate32() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readDate32(generateInput(0xFF, 0xFF, 0xFF, 0xFF)), + LocalDate.ofEpochDay(-1)); + Assert.assertEquals(BinaryStreamUtils.readDate32(generateInput(0, 0, 0, 0)), LocalDate.ofEpochDay(0)); + Assert.assertEquals(BinaryStreamUtils.readDate32(generateInput(1, 0, 0, 0)), LocalDate.ofEpochDay(1)); + Assert.assertEquals(BinaryStreamUtils.readDate32(generateInput(0x17, 0x61, 0, 0)), LocalDate.of(2038, 1, 19)); + + Assert.assertEquals(BinaryStreamUtils.readDate32(generateInput(0xCC, 0xBF, 0xFF, 0xFF)), + LocalDate.of(1925, 1, 1)); + Assert.assertEquals(BinaryStreamUtils.readDate32(generateInput(0xCB, 0xBF, 1, 0)), LocalDate.of(2283, 11, 11)); + } + + @Test(groups = { "unit" }) + public void testWriteDate32() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate32(o, LocalDate.ofEpochDay(-1))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate32(o, LocalDate.ofEpochDay(0))), + generateBytes(0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate32(o, LocalDate.ofEpochDay(1))), + generateBytes(1, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate32(o, LocalDate.of(2038, 1, 19))), + generateBytes(0x17, 0x61, 0, 0)); + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate32(o, LocalDate.of(1925, 1, 1))), + generateBytes(0xCC, 0xBF, 0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDate32(o, LocalDate.of(2283, 11, 11))), + generateBytes(0xCB, 0xBF, 1, 0)); + + Assert.assertThrows(IllegalArgumentException.class, () -> getWrittenBytes( + o -> BinaryStreamUtils.writeDate32(o, LocalDate.of(1925, 1, 1).minus(1L, ChronoUnit.DAYS)))); + Assert.assertThrows(IllegalArgumentException.class, () -> getWrittenBytes( + o -> BinaryStreamUtils.writeDate32(o, LocalDate.of(2283, 11, 11).plus(1L, ChronoUnit.DAYS)))); + } + + @Test(groups = { "unit" }) + public void testReadDateTime32() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readDateTime32(generateInput(-1, 0, 0, 0)), + LocalDateTime.ofEpochSecond(255, 0, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime32(generateInput(0, 0, 0, 0x80)), LocalDateTime + .ofEpochSecond(new BigInteger(1, generateBytes(0x80, 0, 0, 0)).longValue(), 0, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime32(generateInput(0, 0, 0, 0)), + LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime32(generateInput(1, 0, 0, 0)), + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime32(generateInput(0xFF, 0xFF, 0xFF, 0x7F)), + LocalDateTime.ofEpochSecond(Integer.MAX_VALUE, 0, ZoneOffset.UTC)); + + Assert.assertEquals(BinaryStreamUtils.readDateTime32(generateInput(0x2D, 0x9A, 0x0E, 0x61)), + LocalDateTime.of(2021, 8, 7, 14, 35, 25)); + } + + @Test(groups = { "unit" }) + public void testWriteDateTime32() throws IOException { + Assert.assertEquals( + getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime32(o, LocalDateTime.ofEpochSecond(255, 0, ZoneOffset.UTC))), + generateBytes(-1, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDateTime32(o, LocalDateTime.ofEpochSecond( + new BigInteger(1, generateBytes(0x80, 0, 0, 0)).longValue(), 0, ZoneOffset.UTC))), + generateBytes(0, 0, 0, 0x80)); + Assert.assertEquals( + getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime32(o, LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC))), + generateBytes(0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime32(o, LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC))), + generateBytes(1, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDateTime32(o, + LocalDateTime.ofEpochSecond(Integer.MAX_VALUE, 0, ZoneOffset.UTC))), + generateBytes(0xFF, 0xFF, 0xFF, 0x7F)); + + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDateTime32(o, LocalDateTime.of(2021, 8, 7, 14, 35, 25))), + generateBytes(0x2D, 0x9A, 0x0E, 0x61)); + + Assert.assertThrows(IllegalArgumentException.class, () -> getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime32(o, LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC)))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDateTime32(o, + LocalDateTime.ofEpochSecond(BinaryStreamUtils.DATETIME_MAX + 1, 0, ZoneOffset.UTC)))); + } + + @Test(groups = { "unit" }) + public void testReadDateTime64() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readDateTime64(generateInput(-1, 0, 0, 0, 0, 0, 0, 0), 0), + LocalDateTime.ofEpochSecond(255, 0, ZoneOffset.UTC)); + Assert.assertEquals( + BinaryStreamUtils.readDateTime64(generateInput(0xF6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF), 1), + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC)); + Assert.assertEquals( + BinaryStreamUtils.readDateTime64(generateInput(0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF), 1), + LocalDateTime.ofEpochSecond(-2L, 900000000, ZoneOffset.UTC)); + // Actually query "select toDateTime64(-1.000000001::Decimal64(9), 9)" returns: + // 1969-12-31 23:59:59.000000001 + // see /~https://github.com/ClickHouse/ClickHouse/issues/29386 + Assert.assertEquals( + BinaryStreamUtils.readDateTime64(generateInput(0xFF, 0x35, 0x65, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF), 9), + LocalDateTime.ofEpochSecond(-2L, 999999999, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime64(generateInput(0, 0, 0, 0, 0, 0, 0, 0), 0), + LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime64(generateInput(1, 0, 0, 0, 0, 0, 0, 0), 0), + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC)); + + Assert.assertEquals(BinaryStreamUtils.readDateTime64(generateInput(0x0A, 0, 0, 0, 0, 0, 0, 0), 1), + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC)); + Assert.assertEquals(BinaryStreamUtils.readDateTime64(generateInput(1, 0, 0, 0, 0, 0, 0, 0), 9), + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC)); + } + + @Test(groups = { "unit" }) + public void testWriteDateTime64() throws IOException { + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(255L, 0, ZoneOffset.UTC), 0)), + generateBytes(-1, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), 1)), + generateBytes(0xF6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDateTime64(o, + LocalDateTime.ofEpochSecond(-2L, 900000000, ZoneOffset.UTC), 1)), + generateBytes(0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDateTime64(o, + LocalDateTime.ofEpochSecond(-2L, 999999999, ZoneOffset.UTC), 9)), + generateBytes(0xFF, 0x35, 0x65, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), 0)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), 0)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), 1)), + generateBytes(0x0A, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), 9)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0)); + + Assert.assertThrows(IllegalArgumentException.class, () -> getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), -1))); + Assert.assertThrows(IllegalArgumentException.class, () -> getWrittenBytes( + o -> BinaryStreamUtils.writeDateTime64(o, LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), 10))); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDateTime64(o, + LocalDateTime.of(LocalDate.of(1925, 1, 1).minus(1L, ChronoUnit.DAYS), LocalTime.MAX)))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDateTime64(o, + LocalDateTime.of(LocalDate.of(2283, 11, 11).plus(1L, ChronoUnit.DAYS), LocalTime.MIN)))); + } + + @Test(groups = { "unit" }) + public void testReadDecimal32() throws IOException { + byte[][] arr = generateBytes(Integer.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 10; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + Assert.assertEquals(BinaryStreamUtils.readDecimal32(generateInput(bytes), j), + new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), j)); + } + } + + Assert.assertEquals(BinaryStreamUtils.readDecimal32(generateInput(0xFF, 0xFF, 0xFF, 0xFF), 0), + new BigDecimal("-1")); + Assert.assertEquals(BinaryStreamUtils.readDecimal32(generateInput(0, 0xCA, 0x9A, 0x3B), 9), + new BigDecimal("1.000000000")); + Assert.assertEquals(BinaryStreamUtils.readDecimal32(generateInput(0, 0x36, 0x65, 0xC4), 9), + new BigDecimal("-1.000000000")); + } + + @Test(groups = { "unit" }) + public void testWriteDecimal32() throws IOException { + byte[][] arr = generateBytes(Integer.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 10; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + final int scale = j; + BigDecimal d = new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), scale); + if (d.toString().replaceAll("[-.]", "").replaceAll("^0+", "").length() > 9) { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, d, scale))); + } else { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, d, scale)), b); + } + } + } + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, new BigDecimal("-1"), 0)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, new BigDecimal("0.9999999999"), 9)), + generateBytes(0xFF, 0xC9, 0x9A, 0x3B)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, new BigDecimal("-0.9999999999"), 9)), + generateBytes(0x01, 0x36, 0x65, 0xC4)); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, BigDecimal.ZERO, -1))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal32(o, BigDecimal.ZERO, 10))); + } + + @Test(groups = { "unit" }) + public void testReadDecimal64() throws IOException { + byte[][] arr = generateBytes(Long.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 19; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + Assert.assertEquals(BinaryStreamUtils.readDecimal64(generateInput(bytes), j), + new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), j)); + + } + } + + Assert.assertEquals( + BinaryStreamUtils.readDecimal64(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF), 0), + new BigDecimal("-1")); + Assert.assertEquals( + BinaryStreamUtils.readDecimal64(generateInput(0, 0, 0x64, 0xA7, 0xB3, 0xB6, 0xE0, 0x0D), 18), + new BigDecimal("1.000000000000000000")); + Assert.assertEquals( + BinaryStreamUtils.readDecimal64(generateInput(0, 0, 0x9C, 0x58, 0x4C, 0x49, 0x1F, 0xF2), 18), + new BigDecimal("-1.000000000000000000")); + } + + @Test(groups = { "unit" }) + public void testWriteDecimal64() throws IOException { + byte[][] arr = generateBytes(Long.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 19; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + final int scale = j; + BigDecimal d = new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), scale); + if (d.toString().replaceAll("[-.]", "").replaceAll("^0+", "").length() > 18) { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal64(o, d, scale))); + } else { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal64(o, d, scale)), b); + } + } + } + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal64(o, new BigDecimal("-1"), 0)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal64(o, new BigDecimal("0.99999999999999999999"), 18)), + generateBytes(0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x0D)); + Assert.assertEquals( + getWrittenBytes( + o -> BinaryStreamUtils.writeDecimal64(o, new BigDecimal("-0.99999999999999999999"), 18)), + generateBytes(1, 0, 0x9C, 0x58, 0x4C, 0x49, 0x1F, 0xF2)); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal64(o, BigDecimal.ZERO, -1))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal64(o, BigDecimal.ZERO, 19))); + } + + @Test(groups = { "unit" }) + public void testReadDecimal128() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 2, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 39; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + Assert.assertEquals(BinaryStreamUtils.readDecimal128(generateInput(bytes), j), + new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), j)); + + } + } + + Assert.assertEquals(BinaryStreamUtils.readDecimal128(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF), 0), new BigDecimal("-1")); + Assert.assertEquals(BinaryStreamUtils.readDecimal128( + generateInput(0, 0, 0, 0, 0x40, 0x22, 0x8A, 0x09, 0x7A, 0xC4, 0x86, 0x5A, 0xA8, 0x4C, 0x3B, 0x4B), 38), + new BigDecimal("1.00000000000000000000000000000000000000")); + Assert.assertEquals( + BinaryStreamUtils.readDecimal128(generateInput(0, 0, 0, 0, 0xC0, 0xDD, 0x75, 0xF6, 0x85, 0x3B, 0x79, + 0xA5, 0x57, 0xB3, 0xC4, 0xB4), 38), + new BigDecimal("-1.00000000000000000000000000000000000000")); + } + + @Test(groups = { "unit" }) + public void testWriteDecimal128() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 2, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 39; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + final int scale = j; + BigDecimal d = new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), scale); + if (d.toString().replaceAll("[-.]", "").replaceAll("^0+", "").length() > 38) { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, d, scale))); + } else { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, d, scale)), b); + } + } + } + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, new BigDecimal("-1"), 0)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, + new BigDecimal("0.999999999999999999999999999999999999999999"), 38)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x22, 0x8A, 0x09, 0x7A, 0xC4, 0x86, 0x5A, 0xA8, 0x4C, 0x3B, + 0x4B)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, + new BigDecimal("-0.999999999999999999999999999999999999999999"), 38)), + generateBytes(1, 0, 0, 0, 0xC0, 0xDD, 0x75, 0xF6, 0x85, 0x3B, 0x79, 0xA5, 0x57, 0xB3, 0xC4, 0xB4)); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, BigDecimal.ZERO, -1))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal128(o, BigDecimal.ZERO, 39))); + } + + @Test(groups = { "unit" }) + public void testReadDecimal256() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 4, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 77; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + Assert.assertEquals(BinaryStreamUtils.readDecimal256(generateInput(bytes), j), + new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), j)); + + } + } + + Assert.assertEquals(BinaryStreamUtils.readDecimal256(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF), 0), new BigDecimal("-1")); + Assert.assertEquals( + BinaryStreamUtils.readDecimal256( + generateInput(0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, 0x95, 0x71, 0xF1, 0xA5, 0x75, 0x77, 0x79, 0x29, + 0x65, 0xE8, 0xAB, 0xB4, 0x64, 0x07, 0xB5, 0x15, 0x99, 0x11, 0xA7, 0xCC, 0x1B, 0x16), + 76), + new BigDecimal("1.0000000000000000000000000000000000000000000000000000000000000000000000000000")); + Assert.assertEquals( + BinaryStreamUtils.readDecimal256( + generateInput(0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0x6A, 0x8E, 0x0E, 0x5A, 0x8A, 0x88, 0x86, 0xD6, + 0x9A, 0x17, 0x54, 0x4B, 0x9B, 0xF8, 0x4A, 0xEA, 0x66, 0xEE, 0x58, 0x33, 0xE4, 0xE9), + 76), + new BigDecimal("-1.0000000000000000000000000000000000000000000000000000000000000000000000000000")); + } + + @Test(groups = { "unit" }) + public void testWriteDecimal256() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 4, false); + for (int i = 0; i < arr.length; i++) { + byte[] b = arr[i]; + for (int j = 0; j < 77; j++) { + byte[] bytes = new byte[b.length]; + System.arraycopy(b, 0, bytes, 0, b.length); + final int scale = j; + BigDecimal d = new BigDecimal(new BigInteger(BinaryStreamUtils.reverse(bytes)), scale); + if (d.toString().replaceAll("[-.]", "").replaceAll("^0+", "").length() > 76) { + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, d, scale))); + } else { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, d, scale)), b); + } + } + } + + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, new BigDecimal("-1"), 0)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, + new BigDecimal( + "0.9999999999999999999999999999999999999999999999999999999999999999999999999999"), + 76)), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x95, 0x71, 0xF1, 0xA5, 0x75, + 0x77, 0x79, 0x29, 0x65, 0xE8, 0xAB, 0xB4, 0x64, 0x07, 0xB5, 0x15, 0x99, 0x11, 0xA7, 0xCC, 0x1B, + 0x16)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, + new BigDecimal( + "-0.9999999999999999999999999999999999999999999999999999999999999999999999999999"), + 76)), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0x6A, 0x8E, 0x0E, 0x5A, 0x8A, 0x88, 0x86, 0xD6, 0x9A, + 0x17, 0x54, 0x4B, 0x9B, 0xF8, 0x4A, 0xEA, 0x66, 0xEE, 0x58, 0x33, 0xE4, 0xE9)); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, BigDecimal.ZERO, -1))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeDecimal256(o, BigDecimal.ZERO, 77))); + } + + @Test(groups = { "unit" }) + public void testReadFloat32() throws IOException { + byte[][] arr = generateBytes(Integer.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(BinaryStreamUtils.readFloat32(generateInput(bytes)), + Float.intBitsToFloat(new BigInteger(BinaryStreamUtils.reverse(bytes)).intValue())); + } + + // INF and NaN + Assert.assertEquals(BinaryStreamUtils.readFloat32(generateInput(0, 0, 0x80, 0xFF)), Float.NEGATIVE_INFINITY); + Assert.assertEquals(BinaryStreamUtils.readFloat32(generateInput(0, 0, 0x80, 0x7F)), Float.POSITIVE_INFINITY); + Assert.assertEquals(BinaryStreamUtils.readFloat32(generateInput(0, 0, 0xC0, 0x7F)), Float.NaN); + } + + @Test(groups = { "unit" }) + public void testWriteFloat32() throws IOException { + byte[][] arr = generateBytes(Integer.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + float v = Float.intBitsToFloat(new BigInteger(bytes).intValue()); + if (Float.isNaN(v)) { + continue; + } + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat32(o, v)), + BinaryStreamUtils.reverse(bytes)); + } + + // INF and NaN + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat32(o, Float.NEGATIVE_INFINITY)), + generateBytes(0, 0, 0x80, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat32(o, Float.POSITIVE_INFINITY)), + generateBytes(0, 0, 0x80, 0x7F)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat32(o, Float.NaN)), + generateBytes(0, 0, 0xC0, 0x7F)); + } + + @Test(groups = { "unit" }) + public void testReadFloat64() throws IOException { + byte[][] arr = generateBytes(Long.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + Assert.assertEquals(BinaryStreamUtils.readFloat64(generateInput(bytes)), + Double.longBitsToDouble(new BigInteger(BinaryStreamUtils.reverse(bytes)).longValue())); + } + + // INF and NaN + Assert.assertEquals(BinaryStreamUtils.readFloat64(generateInput(0, 0, 0, 0, 0, 0, 0xF0, 0xFF)), + Double.NEGATIVE_INFINITY); + Assert.assertEquals(BinaryStreamUtils.readFloat64(generateInput(0, 0, 0, 0, 0, 0, 0xF0, 0x7F)), + Double.POSITIVE_INFINITY); + Assert.assertEquals(BinaryStreamUtils.readFloat64(generateInput(0, 0, 0, 0, 0, 0, 0xF8, 0x7F)), Double.NaN); + } + + @Test(groups = { "unit" }) + public void testWriteFloat64() throws IOException { + byte[][] arr = generateBytes(Long.BYTES, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + double v = Double.longBitsToDouble(new BigInteger(bytes).longValue()); + if (Double.isNaN(v)) { + continue; + } + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat64(o, v)), + BinaryStreamUtils.reverse(bytes)); + } + + // INF and NaN + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat64(o, Float.NEGATIVE_INFINITY)), + generateBytes(0, 0, 0, 0, 0, 0, 0xF0, 0xFF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat64(o, Float.POSITIVE_INFINITY)), + generateBytes(0, 0, 0, 0, 0, 0, 0xF0, 0x7F)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeFloat64(o, Float.NaN)), + generateBytes(0, 0, 0, 0, 0, 0, 0xF8, 0x7F)); + } + + @Test(groups = { "unit" }) + public void testReadGeoPoint() throws IOException { + Assert.assertEquals( + BinaryStreamUtils.readGeoPoint(generateInput(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), + new double[] { 0D, 0D }); + Assert.assertEquals( + BinaryStreamUtils + .readGeoPoint(generateInput(0, 0, 0, 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, 0, 0, 0, 0xF0, 0xBF)), + new double[] { -1D, -1D }); + Assert.assertEquals( + BinaryStreamUtils + .readGeoPoint(generateInput(0, 0, 0, 0, 0, 0, 0xF0, 0x3F, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F)), + new double[] { 1D, 1D }); + } + + @Test(groups = { "unit" }) + public void testWriteGeoPoint() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeGeoPoint(o, 0D, 0D)), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeGeoPoint(o, -1D, -1D)), + generateBytes(0, 0, 0, 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, 0, 0, 0, 0xF0, 0xBF)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeGeoPoint(o, 1D, 1D)), + generateBytes(0, 0, 0, 0, 0, 0, 0xF0, 0x3F, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F)); + + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeGeoPoint(o, new double[0]))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeGeoPoint(o, new double[1]))); + Assert.assertThrows(IllegalArgumentException.class, + () -> getWrittenBytes(o -> BinaryStreamUtils.writeGeoPoint(o, new double[3]))); + } + + @Test(groups = { "unit" }) + public void testReadGeoRing() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readGeoRing(generateInput(0)), new double[0][]); + double[][] ring = BinaryStreamUtils + .readGeoRing(generateInput(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(ring.length, 1); + Assert.assertEquals(ring[0], new double[] { 0D, 0D }); + + ring = BinaryStreamUtils.readGeoRing(generateInput(2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, 0, 0, 0, 0xF0, 0xBF)); + Assert.assertEquals(ring.length, 2); + Assert.assertEquals(ring[0], new double[] { 0D, 0D }); + Assert.assertEquals(ring[1], new double[] { -1D, -1D }); + + ring = BinaryStreamUtils.readGeoRing( + generateInput(3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, + 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F)); + Assert.assertEquals(ring.length, 3); + Assert.assertEquals(ring[0], new double[] { 0D, 0D }); + Assert.assertEquals(ring[1], new double[] { -1D, -1D }); + Assert.assertEquals(ring[2], new double[] { 1D, 1D }); + } + + @Test(groups = { "unit" }) + public void testWriteGeoRing() throws IOException { + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeGeoRing(o, new double[0][])), generateBytes(0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeGeoRing(o, new double[][] { new double[] { 0D, 0D } })), + generateBytes(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeGeoRing(o, + new double[][] { new double[] { 0D, 0D }, new double[] { -1D, -1D } })), + generateBytes(2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, + 0, 0, 0, 0xF0, 0xBF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeGeoRing(o, + new double[][] { new double[] { 0D, 0D }, new double[] { -1D, -1D }, + new double[] { 1D, 1D } })), + generateBytes(3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, + 0, 0, 0, 0xF0, 0xBF, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F, 0, 0, 0, 0, 0, 0, 0xF0, 0x3F)); + } + + @Test(groups = { "unit" }) + public void testReadGeoPolygon() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readGeoPolygon(generateInput(0)), new double[0][][]); + } + + @Test(groups = { "unit" }) + public void testReadGeoMultiPolygon() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readGeoPolygon(generateInput(0)), new double[0][][][]); + } + + @Test(groups = { "unit" }) + public void testReadInet4Address() throws IOException { + Assert.assertEquals(BinaryStreamUtils.readInet4Address(generateInput(0, 0, 0, 0)).getHostAddress(), "0.0.0.0"); + Assert.assertEquals(BinaryStreamUtils.readInet4Address(generateInput(1, 0, 0, 0)).getHostAddress(), "0.0.0.1"); + Assert.assertEquals(BinaryStreamUtils.readInet4Address(generateInput(0xFF, 0xFF, 0xFF, 0xFF)).getHostAddress(), + "255.255.255.255"); + Assert.assertEquals(BinaryStreamUtils.readInet4Address(generateInput(1, 5, 0xA8, 0xC0)).getHostAddress(), + "192.168.5.1"); + } + + @Test(groups = { "unit" }) + public void testWriteInet4Address() throws IOException { + Assert.assertEquals( + getWrittenBytes( + o -> BinaryStreamUtils.writeInet4Address(o, (Inet4Address) InetAddress.getByName("0.0.0.0"))), + generateBytes(0, 0, 0, 0)); + Assert.assertEquals( + getWrittenBytes( + o -> BinaryStreamUtils.writeInet4Address(o, (Inet4Address) InetAddress.getByName("0.0.0.1"))), + generateBytes(1, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeInet4Address(o, (Inet4Address) InetAddress.getByName("255.255.255.255"))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeInet4Address(o, (Inet4Address) InetAddress.getByName("192.168.5.1"))), + generateBytes(1, 5, 0xA8, 0xC0)); + } + + @Test(groups = { "unit" }) + public void testReadInet6Address() throws IOException { + Assert.assertEquals(BinaryStreamUtils + .readInet6Address(generateInput(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)).getHostAddress(), + "0:0:0:0:0:0:0:0"); + Assert.assertEquals(BinaryStreamUtils + .readInet6Address(generateInput(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)).getHostAddress(), + "0:0:0:0:0:0:0:1"); + Assert.assertEquals( + BinaryStreamUtils.readInet6Address(generateInput(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)).getHostAddress(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + Assert.assertEquals( + BinaryStreamUtils.readInet6Address(generateInput(0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x43, + 0x21, 0x87, 0x65, 0xBA, 0x09, 0xFE, 0xDC)).getHostAddress(), + "1234:5678:90ab:cdef:4321:8765:ba09:fedc"); + Assert.assertEquals(BinaryStreamUtils + .readInet6Address(generateInput(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xC0, 0xA8, 5, 1)) + .getHostAddress(), "0:0:0:0:0:ffff:c0a8:501"); + } + + @Test(groups = { "unit" }) + public void testWriteInet6Address() throws IOException { + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeInet6Address(o, (Inet6Address) InetAddress.getByName("0:0:0:0:0:0:0:0"))), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Assert.assertEquals(getWrittenBytes( + o -> BinaryStreamUtils.writeInet6Address(o, (Inet6Address) InetAddress.getByName("0:0:0:0:0:0:0:1"))), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeInet6Address(o, + (Inet6Address) InetAddress.getByName("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))), + generateBytes(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeInet6Address(o, + (Inet6Address) InetAddress.getByName("1234:5678:90ab:cdef:4321:8765:ba09:fedc"))), + generateBytes(0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x43, 0x21, 0x87, 0x65, 0xBA, 0x09, 0xFE, + 0xDC)); + Assert.assertEquals( + getWrittenBytes(o -> BinaryStreamUtils.writeInet6Address(o, + Inet6Address.getByAddress(null, + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xC0, 0xA8, 5, 1), null))), + generateBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xC0, 0xA8, 5, 1)); + } + + @Test(groups = { "unit" }) + public void testReadUuid() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 2, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + UUID uuid = BinaryStreamUtils.readUuid(generateInput(bytes)); + Assert.assertEquals(uuid.getMostSignificantBits(), + BinaryStreamUtils.readInt64(new ByteArrayInputStream(bytes, 0, Long.BYTES))); + Assert.assertEquals(uuid.getLeastSignificantBits(), + BinaryStreamUtils.readInt64(new ByteArrayInputStream(bytes, Long.BYTES, Long.BYTES))); + } + } + + @Test(groups = { "unit" }) + public void testWriteUuid() throws IOException { + byte[][] arr = generateBytes(Long.BYTES * 2, false); + for (int i = 0; i < arr.length; i++) { + byte[] bytes = arr[i]; + UUID uuid = BinaryStreamUtils.readUuid(generateInput(bytes)); + Assert.assertEquals(getWrittenBytes(o -> BinaryStreamUtils.writeUuid(o, uuid)), bytes); + } + } + + @Test(groups = { "unit" }) + public void testVarInt() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BinaryStreamUtils.writeVarInt(out, 0); + Assert.assertEquals(out.toByteArray(), new byte[] { (byte) 0 }); + Assert.assertEquals(BinaryStreamUtils.readVarInt(generateInput(0)), 0); + + out = new ByteArrayOutputStream(); + BinaryStreamUtils.writeVarInt(out, 1); + Assert.assertEquals(out.toByteArray(), new byte[] { (byte) 1 }); + Assert.assertEquals(BinaryStreamUtils.readVarInt(generateInput(1)), 1); + + out = new ByteArrayOutputStream(); + BinaryStreamUtils.writeVarInt(out, 128); + Assert.assertEquals(out.toByteArray(), new byte[] { (byte) -128, (byte) 1 }); + Assert.assertEquals(BinaryStreamUtils.readVarInt(generateInput(-128, 1)), 128); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseByteValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseByteValueTest.java new file mode 100644 index 000000000..67d93e611 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseByteValueTest.java @@ -0,0 +1,194 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseByteValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testUpdate() throws Exception { + ClickHouseByteValue v = ClickHouseByteValue.of(-1); + Assert.assertEquals(v.getValue(), (byte) -1); + Assert.assertEquals(v.update(true).asByte(), (byte) 1); + Assert.assertEquals(v.update(Boolean.TRUE).getValue(), (byte) 1); + Assert.assertEquals(v.update(false).asByte(), (byte) 0); + Assert.assertEquals(v.update(Boolean.FALSE).getValue(), (byte) 0); + Assert.assertEquals(v.update('a').getValue(), (byte) 97); + Assert.assertEquals(v.update('萌').getValue(), (byte) 12); + Assert.assertEquals(v.update((byte) 2).getValue(), (byte) 2); + Assert.assertEquals(v.update(Byte.valueOf("2")).getValue(), (byte) 2); + Assert.assertEquals(v.update((short) -2).getValue(), (byte) -2); + Assert.assertEquals(v.update((short) -233).getValue(), (byte) 23); + Assert.assertEquals(v.update(Short.valueOf("-2")).getValue(), (byte) -2); + Assert.assertEquals(v.update(123).getValue(), (byte) 123); + Assert.assertEquals(v.update(233).getValue(), (byte) -23); + Assert.assertEquals(v.update(Integer.valueOf(123)).getValue(), (byte) 123); + Assert.assertEquals(v.update(-123L).getValue(), (byte) -123); + Assert.assertEquals(v.update(-233L).getValue(), (byte) 23); + Assert.assertEquals(v.update(Long.valueOf(-123L)).getValue(), (byte) -123); + Assert.assertEquals(v.update(23.3F).getValue(), (byte) 23); + Assert.assertEquals(v.update(233.3F).getValue(), (byte) -23); + Assert.assertEquals(v.update(Float.valueOf(23.3F)).getValue(), (byte) 23); + Assert.assertEquals(v.update(-23.333D).getValue(), (byte) -23); + Assert.assertEquals(v.update(-233.333D).getValue(), (byte) 23); + Assert.assertEquals(v.update(Double.valueOf(-23.333D)).getValue(), (byte) -23); + Assert.assertEquals(v.update(BigInteger.valueOf(Byte.MAX_VALUE)).getValue(), Byte.MAX_VALUE); + Assert.assertThrows(ArithmeticException.class, + () -> v.update(BigInteger.valueOf(Byte.MAX_VALUE + 1)).getValue()); + Assert.assertThrows(ArithmeticException.class, + () -> v.update(BigDecimal.valueOf(Byte.MIN_VALUE * -1.0D)).getValue()); + Assert.assertEquals(v.update("121").getValue(), (byte) 121); + // inconsistent with above :< + Assert.assertThrows(NumberFormatException.class, () -> v.update("233").getValue()); + } + + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseByteValue.ofNull()); + checkNull(ClickHouseByteValue.of(Byte.MIN_VALUE).resetToNullOrEmpty()); + checkNull(ClickHouseByteValue.of(Byte.MAX_VALUE).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseByteValue.of(0), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + (byte) 0, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0", // String + "0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Integer.class, // Key class + Byte.class, // Value class + new Object[] { Byte.valueOf((byte) 0) }, // Array + new Byte[] { Byte.valueOf((byte) 0) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Byte.valueOf((byte) 0) }), // Map + buildMap(new Object[] { 1 }, new Byte[] { Byte.valueOf((byte) 0) }), // typed Map + Arrays.asList(Byte.valueOf((byte) 0)) // Tuple + ); + checkValue(ClickHouseByteValue.of(1), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + (byte) 1, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1", // String + "1", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Integer.class, // Key class + Byte.class, // Value class + new Object[] { Byte.valueOf((byte) 1) }, // Array + new Byte[] { Byte.valueOf((byte) 1) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Byte.valueOf((byte) 1) }), // Map + buildMap(new Object[] { 1 }, new Byte[] { Byte.valueOf((byte) 1) }), // typed Map + Arrays.asList(Byte.valueOf((byte) 1)) // Tuple + ); + checkValue(ClickHouseByteValue.of(2), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + (byte) 2, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2", // String + "2", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Integer.class, // Key class + Byte.class, // Value class + new Object[] { Byte.valueOf((byte) 2) }, // Array + new Byte[] { Byte.valueOf((byte) 2) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Byte.valueOf((byte) 2) }), // Map + buildMap(new Object[] { 1 }, new Byte[] { Byte.valueOf((byte) 2) }), // typed Map + Arrays.asList(Byte.valueOf((byte) 2)) // Tuple + ); + + checkValue(ClickHouseByteValue.of(-1), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + (byte) -1, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:ff")[0], // Inet6Address + "-1", // String + "-1", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Integer.class, // Key class + Byte.class, // Value class + new Object[] { Byte.valueOf((byte) -1) }, // Array + new Byte[] { Byte.valueOf((byte) -1) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Byte.valueOf((byte) -1) }), // Map + buildMap(new Object[] { 1 }, new Byte[] { Byte.valueOf((byte) -1) }), // typed Map + Arrays.asList(Byte.valueOf((byte) -1)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDateTimeValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDateTimeValueTest.java new file mode 100644 index 000000000..bc36c7507 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDateTimeValueTest.java @@ -0,0 +1,195 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; + +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseDateTimeValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testUpdate() { + Assert.assertEquals(ClickHouseDateTimeValue.ofNull(0).update(-1L).getValue(), + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC)); + Assert.assertEquals(ClickHouseDateTimeValue.ofNull(0).update(-1.1F).getValue(), + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC)); + Assert.assertEquals(ClickHouseDateTimeValue.ofNull(3).update(-1L).getValue(), + LocalDateTime.ofEpochSecond(-1L, 999000000, ZoneOffset.UTC)); + Assert.assertEquals(ClickHouseDateTimeValue.ofNull(3).update(-1.1F).getValue(), + LocalDateTime.ofEpochSecond(-2L, 900000000, ZoneOffset.UTC)); + + Assert.assertEquals(ClickHouseDateTimeValue.ofNull(9).update(new BigDecimal(BigInteger.ONE, 9)).getValue(), + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC)); + Assert.assertEquals( + ClickHouseDateTimeValue.ofNull(9).update(new BigDecimal(BigInteger.valueOf(-1L), 9)).getValue(), + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC)); + } + + @Test(groups = { "unit" }) + public void testValueWithoutScale() throws Exception { + // null value + checkNull(ClickHouseDateTimeValue.ofNull(0)); + checkNull(ClickHouseDateTimeValue.of(LocalDateTime.now(), 0).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseDateTimeValue.of(LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), 0), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "1970-01-01 00:00:00", // String + "'1970-01-01 00:00:00'", // SQL Expression + LocalTime.ofSecondOfDay(0L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + LocalDateTime.class, // Value class + new Object[] { LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC) }, // Array + new LocalDateTime[] { LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC) }, // typed + // Array + buildMap(new Object[] { 1 }, new Object[] { LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC) }), // Map + buildMap(new Object[] { 1 }, + new LocalDateTime[] { LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC) }), // typed + // Map + Arrays.asList(LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC)) // Tuple + ); + checkValue(ClickHouseDateTimeValue.of(LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), 0), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1970-01-01 00:00:01", // String + "'1970-01-01 00:00:01'", // SQL Expression + LocalTime.ofSecondOfDay(1L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + LocalDateTime.class, // Value class + new Object[] { LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC) }, // Array + new LocalDateTime[] { LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC) }, // typed + // Array + buildMap(new Object[] { 1 }, new Object[] { LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC) }), // Map + buildMap(new Object[] { 1 }, + new LocalDateTime[] { LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC) }), // typed + // Map + Arrays.asList(LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC)) // Tuple + ); + checkValue(ClickHouseDateTimeValue.of(LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), 0), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "1970-01-01 00:00:02", // String + "'1970-01-01 00:00:02'", // SQL Expression + LocalTime.ofSecondOfDay(2L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + LocalDateTime.class, // Value class + new Object[] { LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC) }, // Array + new LocalDateTime[] { LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC) }, // typed + // Array + buildMap(new Object[] { 1 }, new Object[] { LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC) }), // Map + buildMap(new Object[] { 1 }, + new LocalDateTime[] { LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC) }), // typed + // Map + Arrays.asList(LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC)) // Tuple + ); + } + + @Test(groups = { "unit" }) + public void testValueWithScale() throws Exception { + // null value + checkNull(ClickHouseDateTimeValue.ofNull(3)); + checkNull(ClickHouseDateTimeValue.of(LocalDateTime.now(), 9).resetToNullOrEmpty()); + + // non-null + LocalDateTime dateTime = LocalDateTime.ofEpochSecond(0L, 123456789, ZoneOffset.UTC); + checkValue(ClickHouseDateTimeValue.of(dateTime, 3), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0.123456789F, // float + 0.123456789D, // double + BigDecimal.valueOf(0L), // BigDecimal + BigDecimal.valueOf(0.123D), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + dateTime, // Object + LocalDate.ofEpochDay(0L), // Date + dateTime, // DateTime + dateTime, // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "1970-01-01 00:00:00.123456789", // String + "'1970-01-01 00:00:00.123456789'", // SQL Expression + LocalTime.ofNanoOfDay(123456789L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + LocalDateTime.class, // Value class + new Object[] { dateTime }, // Array + new LocalDateTime[] { dateTime }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { dateTime }), // Map + buildMap(new Object[] { 1 }, new LocalDateTime[] { dateTime }), // typed + // Map + Arrays.asList(dateTime) // Tuple + ); + Assert.assertEquals(ClickHouseDateTimeValue.of(dateTime, 3).asBigDecimal(4), BigDecimal.valueOf(0.1235D)); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDateValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDateValueTest.java new file mode 100644 index 000000000..d799d23a2 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDateValueTest.java @@ -0,0 +1,121 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.UUID; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseDateValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseDateValue.ofNull()); + checkNull(ClickHouseDateValue.of(LocalDate.now()).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseDateValue.of(0), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + LocalDate.ofEpochDay(0L), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "1970-01-01", // String + "'1970-01-01'", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + LocalDate.class, // Value class + new Object[] { LocalDate.ofEpochDay(0L) }, // Array + new LocalDate[] { LocalDate.ofEpochDay(0L) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { LocalDate.ofEpochDay(0L) }), // Map + buildMap(new Object[] { 1 }, new LocalDate[] { LocalDate.ofEpochDay(0L) }), // typed Map + Arrays.asList(LocalDate.ofEpochDay(0L)) // Tuple + ); + checkValue(ClickHouseDateValue.of(1L), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + LocalDate.ofEpochDay(1L), // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(1L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(1L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1970-01-02", // String + "'1970-01-02'", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + LocalDate.class, // Value class + new Object[] { LocalDate.ofEpochDay(1L) }, // Array + new LocalDate[] { LocalDate.ofEpochDay(1L) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { LocalDate.ofEpochDay(1L) }), // Map + buildMap(new Object[] { 1 }, new LocalDate[] { LocalDate.ofEpochDay(1L) }), // typed Map + Arrays.asList(LocalDate.ofEpochDay(1L)) // Tuple + ); + checkValue(ClickHouseDateValue.of(2L), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + LocalDate.ofEpochDay(2L), // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(2L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(2L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "1970-01-03", // String + "'1970-01-03'", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + LocalDate.class, // Value class + new Object[] { LocalDate.ofEpochDay(2L) }, // Array + new LocalDate[] { LocalDate.ofEpochDay(2L) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { LocalDate.ofEpochDay(2L) }), // Map + buildMap(new Object[] { 1 }, new LocalDate[] { LocalDate.ofEpochDay(2L) }), // typed Map + Arrays.asList(LocalDate.ofEpochDay(2L)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDoubleValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDoubleValueTest.java new file mode 100644 index 000000000..cf6090fe8 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseDoubleValueTest.java @@ -0,0 +1,254 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseDoubleValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseDoubleValue.ofNull()); + checkNull(ClickHouseDoubleValue.of(Double.MAX_VALUE).resetToNullOrEmpty()); + + // NaN and Infinity + checkValue(ClickHouseDoubleValue.of(Double.NaN), false, // isInfinity + true, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + Float.NaN, // float + Double.NaN, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + Double.NaN, // Object + LocalDate.ofEpochDay(0L), // Date + NumberFormatException.class, // DateTime + NumberFormatException.class, // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + NumberFormatException.class, // Inet6Address + "NaN", // String + "NaN", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + NumberFormatException.class, // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { Double.NaN }, // Array + new Double[] { Double.NaN }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Double.NaN }), // Map + buildMap(new Object[] { 1 }, new Double[] { Double.NaN }), // typed Map + Arrays.asList(Double.NaN) // Tuple + ); + checkValue(ClickHouseDoubleValue.of(Double.POSITIVE_INFINITY), true, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + 2147483647, // int + 9223372036854775807L, // long + Float.POSITIVE_INFINITY, // float + Double.POSITIVE_INFINITY, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + IllegalArgumentException.class, // Enum + Double.POSITIVE_INFINITY, // Object + DateTimeException.class, // Date + NumberFormatException.class, // DateTime + NumberFormatException.class, // DateTime(9) + Inet4Address.getAllByName("127.255.255.255")[0], // Inet4Address + NumberFormatException.class, // Inet6Address + "Infinity", // String + "Inf", // SQL Expression + DateTimeException.class, // Time + NumberFormatException.class, // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { Double.POSITIVE_INFINITY }, // Array + new Double[] { Double.POSITIVE_INFINITY }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Double.POSITIVE_INFINITY }), // Map + buildMap(new Object[] { 1 }, new Double[] { Double.POSITIVE_INFINITY }), // typed Map + Arrays.asList(Double.POSITIVE_INFINITY) // Tuple + ); + checkValue(ClickHouseDoubleValue.of(Double.NEGATIVE_INFINITY), true, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + -2147483648, // int + -9223372036854775808L, // long + Float.NEGATIVE_INFINITY, // float + Double.NEGATIVE_INFINITY, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + IllegalArgumentException.class, // Enum + Double.NEGATIVE_INFINITY, // Object + DateTimeException.class, // Date + NumberFormatException.class, // DateTime + NumberFormatException.class, // DateTime(9) + Inet4Address.getAllByName("128.0.0.0")[0], // Inet4Address + NumberFormatException.class, // Inet6Address + "-Infinity", // String + "-Inf", // SQL Expression + DateTimeException.class, // Time + NumberFormatException.class, // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { Double.NEGATIVE_INFINITY }, // Array + new Double[] { Double.NEGATIVE_INFINITY }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Double.NEGATIVE_INFINITY }), // Map + buildMap(new Object[] { 1 }, new Double[] { Double.NEGATIVE_INFINITY }), // typed Map + Arrays.asList(Double.NEGATIVE_INFINITY) // Tuple + ); + + // non-null + checkValue(ClickHouseDoubleValue.of(0D), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + 0D, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0.0", // String + "0.0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { 0D }, // Array + new Double[] { 0D }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 0D }), // Map + buildMap(new Object[] { 1 }, new Double[] { 0D }), // typed Map + Arrays.asList(Double.valueOf(0D)) // Tuple + ); + checkValue(ClickHouseDoubleValue.of(1D), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + 1D, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1.0", // String + "1.0", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { 1D }, // Array + new Double[] { 1D }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 1D }), // Map + buildMap(new Object[] { 1 }, new Double[] { 1D }), // typed Map + Arrays.asList(Double.valueOf(1D)) // Tuple + ); + checkValue(ClickHouseDoubleValue.of(2D), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + 2D, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2.0", // String + "2.0", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { 2D }, // Array + new Double[] { 2D }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 2D }), // Map + buildMap(new Object[] { 1 }, new Double[] { 2D }), // typed Map + Arrays.asList(Double.valueOf(2D)) // Tuple + ); + + checkValue(ClickHouseDoubleValue.of(-1D), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + -1D, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:ff")[0], // Inet6Address + "-1.0", // String + "-1.0", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Object.class, // Key class + Double.class, // Value class + new Object[] { -1D }, // Array + new Double[] { -1D }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { -1D }), // Map + buildMap(new Object[] { 1 }, new Double[] { -1D }), // typed Map + Arrays.asList(Double.valueOf(-1D)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseEnumValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseEnumValueTest.java new file mode 100644 index 000000000..bdfc88d0d --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseEnumValueTest.java @@ -0,0 +1,57 @@ +package com.clickhouse.client.data; + +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; +import com.clickhouse.client.ClickHouseFormat; + +public class ClickHouseEnumValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testCopy() { + sameValue(ClickHouseEnumValue.ofNull(), ClickHouseEnumValue.ofNull(), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.String), ClickHouseEnumValue.of(ClickHouseDataType.String), + 3, 9, Object.class, ClickHouseDataType.class, Object.class, Object.class); + ClickHouseEnumValue v = ClickHouseEnumValue.of(ClickHouseDataType.Array); + sameValue(v, v.copy(), 3, 9, Object.class, ClickHouseDataType.class, Object.class, Object.class); + } + + @Test(groups = { "unit" }) + public void testUpdate() { + sameValue(ClickHouseEnumValue.ofNull(), + ClickHouseEnumValue.ofNull().update(ClickHouseDataType.Date32).set(null), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.Date32), + ClickHouseEnumValue.ofNull().update(ClickHouseDataType.Date32), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.Date32), + ClickHouseEnumValue.of(ClickHouseFormat.Arrow).update(ClickHouseDataType.Date32), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update(false), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update(new boolean[] { false }), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update('\0'), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update((byte) 0), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update((short) 0), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update(0), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update(0L), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + sameValue(ClickHouseEnumValue.of(ClickHouseDataType.IntervalYear), + ClickHouseEnumValue.of(ClickHouseDataType.String).update("IntervalYear"), 3, 9, Object.class, + ClickHouseDataType.class, Object.class, Object.class); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseFloatValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseFloatValueTest.java new file mode 100644 index 000000000..428da210e --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseFloatValueTest.java @@ -0,0 +1,254 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseFloatValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseFloatValue.ofNull()); + checkNull(ClickHouseFloatValue.of(Float.MAX_VALUE).resetToNullOrEmpty()); + + // NaN and Infinity + checkValue(ClickHouseFloatValue.of(Float.NaN), false, // isInfinity + true, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + Float.NaN, // float + Double.NaN, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + Float.NaN, // Object + LocalDate.ofEpochDay(0L), // Date + NumberFormatException.class, // DateTime + NumberFormatException.class, // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + NumberFormatException.class, // Inet6Address + "NaN", // String + "NaN", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + NumberFormatException.class, // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { Float.NaN }, // Array + new Float[] { Float.NaN }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Float.NaN }), // Map + buildMap(new Object[] { 1 }, new Float[] { Float.NaN }), // typed Map + Arrays.asList(Float.NaN) // Tuple + ); + checkValue(ClickHouseFloatValue.of(Float.POSITIVE_INFINITY), true, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + 2147483647, // int + 9223372036854775807L, // long + Float.POSITIVE_INFINITY, // float + Double.POSITIVE_INFINITY, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + IllegalArgumentException.class, // Enum + Float.POSITIVE_INFINITY, // Object + DateTimeException.class, // Date + NumberFormatException.class, // DateTime + NumberFormatException.class, // DateTime(9) + Inet4Address.getAllByName("127.255.255.255")[0], // Inet4Address + NumberFormatException.class, // Inet6Address + "Infinity", // String + "Inf", // SQL Expression + DateTimeException.class, // Time + NumberFormatException.class, // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { Float.POSITIVE_INFINITY }, // Array + new Float[] { Float.POSITIVE_INFINITY }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Float.POSITIVE_INFINITY }), // Map + buildMap(new Object[] { 1 }, new Float[] { Float.POSITIVE_INFINITY }), // typed Map + Arrays.asList(Float.POSITIVE_INFINITY) // Tuple + ); + checkValue(ClickHouseFloatValue.of(Float.NEGATIVE_INFINITY), true, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + -2147483648, // int + -9223372036854775808L, // long + Float.NEGATIVE_INFINITY, // float + Double.NEGATIVE_INFINITY, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + IllegalArgumentException.class, // Enum + Float.NEGATIVE_INFINITY, // Object + DateTimeException.class, // Date + NumberFormatException.class, // DateTime + NumberFormatException.class, // DateTime(9) + Inet4Address.getAllByName("128.0.0.0")[0], // Inet4Address + NumberFormatException.class, // Inet6Address + "-Infinity", // String + "-Inf", // SQL Expression + DateTimeException.class, // Time + NumberFormatException.class, // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { Float.NEGATIVE_INFINITY }, // Array + new Float[] { Float.NEGATIVE_INFINITY }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Float.NEGATIVE_INFINITY }), // Map + buildMap(new Object[] { 1 }, new Float[] { Float.NEGATIVE_INFINITY }), // typed Map + Arrays.asList(Float.NEGATIVE_INFINITY) // Tuple + ); + + // non-null + checkValue(ClickHouseFloatValue.of(0F), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + 0F, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0.0", // String + "0.0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { 0F }, // Array + new Float[] { 0F }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 0F }), // Map + buildMap(new Object[] { 1 }, new Float[] { 0F }), // typed Map + Arrays.asList(Float.valueOf(0F)) // Tuple + ); + checkValue(ClickHouseFloatValue.of(1F), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + 1F, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1.0", // String + "1.0", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { 1F }, // Array + new Float[] { 1F }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 1F }), // Map + buildMap(new Object[] { 1 }, new Float[] { 1F }), // typed Map + Arrays.asList(Float.valueOf(1F)) // Tuple + ); + checkValue(ClickHouseFloatValue.of(2F), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + 2F, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2.0", // String + "2.0", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { 2F }, // Array + new Float[] { 2F }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 2F }), // Map + buildMap(new Object[] { 1 }, new Float[] { 2F }), // typed Map + Arrays.asList(Float.valueOf(2F)) // Tuple + ); + + checkValue(ClickHouseFloatValue.of(-1F), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + -1F, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:ff")[0], // Inet6Address + "-1.0", // String + "-1.0", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Object.class, // Key class + Float.class, // Value class + new Object[] { -1F }, // Array + new Float[] { -1F }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { -1F }), // Map + buildMap(new Object[] { 1 }, new Float[] { -1F }), // typed Map + Arrays.asList(Float.valueOf(-1F)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseGeoPointValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseGeoPointValueTest.java new file mode 100644 index 000000000..7a969ea1c --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseGeoPointValueTest.java @@ -0,0 +1,33 @@ +package com.clickhouse.client.data; + +import java.util.Arrays; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseGeoPointValueTest { + @Test(groups = { "unit" }) + public void testUpdate() { + ClickHouseGeoPointValue v = ClickHouseGeoPointValue.of(new double[] { 1D, 2D }); + Assert.assertFalse(v.isNullOrEmpty()); + Assert.assertEquals(v.update(new byte[] { (byte) 1, (byte) -2 }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new Byte[] { (byte) 1, (byte) -2 }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new short[] { (short) 1, (short) -2 }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new Short[] { (short) 1, (short) -2 }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new int[] { 1, -2 }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new Integer[] { 1, -2 }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new long[] { 1L, -2L }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new Long[] { 1L, -2L }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new float[] { 1F, -2F }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new Float[] { 1F, -2F }).getValue(), new double[] { 1D, -2D }); + Assert.assertEquals(v.update(new double[] { 233.3D, -233.33D }).getValue(), new double[] { 233.3D, -233.33D }); + Assert.assertEquals(v.update(new Double[] { 233.3D, -233.33D }).getValue(), new double[] { 233.3D, -233.33D }); + Assert.assertEquals(v.update(new Object[] { 233.3D, -233.33D }).getValue(), new double[] { 233.3D, -233.33D }); + Assert.assertEquals(v.update(Arrays.asList(233.3D, -233.33D)).getValue(), new double[] { 233.3D, -233.33D }); + Assert.assertThrows(UnsupportedOperationException.class, () -> v.update(Object.class)); + Assert.assertThrows(UnsupportedOperationException.class, () -> v.update(Enum.class)); + Assert.assertThrows(UnsupportedOperationException.class, () -> v.update(3)); + Assert.assertThrows(UnsupportedOperationException.class, () -> v.update("1")); + Assert.assertThrows(UnsupportedOperationException.class, () -> v.update(ClickHouseDataType.Array)); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseIntegerValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseIntegerValueTest.java new file mode 100644 index 000000000..daffb3e01 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseIntegerValueTest.java @@ -0,0 +1,155 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseIntegerValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseIntegerValue.ofNull()); + checkNull(ClickHouseIntegerValue.of(Integer.MAX_VALUE).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseIntegerValue.of(0), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + (int) 0, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0", // String + "0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + Integer.class, // Value class + new Object[] { 0 }, // Array + new Integer[] { 0 }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 0 }), // Map + buildMap(new Object[] { 1 }, new Integer[] { 0 }), // typed Map + Arrays.asList(Integer.valueOf(0)) // Tuple + ); + checkValue(ClickHouseIntegerValue.of(1), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + (int) 1, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1", // String + "1", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + Integer.class, // Value class + new Object[] { 1 }, // Array + new Integer[] { 1 }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 1 }), // Map + buildMap(new Object[] { 1 }, new Integer[] { 1 }), // typed Map + Arrays.asList(Integer.valueOf(1)) // Tuple + ); + checkValue(ClickHouseIntegerValue.of(2), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + (int) 2, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2", // String + "2", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + Integer.class, // Value class + new Object[] { 2 }, // Array + new Integer[] { 2 }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 2 }), // Map + buildMap(new Object[] { 1 }, new Integer[] { 2 }), // typed Map + Arrays.asList(Integer.valueOf(2)) // Tuple + ); + + checkValue(ClickHouseIntegerValue.of(-1), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + (int) -1, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:ff")[0], // Inet6Address + "-1", // String + "-1", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Object.class, // Key class + Integer.class, // Value class + new Object[] { -1 }, // Array + new Integer[] { -1 }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { -1 }), // Map + buildMap(new Object[] { 1 }, new Integer[] { -1 }), // typed Map + Arrays.asList(Integer.valueOf(-1)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseLZ4OutputStreamTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseLZ4OutputStreamTest.java new file mode 100644 index 000000000..be2d04bf9 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseLZ4OutputStreamTest.java @@ -0,0 +1,208 @@ +package com.clickhouse.client.data; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHouseLZ4OutputStreamTest { + private byte[] genCompressedByts(int b, int length, int blockSize) throws IOException { + ByteArrayOutputStream bas = new ByteArrayOutputStream(blockSize * 512); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, blockSize)) { + for (int i = 0; i < length; i++) { + out.write(b); + } + out.flush(); + } + + byte[] bytes = bas.toByteArray(); + bas.close(); + return bytes; + } + + @Test(groups = { "unit" }) + public void testWrite() throws IOException { + ByteArrayOutputStream bas = new ByteArrayOutputStream(64); + + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 2)) { + byte[] bytes = new byte[] { (byte) -36, (byte) -86, (byte) 31, (byte) 113, (byte) -106, (byte) 44, + (byte) 99, (byte) 96, (byte) 112, (byte) -7, (byte) 47, (byte) 15, (byte) -63, (byte) 39, + (byte) -73, (byte) -104, (byte) -126, (byte) 12, (byte) 0, (byte) 0, (byte) 0, (byte) 2, (byte) 0, + (byte) 0, (byte) 0, (byte) 32, (byte) 1, (byte) 2 }; + out.write(1); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + out.write(2); + Assert.assertEquals(bas.toByteArray(), bytes); + out.write(3); + Assert.assertEquals(bas.toByteArray(), bytes); + out.flush(); + Assert.assertEquals(bas.toByteArray(), + new byte[] { (byte) -36, (byte) -86, (byte) 31, (byte) 113, (byte) -106, (byte) 44, (byte) 99, + (byte) 96, (byte) 112, (byte) -7, (byte) 47, (byte) 15, (byte) -63, (byte) 39, (byte) -73, + (byte) -104, (byte) -126, (byte) 12, (byte) 0, (byte) 0, (byte) 0, (byte) 2, (byte) 0, + (byte) 0, (byte) 0, (byte) 32, (byte) 1, (byte) 2, (byte) 64, (byte) -39, (byte) 21, + (byte) 50, (byte) -77, (byte) -124, (byte) 25, (byte) 73, (byte) -59, (byte) 9, (byte) 112, + (byte) -38, (byte) 12, (byte) 99, (byte) 71, (byte) 74, (byte) -126, (byte) 11, (byte) 0, + (byte) 0, (byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 0, (byte) 16, (byte) 3 }); + bas.close(); + } + } + + @Test(groups = { "unit" }) + public void testWriteBytes() throws IOException { + Assert.assertThrows(NullPointerException.class, new Assert.ThrowingRunnable() { + @Override + public void run() throws Throwable { + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(new ByteArrayOutputStream(), 3)) { + out.write(null); + } + } + }); + + ByteArrayOutputStream bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + out.write(new byte[0]); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + out.flush(); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + + byte[] bytes = new byte[] { (byte) 13, (byte) 13 }; + out.write(bytes); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + out.flush(); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 2, 3)); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + byte[] bytes = new byte[] { (byte) 13, (byte) 13, (byte) 13 }; + out.write(bytes); + byte[] expected = genCompressedByts(13, 3, 3); + Assert.assertEquals(bas.toByteArray(), expected); + out.flush(); + Assert.assertEquals(bas.toByteArray(), expected); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + byte[] bytes = new byte[] { (byte) 13, (byte) 13, (byte) 13, (byte) 13 }; + out.write(bytes); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 3, 3)); + out.flush(); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 4, 3)); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + byte[] bytes = new byte[] { (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13 }; + out.write(bytes); + byte[] expected = genCompressedByts(13, 6, 3); + Assert.assertEquals(bas.toByteArray(), expected); + out.flush(); + Assert.assertEquals(bas.toByteArray(), expected); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + byte[] bytes = new byte[] { (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13 }; + out.write(bytes); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 6, 3)); + out.flush(); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 7, 3)); + bas.close(); + } + } + + @Test(groups = { "unit" }) + public void testWriteBytesWithOffset() throws IOException { + Assert.assertThrows(NullPointerException.class, new Assert.ThrowingRunnable() { + @Override + public void run() throws Throwable { + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(new ByteArrayOutputStream(), 3)) { + out.write(null, 0, 1); + } + } + }); + Assert.assertThrows(IndexOutOfBoundsException.class, new Assert.ThrowingRunnable() { + @Override + public void run() throws Throwable { + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(new ByteArrayOutputStream(), 3)) { + out.write(new byte[0], 0, 1); + } + } + }); + Assert.assertThrows(IndexOutOfBoundsException.class, new Assert.ThrowingRunnable() { + @Override + public void run() throws Throwable { + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(new ByteArrayOutputStream(), 3)) { + out.write(new byte[0], -1, 0); + } + } + }); + Assert.assertThrows(IndexOutOfBoundsException.class, new Assert.ThrowingRunnable() { + @Override + public void run() throws Throwable { + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(new ByteArrayOutputStream(), 3)) { + out.write(new byte[1], 1, 1); + } + } + }); + + final byte[] bytes = new byte[] { (byte) 0, (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13, (byte) 13, + (byte) 13, (byte) 0 }; + ByteArrayOutputStream bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + out.write(bytes, 1, 0); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + out.flush(); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + out.write(bytes, 1, 2); + Assert.assertEquals(bas.toByteArray(), new byte[0]); + out.flush(); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 2, 3)); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + out.write(bytes, 1, 3); + byte[] expected = genCompressedByts(13, 3, 3); + Assert.assertEquals(bas.toByteArray(), expected); + out.flush(); + Assert.assertEquals(bas.toByteArray(), expected); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + out.write(bytes, 1, 4); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 3, 3)); + out.flush(); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 4, 3)); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + out.write(bytes, 1, 6); + byte[] expected = genCompressedByts(13, 6, 3); + Assert.assertEquals(bas.toByteArray(), expected); + out.flush(); + Assert.assertEquals(bas.toByteArray(), expected); + bas.close(); + } + + bas = new ByteArrayOutputStream(64); + try (ClickHouseLZ4OutputStream out = new ClickHouseLZ4OutputStream(bas, 3)) { + out.write(bytes, 1, 7); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 6, 3)); + out.flush(); + Assert.assertEquals(bas.toByteArray(), genCompressedByts(13, 7, 3)); + bas.close(); + } + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseLongValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseLongValueTest.java new file mode 100644 index 000000000..cfbc191ab --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseLongValueTest.java @@ -0,0 +1,306 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; +import org.junit.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseValues; + +public class ClickHouseLongValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testSignedValue() throws Exception { + // null value + checkNull(ClickHouseLongValue.ofNull(false)); + checkNull(ClickHouseLongValue.of(Long.MAX_VALUE, false).resetToNullOrEmpty()); + checkNull(ClickHouseLongValue.of(Long.MIN_VALUE, false).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseLongValue.of(0, false), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + 0L, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0", // String + "0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { 0L }, // Array + new Long[] { 0L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 0L }), // Map + buildMap(new Object[] { 1 }, new Long[] { 0L }), // typed Map + Arrays.asList(Long.valueOf(0L)) // Tuple + ); + checkValue(ClickHouseLongValue.of(1, false), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + 1L, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1", // String + "1", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { 1L }, // Array + new Long[] { 1L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 1L }), // Map + buildMap(new Object[] { 1 }, new Long[] { 1L }), // typed Map + Arrays.asList(Long.valueOf(1L)) // Tuple + ); + checkValue(ClickHouseLongValue.of(2, false), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + 2L, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2", // String + "2", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { 2L }, // Array + new Long[] { 2L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 2L }), // Map + buildMap(new Object[] { 1 }, new Long[] { 2L }), // typed Map + Arrays.asList(Long.valueOf(2L)) // Tuple + ); + + ClickHouseLongValue v = ClickHouseLongValue.of(-1, false); + Assert.assertFalse(v.isUnsigned()); + checkValue(ClickHouseLongValue.of(-1, false), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + -1L, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:ff")[0], // Inet6Address + "-1", // String + "-1", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { -1L }, // Array + new Long[] { -1L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { -1L }), // Map + buildMap(new Object[] { 1 }, new Long[] { -1L }), // typed Map + Arrays.asList(Long.valueOf(-1L)) // Tuple + ); + } + + @Test(groups = { "unit" }) + public void testUnsignedValue() throws Exception { + // null value + checkNull(ClickHouseLongValue.ofNull(true)); + checkNull(ClickHouseLongValue.of(Long.MAX_VALUE, true).resetToNullOrEmpty()); + checkNull(ClickHouseLongValue.of(Long.MIN_VALUE, true).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseLongValue.of(0, true), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + 0L, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0", // String + "0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { 0L }, // Array + new Long[] { 0L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 0L }), // Map + buildMap(new Object[] { 1 }, new Long[] { 0L }), // typed Map + Arrays.asList(Long.valueOf(0L)) // Tuple + ); + checkValue(ClickHouseLongValue.of(1, true), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + 1L, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1", // String + "1", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { 1L }, // Array + new Long[] { 1L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 1L }), // Map + buildMap(new Object[] { 1 }, new Long[] { 1L }), // typed Map + Arrays.asList(Long.valueOf(1L)) // Tuple + ); + checkValue(ClickHouseLongValue.of(2, true), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + 2L, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2", // String + "2", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { 2L }, // Array + new Long[] { 2L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { 2L }), // Map + buildMap(new Object[] { 1 }, new Long[] { 2L }), // typed Map + Arrays.asList(Long.valueOf(2L)) // Tuple + ); + + ClickHouseLongValue v = ClickHouseLongValue.of(-1, true); + Assert.assertTrue(v.isUnsigned()); + BigInteger bigInt = new BigInteger(1, new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }); + checkValue(v, false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + new BigDecimal(bigInt), // BigDecimal + new BigDecimal(bigInt, 3), // BigDecimal + bigInt, // BigInteger + IllegalArgumentException.class, // Enum + -1L, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(new BigDecimal(bigInt, 9).longValue(), + new BigDecimal(bigInt, 9).remainder(BigDecimal.ONE).multiply(ClickHouseValues.NANOS).intValue(), + ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:ffff:ffff:ffff:ffff")[0], // Inet6Address + "18446744073709551615", // String + "18446744073709551615", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Object.class, // Key class + Long.class, // Value class + new Object[] { -1L }, // Array + new Long[] { -1L }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { -1L }), // Map + buildMap(new Object[] { 1 }, new Long[] { -1L }), // typed Map + Arrays.asList(Long.valueOf(-1L)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseNestedValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseNestedValueTest.java new file mode 100644 index 000000000..d08bbd884 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseNestedValueTest.java @@ -0,0 +1,147 @@ +package com.clickhouse.client.data; + +import java.time.LocalDate; +import java.util.Arrays; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseColumn; + +public class ClickHouseNestedValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testMultipleValues() throws Exception { + // single type + checkValue( + ClickHouseNestedValue.of(ClickHouseColumn.parse("a String not null, b String null"), + new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }), + UnsupportedOperationException.class, // isInfinity + UnsupportedOperationException.class, // isNan + false, // isNull + UnsupportedOperationException.class, // boolean + UnsupportedOperationException.class, // byte + UnsupportedOperationException.class, // short + UnsupportedOperationException.class, // int + UnsupportedOperationException.class, // long + UnsupportedOperationException.class, // float + UnsupportedOperationException.class, // double + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigInteger + UnsupportedOperationException.class, // Enum + new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }, // Object + UnsupportedOperationException.class, // Date + UnsupportedOperationException.class, // DateTime + UnsupportedOperationException.class, // DateTime(9) + UnsupportedOperationException.class, // Inet4Address + UnsupportedOperationException.class, // Inet6Address + "[[a1, a2], [null, b2]]", // String + "['a1','a2'],[NULL,'b2']", // SQL Expression + UnsupportedOperationException.class, // Time + UnsupportedOperationException.class, // UUID + String.class, // Key class + Object[].class, // Value class + new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }, // Array + new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }, // typed Array + buildMap(new Object[] { "a", "b" }, + new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }), // Map + buildMap(new String[] { "a", "b" }, + new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }), // typed Map + Arrays.asList(new Object[][] { new String[] { "a1", "a2" }, new String[] { null, "b2" } }) // Tuple + ); + + // mixed types + checkValue( + ClickHouseNestedValue.of(ClickHouseColumn.parse("a Nullable(UInt8), b Date"), + new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }), + UnsupportedOperationException.class, // isInfinity + UnsupportedOperationException.class, // isNan + false, // isNull + UnsupportedOperationException.class, // boolean + UnsupportedOperationException.class, // byte + UnsupportedOperationException.class, // short + UnsupportedOperationException.class, // int + UnsupportedOperationException.class, // long + UnsupportedOperationException.class, // float + UnsupportedOperationException.class, // double + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigInteger + UnsupportedOperationException.class, // Enum + new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }, // Object + UnsupportedOperationException.class, // Date + UnsupportedOperationException.class, // DateTime + UnsupportedOperationException.class, // DateTime(9) + UnsupportedOperationException.class, // Inet4Address + UnsupportedOperationException.class, // Inet6Address + "[[1, null], [1970-01-02, 1970-01-03]]", // String + "[1,NULL],['1970-01-02','1970-01-03']", // SQL Expression + UnsupportedOperationException.class, // Time + UnsupportedOperationException.class, // UUID + String.class, // Key class + Object[].class, // Value class + new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }, // Array + new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }, // typed Array + buildMap(new Object[] { "a", "b" }, + new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }), // Map + buildMap(new String[] { "a", "b" }, + new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }), // typed Map + Arrays.asList(new Object[][] { new Short[] { (short) 1, null }, + new LocalDate[] { LocalDate.ofEpochDay(1L), LocalDate.ofEpochDay(2L) } }) // Tuple + ); + } + + @Test(groups = { "unit" }) + public void testSingleValue() throws Exception { + // null value + checkNull(ClickHouseNestedValue.ofEmpty(ClickHouseColumn.parse("a Nullable(String)")), false, 3, 9); + checkNull(ClickHouseNestedValue.ofEmpty(ClickHouseColumn.parse("a String not null")), false, 3, 9); + checkNull( + ClickHouseNestedValue.ofEmpty(ClickHouseColumn.parse("a String null")).update("x").resetToNullOrEmpty(), + false, 3, 9); + checkNull( + ClickHouseNestedValue.of(ClickHouseColumn.parse("a String not null, b Int8"), + new Object[][] { new String[] { "a" }, new Byte[] { (byte) 1 } }).resetToNullOrEmpty(), + false, 3, 9); + + checkValue( + ClickHouseNestedValue.of(ClickHouseColumn.parse("a Int8 not null"), + new Object[][] { new Byte[] { (byte) 1 } }), + UnsupportedOperationException.class, // isInfinity + UnsupportedOperationException.class, // isNan + false, // isNull + UnsupportedOperationException.class, // boolean + UnsupportedOperationException.class, // byte + UnsupportedOperationException.class, // short + UnsupportedOperationException.class, // int + UnsupportedOperationException.class, // long + UnsupportedOperationException.class, // float + UnsupportedOperationException.class, // double + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigInteger + UnsupportedOperationException.class, // Enum + new Object[][] { new Byte[] { (byte) 1 } }, // Object + UnsupportedOperationException.class, // Date + UnsupportedOperationException.class, // DateTime + UnsupportedOperationException.class, // DateTime(9) + UnsupportedOperationException.class, // Inet4Address + UnsupportedOperationException.class, // Inet6Address + "[[1]]", // String + "[1]", // SQL Expression + UnsupportedOperationException.class, // Time + UnsupportedOperationException.class, // UUID + String.class, // Key class + Object[].class, // Value class + new Object[][] { new Byte[] { Byte.valueOf((byte) 1) } }, // Array + new Object[][] { new Byte[] { Byte.valueOf((byte) 1) } }, // typed Array + buildMap(new Object[] { "a" }, new Object[][] { new Byte[] { Byte.valueOf((byte) 1) } }), // Map + buildMap(new String[] { "a" }, new Object[][] { new Byte[] { Byte.valueOf((byte) 1) } }), // typed Map + Arrays.asList(new Object[][] { new Byte[] { Byte.valueOf((byte) 1) } }) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHousePipedStreamTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHousePipedStreamTest.java new file mode 100644 index 000000000..e0172bf94 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHousePipedStreamTest.java @@ -0,0 +1,260 @@ +package com.clickhouse.client.data; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ClickHousePipedStreamTest { + @Test(groups = { "unit" }) + public void testRead() throws Exception { + ClickHousePipedStream stream = new ClickHousePipedStream(4, 3, 1); + Assert.assertEquals(stream.queue.size(), 0); + try (InputStream in = stream.getInput()) { + in.read(); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + + ByteBuffer buf = ByteBuffer.allocate(1).put(new byte[] { (byte) 3 }); + stream.queue.put((ByteBuffer) buf.rewind()); + Assert.assertEquals(stream.queue.size(), 1); + try (InputStream in = stream.getInput()) { + Assert.assertEquals(in.read(), 3); + in.read(); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + + buf = ByteBuffer.allocate(2).put(new byte[] { (byte) 3, (byte) 4 }); + stream.queue.put((ByteBuffer) buf.rewind()); + Assert.assertEquals(stream.queue.size(), 1); + try (InputStream in = stream.getInput()) { + Assert.assertEquals(in.read(), 3); + Assert.assertEquals(in.read(), 4); + in.read(); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + + stream.queue.clear(); + stream.queue.put(ClickHousePipedStream.EMPTY); + Assert.assertEquals(stream.queue.size(), 1); + try (InputStream in = stream.getInput()) { + Assert.assertEquals(in.read(), -1); + } + + stream.queue.put((ByteBuffer) buf.rewind()); + stream.queue.put(buf); + stream.queue.put(ClickHousePipedStream.EMPTY); + Assert.assertEquals(stream.queue.size(), 3); + try (InputStream in = stream.getInput()) { + Assert.assertEquals(in.read(), 3); + Assert.assertEquals(in.read(), 4); + Assert.assertEquals(in.read(), -1); + } + + try (InputStream in = stream.getInput()) { + in.close(); + in.read(); + Assert.fail("Read should fail"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("closed") > 0); + } + } + + @Test(groups = { "unit" }) + public void testReadBytes() throws Exception { + ClickHousePipedStream stream = new ClickHousePipedStream(4, 3, 1); + Assert.assertEquals(stream.queue.size(), 0); + byte[] bytes = new byte[3]; + try (InputStream in = stream.getInput()) { + in.read(bytes); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + try (InputStream in = stream.getInput()) { + in.read(bytes, 0, 1); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + + ByteBuffer buf = ByteBuffer.allocate(2).put(new byte[] { (byte) 3, (byte) 4 }); + stream.queue.put((ByteBuffer) buf.rewind()); + Assert.assertEquals(stream.queue.size(), 1); + try (InputStream in = stream.getInput()) { + in.read(bytes); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + stream.queue.put((ByteBuffer) buf.rewind()); + Assert.assertEquals(stream.queue.size(), 1); + try (InputStream in = stream.getInput()) { + Assert.assertEquals(in.read(bytes, 0, 2), 2); + in.read(bytes, 0, 1); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + stream.queue.put((ByteBuffer) buf.rewind()); + Assert.assertEquals(stream.queue.size(), 1); + try (InputStream in = stream.getInput()) { + in.read(bytes, 0, 3); + Assert.fail("Read should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Read timed out") == 0); + } + + buf = ByteBuffer.allocate(2).put(new byte[] { (byte) 3, (byte) 4 }); + stream.queue.put((ByteBuffer) buf.rewind()); + stream.queue.put(ClickHousePipedStream.EMPTY); + Assert.assertEquals(stream.queue.size(), 2); + try (InputStream in = stream.getInput()) { + Assert.assertEquals(in.read(bytes, 0, 3), 2); + Assert.assertEquals(in.read(bytes, 0, 1), -1); + Assert.assertEquals(in.read(bytes, 0, 2), -1); + } + + try (InputStream in = stream.getInput()) { + in.close(); + in.read(bytes, 0, 3); + Assert.fail("Read should fail"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("closed") > 0); + } + } + + @Test(groups = { "unit" }) + public void testWrite() throws Exception { + ClickHousePipedStream stream = new ClickHousePipedStream(2, 3, 2); + Assert.assertEquals(stream.queue.size(), 0); + try (OutputStream out = stream) { + out.write(5); + Assert.assertEquals(stream.queue.size(), 0); + out.write(6); + Assert.assertEquals(stream.queue.size(), 0); + out.write(7); + Assert.assertEquals(stream.queue.size(), 1); + out.flush(); + Assert.assertEquals(stream.queue.size(), 2); + Assert.assertEquals(stream.queue.take().array(), new byte[] { (byte) 5, (byte) 6 }); + Assert.assertEquals(stream.queue.take().array(), new byte[] { (byte) 7, (byte) 0 }); + } + + stream = new ClickHousePipedStream(1, 1, 2); + Assert.assertEquals(stream.queue.size(), 0); + try (OutputStream out = stream) { + out.write(5); + Assert.assertEquals(stream.queue.size(), 0); + out.write(6); + Assert.assertEquals(stream.queue.size(), 1); + out.write(7); + Assert.fail("Write should be timed out"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("Write timed out") == 0); + } + + Assert.assertEquals(stream.queue.size(), 1); + stream.queue.clear(); + Assert.assertEquals(stream.queue.size(), 0); + try (OutputStream out = stream) { + out.close(); + out.write(1); + Assert.fail("Write should fail"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("closed") > 0); + } + } + + @Test(groups = { "unit" }) + public void testWriteBytes() throws Exception { + ClickHousePipedStream stream = new ClickHousePipedStream(2, 3, 2); + Assert.assertEquals(stream.queue.size(), 0); + try (OutputStream out = stream) { + out.write(new byte[] { (byte) 9, (byte) 10 }); + Assert.assertEquals(stream.queue.size(), 0); + out.flush(); + Assert.assertEquals(stream.queue.size(), 1); + out.write(new byte[] { (byte) 11, (byte) 12 }, 1, 1); + Assert.assertEquals(stream.queue.size(), 1); + out.flush(); + Assert.assertEquals(stream.queue.size(), 2); + Assert.assertEquals(stream.queue.take().array(), new byte[] { (byte) 9, (byte) 10 }); + Assert.assertEquals(stream.queue.take().array(), new byte[] { (byte) 12, (byte) 0 }); + } + + try (OutputStream out = stream) { + out.close(); + out.write(new byte[] { (byte) 13, (byte) 14 }, 0, 1); + Assert.fail("Write should fail"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().indexOf("closed") > 0); + } + } + + @Test(groups = { "unit" }) + public void testPipedStream() throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(2); + for (int bufferSize = -1; bufferSize < 10; bufferSize++) { + for (int queueLength = -1; queueLength < 10; queueLength++) { + ClickHousePipedStream stream = new ClickHousePipedStream(bufferSize, queueLength, 10000); + try (InputStream in = stream.getInput(); OutputStream out = stream) { + int count = 10000; + CountDownLatch latch = new CountDownLatch(count + 1); + executor.execute(() -> { + for (int i = 0; i < count; i++) { + byte[] bytes = new byte[] { (byte) (0xFF & i), (byte) (0xFF & i + 1), + (byte) (0xFF & i + 2) }; + try { + out.write(bytes); + } catch (IOException e) { + Assert.fail("Failed to write", e); + } + } + + try { + out.close(); + } catch (IOException e) { + Assert.fail("Failed to write", e); + } + }); + + executor.execute(() -> { + for (int i = 0; i < count; i++) { + byte[] bytes = new byte[] { (byte) (0xFF & i), (byte) (0xFF & i + 1), + (byte) (0xFF & i + 2) }; + byte[] b = new byte[bytes.length]; + try { + Assert.assertEquals(in.read(b), b.length); + latch.countDown(); + Assert.assertEquals(b, bytes); + } catch (IOException e) { + Assert.fail("Failed to read", e); + } + } + + try { + Assert.assertEquals(in.read(), -1); + latch.countDown(); + } catch (IOException e) { + Assert.fail("Failed to read EOF", e); + } + }); + + latch.await(); + } + } + } + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessorTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessorTest.java new file mode 100644 index 000000000..0eb7b27f8 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessorTest.java @@ -0,0 +1,153 @@ +package com.clickhouse.client.data; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetAddress; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseValue; + +public class ClickHouseRowBinaryProcessorTest { + private ClickHouseRowBinaryProcessor newProcessor(int... bytes) throws IOException { + return new ClickHouseRowBinaryProcessor(new ClickHouseConfig(), BinaryStreamUtilsTest.generateInput(bytes), + null, Collections.emptyList(), null); + } + + @Test(groups = { "unit" }) + public void testDeserializeArray() throws IOException { + + ClickHouseValue value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("a", "Array(UInt8)"), null, BinaryStreamUtilsTest.generateInput(2, 1, 2)); + Assert.assertTrue(value instanceof ClickHouseArrayValue); + Short[] shortArray = value.asObject(Short[].class); + Assert.assertEquals(shortArray.length, 2); + Assert.assertEquals(shortArray[0], Short.valueOf("1")); + Assert.assertEquals(shortArray[1], Short.valueOf("2")); + + value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("a", "Array(Nullable(Int8))"), null, + BinaryStreamUtilsTest.generateInput(2, 0, 1, 0, 2)); + Assert.assertTrue(value instanceof ClickHouseArrayValue); + Byte[] byteArray = value.asObject(Byte[].class); + Assert.assertEquals(byteArray.length, 2); + Assert.assertEquals(byteArray[0], Byte.valueOf("1")); + Assert.assertEquals(byteArray[1], Byte.valueOf("2")); + + value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("a", "Array(Array(UInt8))"), null, BinaryStreamUtilsTest.generateInput(1, 2, 1, 2)); + Assert.assertTrue(value instanceof ClickHouseArrayValue); + Object[] array = (Object[]) value.asObject(); + Assert.assertEquals(array.length, 1); + shortArray = (Short[]) array[0]; + Assert.assertEquals(shortArray.length, 2); + Assert.assertEquals(shortArray[0], Short.valueOf("1")); + Assert.assertEquals(shortArray[1], Short.valueOf("2")); + + // SELECT arrayZip(['a', 'b', 'c'], [3, 2, 1]) + value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("a", "Array(Tuple(String, UInt8))"), null, + BinaryStreamUtilsTest.generateInput(3, 1, 0x61, 3, 1, 0x62, 2, 1, 0x63, 1)); + Assert.assertTrue(value instanceof ClickHouseArrayValue); + array = (Object[]) value.asObject(); + Assert.assertEquals(array.length, 3); + Assert.assertEquals(((List) array[0]).size(), 2); + Assert.assertEquals(((List) array[0]).get(0), "a"); + Assert.assertEquals(((List) array[0]).get(1), Short.valueOf("3")); + Assert.assertEquals(((List) array[1]).size(), 2); + Assert.assertEquals(((List) array[1]).get(0), "b"); + Assert.assertEquals(((List) array[1]).get(1), Short.valueOf("2")); + Assert.assertEquals(((List) array[2]).size(), 2); + Assert.assertEquals(((List) array[2]).get(0), "c"); + Assert.assertEquals(((List) array[2]).get(1), Short.valueOf("1")); + + // insert into x values([{ 'a' : (null, 3), 'b' : (1, 2), 'c' : (2, 1)}]) + value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("a", "Array(Map(String, Tuple(Nullable(UInt8), UInt16)))"), null, + BinaryStreamUtilsTest.generateInput(1, 3, 1, 0x61, 1, 3, 0, 1, 0x62, 0, 1, 2, 0, 1, 0x63, 0, 2, 1, 0)); + Assert.assertTrue(value instanceof ClickHouseArrayValue); + array = (Object[]) value.asObject(); + Assert.assertEquals(array.length, 1); + Map> map = (Map>) array[0]; + Assert.assertEquals(map.size(), 3); + List l = map.get("a"); + Assert.assertEquals(l.size(), 2); + Assert.assertEquals(l.get(0), null); + Assert.assertEquals(l.get(1), 3); + l = map.get("b"); + Assert.assertEquals(l.size(), 2); + Assert.assertEquals(l.get(0), Short.valueOf("1")); + Assert.assertEquals(l.get(1), 2); + l = map.get("c"); + Assert.assertEquals(l.size(), 2); + Assert.assertEquals(l.get(0), Short.valueOf("2")); + Assert.assertEquals(l.get(1), 1); + } + + @Test(groups = { "unit" }) + public void testDeserializeMap() throws IOException { + ClickHouseValue value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("m", "Map(UInt8, UInt8)"), null, + BinaryStreamUtilsTest.generateInput(2, 2, 2, 1, 1)); + Assert.assertTrue(value instanceof ClickHouseMapValue); + Map map = (Map) value.asObject(); + Assert.assertEquals(map.size(), 2); + Assert.assertEquals(map.get((short) 2), (short) 2); + Assert.assertEquals(map.get((short) 1), (short) 1); + + value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("m", "Map(String, UInt32)"), null, + BinaryStreamUtilsTest.generateInput(2, 1, 0x32, 2, 0, 0, 0, 1, 0x31, 1, 0, 0, 0, 0)); + Assert.assertTrue(value instanceof ClickHouseMapValue); + map = (Map) value.asObject(); + Assert.assertEquals(map.size(), 2); + Assert.assertEquals(map.get("2"), 2L); + Assert.assertEquals(map.get("1"), 1L); + } + + @Test(groups = { "unit" }) + public void testDeserializeNested() throws IOException { + ClickHouseValue value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("n", "Nested(n1 UInt8, n2 Nullable(String), n3 Int16)"), null, + BinaryStreamUtilsTest.generateInput(1, 1, 1, 0, 1, 0x32, 1, 3, 0)); + Assert.assertTrue(value instanceof ClickHouseNestedValue); + + List columns = ((ClickHouseNestedValue) value).getColumns(); + Object[][] values = (Object[][]) value.asObject(); + Assert.assertEquals(columns.size(), 3); + Assert.assertEquals(columns.get(0).getColumnName(), "n1"); + Assert.assertEquals(columns.get(1).getColumnName(), "n2"); + Assert.assertEquals(columns.get(2).getColumnName(), "n3"); + Assert.assertEquals(values.length, 3); + Assert.assertEquals(values[0], new Short[] { Short.valueOf("1") }); + Assert.assertEquals(values[1], new String[] { "2" }); + Assert.assertEquals(values[2], new Short[] { Short.valueOf("3") }); + } + + @Test(groups = { "unit" }) + public void testDeserializeTuple() throws IOException { + ClickHouseValue value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("t", "Tuple(UInt8, String)"), null, + BinaryStreamUtilsTest.generateInput(1, 1, 0x61)); + Assert.assertTrue(value instanceof ClickHouseTupleValue); + List values = (List) value.asObject(); + Assert.assertEquals(values.size(), 2); + Assert.assertEquals(values.get(0), (short) 1); + Assert.assertEquals(values.get(1), "a"); + + value = ClickHouseRowBinaryProcessor.getMappedFunctions().deserialize( + ClickHouseColumn.of("t", "Tuple(UInt32, Int128, Nullable(IPv4)))"), value, + BinaryStreamUtilsTest.generateInput(1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0x05, 0xa8, 0xc0)); + Assert.assertTrue(value instanceof ClickHouseTupleValue); + values = (List) value.asObject(); + Assert.assertEquals(values.size(), 3); + Assert.assertEquals(values.get(0), 1L); + Assert.assertEquals(values.get(1), BigInteger.valueOf(2)); + Assert.assertEquals(values.get(2), InetAddress.getByName("192.168.5.1")); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseShortValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseShortValueTest.java new file mode 100644 index 000000000..453e9cb2d --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseShortValueTest.java @@ -0,0 +1,155 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.UUID; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseShortValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseShortValue.ofNull()); + checkNull(ClickHouseShortValue.of(Short.MAX_VALUE).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseShortValue.of(0), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + (short) 0, // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "0", // String + "0", // SQL Expression + LocalTime.ofSecondOfDay(0), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + Short.class, // Value class + new Object[] { Short.valueOf((short) 0) }, // Array + new Short[] { Short.valueOf((short) 0) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Short.valueOf((short) 0) }), // Map + buildMap(new Object[] { 1 }, new Short[] { Short.valueOf((short) 0) }), // typed Map + Arrays.asList(Short.valueOf((short) 0)) // Tuple + ); + checkValue(ClickHouseShortValue.of(1), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + (short) 1, // Object + LocalDate.ofEpochDay(1L), // Date + LocalDateTime.ofEpochSecond(1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 1, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "1", // String + "1", // SQL Expression + LocalTime.ofSecondOfDay(1), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + Short.class, // Value class + new Object[] { Short.valueOf((short) 1) }, // Array + new Short[] { Short.valueOf((short) 1) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Short.valueOf((short) 1) }), // Map + buildMap(new Object[] { 1 }, new Short[] { Short.valueOf((short) 1) }), // typed Map + Arrays.asList(Short.valueOf((short) 1)) // Tuple + ); + checkValue(ClickHouseShortValue.of(2), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + (short) 2, // Object + LocalDate.ofEpochDay(2L), // Date + LocalDateTime.ofEpochSecond(2L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(0L, 2, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "2", // String + "2", // SQL Expression + LocalTime.ofSecondOfDay(2), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + Short.class, // Value class + new Object[] { Short.valueOf((short) 2) }, // Array + new Short[] { Short.valueOf((short) 2) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Short.valueOf((short) 2) }), // Map + buildMap(new Object[] { 1 }, new Short[] { Short.valueOf((short) 2) }), // typed Map + Arrays.asList(Short.valueOf((short) 2)) // Tuple + ); + + checkValue(ClickHouseShortValue.of(-1), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + (short) -1, // Object + LocalDate.ofEpochDay(-1L), // Date + LocalDateTime.ofEpochSecond(-1L, 0, ZoneOffset.UTC), // DateTime + LocalDateTime.ofEpochSecond(-1L, 999999999, ZoneOffset.UTC), // DateTime(9) + Inet4Address.getAllByName("255.255.255.255")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:ff")[0], // Inet6Address + "-1", // String + "-1", // SQL Expression + java.time.DateTimeException.class, // Time + UUID.fromString("00000000-0000-0000-ffff-ffffffffffff"), // UUID + Object.class, // Key class + Short.class, // Value class + new Object[] { Short.valueOf((short) -1) }, // Array + new Short[] { Short.valueOf((short) -1) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Short.valueOf((short) -1) }), // Map + buildMap(new Object[] { 1 }, new Short[] { Short.valueOf((short) -1) }), // typed Map + Arrays.asList(Short.valueOf((short) -1)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java new file mode 100644 index 000000000..8c44cd6bd --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java @@ -0,0 +1,228 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.Arrays; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; + +public class ClickHouseStringValueTest extends BaseClickHouseValueTest { + @DataProvider(name = "stateProvider") + public Object[][] getNodeSelectors() { + return new Object[][] { new Object[] { null, true, false, false }, new Object[] { "2", false, false, false }, + new Object[] { "NaN", false, true, false }, new Object[] { "-Infinity", false, false, true }, + new Object[] { "Infinity", false, false, true }, new Object[] { "+Infinity", false, false, true } }; + } + + @Test(groups = { "unit" }) + public void testInitiation() { + String value = null; + ClickHouseStringValue v = ClickHouseStringValue.of(null, value); + Assert.assertEquals(v.asString(), value); + Assert.assertEquals(v.getValue(), value); + + v = ClickHouseStringValue.of(null, value = ""); + Assert.assertEquals(v.asString(), value); + Assert.assertEquals(v.getValue(), value); + + v = ClickHouseStringValue.of(null, value = "123"); + Assert.assertEquals(v.asString(), value); + Assert.assertEquals(v.getValue(), value); + + // same instance but different value + Assert.assertEquals(v, v.update("321")); + Assert.assertEquals(v, ClickHouseStringValue.of(v, "456")); + Assert.assertNotEquals(v.asString(), v.update("789").asString()); + Assert.assertNotEquals(v.asString(), v.update("987").asString()); + } + + @Test(dataProvider = "stateProvider", groups = { "unit" }) + public void testState(String value, boolean isNull, boolean isNaN, boolean isInf) { + ClickHouseStringValue v = ClickHouseStringValue.of(null, value); + Assert.assertEquals(v.isNullOrEmpty(), isNull); + Assert.assertEquals(v.isNaN(), isNaN); + Assert.assertEquals(v.isInfinity(), isInf); + } + + @Test(groups = { "unit" }) + public void testTypeConversion() { + Assert.assertEquals(ClickHouseStringValue.of(null, "2021-03-04 15:06:27.123456789").asDateTime(), + LocalDateTime.of(2021, 3, 4, 15, 6, 27, 123456789)); + } + + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseStringValue.ofNull()); + checkNull(ClickHouseStringValue.of("abc").resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseStringValue.of(""), NumberFormatException.class, // isInfinity + NumberFormatException.class, // isNan + false, // isNull + false, // boolean + NumberFormatException.class, // byte + NumberFormatException.class, // short + NumberFormatException.class, // int + NumberFormatException.class, // long + NumberFormatException.class, // float + NumberFormatException.class, // double + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigDecimal + NumberFormatException.class, // BigInteger + IllegalArgumentException.class, // Enum + "", // Object + DateTimeParseException.class, // Date + DateTimeParseException.class, // DateTime + DateTimeParseException.class, // DateTime(9) + Inet4Address.getAllByName("127.0.0.1")[0], // Inet4Address + IllegalArgumentException.class, // Inet6Address + "", // String + "''", // SQL Expression + DateTimeParseException.class, // Time + IllegalArgumentException.class, // UUID + Object.class, // Key class + String.class, // Value class + new Object[] { "" }, // Array + new String[] { "" }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { "" }), // Map + buildMap(new Object[] { 1 }, new String[] { "" }), // typed Map + Arrays.asList("") // Tuple + ); + + // numbers + checkValue(ClickHouseStringValue.of("0"), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + IllegalArgumentException.class, // Enum + "0", // Object + DateTimeParseException.class, // Date + DateTimeParseException.class, // DateTime + DateTimeParseException.class, // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + IllegalArgumentException.class, // Inet6Address + "0", // String + "'0'", // SQL Expression + DateTimeParseException.class, // Time + IllegalArgumentException.class, // UUID + Object.class, // Key class + String.class, // Value class + new Object[] { "0" }, // Array + new String[] { "0" }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { "0" }), // Map + buildMap(new Object[] { 1 }, new String[] { "0" }), // typed Map + Arrays.asList("0") // Tuple + ); + checkValue(ClickHouseStringValue.of("1"), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + IllegalArgumentException.class, // Enum + "1", // Object + DateTimeParseException.class, // Date + DateTimeParseException.class, // DateTime + DateTimeParseException.class, // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + IllegalArgumentException.class, // Inet6Address + "1", // String + "'1'", // SQL Expression + DateTimeParseException.class, // Time + IllegalArgumentException.class, // UUID + Object.class, // Key class + String.class, // Value class + new Object[] { "1" }, // Array + new String[] { "1" }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { "1" }), // Map + buildMap(new Object[] { 1 }, new String[] { "1" }), // typed Map + Arrays.asList("1") // Tuple + ); + checkValue(ClickHouseStringValue.of("2"), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + IllegalArgumentException.class, // Enum + "2", // Object + DateTimeParseException.class, // Date + DateTimeParseException.class, // DateTime + DateTimeParseException.class, // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + IllegalArgumentException.class, // Inet6Address + "2", // String + "'2'", // SQL Expression + DateTimeParseException.class, // Time + IllegalArgumentException.class, // UUID + Object.class, // Key class + String.class, // Value class + new Object[] { "2" }, // Array + new String[] { "2" }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { "2" }), // Map + buildMap(new Object[] { 1 }, new String[] { "2" }), // typed Map + Arrays.asList("2") // Tuple + ); + checkValue(ClickHouseStringValue.of("-1"), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) -1, // byte + (short) -1, // short + -1, // int + -1L, // long + -1F, // float + -1D, // double + BigDecimal.valueOf(-1L), // BigDecimal + new BigDecimal(BigInteger.valueOf(-1L), 3), // BigDecimal + BigInteger.valueOf(-1L), // BigInteger + IllegalArgumentException.class, // Enum + "-1", // Object + DateTimeParseException.class, // Date + DateTimeParseException.class, // DateTime + DateTimeParseException.class, // DateTime(9) + IllegalArgumentException.class, // Inet4Address + IllegalArgumentException.class, // Inet6Address + "-1", // String + "'-1'", // SQL Expression + DateTimeParseException.class, // Time + IllegalArgumentException.class, // UUID + Object.class, // Key class + String.class, // Value class + new Object[] { "-1" }, // Array + new String[] { "-1" }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { "-1" }), // Map + buildMap(new Object[] { 1 }, new String[] { "-1" }), // typed Map + Arrays.asList("-1") // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseTimeValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseTimeValueTest.java new file mode 100644 index 000000000..d45f7ddb9 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseTimeValueTest.java @@ -0,0 +1,121 @@ +package com.clickhouse.client.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.UUID; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; +import com.clickhouse.client.ClickHouseDataType; + +public class ClickHouseTimeValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testValue() throws Exception { + // null value + checkNull(ClickHouseTimeValue.ofNull()); + checkNull(ClickHouseTimeValue.of(LocalTime.now()).resetToNullOrEmpty()); + + // non-null + checkValue(ClickHouseTimeValue.of(0), false, // isInfinity + false, // isNan + false, // isNull + false, // boolean + (byte) 0, // byte + (short) 0, // short + 0, // int + 0L, // long + 0F, // float + 0D, // double + BigDecimal.valueOf(0L), // BigDecimal + new BigDecimal(BigInteger.ZERO, 3), // BigDecimal + BigInteger.ZERO, // BigInteger + ClickHouseDataType.values()[0].name(), // Enum + LocalTime.ofSecondOfDay(0L), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0L)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(0L)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.0")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:0")[0], // Inet6Address + "00:00:00", // String + "'00:00:00'", // SQL Expression + LocalTime.ofSecondOfDay(0L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000000"), // UUID + Object.class, // Key class + LocalTime.class, // Value class + new Object[] { LocalTime.ofSecondOfDay(0L) }, // Array + new LocalTime[] { LocalTime.ofSecondOfDay(0L) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { LocalTime.ofSecondOfDay(0L) }), // Map + buildMap(new Object[] { 1 }, new LocalTime[] { LocalTime.ofSecondOfDay(0L) }), // typed Map + Arrays.asList(LocalTime.ofSecondOfDay(0L)) // Tuple + ); + checkValue(ClickHouseTimeValue.of(1), false, // isInfinity + false, // isNan + false, // isNull + true, // boolean + (byte) 1, // byte + (short) 1, // short + 1, // int + 1L, // long + 1F, // float + 1D, // double + BigDecimal.valueOf(1L), // BigDecimal + new BigDecimal(BigInteger.ONE, 3), // BigDecimal + BigInteger.ONE, // BigInteger + ClickHouseDataType.values()[1].name(), // Enum + LocalTime.ofSecondOfDay(1L), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(1L)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(1L)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.1")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:1")[0], // Inet6Address + "00:00:01", // String + "'00:00:01'", // SQL Expression + LocalTime.ofSecondOfDay(1L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000001"), // UUID + Object.class, // Key class + LocalTime.class, // Value class + new Object[] { LocalTime.ofSecondOfDay(1L) }, // Array + new LocalTime[] { LocalTime.ofSecondOfDay(1L) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { LocalTime.ofSecondOfDay(1L) }), // Map + buildMap(new Object[] { 1 }, new LocalTime[] { LocalTime.ofSecondOfDay(1L) }), // typed Map + Arrays.asList(LocalTime.ofSecondOfDay(1L)) // Tuple + ); + checkValue(ClickHouseTimeValue.of(2), false, // isInfinity + false, // isNan + false, // isNull + IllegalArgumentException.class, // boolean + (byte) 2, // byte + (short) 2, // short + 2, // int + 2L, // long + 2F, // float + 2D, // double + BigDecimal.valueOf(2L), // BigDecimal + new BigDecimal(BigInteger.valueOf(2L), 3), // BigDecimal + BigInteger.valueOf(2L), // BigInteger + ClickHouseDataType.values()[2].name(), // Enum + LocalTime.ofSecondOfDay(2L), // Object + LocalDate.ofEpochDay(0L), // Date + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(2L)), // DateTime + LocalDateTime.of(LocalDate.ofEpochDay(0L), LocalTime.ofSecondOfDay(2L)), // DateTime(9) + Inet4Address.getAllByName("0.0.0.2")[0], // Inet4Address + Inet6Address.getAllByName("0:0:0:0:0:0:0:2")[0], // Inet6Address + "00:00:02", // String + "'00:00:02'", // SQL Expression + LocalTime.ofSecondOfDay(2L), // Time + UUID.fromString("00000000-0000-0000-0000-000000000002"), // UUID + Object.class, // Key class + LocalTime.class, // Value class + new Object[] { LocalTime.ofSecondOfDay(2L) }, // Array + new LocalTime[] { LocalTime.ofSecondOfDay(2L) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { LocalTime.ofSecondOfDay(2L) }), // Map + buildMap(new Object[] { 1 }, new LocalTime[] { LocalTime.ofSecondOfDay(2L) }), // typed Map + Arrays.asList(LocalTime.ofSecondOfDay(2L)) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseTupleValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseTupleValueTest.java new file mode 100644 index 000000000..d0f40dfbd --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseTupleValueTest.java @@ -0,0 +1,120 @@ +package com.clickhouse.client.data; + +import java.util.Arrays; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseClickHouseValueTest; + +public class ClickHouseTupleValueTest extends BaseClickHouseValueTest { + @Test(groups = { "unit" }) + public void testMultipleValues() throws Exception { + // single type + checkValue(ClickHouseTupleValue.of("one", "two"), UnsupportedOperationException.class, // isInfinity + UnsupportedOperationException.class, // isNan + false, // isNull + UnsupportedOperationException.class, // boolean + UnsupportedOperationException.class, // byte + UnsupportedOperationException.class, // short + UnsupportedOperationException.class, // int + UnsupportedOperationException.class, // long + UnsupportedOperationException.class, // float + UnsupportedOperationException.class, // double + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigInteger + UnsupportedOperationException.class, // Enum + Arrays.asList("one", "two"), // Object + UnsupportedOperationException.class, // Date + UnsupportedOperationException.class, // DateTime + UnsupportedOperationException.class, // DateTime(9) + UnsupportedOperationException.class, // Inet4Address + UnsupportedOperationException.class, // Inet6Address + "[one, two]", // String + "('one','two')", // SQL Expression + UnsupportedOperationException.class, // Time + UnsupportedOperationException.class, // UUID + Integer.class, // Key class + String.class, // Value class + new Object[] { "one", "two" }, // Array + new Object[] { "one", "two" }, // typed Array + buildMap(new Object[] { 1, 2 }, new Object[] { "one", "two" }), // Map + buildMap(new Integer[] { 1, 2 }, new String[] { "one", "two" }), // typed Map + Arrays.asList("one", "two") // Tuple + ); + + // mixed types + checkValue(ClickHouseTupleValue.of("seven", (byte) 7), UnsupportedOperationException.class, // isInfinity + UnsupportedOperationException.class, // isNan + false, // isNull + UnsupportedOperationException.class, // boolean + UnsupportedOperationException.class, // byte + UnsupportedOperationException.class, // short + UnsupportedOperationException.class, // int + UnsupportedOperationException.class, // long + UnsupportedOperationException.class, // float + UnsupportedOperationException.class, // double + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigInteger + UnsupportedOperationException.class, // Enum + Arrays.asList("seven", (byte) 7), // Object + UnsupportedOperationException.class, // Date + UnsupportedOperationException.class, // DateTime + UnsupportedOperationException.class, // DateTime(9) + UnsupportedOperationException.class, // Inet4Address + UnsupportedOperationException.class, // Inet6Address + "[seven, 7]", // String + "('seven',7)", // SQL Expression + UnsupportedOperationException.class, // Time + UnsupportedOperationException.class, // UUID + Integer.class, // Key class + Object.class, // Value class + new Object[] { "seven", Byte.valueOf((byte) 7) }, // Array + new Object[] { "seven", Byte.valueOf((byte) 7) }, // typed Array + buildMap(new Object[] { 1, 2 }, new Object[] { "seven", Byte.valueOf((byte) 7) }), // Map + buildMap(new Integer[] { 1, 2 }, new Object[] { "seven", Byte.valueOf((byte) 7) }), // typed + // Map + Arrays.asList("seven", (byte) 7) // Tuple + ); + } + + @Test(groups = { "unit" }) + public void testSingleValue() throws Exception { + // null value + checkNull(ClickHouseTupleValue.of().resetToNullOrEmpty(), false, 3, 9); + checkNull(ClickHouseTupleValue.of(ClickHouseByteValue.of(0).asByte()).resetToNullOrEmpty(), false, 3, 9); + + // non-null + checkValue(ClickHouseTupleValue.of(ClickHouseByteValue.of(0).asByte()), UnsupportedOperationException.class, // isInfinity + UnsupportedOperationException.class, // isNan + false, // isNull + UnsupportedOperationException.class, // boolean + UnsupportedOperationException.class, // byte + UnsupportedOperationException.class, // short + UnsupportedOperationException.class, // int + UnsupportedOperationException.class, // long + UnsupportedOperationException.class, // float + UnsupportedOperationException.class, // double + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigDecimal + UnsupportedOperationException.class, // BigInteger + UnsupportedOperationException.class, // Enum + Arrays.asList((byte) 0), // Object + UnsupportedOperationException.class, // Date + UnsupportedOperationException.class, // DateTime + UnsupportedOperationException.class, // DateTime(9) + UnsupportedOperationException.class, // Inet4Address + UnsupportedOperationException.class, // Inet6Address + "[0]", // String + "(0)", // SQL Expression + UnsupportedOperationException.class, // Time + UnsupportedOperationException.class, // UUID + Integer.class, // Key class + Byte.class, // Value class + new Object[] { Byte.valueOf((byte) 0) }, // Array + new Byte[] { Byte.valueOf((byte) 0) }, // typed Array + buildMap(new Object[] { 1 }, new Object[] { Byte.valueOf((byte) 0) }), // Map + buildMap(new Integer[] { 1 }, new Byte[] { Byte.valueOf((byte) 0) }), // typed Map + Arrays.asList((byte) 0) // Tuple + ); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickhouseLZ4InputStreamTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickhouseLZ4InputStreamTest.java new file mode 100644 index 000000000..2ca7d2528 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickhouseLZ4InputStreamTest.java @@ -0,0 +1,34 @@ +package com.clickhouse.client.data; + +import org.testng.Assert; +import org.testng.annotations.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ClickhouseLZ4InputStreamTest { + @Test(groups = { "unit" }) + public void testLZ4Stream() throws IOException { + StringBuilder sb = new StringBuilder(); + byte[] result = null; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ClickHouseLZ4OutputStream outputStream = new ClickHouseLZ4OutputStream(baos, 1024 * 1024)) { + for (int i = 0; i < 100000; i++) { + outputStream.write(("test" + i).getBytes()); + sb.append("test").append(i); + } + outputStream.flush(); + result = baos.toByteArray(); + // System.out.println(result.length); + Assert.assertTrue(result.length < sb.length() / 2); + } + + try (ByteArrayInputStream bais = new ByteArrayInputStream(result); + ClickHouseLZ4InputStream is = new ClickHouseLZ4InputStream(bais)) { + byte[] buf = new byte[20000000]; + int read = is.read(buf); + // System.out.println(read); + Assert.assertEquals(new String(buf, 0, read), sb.toString()); + } + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/WriterFunction.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/WriterFunction.java new file mode 100644 index 000000000..cadfd745f --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/WriterFunction.java @@ -0,0 +1,9 @@ +package com.clickhouse.client.data; + +import java.io.IOException; +import java.io.OutputStream; + +@FunctionalInterface +public interface WriterFunction { + void write(OutputStream o) throws IOException; +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/logging/JdkLoggerTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/logging/JdkLoggerTest.java new file mode 100644 index 000000000..654fa35b3 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/logging/JdkLoggerTest.java @@ -0,0 +1,43 @@ +package com.clickhouse.client.logging; + +import java.util.Collections; +import org.testng.annotations.Test; + +public class JdkLoggerTest extends LoggerTest { + private final JdkLoggerFactory factory = new JdkLoggerFactory(); + + @Override + protected Logger getLogger(Class clazz) { + return factory.get(clazz); + } + + @Override + protected Logger getLogger(String name) { + return factory.get(name); + } + + @Test(groups = { "unit" }) + public void testInstantiation() { + checkInstance(JdkLogger.class); + } + + @Test(groups = { "unit" }) + public void testLogMessage() { + logMessage(Collections.singletonMap("key", "value")); + } + + @Test(groups = { "unit" }) + public void testLogWithFormat() { + logWithFormat("msg %s %s %s %s", 1, 2.2, "3", new Object()); + } + + @Test(groups = { "unit" }) + public void testLogThrowable() { + logThrowable("msg", new Exception("test exception")); + } + + @Test(groups = { "unit" }) + public void testLogWithFormatAndThrowable() { + logWithFormatAndThrowable("msg %s %s %s %s", 1, 2.2, "3", new Object(), new Exception("test exception")); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/logging/LogMessageTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/logging/LogMessageTest.java new file mode 100644 index 000000000..0245e52b1 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/logging/LogMessageTest.java @@ -0,0 +1,41 @@ +package com.clickhouse.client.logging; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class LogMessageTest { + @Test(groups = { "unit" }) + public void testMessageWithNoArgument() { + String message = "test %s"; + LogMessage msg = LogMessage.of(message); + Assert.assertEquals(message, msg.getMessage()); + Assert.assertNull(msg.getThrowable()); + + msg = LogMessage.of(1); + Assert.assertEquals("1", msg.getMessage()); + Assert.assertNull(msg.getThrowable()); + } + + @Test(groups = { "unit" }) + public void testMessageWithArguments() { + LogMessage msg = LogMessage.of("test %s - %s", "test", 1); + Assert.assertEquals("test test - 1", msg.getMessage()); + Assert.assertNull(msg.getThrowable()); + + msg = LogMessage.of("test", "test", 1); + Assert.assertEquals("test", msg.getMessage()); + Assert.assertNull(msg.getThrowable()); + } + + @Test(groups = { "unit" }) + public void testMessageWithThrowable() { + Throwable t = new Exception(); + LogMessage msg = LogMessage.of("test", t); + Assert.assertEquals("test", msg.getMessage()); + Assert.assertEquals(t, msg.getThrowable()); + + msg = LogMessage.of("test %s", 1, t); + Assert.assertEquals("test 1", msg.getMessage()); + Assert.assertEquals(t, msg.getThrowable()); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/logging/LoggerTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/logging/LoggerTest.java new file mode 100644 index 000000000..b26386908 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/logging/LoggerTest.java @@ -0,0 +1,89 @@ +package com.clickhouse.client.logging; + +import org.testng.Assert; + +public abstract class LoggerTest { + protected Logger getLogger(Class clazz) { + return getLogger(clazz.getName()); + } + + protected Logger getLogger(String name) { + return null; + } + + protected void checkInstance(Class clazz) { + Logger logger1 = getLogger(LoggerTest.class); + Logger logger2 = getLogger(LoggerTest.class.getName()); + + Assert.assertTrue(clazz.isInstance(logger1)); + Assert.assertTrue(clazz.isInstance(logger2)); + + Assert.assertEquals(logger1.unwrap(), logger2.unwrap()); + } + + protected void logMessage(Object message) { + Logger logger = getLogger(LoggerTest.class); + + logger.trace(null); + logger.trace(message); + logger.debug(null); + logger.debug(message); + logger.info(null); + logger.info(message); + logger.warn(null); + logger.warn(message); + logger.error(null); + logger.error(message); + } + + protected void logWithFormat(Object message, Object... arguments) { + Logger logger = getLogger(LoggerTest.class); + + Assert.assertTrue(arguments.length > 0); + Assert.assertFalse(arguments[arguments.length - 1] instanceof Throwable); + + logger.trace(null, arguments); + logger.trace(message, arguments); + logger.debug(null, arguments); + logger.debug(message, arguments); + logger.info(null, arguments); + logger.info(message, arguments); + logger.warn(null, arguments); + logger.warn(message, arguments); + logger.error(null, arguments); + logger.error(message, arguments); + } + + protected void logThrowable(Object message, Throwable t) { + Logger logger = getLogger(Slf4jLoggerTest.class); + + logger.trace(null, t); + logger.trace(message, t); + logger.debug(null, t); + logger.debug(message, t); + logger.info(null, t); + logger.info(message, t); + logger.warn(null, t); + logger.warn(message, t); + logger.error(null, t); + logger.error(message, t); + } + + protected void logWithFormatAndThrowable(Object message, Object... arguments) { + Logger logger = getLogger(LoggerTest.class); + + Assert.assertTrue(arguments.length > 0); + Assert.assertTrue(arguments[arguments.length - 1] instanceof Throwable); + + logger.trace(null, arguments); + logger.trace(message, arguments); + logger.debug(null, arguments); + logger.debug(message, arguments); + logger.info(null, arguments); + logger.info(message, arguments); + logger.warn(null, arguments); + logger.warn(message, arguments); + logger.error(null, arguments); + logger.error(message, arguments); + } +} diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/logging/Slf4jLoggerTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/logging/Slf4jLoggerTest.java new file mode 100644 index 000000000..2ea205b31 --- /dev/null +++ b/clickhouse-client/src/test/java/com/clickhouse/client/logging/Slf4jLoggerTest.java @@ -0,0 +1,43 @@ +package com.clickhouse.client.logging; + +import java.util.Collections; +import org.testng.annotations.Test; + +public class Slf4jLoggerTest extends LoggerTest { + private final Slf4jLoggerFactory factory = new Slf4jLoggerFactory(); + + @Override + protected Logger getLogger(Class clazz) { + return factory.get(clazz); + } + + @Override + protected Logger getLogger(String name) { + return factory.get(name); + } + + @Test(groups = { "unit" }) + public void testInstantiation() { + checkInstance(Slf4jLogger.class); + } + + @Test(groups = { "unit" }) + public void testLogMessage() { + logMessage(Collections.singletonMap("key", "value")); + } + + @Test(groups = { "unit" }) + public void testLogWithFormat() { + logWithFormat("msg %s %s %s %s", 1, 2.2, "3", new Object()); + } + + @Test(groups = { "unit" }) + public void testLogThrowable() { + logThrowable("msg", new Exception("test exception")); + } + + @Test(groups = { "unit" }) + public void testLogWithFormatAndThrowable() { + logWithFormatAndThrowable("msg %s %s %s %s", 1, 2.2, "3", new Object(), new Exception("test exception")); + } +} diff --git a/clickhouse-client/src/test/resources/META-INF/services/com.clickhouse.client.ClickHouseClient b/clickhouse-client/src/test/resources/META-INF/services/com.clickhouse.client.ClickHouseClient new file mode 100644 index 000000000..b7b850ba0 --- /dev/null +++ b/clickhouse-client/src/test/resources/META-INF/services/com.clickhouse.client.ClickHouseClient @@ -0,0 +1 @@ +com.clickhouse.client.ClickHouseTestClient diff --git a/clickhouse-client/src/test/resources/containers/clickhouse-server/config.d/ports.xml b/clickhouse-client/src/test/resources/containers/clickhouse-server/config.d/ports.xml new file mode 100644 index 000000000..b6836aa94 --- /dev/null +++ b/clickhouse-client/src/test/resources/containers/clickhouse-server/config.d/ports.xml @@ -0,0 +1,8 @@ + + 8123 + 9000 + 9004 + 9005 + 9009 + 9100 + \ No newline at end of file diff --git a/clickhouse-client/src/test/resources/containers/clickhouse-server/users.d/users.xml b/clickhouse-client/src/test/resources/containers/clickhouse-server/users.d/users.xml new file mode 100644 index 000000000..1554ef7e1 --- /dev/null +++ b/clickhouse-client/src/test/resources/containers/clickhouse-server/users.d/users.xml @@ -0,0 +1,40 @@ + + + + 1 + default + + ::/0 + + dba + default + + + default + + ::/0 + + bar + default + + + default + + ::/0 + + + default + + + default + + ::/0 + + 123 + default + + system + + + + \ No newline at end of file diff --git a/clickhouse-client/src/test/resources/log4j.properties b/clickhouse-client/src/test/resources/log4j.properties new file mode 100644 index 000000000..2596a2ac7 --- /dev/null +++ b/clickhouse-client/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +plog4j.rootLogger=WARN, STDOUT +log4j.category.com.clickhouse.client=DEBUG +log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender +log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout +log4j.appender.STDOUT.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.sss} [%t] [%-5p] {%c{1}:%L} - %m%n diff --git a/clickhouse-grpc-client/pom.xml b/clickhouse-grpc-client/pom.xml index 796d2ec0f..fa8725757 100644 --- a/clickhouse-grpc-client/pom.xml +++ b/clickhouse-grpc-client/pom.xml @@ -1,9 +1,8 @@ - + 4.0.0 - tech.clickhouse + com.clickhouse clickhouse-java ${revision} @@ -22,23 +21,106 @@ clickhouse-client ${revision} + + ${project.parent.groupId} + io.grpc + + + * + * + + + + + + com.google.code.gson + gson + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-netty-shaded + provided + + + io.grpc + grpc-okhttp + provided + + + + org.apache.tomcat + annotations-api + provided + + + + ${project.parent.groupId} + clickhouse-client + ${revision} + test-jar + test + + + dnsjava + dnsjava + test + + + org.slf4j + slf4j-log4j12 + test + + + org.testcontainers + testcontainers + test + + + org.testng + testng + test + + + + kr.motd.maven + os-maven-plugin + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + target/generated-sources/protobuf/grpc-java + target/generated-sources/protobuf/java + + + + + org.apache.maven.plugins maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - true - - -Xlint:all - -Werror - - + + + org.xolstice.maven.plugins + protobuf-maven-plugin - + \ No newline at end of file diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcChannelFactory.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcChannelFactory.java new file mode 100644 index 000000000..d6391f883 --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcChannelFactory.java @@ -0,0 +1,183 @@ +package com.clickhouse.client.grpc; + +import java.io.FileNotFoundException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Status; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.grpc.config.ClickHouseGrpcClientOption; +import com.clickhouse.client.grpc.impl.ClickHouseGrpc; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; + +public abstract class ClickHouseGrpcChannelFactory { + private static final Logger log = LoggerFactory.getLogger(ClickHouseGrpcChannelFactory.class); + + private static final String PROP_NAME = "name"; + private static final String PROP_SERVICE = "service"; + private static final String PROP_METHOD = "method"; + private static final String PROP_METHOD_CONFIG = "methodConfig"; + private static final String PROP_RETRY_POLICY = "retryPolicy"; + private static final String PROP_MAX_ATTEMPTS = "maxAttempts"; + + private static final String serviceName = ClickHouseGrpc.SERVICE_NAME; + private static final String methodName = ClickHouseGrpc.getExecuteQueryWithStreamIOMethod().getBareMethodName(); + + // you can override this with ~/.clickhouse/grpc-config.json + // more details at: + // /~https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto + private static final Map defaultServiceConfig; + + static { + Map name = new HashMap<>(); + name.put(PROP_SERVICE, serviceName); + name.put(PROP_METHOD, methodName); + + Map retryPolicy = new HashMap<>(); + retryPolicy.put(PROP_MAX_ATTEMPTS, 5D); + retryPolicy.put("initialBackoff", "0.5s"); + retryPolicy.put("maxBackoff", "30s"); + retryPolicy.put("backoffMultiplier", 2D); + retryPolicy.put("retryableStatusCodes", Collections.singletonList(Status.UNAVAILABLE.getCode().name())); + + Map methodConfig = new HashMap<>(); + methodConfig.put(PROP_NAME, Collections.singletonList(name)); + methodConfig.put(PROP_RETRY_POLICY, retryPolicy); + + Map config = new HashMap<>(); + config.put(PROP_METHOD_CONFIG, Collections.singletonList(methodConfig)); + + defaultServiceConfig = Collections.unmodifiableMap(config); + } + + public static ClickHouseGrpcChannelFactory getFactory(ClickHouseConfig config, ClickHouseNode server) { + return ((boolean) config.getOption(ClickHouseGrpcClientOption.USE_OKHTTP)) + ? new OkHttpChannelFactoryImpl(config, server) + : new NettyChannelFactoryImpl(config, server); + } + + protected final ClickHouseConfig config; + protected final ClickHouseNode server; + + protected ClickHouseGrpcChannelFactory(ClickHouseConfig config, ClickHouseNode server) { + this.config = ClickHouseChecker.nonNull(config, "config"); + this.server = ClickHouseChecker.nonNull(server, "server"); + } + + protected Map getDefaultServiceConfig() { + Map config = defaultServiceConfig; + try { + config = new Gson().fromJson(new JsonReader(new InputStreamReader( + ClickHouseUtils.getFileInputStream("grpc-config.json"), StandardCharsets.UTF_8)), Map.class); + } catch (FileNotFoundException e) { + log.debug("Use default service config due to: %s", e.getMessage()); + } catch (Exception e) { + log.debug("Failed to load service config", e); + } + + return config; + } + + protected abstract ManagedChannelBuilder getChannelBuilder(); + + @SuppressWarnings("unchecked") + protected void setupRetry() { + ManagedChannelBuilder builder = getChannelBuilder(); + + if (config.isRetry()) { + Map serviceConfig = getDefaultServiceConfig(); + int maxAttempts = -1; + Object value = serviceConfig.get(PROP_METHOD_CONFIG); + if (value instanceof List) { + for (Object o : ((List) value)) { + if (!(o instanceof Map)) { + continue; + } + + Map methodConfig = (Map) o; + value = methodConfig.get(PROP_NAME); + boolean matched = value instanceof List; + if (matched) { + matched = false; + for (Object n : ((List) value)) { + if (!(n instanceof Map)) { + continue; + } + + Map m = (Map) n; + Object v = m.get(PROP_SERVICE); + if (v != null && !serviceName.equals(v)) { + continue; + } + v = m.get(PROP_METHOD); + if (v != null && !methodName.equals(v)) { + continue; + } + + matched = true; + break; + } + } + + if (!matched) { + continue; + } + + value = methodConfig.get(PROP_RETRY_POLICY); + if (value instanceof Map) { + Map m = (Map) value; + value = m.get(PROP_MAX_ATTEMPTS); + if (value instanceof Number) { + maxAttempts = ((Number) value).intValue(); + } + } + } + } + + builder.defaultServiceConfig(serviceConfig).enableRetry(); + if (maxAttempts > 0) { + builder.maxRetryAttempts(maxAttempts); + } + } else { + builder.disableRetry(); + } + } + + protected abstract void setupSsl(); + + protected abstract void setupTimeout(); + + protected void setupMisc() { + ManagedChannelBuilder builder = getChannelBuilder(); + if ((boolean) config.getOption(ClickHouseGrpcClientOption.USE_FULL_STREAM_DECOMPRESSION)) { + builder.enableFullStreamDecompression(); + } + + // TODO add interceptor to customize retry + builder.maxInboundMessageSize((int) config.getOption(ClickHouseGrpcClientOption.MAX_INBOUND_MESSAGE_SIZE)) + .maxInboundMetadataSize((int) config.getOption(ClickHouseGrpcClientOption.MAX_INBOUND_METADATA_SIZE)); + } + + public ManagedChannel create() { + log.debug("Establishing channel to [%s]", server); + + setupRetry(); + setupSsl(); + setupMisc(); + + ManagedChannel c = getChannelBuilder().build(); + log.debug("Channel established: %s", c); + return c; + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcClient.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcClient.java new file mode 100644 index 000000000..184ccbe0c --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcClient.java @@ -0,0 +1,347 @@ +package com.clickhouse.client.grpc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import com.google.protobuf.ByteString; +import io.grpc.Context; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.stub.StreamObserver; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseCredentials; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseRequest; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.data.ClickHouseExternalTable; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.grpc.impl.ClickHouseGrpc; +import com.clickhouse.client.grpc.impl.ExternalTable; +import com.clickhouse.client.grpc.impl.NameAndType; +import com.clickhouse.client.grpc.impl.QueryInfo; +import com.clickhouse.client.grpc.impl.Result; +import com.clickhouse.client.grpc.impl.QueryInfo.Builder; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; + +public class ClickHouseGrpcClient implements ClickHouseClient { + private static final Logger log = LoggerFactory.getLogger(ClickHouseGrpcClient.class); + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + // not going to offload executor to ClickHouseGrpcFuture, as we can manage + // thread pool better here + private final AtomicReference executor = new AtomicReference<>(); + + // do NOT use below members directly without ReadWriteLock + private final AtomicReference config = new AtomicReference<>(); + private final AtomicReference server = new AtomicReference<>(); + private final AtomicReference channel = new AtomicReference<>(); + + protected static QueryInfo convert(ClickHouseNode server, ClickHouseRequest request) { + ClickHouseConfig config = request.getConfig(); + ClickHouseCredentials credentials = server.getCredentials().orElse(config.getDefaultCredentials()); + + Builder builder = QueryInfo.newBuilder(); + builder.setDatabase(server.getDatabase()).setUserName(credentials.getUserName()) + .setPassword(credentials.getPassword()).setOutputFormat(request.getFormat().name()); + + Optional optionalValue = request.getSessionId(); + if (optionalValue.isPresent()) { + builder.setSessionId(optionalValue.get()); + } + if (config.isSessionCheck()) { + builder.setSessionCheck(true); + } + if (config.getSessionTimeout() > 0) { + builder.setSessionTimeout(config.getSessionTimeout()); + } + + optionalValue = request.getQueryId(); + if (optionalValue.isPresent()) { + builder.setQueryId(optionalValue.get()); + } + + // builder.setNextQueryInfo(true); + for (Entry s : request.getSettings().entrySet()) { + builder.putSettings(s.getKey(), String.valueOf(s.getValue())); + } + + Optional input = request.getInputStream(); + if (input.isPresent()) { + try { + builder.setInputData(ByteString.readFrom(input.get())); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + List externalTables = request.getExternalTables(); + if (!externalTables.isEmpty()) { + for (ClickHouseExternalTable external : externalTables) { + ExternalTable.Builder b = ExternalTable.newBuilder().setName(external.getName()); + for (ClickHouseColumn c : ClickHouseColumn.parse(external.getStructure())) { + b.addColumns(NameAndType.newBuilder().setName(c.getColumnName()).setType(c.getOriginalTypeName()) + .build()); + } + if (external.getFormat() != null) { + b.setFormat(external.getFormat().name()); + } + + try { + builder.addExternalTables(b.setData(ByteString.readFrom(external.getContent())).build()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + + List stmts = request.getStatements(false); + int size = stmts.size(); + String sql; + if (size == 0) { + throw new IllegalArgumentException("At least one SQL statement is required for execution"); + } else if (size == 1) { + sql = stmts.get(0); + } else { // consolidate statements into one + if (!builder.getSessionCheck()) { + builder.setSessionCheck(true); + } + + if (ClickHouseChecker.isNullOrEmpty(builder.getSessionId())) { + builder.setSessionId(UUID.randomUUID().toString()); + } + + // builder.getSessionTimeout() + StringBuilder sb = new StringBuilder(); + for (String s : stmts) { + sb.append(s).append(';').append('\n'); + } + sql = sb.toString(); + } + + log.debug("Query: %s", sql); + + return builder.setQuery(sql).build(); + } + + protected void fill(ClickHouseRequest request, StreamObserver observer) { + try { + observer.onNext(convert(getServer(), request)); + } finally { + observer.onCompleted(); + } + } + + protected ClickHouseNode getServer() { + lock.readLock().lock(); + try { + return server.get(); + } finally { + lock.readLock().unlock(); + } + } + + protected ManagedChannel getChannel(ClickHouseRequest request) { + boolean prepared = true; + ClickHouseNode newNode = ClickHouseChecker.nonNull(request, "request").getServer(); + + lock.readLock().lock(); + ManagedChannel c = channel.get(); + try { + prepared = c != null && newNode.equals(server.get()); + + if (prepared) { + return c; + } + } finally { + lock.readLock().unlock(); + } + + lock.writeLock().lock(); + c = channel.get(); + try { + // first time? + if (c == null) { + server.set(newNode); + channel.set(c = ClickHouseGrpcChannelFactory.getFactory(getConfig(), newNode).create()); + } else if (!newNode.equals(server.get())) { + log.debug("Shutting down channel: %s", c); + c.shutdownNow(); + + server.set(newNode); + channel.set(c = ClickHouseGrpcChannelFactory.getFactory(getConfig(), newNode).create()); + } + + return c; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean accept(ClickHouseProtocol protocol) { + return ClickHouseProtocol.GRPC == protocol || ClickHouseClient.super.accept(protocol); + } + + @Override + public ClickHouseConfig getConfig() { + lock.readLock().lock(); + try { + return config.get(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void init(ClickHouseConfig config) { + lock.writeLock().lock(); + try { + this.config.set(config); + ClickHouseClient.super.init(config); + if (this.executor.get() == null) { // only initialize once + int threads = config.getMaxThreadsPerClient(); + this.executor.set(threads <= 0 ? ClickHouseClient.getExecutorService() + : ClickHouseUtils.newThreadPool(ClickHouseGrpcClient.class.getSimpleName(), threads, + config.getMaxQueuedRequests())); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void close() { + lock.writeLock().lock(); + + ExecutorService s = executor.get(); + ManagedChannel m = channel.get(); + + try { + server.set(null); + + if (s != null) { + s.shutdown(); + executor.set(null); + } + + if (m != null) { + m.shutdown(); + channel.set(null); + } + + ClickHouseConfig c = config.get(); + if (c != null) { + config.set(null); + } + + // shutdown* won't shutdown commonPool, so awaitTermination will always time out + // on the other hand, for a client-specific thread pool, we'd better shut it + // down for real + if (s != null && c != null && c.getMaxThreadsPerClient() > 0 + && !s.awaitTermination((int) c.getOption(ClickHouseClientOption.CONNECTION_TIMEOUT), + TimeUnit.MILLISECONDS)) { + s.shutdownNow(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (RuntimeException e) { + log.warn("Exception occurred when closing client", e); + } finally { + try { + if (m != null) { + m.shutdownNow(); + } + + if (s != null) { + s.shutdownNow(); + } + } finally { + lock.writeLock().unlock(); + } + } + } + + protected CompletableFuture executeAsync(ClickHouseRequest sealedRequest, + ManagedChannel channel, ClickHouseNode server) { + // reuse stub? + ClickHouseGrpc.ClickHouseStub stub = ClickHouseGrpc.newStub(channel); + stub.withCompression(sealedRequest.getCompression().encoding()); + + final ClickHouseStreamObserver responseObserver = new ClickHouseStreamObserver(sealedRequest.getConfig(), + server); + final StreamObserver requestObserver = stub.executeQueryWithStreamIO(responseObserver); + + if (sealedRequest.hasInputStream()) { + executor.get().execute(() -> fill(sealedRequest, requestObserver)); + } else { + fill(sealedRequest, requestObserver); + } + + // return new ClickHouseGrpcFuture(server, sealedRequest, requestObserver, + // responseObserver); + return CompletableFuture.supplyAsync(() -> { + int timeout = sealedRequest.getConfig().getConnectionTimeout() / 1000 + + sealedRequest.getConfig().getMaxExecutionTime(); + try { + if (!responseObserver.await(timeout, TimeUnit.SECONDS)) { + if (!Context.current().withCancellation().cancel(new StatusException(Status.CANCELLED))) { + requestObserver.onError(new StatusException(Status.CANCELLED)); + } + throw new CompletionException( + ClickHouseUtils.format("Timed out after waiting for %d %s", timeout, TimeUnit.SECONDS), + null); + } + } catch (InterruptedException e) { + throw new CompletionException(e); + } + + try { + return new ClickHouseGrpcResponse(sealedRequest.getConfig(), server, sealedRequest.getSettings(), + responseObserver); + } catch (ClickHouseException e) { + throw new CompletionException(e); + } + }, executor.get()); + } + + protected CompletableFuture executeSync(ClickHouseRequest sealedRequest, + ManagedChannel channel, ClickHouseNode server) throws ClickHouseException { + ClickHouseGrpc.ClickHouseBlockingStub stub = ClickHouseGrpc.newBlockingStub(channel); + stub.withCompression(sealedRequest.getCompression().encoding()); + Result result = stub.executeQuery(convert(server, sealedRequest)); + + // TODO not as elegant as ClickHouseImmediateFuture :< + return CompletableFuture.completedFuture( + new ClickHouseGrpcResponse(sealedRequest.getConfig(), server, sealedRequest.getSettings(), result)); + } + + @Override + public CompletableFuture execute(ClickHouseRequest request) throws ClickHouseException { + // sealedRequest is an immutable copy of the original request + final ClickHouseRequest sealedRequest = request.seal(); + final ManagedChannel c = getChannel(sealedRequest); + final ClickHouseNode s = getServer(); + + return sealedRequest.getConfig().isAsync() ? executeAsync(sealedRequest, c, s) + : executeSync(sealedRequest, c, s); + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcFuture.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcFuture.java new file mode 100644 index 000000000..560ee8c6d --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcFuture.java @@ -0,0 +1,88 @@ +package com.clickhouse.client.grpc; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import io.grpc.Context; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.stub.StreamObserver; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseRequest; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.grpc.impl.QueryInfo; + +@Deprecated +public class ClickHouseGrpcFuture implements Future { + private final ClickHouseNode server; + private final ClickHouseRequest request; + + private final StreamObserver requestObserver; + private final ClickHouseStreamObserver responseObserver; + + protected ClickHouseGrpcFuture(ClickHouseNode server, ClickHouseRequest request, + StreamObserver requestObserver, ClickHouseStreamObserver responseObserver) { + this.server = ClickHouseChecker.nonNull(server, "server"); + this.request = ClickHouseChecker.nonNull(request, "request").seal(); + + this.requestObserver = ClickHouseChecker.nonNull(requestObserver, "requestObserver"); + this.responseObserver = ClickHouseChecker.nonNull(responseObserver, "responseObserver"); + } + + public ClickHouseNode getServer() { + return server; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + boolean cancelled = true; + + if (mayInterruptIfRunning) { + cancelled = Context.current().withCancellation().cancel(new StatusException(Status.CANCELLED)); + } else { + requestObserver.onError(new StatusException(Status.CANCELLED)); + } + + return cancelled; + } + + @Override + public boolean isCancelled() { + return responseObserver.isCancelled(); + } + + @Override + public boolean isDone() { + return responseObserver.isCompleted(); + } + + @Override + public ClickHouseResponse get() throws InterruptedException, ExecutionException { + try { + return get(request.getConfig().getConnectionTimeout() / 1000 + request.getConfig().getMaxExecutionTime(), + TimeUnit.SECONDS); + } catch (TimeoutException e) { + cancel(true); + throw new InterruptedException(e.getMessage()); + } + } + + @Override + public ClickHouseResponse get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + if (!responseObserver.await(timeout, unit)) { + cancel(true); + throw new TimeoutException(ClickHouseUtils.format("Timed out after waiting for %d %s", timeout, unit)); + } + + try { + return new ClickHouseGrpcResponse(request.getConfig(), server, request.getSettings(), responseObserver); + } catch (ClickHouseException e) { + throw new ExecutionException(e); + } + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponse.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponse.java new file mode 100644 index 000000000..2cba35e73 --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponse.java @@ -0,0 +1,95 @@ +package com.clickhouse.client.grpc; + +import java.util.Map; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseResponseSummary; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.grpc.impl.Result; + +public class ClickHouseGrpcResponse extends ClickHouseResponse { + private final ClickHouseStreamObserver observer; + private final Result result; + + protected ClickHouseGrpcResponse(ClickHouseConfig config, ClickHouseNode server, Map settings, + ClickHouseStreamObserver observer) throws ClickHouseException { + super(config, server, settings, observer.getInputStream(), null, observer.getError()); + + this.observer = observer; + this.result = null; + + throwErrorIfAny(); + } + + protected ClickHouseGrpcResponse(ClickHouseConfig config, ClickHouseNode server, Map settings, + Result result) throws ClickHouseException { + super(config, server, settings, result.getOutput().newInput(), null, + result.hasException() + ? new ClickHouseException(result.getException().getCode(), + result.getException().getDisplayText(), null) + : null); + + this.observer = null; + this.result = result; + + throwErrorIfAny(); + } + + @Override + public ClickHouseResponseSummary getSummary() { + ClickHouseResponseSummary summary = super.getSummary(); + + if (result != null && (result.hasProgress() || result.hasStats())) { + summary = new ClickHouseResponseSummary() { + @Override + public long getAllocatedBytes() { + return result.getStats().getAllocatedBytes(); + } + + @Override + public long getBlocks() { + return result.getStats().getBlocks(); + } + + @Override + public long getReadBytes() { + return result.getProgress().getReadBytes(); + } + + @Override + public long getReadRows() { + return result.getProgress().getReadRows(); + } + + @Override + public long getRows() { + return result.getStats().getRows(); + } + + @Override + public long getRowsBeforeLimit() { + return result.getStats().getRowsBeforeLimit(); + } + + @Override + public long getTotalRowsToRead() { + return result.getProgress().getTotalRowsToRead(); + } + + @Override + public long getWriteBytes() { + return result.getProgress().getWrittenBytes(); + } + + @Override + public long getWriteRows() { + return result.getProgress().getWrittenRows(); + } + }; + } else if (observer != null) { + summary = observer.getSummary(); + } + return summary; + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponseSummary.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponseSummary.java new file mode 100644 index 000000000..18a39c02d --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponseSummary.java @@ -0,0 +1,87 @@ +package com.clickhouse.client.grpc; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import com.clickhouse.client.ClickHouseResponseSummary; + +public class ClickHouseGrpcResponseSummary implements ClickHouseResponseSummary { + // grpc + protected final AtomicInteger results; + + // stats + protected final AtomicLong allocatedBytes; + protected final AtomicLong blocks; + protected final AtomicLong rows; + protected final AtomicLong rowsBeforeLimit; + + // progress + protected final AtomicLong readBytes; + protected final AtomicLong readRows; + protected final AtomicLong totalRowsToRead; + protected final AtomicLong writeBytes; + protected final AtomicLong writeRows; + + protected ClickHouseGrpcResponseSummary() { + this.results = new AtomicInteger(0); + + this.readBytes = new AtomicLong(0L); + this.readRows = new AtomicLong(0L); + this.totalRowsToRead = new AtomicLong(0L); + this.writeBytes = new AtomicLong(0L); + this.writeRows = new AtomicLong(0L); + + this.allocatedBytes = new AtomicLong(0L); + this.blocks = new AtomicLong(0L); + this.rows = new AtomicLong(0L); + this.rowsBeforeLimit = new AtomicLong(0L); + } + + public int getResults() { + return results.get(); + } + + @Override + public long getAllocatedBytes() { + return allocatedBytes.get(); + } + + @Override + public long getBlocks() { + return blocks.get(); + } + + @Override + public long getRows() { + return rows.get(); + } + + @Override + public long getRowsBeforeLimit() { + return rowsBeforeLimit.get(); + } + + @Override + public long getReadBytes() { + return readBytes.get(); + } + + @Override + public long getReadRows() { + return readRows.get(); + } + + @Override + public long getTotalRowsToRead() { + return totalRowsToRead.get(); + } + + @Override + public long getWriteBytes() { + return writeBytes.get(); + } + + @Override + public long getWriteRows() { + return writeRows.get(); + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseStreamObserver.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseStreamObserver.java new file mode 100644 index 000000000..caf5320e4 --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseStreamObserver.java @@ -0,0 +1,204 @@ +package com.clickhouse.client.grpc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.stub.StreamObserver; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseDataStreamFactory; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.data.ClickHousePipedStream; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.grpc.impl.Exception; +import com.clickhouse.client.grpc.impl.LogEntry; +import com.clickhouse.client.grpc.impl.Progress; +import com.clickhouse.client.grpc.impl.Result; +import com.clickhouse.client.grpc.impl.Stats; +import com.clickhouse.client.logging.Logger; +import com.clickhouse.client.logging.LoggerFactory; + +public class ClickHouseStreamObserver implements StreamObserver { + private static final Logger log = LoggerFactory.getLogger(ClickHouseStreamObserver.class); + + private final ClickHouseNode server; + + private final CountDownLatch startLatch; + private final CountDownLatch finishLatch; + + private final ClickHousePipedStream stream; + + private final ClickHouseGrpcResponseSummary summary; + + private Throwable error; + + protected ClickHouseStreamObserver(ClickHouseConfig config, ClickHouseNode server) { + this.server = server; + + this.startLatch = new CountDownLatch(1); + this.finishLatch = new CountDownLatch(1); + + this.stream = ClickHouseDataStreamFactory.getInstance().createPipedStream(config); + + this.summary = new ClickHouseGrpcResponseSummary(); + + this.error = null; + } + + protected void checkClosed() { + if (finishLatch.getCount() == 0) { + throw new IllegalStateException("closed observer"); + } + } + + protected void setError(Throwable error) { + if (this.error == null) { + this.error = error; + } + } + + protected boolean updateStatus(Result result) { + summary.results.incrementAndGet(); + + log.info(() -> { + for (LogEntry e : result.getLogsList()) { + String logLevel = e.getLevel().name(); + int index = logLevel.indexOf('_'); + if (index > 0) { + logLevel = logLevel.substring(index + 1); + } + log.info("%s.%s [ %s ] {%s} <%s> %s: %s", e.getTime(), e.getTimeMicroseconds(), e.getThreadId(), + e.getQueryId(), logLevel, e.getSource(), e.getText()); + } + }); + + boolean proceed = true; + + if (result.hasStats()) { + Stats stats = result.getStats(); + summary.allocatedBytes.set(stats.getAllocatedBytes()); + summary.blocks.set(stats.getBlocks()); + summary.rows.set(stats.getRows()); + summary.rowsBeforeLimit.set(stats.getRowsBeforeLimit()); + } + + if (result.hasProgress()) { + Progress prog = result.getProgress(); + summary.readBytes.set(prog.getReadBytes()); + summary.readRows.set(prog.getReadRows()); + summary.totalRowsToRead.set(prog.getTotalRowsToRead()); + summary.writeBytes.set(prog.getWrittenBytes()); + summary.writeRows.set(prog.getWrittenRows()); + } + + if (result.getCancelled()) { + proceed = false; + onError(new StatusException(Status.CANCELLED)); + } else if (result.hasException()) { + proceed = false; + Exception e = result.getException(); + log.error("Server error: Code=%s, %s", e.getCode(), e.getDisplayText()); + log.error(e.getStackTrace()); + + if (error == null) { + error = new ClickHouseException(result.getException().getCode(), result.getException().getDisplayText(), + null); + } + } + + return proceed; + } + + public boolean isCompleted() { + return finishLatch.getCount() == 0; + } + + public boolean isCancelled() { + return isCompleted() && error != null; + } + + public ClickHouseGrpcResponseSummary getSummary() { + return summary; + } + + public Throwable getError() { + return error; + } + + @Override + public void onNext(Result value) { + try { + checkClosed(); + + log.trace("Got result: %s", value); + + // consume value in a worker thread might not be helpful + if (updateStatus(value)) { + try { + // TODO close output stream if value.getOutput().isEmpty()? + value.getOutput().writeTo(stream); + } catch (IOException e) { + onError(e); + } + } + } finally { + startLatch.countDown(); + } + } + + @Override + public void onError(Throwable t) { + try { + log.error("Query failed", t); + + setError(t); + try { + stream.close(); + } catch (IOException e) { + // ignore + } + checkClosed(); + // Status status = Status.fromThrowable(error = t); + } finally { + startLatch.countDown(); + finishLatch.countDown(); + } + } + + @Override + public void onCompleted() { + log.trace("Query finished"); + + try { + stream.flush(); + } catch (IOException e) { + if (error == null) { + error = e; + } + log.error("Failed to flush output", e); + } finally { + startLatch.countDown(); + finishLatch.countDown(); + + try { + stream.close(); + } catch (IOException e) { + log.warn("Failed to close output stream", e); + } + } + } + + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + return startLatch.await(timeout, unit); + } + + public boolean awaitCompletion(long timeout, TimeUnit unit) throws InterruptedException { + return finishLatch.await(timeout, unit); + } + + public InputStream getInputStream() { + return stream.getInput(); + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/NettyChannelFactoryImpl.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/NettyChannelFactoryImpl.java new file mode 100644 index 000000000..5f2a3e683 --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/NettyChannelFactoryImpl.java @@ -0,0 +1,94 @@ +package com.clickhouse.client.grpc; + +import java.io.FileNotFoundException; +import javax.net.ssl.SSLException; +import io.grpc.ManagedChannelBuilder; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.shaded.io.netty.channel.ChannelOption; +import io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2SecurityUtil; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; +import io.grpc.netty.shaded.io.netty.handler.ssl.SupportedCipherSuiteFilter; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.config.ClickHouseSslMode; +import com.clickhouse.client.grpc.config.ClickHouseGrpcClientOption; + +final class NettyChannelFactoryImpl extends ClickHouseGrpcChannelFactory { + private final NettyChannelBuilder builder; + + NettyChannelFactoryImpl(ClickHouseConfig config, ClickHouseNode server) { + super(config, server); + + builder = NettyChannelBuilder.forAddress(server.getHost(), server.getPort()); + + int flowControlWindow = (int) config.getOption(ClickHouseGrpcClientOption.FLOW_CONTROL_WINDOW); + if (flowControlWindow > 0) { + builder.flowControlWindow(flowControlWindow); // what about initialFlowControlWindow? + } + } + + protected SslContext getSslContext() throws SSLException { + SslContextBuilder builder = SslContextBuilder.forClient(); + + ClickHouseSslMode sslMode = config.getSslMode(); + if (sslMode == ClickHouseSslMode.NONE) { + builder.trustManager(InsecureTrustManagerFactory.INSTANCE); + } else if (sslMode == ClickHouseSslMode.STRICT) { + String sslRootCert = config.getSslRootCert(); + if (!ClickHouseChecker.isNullOrEmpty(sslRootCert)) { + try { + builder.trustManager(ClickHouseUtils.getFileInputStream(sslRootCert)); + } catch (FileNotFoundException e) { + throw new SSLException("Failed to setup trust manager using given root certificate", e); + } + } + + String sslCert = config.getSslCert(); + String sslKey = config.getSslKey(); + if (!ClickHouseChecker.isNullOrEmpty(sslCert) && !ClickHouseChecker.isNullOrEmpty(sslKey)) { + try { + builder.keyManager(ClickHouseUtils.getFileInputStream(sslCert), + ClickHouseUtils.getFileInputStream(sslKey)); + } catch (FileNotFoundException e) { + throw new SSLException("Failed to setup key manager using given certificate and key", e); + } + } + } + + builder.sslProvider(SslProvider.JDK).ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE); + + return builder.build(); + } + + @Override + protected ManagedChannelBuilder getChannelBuilder() { + return builder; + } + + @Override + protected void setupSsl() { + if (!config.isSsl()) { + builder.usePlaintext(); + } else { + SslContext sslContext; + try { + sslContext = getSslContext(); + } catch (SSLException e) { + throw new IllegalStateException("Failed to build ssl context", e); + } + + builder.useTransportSecurity().sslContext(sslContext); + } + } + + @Override + protected void setupTimeout() { + builder.withOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeout()); + // .withOption(ChannelOption.SO_TIMEOUT, config.getSocketTimeout()); + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/OkHttpChannelFactoryImpl.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/OkHttpChannelFactoryImpl.java new file mode 100644 index 000000000..ce3386700 --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/OkHttpChannelFactoryImpl.java @@ -0,0 +1,49 @@ +package com.clickhouse.client.grpc; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import io.grpc.ManagedChannelBuilder; +import io.grpc.okhttp.OkHttpChannelBuilder; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseSslContextProvider; +import com.clickhouse.client.grpc.config.ClickHouseGrpcClientOption; + +final class OkHttpChannelFactoryImpl extends ClickHouseGrpcChannelFactory { + private final OkHttpChannelBuilder builder; + + OkHttpChannelFactoryImpl(ClickHouseConfig config, ClickHouseNode server) { + super(config, server); + + builder = OkHttpChannelBuilder.forAddress(server.getHost(), server.getPort()); + + int flowControlWindow = (int) config.getOption(ClickHouseGrpcClientOption.FLOW_CONTROL_WINDOW); + if (flowControlWindow > 0) { + builder.flowControlWindow(flowControlWindow); + } + } + + @Override + protected ManagedChannelBuilder getChannelBuilder() { + return builder; + } + + @Override + protected void setupSsl() { + if (!config.isSsl()) { + builder.usePlaintext(); + } else { + try { + builder.useTransportSecurity().sslSocketFactory(ClickHouseSslContextProvider.getProvider() + .getSslContext(SSLContext.class, config).get().getSocketFactory()); + } catch (SSLException e) { + throw new IllegalStateException(e); + } + } + } + + @Override + protected void setupTimeout() { + // custom socket factory? + } +} diff --git a/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/config/ClickHouseGrpcClientOption.java b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/config/ClickHouseGrpcClientOption.java new file mode 100644 index 000000000..c530646f1 --- /dev/null +++ b/clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/config/ClickHouseGrpcClientOption.java @@ -0,0 +1,67 @@ +package com.clickhouse.client.grpc.config; + +import com.clickhouse.client.ClickHouseChecker; +import com.clickhouse.client.config.ClickHouseConfigOption; + +/** + * gRPC client options. + */ +public enum ClickHouseGrpcClientOption implements ClickHouseConfigOption { + /** + * Flow control window. + */ + FLOW_CONTROL_WINDOW("flow_control_window", 0, + "Size of flow control window in byte, 0 or negative number are same as default"), + /** + * Maximum message size. + */ + MAX_INBOUND_MESSAGE_SIZE("max_inbound_message_size", 4 * 1024 * 1024, + "The maximum message size allowed to be received."), + /** + * Maximum size of metadata. + */ + MAX_INBOUND_METADATA_SIZE("max_inbound_metadata_size", 8192, + "The maximum size of metadata allowed to be received. This enforces HTTP/2 SETTINGS_MAX_HEADER_LIST_SIZE, the maximum size of header list that client is prepared to accept."), + /** + * Whether to use Okhttp instead of Netty. + */ + USE_OKHTTP("use_okhttp", false, + "Whether to use lightweight transport based on Okhttp instead of Netty. In many cases Netty is faster than Okhttp."), + /** + * Whether to use full stream decompression. + */ + USE_FULL_STREAM_DECOMPRESSION("use_full_stream_decompression", false, + "Whether to use full stream decompression for better compression ratio or not."); + + private final String key; + private final Object defaultValue; + private final Class clazz; + private final String description; + + ClickHouseGrpcClientOption(String key, T defaultValue, String description) { + this.key = ClickHouseChecker.nonNull(key, "key"); + this.defaultValue = ClickHouseChecker.nonNull(defaultValue, "defaultValue"); + this.clazz = defaultValue.getClass(); + this.description = ClickHouseChecker.nonNull(description, "description"); + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Class getValueType() { + return clazz; + } +} diff --git a/clickhouse-grpc-client/src/main/java9/module-info.java b/clickhouse-grpc-client/src/main/java9/module-info.java new file mode 100644 index 000000000..b8deb302c --- /dev/null +++ b/clickhouse-grpc-client/src/main/java9/module-info.java @@ -0,0 +1,20 @@ +module com.clickhouse.client.grpc { + exports com.clickhouse.client.grpc; + exports com.clickhouse.client.grpc.config; + // exports com.clickhouse.client.grpc.impl; + + provides com.clickhouse.client.ClickHouseClient with com.clickhouse.client.grpc.ClickHouseGrpcClient; + + requires java.base; + + requires static grpc.netty.shaded; + requires static grpc.okhttp; + + requires transitive com.clickhouse.client; + requires transitive com.google.gson; + requires transitive com.google.protobuf; + requires transitive io.grpc; + // requires transitive grpc.core; + // requires transitive grpc.protobuf; + // requires transitive grpc.stub; +} diff --git a/clickhouse-grpc-client/src/main/proto/clickhouse_grpc.proto b/clickhouse-grpc-client/src/main/proto/clickhouse_grpc.proto new file mode 100644 index 000000000..18e054a9e --- /dev/null +++ b/clickhouse-grpc-client/src/main/proto/clickhouse_grpc.proto @@ -0,0 +1,159 @@ +/* This file describes gRPC protocol supported in ClickHouse. + * + * To use this protocol a client should send one or more messages of the QueryInfo type + * and then receive one or more messages of the Result type. + * According to that the service provides four methods for that: + * ExecuteQuery(QueryInfo) returns (Result) + * ExecuteQueryWithStreamInput(stream QueryInfo) returns (Result) + * ExecuteQueryWithStreamOutput(QueryInfo) returns (stream Result) + * ExecuteQueryWithStreamIO(stream QueryInfo) returns (stream Result) + * It's up to the client to choose which method to use. + * For example, ExecuteQueryWithStreamInput() allows the client to add data multiple times + * while executing a query, which is suitable for inserting many rows. + */ + +// based on /~https://github.com/ClickHouse/ClickHouse/blob/9a40ce87e98a1c494dde0ddc19b6520b26dfe0b6/src/Server/grpc_protos/clickhouse_grpc.proto + +syntax = "proto3"; + +package clickhouse.grpc; + +// https://developers.google.com/protocol-buffers/docs/proto3#options +option java_package = "com.clickhouse.client.grpc.impl"; +option java_outer_classname = "ClickHouseGrpcImpl"; +option java_multiple_files = true; +option optimize_for = SPEED; + +message NameAndType { + string name = 1; + string type = 2; +} + +// Describes an external table - a table which will exists only while a query is executing. +message ExternalTable { + // Name of the table. If omitted, "_data" is used. + string name = 1; + + // Columns of the table. Types are required, names can be omitted. If the names are omitted, "_1", "_2", ... is used. + repeated NameAndType columns = 2; + + // Data to insert to the external table. + // If a method with streaming input (i.e. ExecuteQueryWithStreamInput() or ExecuteQueryWithStreamIO()) is used, + // then data for insertion to the same external table can be split between multiple QueryInfos. + bytes data = 3; + + // Format of the data to insert to the external table. + string format = 4; + + // Settings for executing that insertion, applied after QueryInfo.settings. + map settings = 5; +} + +// Information about a query which a client sends to a ClickHouse server. +// The first QueryInfo can set any of the following fields. Extra QueryInfos only add extra data. +// In extra QueryInfos only `input_data`, `external_tables`, `next_query_info` and `cancel` fields can be set. +message QueryInfo { + string query = 1; + string query_id = 2; + map settings = 3; + + // Default database. + string database = 4; + + // Input data, used both as data for INSERT query and as data for the input() function. + bytes input_data = 5; + + // Delimiter for input_data, inserted between input_data from adjacent QueryInfos. + bytes input_data_delimiter = 6; + + // Default output format. If not specified, 'TabSeparated' is used. + string output_format = 7; + + repeated ExternalTable external_tables = 8; + + string user_name = 9; + string password = 10; + string quota = 11; + + // Works exactly like sessions in the HTTP protocol. + string session_id = 12; + bool session_check = 13; + uint32 session_timeout = 14; + + // Set `cancel` to true to stop executing the query. + bool cancel = 15; + + // If true there will be at least one more QueryInfo in the input stream. + // `next_query_info` is allowed to be set only if a method with streaming input (i.e. ExecuteQueryWithStreamInput() or ExecuteQueryWithStreamIO()) is used. + bool next_query_info = 16; +} + +enum LogsLevel { + LOG_NONE = 0; + LOG_FATAL = 1; + LOG_CRITICAL = 2; + LOG_ERROR = 3; + LOG_WARNING = 4; + LOG_NOTICE = 5; + LOG_INFORMATION = 6; + LOG_DEBUG = 7; + LOG_TRACE = 8; +} + +message LogEntry { + uint32 time = 1; + uint32 time_microseconds = 2; + uint64 thread_id = 3; + string query_id = 4; + LogsLevel level = 5; + string source = 6; + string text = 7; +} + +message Progress { + uint64 read_rows = 1; + uint64 read_bytes = 2; + uint64 total_rows_to_read = 3; + uint64 written_rows = 4; + uint64 written_bytes = 5; +} + +message Stats { + uint64 rows = 1; + uint64 blocks = 2; + uint64 allocated_bytes = 3; + bool applied_limit = 4; + uint64 rows_before_limit = 5; +} + +message Exception { + int32 code = 1; + string name = 2; + string display_text = 3; + string stack_trace = 4; +} + +// Result of execution of a query which is sent back by the ClickHouse server to the client. +message Result { + // Output of the query, represented in the `output_format` or in a format specified in `query`. + bytes output = 1; + bytes totals = 2; + bytes extremes = 3; + + repeated LogEntry logs = 4; + Progress progress = 5; + Stats stats = 6; + + // Set by the ClickHouse server if there was an exception thrown while executing. + Exception exception = 7; + + // Set by the ClickHouse server if executing was cancelled by the `cancel` field in QueryInfo. + bool cancelled = 8; +} + +service ClickHouse { + rpc ExecuteQuery(QueryInfo) returns (Result) {} + rpc ExecuteQueryWithStreamInput(stream QueryInfo) returns (Result) {} + rpc ExecuteQueryWithStreamOutput(QueryInfo) returns (stream Result) {} + rpc ExecuteQueryWithStreamIO(stream QueryInfo) returns (stream Result) {} +} diff --git a/clickhouse-grpc-client/src/main/resources/META-INF/services/com.clickhouse.client.ClickHouseClient b/clickhouse-grpc-client/src/main/resources/META-INF/services/com.clickhouse.client.ClickHouseClient new file mode 100644 index 000000000..ed6e2a595 --- /dev/null +++ b/clickhouse-grpc-client/src/main/resources/META-INF/services/com.clickhouse.client.ClickHouseClient @@ -0,0 +1 @@ +com.clickhouse.client.grpc.ClickHouseGrpcClient diff --git a/clickhouse-grpc-client/src/test/java/com/clickhouse/client/grpc/ClickHouseGrpcChannelFactoryTest.java b/clickhouse-grpc-client/src/test/java/com/clickhouse/client/grpc/ClickHouseGrpcChannelFactoryTest.java new file mode 100644 index 000000000..09aeb9ec9 --- /dev/null +++ b/clickhouse-grpc-client/src/test/java/com/clickhouse/client/grpc/ClickHouseGrpcChannelFactoryTest.java @@ -0,0 +1,28 @@ +package com.clickhouse.client.grpc; + +import org.testng.Assert; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseIntegrationTest; +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseRequest; +import com.clickhouse.client.grpc.config.ClickHouseGrpcClientOption; + +public class ClickHouseGrpcChannelFactoryTest extends BaseIntegrationTest { + @Test(groups = { "integration" }) + public void testGetFactory() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + try (ClickHouseClient client = ClickHouseClient.newInstance()) { + ClickHouseRequest request = client.connect(server); + Assert.assertTrue(ClickHouseGrpcChannelFactory.getFactory(request.getConfig(), + server) instanceof NettyChannelFactoryImpl); + Assert.assertTrue(ClickHouseGrpcChannelFactory.getFactory( + request.option(ClickHouseGrpcClientOption.USE_OKHTTP, true).getConfig(), + server) instanceof OkHttpChannelFactoryImpl); + Assert.assertTrue(ClickHouseGrpcChannelFactory.getFactory( + request.option(ClickHouseGrpcClientOption.USE_OKHTTP, false).getConfig(), + server) instanceof NettyChannelFactoryImpl); + } + } +} diff --git a/clickhouse-grpc-client/src/test/java/com/clickhouse/client/grpc/ClickHouseGrpcClientTest.java b/clickhouse-grpc-client/src/test/java/com/clickhouse/client/grpc/ClickHouseGrpcClientTest.java new file mode 100644 index 000000000..595bd7b99 --- /dev/null +++ b/clickhouse-grpc-client/src/test/java/com/clickhouse/client/grpc/ClickHouseGrpcClientTest.java @@ -0,0 +1,698 @@ +package com.clickhouse.client.grpc; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import com.clickhouse.client.BaseIntegrationTest; +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseCompression; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseRecord; +import com.clickhouse.client.ClickHouseRequest; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseResponseSummary; +import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.ClickHouseValue; +import com.clickhouse.client.ClickHouseWriter; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.data.ClickHouseDateTimeValue; +import com.clickhouse.client.data.ClickHouseExternalTable; +import com.clickhouse.client.data.ClickHouseIntegerValue; +import com.clickhouse.client.data.ClickHouseIpv4Value; +import com.clickhouse.client.data.ClickHouseIpv6Value; +import com.clickhouse.client.exception.ClickHouseException; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseDataProcessor; +import com.clickhouse.client.ClickHouseDataType; +import com.clickhouse.client.ClickHouseFormat; + +public class ClickHouseGrpcClientTest extends BaseIntegrationTest { + @DataProvider(name = "simpleTypeProvider") + public Object[][] getSimpleTypes() { + return new Object[][] { + { ClickHouseDataType.Enum.name() + "('v-1' = -1, 'v0' = 0, 'v+1' = 1)", "0", "-1", "1" }, + { ClickHouseDataType.Enum8.name() + "('v-1' = -1, 'v0' = 0, 'v+1' = 1)", "0", "-1", "1" }, + { ClickHouseDataType.Enum16.name() + "('v-1' = -1, 'v0' = 0, 'v+1' = 1)", "0", "-1", "1" }, + { ClickHouseDataType.Int8.name(), "0", "-1", "1" }, + { ClickHouseDataType.UInt8.name(), "0", "255", "1" }, + { ClickHouseDataType.Int16.name(), "0", "-1", "1" }, + { ClickHouseDataType.UInt16.name(), "0", "65535", "1" }, + { ClickHouseDataType.Int32.name(), "0", "-1", "1" }, + { ClickHouseDataType.UInt32.name(), "0", "4294967295", "1" }, + { ClickHouseDataType.Int64.name(), "0", "-1", "1" }, + { ClickHouseDataType.UInt64.name(), "0", "18446744073709551615", "1" }, + { ClickHouseDataType.Int128.name(), "0", "-1", "1" }, + { ClickHouseDataType.UInt128.name(), "0", "340282366920938463463374607431768211455", "1" }, + { ClickHouseDataType.Int256.name(), "0", "-1", "1" }, + { ClickHouseDataType.UInt256.name(), "0", + "115792089237316195423570985008687907853269984665640564039457584007913129639935", "1" }, + { ClickHouseDataType.Float32.name(), "0.0", "-1.0", "1.0" }, + { ClickHouseDataType.Float64.name(), "0.0", "-1.0", "1.0" }, + { ClickHouseDataType.Date.name(), "1970-01-01", "1970-01-01", "1970-01-02" }, + { ClickHouseDataType.Date32.name(), "1970-01-01", "1969-12-31", "1970-01-02" }, + { ClickHouseDataType.DateTime.name(), "1970-01-01 00:00:00", "1970-01-01 00:00:00", + "1970-01-01 00:00:01" }, + { ClickHouseDataType.DateTime32.name(), "1970-01-01 00:00:00", "1970-01-01 00:00:00", + "1970-01-01 00:00:01" }, + { ClickHouseDataType.DateTime64.name() + "(3)", "1970-01-01 00:00:00", "1969-12-31 23:59:59.999", + "1970-01-01 00:00:00.001" }, + { ClickHouseDataType.Decimal.name() + "(10,9)", "0E-9", "-1.000000000", "1.000000000" }, + { ClickHouseDataType.Decimal32.name() + "(1)", "0.0", "-1.0", "1.0" }, + { ClickHouseDataType.Decimal64.name() + "(3)", "0.000", "-1.000", "1.000" }, + { ClickHouseDataType.Decimal128.name() + "(5)", "0.00000", "-1.00000", "1.00000" }, + { ClickHouseDataType.Decimal256.name() + "(7)", "0E-7", "-1.0000000", "1.0000000" }, + { ClickHouseDataType.FixedString.name() + "(3)", "0\0\0", "-1\0", "1\0\0" }, + { ClickHouseDataType.String.name(), "0", "-1", "1" }, + { ClickHouseDataType.UUID.name(), "00000000-0000-0000-0000-000000000000", + "00000000-0000-0000-ffff-ffffffffffff", "00000000-0000-0000-0000-000000000001" } }; + } + + private void execute(ClickHouseRequest request, String sql) throws Exception { + try (ClickHouseResponse response = request.query(sql).execute().get()) { + for (ClickHouseRecord record : response.records()) { + for (ClickHouseValue value : record) { + Assert.assertNotNull(value); + } + } + } + } + + @Test(groups = { "unit" }) + public void testInit() throws Exception { + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC)) { + Assert.assertTrue(client instanceof ClickHouseGrpcClient); + } + } + + @Test(groups = { "integration" }) + public void testOpenCloseConnection() throws Exception { + for (int i = 0; i < 100; i++) { + Assert.assertTrue(ClickHouseClient.test(getServer(ClickHouseProtocol.GRPC), 3000)); + } + } + + @Test(groups = { "integration" }) + public void testQueryWithNoResult() throws Exception { + String sql = "select * from system.numbers limit 0"; + + try (ClickHouseClient client = ClickHouseClient.builder().build()) { + // header without row + try (ClickHouseResponse response = client.connect(getServer(ClickHouseProtocol.GRPC)) + .option(ClickHouseClientOption.MAX_BUFFER_SIZE, 8) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).query(sql).execute().get()) { + Assert.assertEquals(response.getColumns().size(), 1); + Assert.assertNotEquals(response.getColumns(), ClickHouseDataProcessor.DEFAULT_COLUMNS); + for (ClickHouseRecord record : response.records()) { + Assert.fail(ClickHouseUtils.format("Should have no record, but we got: %s", record)); + } + } + + // no header and row + try (ClickHouseResponse response = client.connect(getServer(ClickHouseProtocol.GRPC)) + .option(ClickHouseClientOption.MAX_BUFFER_SIZE, 8).format(ClickHouseFormat.RowBinary).query(sql) + .execute().get()) { + Assert.assertEquals(response.getColumns(), Collections.emptyList()); + for (ClickHouseRecord record : response.records()) { + Assert.fail(ClickHouseUtils.format("Should have no record, but we got: %s", record)); + } + } + + // custom header and row + try (ClickHouseResponse response = client.connect(getServer(ClickHouseProtocol.GRPC)) + .option(ClickHouseClientOption.MAX_BUFFER_SIZE, 8).format(ClickHouseFormat.RowBinary).query(sql) + .execute().get()) { + Assert.assertEquals(response.getColumns(), Collections.emptyList()); + for (ClickHouseRecord record : response.records()) { + Assert.fail(ClickHouseUtils.format("Should have no record, but we got: %s", record)); + } + } + } + } + + @Test(groups = { "integration" }) + public void testQuery() throws Exception { + ClickHouseNode node = getServer(ClickHouseProtocol.GRPC); + + try (ClickHouseClient client = ClickHouseClient.builder().build()) { + Assert.assertTrue(client instanceof ClickHouseGrpcClient); + + // "select * from system.data_type_families" + int limit = 10000; + String sql = "select number, toString(number) from system.numbers limit " + limit; + + try (ClickHouseResponse response = client.connect(node).option(ClickHouseClientOption.MAX_BUFFER_SIZE, 8) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).set("send_logs_level", "trace") + .set("enable_optimize_predicate_expression", 1) + .set("log_queries_min_type", "EXCEPTION_WHILE_PROCESSING").query(sql).execute().get()) { + List columns = response.getColumns(); + int index = 0; + for (ClickHouseRecord record : response.records()) { + String col1 = String.valueOf(record.getValue(0).asBigInteger()); + String col2 = record.getValue(1).asString(); + Assert.assertEquals(record.size(), columns.size()); + Assert.assertEquals(col1, col2); + Assert.assertEquals(col1, String.valueOf(index++)); + } + + // int counter = 0; + // for (ClickHouseValue value : response.values()) { + // Assert.assertEquals(value.asString(), String.valueOf(index)); + // index += counter++ % 2; + // } + Assert.assertEquals(index, limit); + // Thread.sleep(30000); + /* + * while (response.hasError()) { int index = 0; for (ClickHouseColumn c : + * columns) { // RawValue v = response.getRawValue(index++); // String v = + * response.getValue(index++, String.class) } + * + * } byte[] bytes = in.readAllBytes(); String str = new String(bytes); + */ + } catch (Exception e) { + Assert.fail("Query failed", e); + } + } + } + + @Test(groups = "integration") + public void testQueryInSameThread() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + try (ClickHouseClient client = ClickHouseClient.builder().addOption(ClickHouseClientOption.ASYNC, false) + .build()) { + CompletableFuture future = client.connect(server) + .format(ClickHouseFormat.TabSeparatedWithNamesAndTypes).query("select 1,2").execute(); + // Assert.assertTrue(future instanceof ClickHouseImmediateFuture); + Assert.assertTrue(future.isDone()); + try (ClickHouseResponse resp = future.get()) { + Assert.assertEquals(resp.getColumns().size(), 2); + for (ClickHouseRecord record : resp.records()) { + Assert.assertEquals(record.size(), 2); + Assert.assertEquals(record.getValue(0).asInteger(), 1); + Assert.assertEquals(record.getValue(1).asInteger(), 2); + } + + ClickHouseResponseSummary summary = resp.getSummary(); + Assert.assertEquals(summary.getRows(), 1); + } + } + } + + @Test(groups = "integration") + public void testQueryIntervalTypes() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC)) { + for (ClickHouseDataType type : new ClickHouseDataType[] { ClickHouseDataType.IntervalYear, + ClickHouseDataType.IntervalQuarter, ClickHouseDataType.IntervalMonth, + ClickHouseDataType.IntervalWeek, ClickHouseDataType.IntervalDay, ClickHouseDataType.IntervalHour, + ClickHouseDataType.IntervalMinute, ClickHouseDataType.IntervalSecond }) { + try (ClickHouseResponse resp = client.connect(server) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query(ClickHouseUtils.format( + "select to%1$s(0), to%1$s(-1), to%1$s(1), to%1$s(%2$d), to%1$s(%3$d)", type.name(), + Long.MIN_VALUE, Long.MAX_VALUE)) + .execute().get()) { + List records = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + records.add(record); + } + + Assert.assertEquals(records.size(), 1); + ClickHouseRecord r = records.get(0); + Assert.assertEquals(r.getValue(0).asString(), "0"); + Assert.assertEquals(r.getValue(1).asString(), "-1"); + Assert.assertEquals(r.getValue(2).asString(), "1"); + Assert.assertEquals(r.getValue(3).asString(), String.valueOf(Long.MIN_VALUE)); + Assert.assertEquals(r.getValue(4).asString(), String.valueOf(Long.MAX_VALUE)); + } + } + } + } + + @Test(groups = "integration") + public void testReadWriteDateTimeTypes() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + ClickHouseClient.send(server, "drop table if exists test_datetime_types", + "create table test_datetime_types(no UInt8, d0 DateTime32, d1 DateTime64(5), d2 DateTime(3)) engine=Memory") + .get(); + ClickHouseClient.send(server, "insert into test_datetime_types values(:no, :d0, :d1, :d2)", + new ClickHouseValue[] { ClickHouseIntegerValue.ofNull(), ClickHouseDateTimeValue.ofNull(0), + ClickHouseDateTimeValue.ofNull(3), ClickHouseDateTimeValue.ofNull(9) }, + new Object[] { 0, "1970-01-01 00:00:00", "1970-01-01 00:00:00.123456", + "1970-01-01 00:00:00.123456789" }, + new Object[] { 1, -1, -1, -1 }, new Object[] { 2, 1, 1, 1 }, new Object[] { 3, 2.1, 2.1, 2.1 }).get(); + + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + ClickHouseResponse resp = client.connect(server).format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * except(no) from test_datetime_types order by no").execute().get()) { + List list = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + list.add(record); + } + + Assert.assertEquals(list.size(), 4); + } + } + + @Test(groups = "integration") + public void testReadWriteDomains() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + ClickHouseClient.send(server, "drop table if exists test_domain_types", + "create table test_domain_types(no UInt8, ipv4 IPv4, nipv4 Nullable(IPv4), ipv6 IPv6, nipv6 Nullable(IPv6)) engine=Memory") + .get(); + + ClickHouseClient.send(server, "insert into test_domain_types values(:no, :i0, :i1, :i2, :i3)", + new ClickHouseValue[] { ClickHouseIntegerValue.ofNull(), ClickHouseIpv4Value.ofNull(), + ClickHouseIpv4Value.ofNull(), ClickHouseIpv6Value.ofNull(), ClickHouseIpv6Value.ofNull() }, + new Object[] { 0, + (Inet4Address) InetAddress.getByAddress(new byte[] { (byte) 0, (byte) 0, (byte) 0, (byte) 0 }), + null, + Inet6Address.getByAddress(null, + new byte[] { (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0 }, + null), + null }, + new Object[] { 1, + (Inet4Address) InetAddress.getByAddress(new byte[] { (byte) 0, (byte) 0, (byte) 0, (byte) 1 }), + (Inet4Address) InetAddress + .getByAddress(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }), + Inet6Address.getByAddress(null, + new byte[] { (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 1 }, + null), + Inet6Address.getByAddress(null, + new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }, + null) }) + .get(); + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + ClickHouseResponse resp = client.connect(server).format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * except(no) from test_domain_types order by no").execute().get()) { + List list = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + list.add(record); + } + + Assert.assertEquals(list.size(), 2); + } + } + + @Test(groups = "integration") + public void testReadWriteGeoTypes() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + ClickHouseClient.send(server, "set allow_experimental_geo_types=1", "drop table if exists test_geo_types", + "create table test_geo_types(no UInt8, p Point, r Ring, pg Polygon, mp MultiPolygon) engine=Memory") + .get(); + + // write + ClickHouseClient.send(server, + "insert into test_geo_types values(0, (0,0), " + "[(0,0),(0,0)], [[(0,0),(0,0)],[(0,0),(0,0)]], " + + "[[[(0,0),(0,0)],[(0,0),(0,0)]],[[(0,0),(0,0)],[(0,0),(0,0)]]])", + "insert into test_geo_types values(1, (-1,-1), " + + "[(-1,-1),(-1,-1)], [[(-1,-1),(-1,-1)],[(-1,-1),(-1,-1)]], " + + "[[[(-1,-1),(-1,-1)],[(-1,-1),(-1,-1)]],[[(-1,-1),(-1,-1)],[(-1,-1),(-1,-1)]]])", + "insert into test_geo_types values(2, (1,1), " + "[(1,1),(1,1)], [[(1,1),(1,1)],[(1,1),(1,1)]], " + + "[[[(1,1),(1,1)],[(1,1),(1,1)]],[[(1,1),(1,1)],[(1,1),(1,1)]]])") + .get(); + + // read + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + ClickHouseResponse resp = client.connect(server).format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * except(no) from test_geo_types order by no").execute().get()) { + List records = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + String[] values = new String[record.size()]; + int index = 0; + for (ClickHouseValue v : record) { + values[index++] = v.asString(); + } + records.add(values); + } + + Assert.assertEquals(records.size(), 3); + Assert.assertEquals(records.get(0)[0], "(0.0,0.0)"); + Assert.assertEquals(records.get(0)[1], "[(0.0,0.0),(0.0,0.0)]"); + Assert.assertEquals(records.get(0)[2], "[[(0.0,0.0),(0.0,0.0)],[(0.0,0.0),(0.0,0.0)]]"); + Assert.assertEquals(records.get(0)[3], + "[[[(0.0,0.0),(0.0,0.0)],[(0.0,0.0),(0.0,0.0)]],[[(0.0,0.0),(0.0,0.0)],[(0.0,0.0),(0.0,0.0)]]]"); + Assert.assertEquals(records.get(1)[0], "(-1.0,-1.0)"); + Assert.assertEquals(records.get(1)[1], "[(-1.0,-1.0),(-1.0,-1.0)]"); + Assert.assertEquals(records.get(1)[2], "[[(-1.0,-1.0),(-1.0,-1.0)],[(-1.0,-1.0),(-1.0,-1.0)]]"); + Assert.assertEquals(records.get(1)[3], + "[[[(-1.0,-1.0),(-1.0,-1.0)],[(-1.0,-1.0),(-1.0,-1.0)]],[[(-1.0,-1.0),(-1.0,-1.0)],[(-1.0,-1.0),(-1.0,-1.0)]]]"); + Assert.assertEquals(records.get(2)[0], "(1.0,1.0)"); + Assert.assertEquals(records.get(2)[1], "[(1.0,1.0),(1.0,1.0)]"); + Assert.assertEquals(records.get(2)[2], "[[(1.0,1.0),(1.0,1.0)],[(1.0,1.0),(1.0,1.0)]]"); + Assert.assertEquals(records.get(2)[3], + "[[[(1.0,1.0),(1.0,1.0)],[(1.0,1.0),(1.0,1.0)]],[[(1.0,1.0),(1.0,1.0)],[(1.0,1.0),(1.0,1.0)]]]"); + } + } + + @Test(dataProvider = "simpleTypeProvider", groups = "integration") + public void testReadWriteSimpleTypes(String dataType, String zero, String negativeOne, String positiveOne) + throws Exception { + // if (ClickHouseDataType.Date32.name().equals(dataType)) { + // return; + // } + + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + String typeName = dataType; + String columnName = typeName.toLowerCase(); + int currIdx = columnName.indexOf('('); + if (currIdx > 0) { + columnName = columnName.substring(0, currIdx); + } + String dropTemplate = "drop table if exists test_%s"; + String createTemplate = "create table test_%1$s(no UInt8, %1$s %2$s, n%1$s Nullable(%2$s)) engine=Memory"; + String insertTemplate = "insert into table test_%s values(%s, %s, %s)"; + + String negativeOneValue = "-1"; + String zeroValue = "0"; + String positiveOneValue = "1"; + if (dataType.startsWith(ClickHouseDataType.FixedString.name())) { + negativeOneValue = "'-1'"; + zeroValue = "'0'"; + positiveOneValue = "'1'"; + } else if (dataType.startsWith(ClickHouseDataType.UUID.name())) { + negativeOneValue = ClickHouseUtils.format("'%s'", ClickHouseIntegerValue.of(-1).asUuid()); + zeroValue = ClickHouseUtils.format("'%s'", ClickHouseIntegerValue.of(0).asUuid()); + positiveOneValue = ClickHouseUtils.format("'%s'", ClickHouseIntegerValue.of(1).asUuid()); + } + + try { + ClickHouseClient + .send(server, ClickHouseUtils.format(dropTemplate, columnName), + ClickHouseUtils.format(createTemplate, columnName, typeName), + ClickHouseUtils.format(insertTemplate, columnName, 0, zeroValue, null), + ClickHouseUtils.format(insertTemplate, columnName, 1, zeroValue, zeroValue), + ClickHouseUtils.format(insertTemplate, columnName, 2, negativeOneValue, negativeOneValue), + ClickHouseUtils.format(insertTemplate, columnName, 3, positiveOneValue, positiveOneValue)) + .get(); + } catch (ExecutionException e) { + // maybe the type is just not supported, for example: Date32 + Throwable cause = e.getCause(); + Assert.assertTrue(cause instanceof ClickHouseException); + return; + } + + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + ClickHouseResponse resp = client.connect(server).format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query(ClickHouseUtils.format("select * except(no) from test_%s order by no", columnName)) + .execute().get()) { + List records = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + String[] values = new String[record.size()]; + int index = 0; + for (ClickHouseValue v : record) { + values[index++] = v.asString(); + } + records.add(values); + } + + Assert.assertEquals(records.size(), 4); + Assert.assertEquals(records.get(0)[0], zero); + Assert.assertEquals(records.get(0)[1], null); + Assert.assertEquals(records.get(1)[0], zero); + Assert.assertEquals(records.get(1)[1], zero); + Assert.assertEquals(records.get(2)[0], negativeOne); + Assert.assertEquals(records.get(2)[1], negativeOne); + Assert.assertEquals(records.get(3)[0], positiveOne); + Assert.assertEquals(records.get(3)[1], positiveOne); + } + } + + @Test(groups = "integration") + public void testReadWriteMap() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + try { + ClickHouseClient + .send(server, "drop table if exists test_map_types", + "create table test_map_types(no UInt32, m Map(LowCardinality(String), Int32))engine=Memory") + .get(); + } catch (ExecutionException e) { + // looks like LowCardinality(String) as key is not supported even in 21.8 + Throwable cause = e.getCause(); + Assert.assertTrue(cause instanceof ClickHouseException); + return; + } + + // write + ClickHouseClient.send(server, "insert into test_map_types values (1, {'key1' : 1})").get(); + ClickHouseClient.send(server, "insert into test_map_types values (:n,:m)", + new String[][] { new String[] { "-1", "{'key-1' : -1}" }, new String[] { "-2", "{'key-2' : -2}" } }) + .get(); + ClickHouseClient.send(server, "insert into test_map_types values (3, :m)", + Collections.singletonMap("m", "{'key3' : 3}")).get(); + + // read + try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.GRPC); + ClickHouseResponse resp = client.connect(server).format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * except(no) from test_map_types order by no").execute().get()) { + List records = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + String[] values = new String[record.size()]; + int index = 0; + for (ClickHouseValue v : record) { + values[index++] = v.asString(); + } + records.add(values); + } + + Assert.assertEquals(records.size(), 4); + } + } + + @Test(groups = "integration") + public void testQueryWithMultipleExternalTables() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + int tables = 30; + int rows = 10; + try (ClickHouseClient client = ClickHouseClient.builder().build()) { + try (ClickHouseResponse resp = client.connect(server).query("drop table if exists test_ext_data_query") + .execute().get()) { + } + + String ddl = "create table test_ext_data_query (\n" + " Cb String,\n" + " CREATETIME DateTime64(3),\n" + + " TIMESTAMP UInt64,\n" + " Cc String,\n" + " Ca1 UInt64,\n" + " Ca2 UInt64,\n" + + " Ca3 UInt64\n" + ") engine = MergeTree()\n" + "PARTITION BY toYYYYMMDD(CREATETIME)\n" + + "ORDER BY (Cb, CREATETIME, Cc);"; + try (ClickHouseResponse resp = client.connect(server).query(ddl).execute().get()) { + } + } + + String template = "avgIf(Ca1, Cb in L%1$d) as avgCa1%2$d, sumIf(Ca1, Cb in L%1$d) as sumCa1%2$d, minIf(Ca1, Cb in L%1$d) as minCa1%2$d, maxIf(Ca1, Cb in L%1$d) as maxCa1%2$d, anyIf(Ca1, Cb in L%1$d) as anyCa1%2$d, avgIf(Ca2, Cb in L%1$d) as avgCa2%2$d, sumIf(Ca2, Cb in L%1$d) as sumCa2%2$d, minIf(Ca2, Cb in L%1$d) as minCa2%2$d, maxIf(Ca2, Cb in L%1$d) as maxCa2%2$d, anyIf(Ca2, Cb in L%1$d) as anyCa2%2$d, avgIf(Ca3, Cb in L%1$d) as avgCa3%2$d, sumIf(Ca3, Cb in L%1$d) as sumCa3%2$d, minIf(Ca3, Cb in L%1$d) as minCa3%2$d, maxIf(Ca3, Cb in L%1$d) as maxCa3%2$d, anyIf(Ca3, Cb in L%1$d) as anyCa3%2$d"; + StringBuilder sql = new StringBuilder().append("select "); + List extTableList = new ArrayList<>(tables); + for (int i = 0; i < tables; i++) { + sql.append(ClickHouseUtils.format(template, i, i + 1)).append(','); + List valueList = new ArrayList<>(rows); + for (int j = i, size = i + rows; j < size; j++) { + valueList.add(String.valueOf(j)); + } + String dnExtString = String.join("\n", valueList); + InputStream inputStream = new ByteArrayInputStream(dnExtString.getBytes(Charset.forName("UTF-8"))); + ClickHouseExternalTable extTable = ClickHouseExternalTable.builder().withName("L" + i) + .withContent(inputStream).addColumn("Cb", "String").build(); + extTableList.add(extTable); + } + + if (tables > 0) { + sql.deleteCharAt(sql.length() - 1); + } else { + sql.append('*'); + } + sql.append( + " from test_ext_data_query where TIMESTAMP >= 1625796480 and TIMESTAMP < 1625796540 and Cc = 'eth0'"); + + try (ClickHouseClient client = ClickHouseClient.builder().build(); + ClickHouseResponse resp = client.connect(server).query(sql.toString()) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).external(extTableList).execute().get()) { + Assert.assertNotNull(resp.getColumns()); + Assert.assertTrue(tables <= 0 || resp.records().iterator().hasNext()); + } + } + + @Test(groups = { "integration" }) + public void testMutation() throws Exception { + ClickHouseNode node = getServer(ClickHouseProtocol.GRPC); + + try (ClickHouseClient client = ClickHouseClient.builder().build()) { + Assert.assertTrue(client instanceof ClickHouseGrpcClient); + + ClickHouseRequest request = client.connect(node).option(ClickHouseClientOption.MAX_BUFFER_SIZE, 8) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).set("send_logs_level", "trace") + .set("enable_optimize_predicate_expression", 1) + .set("log_queries_min_type", "EXCEPTION_WHILE_PROCESSING"); + execute(request, "drop table if exists test_grpc_mutation;"); + execute(request, "create table if not exists test_grpc_mutation(a String, b UInt32) engine = Memory;"); + execute(request, "insert into test_grpc_mutation values('a', 1)('b', 2)"); + } + } + + @Test(groups = { "integration" }) + public void testFormat() throws Exception { + String sql = "select 1, 2"; + ClickHouseNode node = getServer(ClickHouseProtocol.GRPC); + + try (ClickHouseClient client = ClickHouseClient.builder().build()) { + try (ClickHouseResponse response = client.connect(node).option(ClickHouseClientOption.MAX_BUFFER_SIZE, 8) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).query(sql).execute().get()) { + Assert.assertEquals(response.getColumns().size(), 2); + int counter = 0; + for (ClickHouseRecord record : response.records()) { + Assert.assertEquals(record.getValue(0).asShort(), 1); + Assert.assertEquals(record.getValue(1).asShort(), 2); + counter++; + } + Assert.assertEquals(counter, 1); + } + + // now let's try again using unsupported formats + try (ClickHouseResponse response = client.connect(node).query(sql).format(ClickHouseFormat.CSV).execute() + .get()) { + String results = new BufferedReader( + new InputStreamReader(response.getInputStream(), StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("\n")); + Assert.assertEquals(results, "1,2"); + } + + try (ClickHouseResponse response = client.connect(node).query(sql).format(ClickHouseFormat.JSONEachRow) + .execute().get()) { + String results = new BufferedReader( + new InputStreamReader(response.getInputStream(), StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("\n")); + Assert.assertEquals(results, "{\"1\":1,\"2\":2}"); + } + } + } + + @Test(groups = { "integration" }) + public void testDump() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + Path temp = Files.createTempFile("dump", ".tsv"); + Assert.assertEquals(Files.size(temp), 0L); + + int lines = 10000; + ClickHouseResponseSummary summary = ClickHouseClient.dump(server, "select * from system.numbers limit " + lines, + ClickHouseFormat.TabSeparated, ClickHouseCompression.BROTLI, temp.toString()).get(); + Assert.assertNotNull(summary); + Assert.assertEquals(summary.getReadRows(), lines); + + int counter = 0; + for (String line : Files.readAllLines(temp)) { + Assert.assertEquals(String.valueOf(counter++), line); + } + Assert.assertEquals(counter, lines); + + Files.delete(temp); + } + + @Test(groups = { "integration" }) + public void testCustomLoad() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + ClickHouseClient.send(server, "drop table if exists test_grpc_custom_load", + "create table if not exists test_grpc_custom_load(n UInt32, s Nullable(String)) engine = Memory").get(); + + ClickHouseClient.load(server, "test_grpc_custom_load", ClickHouseFormat.TabSeparated, + ClickHouseCompression.NONE, new ClickHouseWriter() { + @Override + public void write(OutputStream output) throws IOException { + output.write("1\t\\N\n".getBytes()); + output.write("2\t123".getBytes()); + } + }).get(); + + try (ClickHouseClient client = ClickHouseClient.newInstance(server.getProtocol()); + ClickHouseResponse resp = client.connect(server).query("select * from test_grpc_custom_load order by n") + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).execute().get()) { + Assert.assertNotNull(resp.getColumns()); + List values = new ArrayList<>(); + for (ClickHouseRecord record : resp.records()) { + String[] arr = new String[2]; + arr[0] = record.getValue(0).asString(); + arr[1] = record.getValue(1).asString(); + values.add(arr); + } + + Assert.assertEquals(values.size(), 2); + Assert.assertEquals(values.get(0), new String[] { "1", null }); + Assert.assertEquals(values.get(1), new String[] { "2", "123" }); + } + } + + @Test(groups = { "integration" }) + public void testLoadCsv() throws Exception { + ClickHouseNode server = getServer(ClickHouseProtocol.GRPC); + + List summaries = ClickHouseClient + .send(server, "drop table if exists test_grpc_load_data", + "create table if not exists test_grpc_load_data(n UInt32) engine = Memory") + .get(); + Assert.assertNotNull(summaries); + Assert.assertEquals(summaries.size(), 2); + + Path temp = Files.createTempFile("data", ".tsv"); + Assert.assertEquals(Files.size(temp), 0L); + + int lines = 10000; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < lines; i++) { + builder.append(i).append('\n'); + } + Files.write(temp, builder.toString().getBytes()); + Assert.assertTrue(Files.size(temp) > 0L); + + ClickHouseResponseSummary summary = ClickHouseClient.load(server, "test_grpc_load_data", + ClickHouseFormat.TabSeparated, ClickHouseCompression.NONE, temp.toString()).get(); + Assert.assertNotNull(summary); + Assert.assertEquals(summary.getWriteRows(), lines); + + try (ClickHouseClient client = ClickHouseClient.newInstance(server.getProtocol()); + ClickHouseResponse resp = client.connect(server) + .query("select min(n), max(n), count(1), uniqExact(n) from test_grpc_load_data") + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes).execute().get()) { + Assert.assertNotNull(resp.getColumns()); + for (ClickHouseRecord record : resp.records()) { + Assert.assertNotNull(record); + Assert.assertEquals(record.getValue(0).asLong(), 0L); + Assert.assertEquals(record.getValue(1).asLong(), lines - 1); + Assert.assertEquals(record.getValue(2).asLong(), lines); + Assert.assertEquals(record.getValue(3).asLong(), lines); + } + } finally { + Files.delete(temp); + } + } +} diff --git a/clickhouse-grpc-client/src/test/resources/log4j.properties b/clickhouse-grpc-client/src/test/resources/log4j.properties new file mode 100644 index 000000000..204c71e96 --- /dev/null +++ b/clickhouse-grpc-client/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=INFO, STDOUT +log4j.category.com.clickhouse.client=DEBUG +log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender +log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout +log4j.appender.STDOUT.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.sss} [%t] [%-5p] {%c{1}:%L} - %m%n diff --git a/clickhouse-http-client/pom.xml b/clickhouse-http-client/pom.xml index c8f3289c0..5a558e0fb 100644 --- a/clickhouse-http-client/pom.xml +++ b/clickhouse-http-client/pom.xml @@ -1,13 +1,13 @@ - + 4.0.0 - tech.clickhouse + com.clickhouse clickhouse-java ${revision} + ${project.parent.groupId} clickhouse-http-client ${revision} jar @@ -16,10 +16,6 @@ HTTP client for ClickHouse /~https://github.com/ClickHouse/clickhouse-jdbc/tree/master/clickhouse-http-client - - 4.5.13 - - ${project.parent.groupId} @@ -29,12 +25,10 @@ org.apache.httpcomponents httpclient - ${httpclient.version} org.apache.httpcomponents httpmime - ${httpclient.version} @@ -60,30 +54,4 @@ test - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - true - - -Xlint:all - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - - - - + \ No newline at end of file diff --git a/clickhouse-jdbc/pom.xml b/clickhouse-jdbc/pom.xml index bc752ac94..55f4db7f3 100644 --- a/clickhouse-jdbc/pom.xml +++ b/clickhouse-jdbc/pom.xml @@ -1,9 +1,8 @@ - + 4.0.0 - tech.clickhouse + com.clickhouse clickhouse-java ${revision} @@ -93,6 +92,19 @@ slf4j-api + + ${project.parent.groupId} + clickhouse-client + ${revision} + test + + + ${project.parent.groupId} + clickhouse-client + ${revision} + test-jar + test + org.slf4j slf4j-log4j12 @@ -100,7 +112,7 @@ org.mockito - mockito-all + mockito-core test @@ -142,7 +154,7 @@ javacc - ${jdk.version} + 1.8 true ru.yandex.clickhouse.jdbc.parser src/main/javacc @@ -151,15 +163,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - true - - org.apache.maven.plugins maven-jar-plugin @@ -220,9 +223,9 @@ - - - + + + @@ -264,4 +267,4 @@ - + \ No newline at end of file diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseDriver.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseDriver.java index ab14046e1..a7673cafe 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseDriver.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseDriver.java @@ -46,7 +46,7 @@ public class ClickHouseDriver implements Driver { } catch (SQLException e) { throw new RuntimeException(e); } - logger.debug("Driver registered"); + logger.info("Driver registered"); } @Override diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java index 98fce4ccf..df28df16f 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java @@ -144,6 +144,10 @@ public boolean isStreaming() { */ protected String currentDatabase; + protected String getQueryId() { + return queryId; + } + protected ClickHouseSqlStatement getLastStatement() { ClickHouseSqlStatement stmt = null; diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/except/ClickHouseExceptionSpecifier.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/except/ClickHouseExceptionSpecifier.java index c4bbe0848..3f9620a1b 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/except/ClickHouseExceptionSpecifier.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/except/ClickHouseExceptionSpecifier.java @@ -9,9 +9,9 @@ import java.net.ConnectException; import java.net.SocketTimeoutException; - /** - * Specify ClickHouse exception to ClickHouseException and fill it with a vendor code. + * Specify ClickHouse exception to ClickHouseException and fill it with a vendor + * code. */ public final class ClickHouseExceptionSpecifier { @@ -58,29 +58,36 @@ private static ClickHouseException specify(String clickHouseMessage, Throwable c return new ClickHouseException(code, messageHolder, host, port); } catch (Exception e) { - log.error("Unsupported ClickHouse error format, please fix ClickHouseExceptionSpecifier, message: {}, error: {}", clickHouseMessage, e.getMessage()); + log.error( + "Unsupported ClickHouse error format, please fix ClickHouseExceptionSpecifier, message: {}, error: {}", + clickHouseMessage, e.getMessage()); return new ClickHouseUnknownException(clickHouseMessage, cause, host, port); } } private static int getErrorCode(String errorMessage) { int startIndex = errorMessage.indexOf(' '); - int endIndex = startIndex == -1 ? -1 : errorMessage.indexOf(',', startIndex); - - if (startIndex == -1 || endIndex == -1) { - return -1; + if (startIndex >= 0) { + for (int i = ++startIndex, len = errorMessage.length(); i < len; i++) { + char ch = errorMessage.charAt(i); + if (ch == '.' || ch == ',' || Character.isWhitespace(ch)) { + try { + return Integer.parseInt(errorMessage.substring(startIndex, i)); + } catch (NumberFormatException e) { + // ignore + } + break; + } + } } - try { - return Integer.parseInt(errorMessage.substring(startIndex + 1, endIndex)); - } catch(NumberFormatException e) { - return -1; - } + return -1; } private static ClickHouseException getException(Throwable cause, String host, int port) { if (cause instanceof SocketTimeoutException) - // if we've got SocketTimeoutException, we'll say that the query is not good. This is not the same as SOCKET_TIMEOUT of clickhouse + // if we've got SocketTimeoutException, we'll say that the query is not good. + // This is not the same as SOCKET_TIMEOUT of clickhouse // but it actually could be a failing ClickHouse { return new ClickHouseException(ClickHouseErrorCode.TIMEOUT_EXCEEDED.code, cause, host, port); diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java index 1b59f6d66..2f9b53568 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java @@ -19,6 +19,14 @@ public enum ClickHouseConnectionSettings implements DriverPropertyCreator { CHECK_FOR_REDIRECTS("check_for_redirects", false, "whether we should check for 307 redirect using GET before sending POST to given URL"), MAX_REDIRECTS("max_redirects", 5, "number of redirect checks before using last URL"), + /* + * + * */ + DATA_TRANSFER_TIMEOUT( "dataTransferTimeout", 10000, "Timeout for data transfer. " + + " socketTimeout + dataTransferTimeout is sent to ClickHouse as max_execution_time. " + + " ClickHouse rejects request execution if its time exceeds max_execution_time"), + + /** * for ConnectionManager */ diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java index 9e8d831d7..10da23b81 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java @@ -16,6 +16,7 @@ public class ClickHouseProperties { private int apacheBufferSize; private int socketTimeout; private int connectionTimeout; + private int dataTransferTimeout; private int timeToLiveMillis; private int defaultMaxPerRoute; private int maxTotal; @@ -109,6 +110,7 @@ public ClickHouseProperties(Properties info) { this.apacheBufferSize = (Integer)getSetting(info, ClickHouseConnectionSettings.APACHE_BUFFER_SIZE); this.socketTimeout = (Integer)getSetting(info, ClickHouseConnectionSettings.SOCKET_TIMEOUT); this.connectionTimeout = (Integer)getSetting(info, ClickHouseConnectionSettings.CONNECTION_TIMEOUT); + this.dataTransferTimeout = (Integer)getSetting(info, ClickHouseConnectionSettings.DATA_TRANSFER_TIMEOUT); this.timeToLiveMillis = (Integer)getSetting(info, ClickHouseConnectionSettings.TIME_TO_LIVE_MILLIS); this.defaultMaxPerRoute = (Integer)getSetting(info, ClickHouseConnectionSettings.DEFAULT_MAX_PER_ROUTE); this.maxTotal = (Integer)getSetting(info, ClickHouseConnectionSettings.MAX_TOTAL); @@ -176,6 +178,7 @@ public Properties asProperties() { ret.put(ClickHouseConnectionSettings.APACHE_BUFFER_SIZE.getKey(), String.valueOf(apacheBufferSize)); ret.put(ClickHouseConnectionSettings.SOCKET_TIMEOUT.getKey(), String.valueOf(socketTimeout)); ret.put(ClickHouseConnectionSettings.CONNECTION_TIMEOUT.getKey(), String.valueOf(connectionTimeout)); + ret.put(ClickHouseConnectionSettings.DATA_TRANSFER_TIMEOUT.getKey(), String.valueOf(dataTransferTimeout)); ret.put(ClickHouseConnectionSettings.TIME_TO_LIVE_MILLIS.getKey(), String.valueOf(timeToLiveMillis)); ret.put(ClickHouseConnectionSettings.DEFAULT_MAX_PER_ROUTE.getKey(), String.valueOf(defaultMaxPerRoute)); ret.put(ClickHouseConnectionSettings.MAX_TOTAL.getKey(), String.valueOf(maxTotal)); @@ -246,6 +249,7 @@ public ClickHouseProperties(ClickHouseProperties properties) { setApacheBufferSize(properties.apacheBufferSize); setSocketTimeout(properties.socketTimeout); setConnectionTimeout(properties.connectionTimeout); + setDataTransferTimeout(properties.dataTransferTimeout); setTimeToLiveMillis(properties.timeToLiveMillis); setDefaultMaxPerRoute(properties.defaultMaxPerRoute); setMaxTotal(properties.maxTotal); @@ -550,6 +554,14 @@ public void setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; } + public int getDataTransferTimeout() { + return dataTransferTimeout; + } + + public void setDataTransferTimeout(int dataTransferTimeout) { + this.dataTransferTimeout = dataTransferTimeout; + } + public String getUser() { return user; } diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseBitmap.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseBitmap.java index d78b26e4d..078ff41d2 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseBitmap.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseBitmap.java @@ -356,6 +356,7 @@ public static ClickHouseBitmap deserialize(byte[] bytes, ClickHouseDataType inne // consume map size(long in little-endian byte order) byte[] bitmaps = new byte[4]; buffer.get(bitmaps); + if (buffer.get() != 0 || buffer.get() != 0 || buffer.get() != 0 || buffer.get() != 0) { throw new IllegalStateException( "Not able to deserialize ClickHouseBitmap for too many bitmaps(>" + 0xFFFFFFFFL + ")!"); @@ -398,24 +399,25 @@ private static ByteBuffer flip(ByteBuffer buffer) { private static int byteLength(ClickHouseDataType type) { int byteLen = 0; switch (Objects.requireNonNull(type)) { - case Int8: - case UInt8: - byteLen = 1; - break; - case Int16: - case UInt16: - byteLen = 2; - break; - case Int32: - case UInt32: - byteLen = 4; - break; - case Int64: - case UInt64: - byteLen = 8; - break; - default: - throw new IllegalArgumentException("Only native integer types are supported but we got: " + type.name()); + case Int8: + case UInt8: + byteLen = 1; + break; + case Int16: + case UInt16: + byteLen = 2; + break; + case Int32: + case UInt32: + byteLen = 4; + break; + case Int64: + case UInt64: + byteLen = 8; + break; + default: + throw new IllegalArgumentException( + "Only native integer types are supported but we got: " + type.name()); } return byteLen; diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/BalancedClickhouseDataSourceTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/BalancedClickhouseDataSourceTest.java index d321f1bbd..d6cac407f 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/BalancedClickhouseDataSourceTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/BalancedClickhouseDataSourceTest.java @@ -1,12 +1,13 @@ package ru.yandex.clickhouse; +import java.net.SocketException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.except.ClickHouseException; @@ -16,12 +17,12 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; -public class BalancedClickhouseDataSourceTest { +public class BalancedClickhouseDataSourceTest extends JdbcIntegrationTest { private BalancedClickhouseDataSource dataSource; private BalancedClickhouseDataSource doubleDataSource; - @Test + @Test(groups = "unit") public void testUrlSplit() throws Exception { assertEquals(Arrays.asList("jdbc:clickhouse://localhost:1234/ppc"), BalancedClickhouseDataSource.splitUrl("jdbc:clickhouse://localhost:1234/ppc")); @@ -36,7 +37,7 @@ public void testUrlSplit() throws Exception { } - @Test + @Test(groups = "unit") public void testUrlSplitValidHostName() throws Exception { assertEquals(Arrays.asList("jdbc:clickhouse://localhost:1234", "jdbc:clickhouse://_0another-host.com:4321"), BalancedClickhouseDataSource.splitUrl("jdbc:clickhouse://localhost:1234,_0another-host.com:4321")); @@ -44,20 +45,20 @@ public void testUrlSplitValidHostName() throws Exception { } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(groups = "unit", expectedExceptions = IllegalArgumentException.class) public void testUrlSplitInvalidHostName() throws Exception { BalancedClickhouseDataSource.splitUrl("jdbc:clickhouse://localhost:1234,_0ano^ther-host.com:4321"); } - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newBalancedDataSource(); - String address = ClickHouseContainerForTest.getClickHouseHttpAddress(); - doubleDataSource = ClickHouseContainerForTest.newBalancedDataSource(address, address); + dataSource = newBalancedDataSource(); + String address = getClickHouseHttpAddress(); + doubleDataSource = newBalancedDataSource(address, address); } - @Test + @Test(groups = "integration") public void testSingleDatabaseConnection() throws Exception { Connection connection = dataSource.getConnection(); connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); @@ -79,7 +80,7 @@ public void testSingleDatabaseConnection() throws Exception { assertEquals(42, rs.getInt("i")); } - @Test + @Test(groups = "integration") public void testDoubleDatabaseConnection() throws Exception { Connection connection = doubleDataSource.getConnection(); connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); @@ -117,16 +118,16 @@ public void testDoubleDatabaseConnection() throws Exception { } - @Test + @Test(groups = "integration") public void testCorrectActualizationDatabaseConnection() throws Exception { dataSource.actualize(); Connection connection = dataSource.getConnection(); } - @Test + @Test(groups = "integration") public void testDisableConnection() throws Exception { - BalancedClickhouseDataSource badDatasource = ClickHouseContainerForTest.newBalancedDataSource("not.existed.url:8123"); + BalancedClickhouseDataSource badDatasource = newBalancedDataSource("not.existed.url:8123"); badDatasource.actualize(); try { Connection connection = badDatasource.getConnection(); @@ -137,9 +138,9 @@ public void testDisableConnection() throws Exception { } - @Test + @Test(groups = "integration") public void testWorkWithEnabledUrl() throws Exception { - BalancedClickhouseDataSource halfDatasource = ClickHouseContainerForTest.newBalancedDataSource("not.existed.url:8123", ClickHouseContainerForTest.getClickHouseHttpAddress()); + BalancedClickhouseDataSource halfDatasource = newBalancedDataSource("not.existed.url:8123", getClickHouseHttpAddress()); halfDatasource.actualize(); Connection connection = halfDatasource.getConnection(); @@ -178,16 +179,16 @@ public void testWorkWithEnabledUrl() throws Exception { assertEquals(42, rs.getInt("i")); } - @Test + @Test(groups = "integration") public void testConstructWithClickHouseProperties() { final ClickHouseProperties properties = new ClickHouseProperties(); properties.setMaxThreads(3); properties.setSocketTimeout(67890); properties.setPassword("888888"); //without connection parameters - String hostAddr = ClickHouseContainerForTest.getClickHouseHttpAddress(); - String ipAddr = ClickHouseContainerForTest.getClickHouseHttpAddress(true); - BalancedClickhouseDataSource dataSource = ClickHouseContainerForTest.newBalancedDataSourceWithSuffix( + String hostAddr = getClickHouseHttpAddress(); + String ipAddr = getClickHouseHttpAddress(true); + BalancedClickhouseDataSource dataSource = newBalancedDataSourceWithSuffix( "click", properties, hostAddr, ipAddr); ClickHouseProperties dataSourceProperties = dataSource.getProperties(); assertEquals(dataSourceProperties.getMaxThreads().intValue(), 3); @@ -198,7 +199,7 @@ public void testConstructWithClickHouseProperties() { assertEquals(dataSource.getAllClickhouseUrls().get(0), "jdbc:clickhouse://" + hostAddr + "/click"); assertEquals(dataSource.getAllClickhouseUrls().get(1), "jdbc:clickhouse://" + ipAddr + "/click"); // with connection parameters - dataSource = ClickHouseContainerForTest.newBalancedDataSourceWithSuffix( + dataSource = newBalancedDataSourceWithSuffix( "click?socket_timeout=12345&user=readonly", properties, hostAddr, ipAddr); dataSourceProperties = dataSource.getProperties(); assertEquals(dataSourceProperties.getMaxThreads().intValue(), 3); @@ -212,22 +213,20 @@ public void testConstructWithClickHouseProperties() { assertEquals(dataSource.getAllClickhouseUrls().get(1), "jdbc:clickhouse://" + ipAddr + "/click?socket_timeout=12345&user=readonly"); } - @Test + @Test(groups = "integration") public void testConnectionWithAuth() throws SQLException { final ClickHouseProperties properties = new ClickHouseProperties(); - final String hostAddr = ClickHouseContainerForTest.getClickHouseHttpAddress(); - final String ipAddr = ClickHouseContainerForTest.getClickHouseHttpAddress(true); + final String hostAddr = getClickHouseHttpAddress(); + final String ipAddr = getClickHouseHttpAddress(true); - final BalancedClickhouseDataSource dataSource0 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource0 = newBalancedDataSourceWithSuffix( "default?user=foo&password=bar", properties, hostAddr, ipAddr); assertTrue(dataSource0.getConnection().createStatement().execute("SELECT 1")); - final BalancedClickhouseDataSource dataSource1 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource1 = newBalancedDataSourceWithSuffix( "default?user=foo", properties, hostAddr, @@ -236,8 +235,7 @@ public void testConnectionWithAuth() throws SQLException { // () -> dataSource1.getConnection().createStatement().execute("SELECT 1")); - final BalancedClickhouseDataSource dataSource2 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource2 = newBalancedDataSourceWithSuffix( "default?user=oof", properties, hostAddr, @@ -246,8 +244,7 @@ public void testConnectionWithAuth() throws SQLException { properties.setUser("foo"); properties.setPassword("bar"); - final BalancedClickhouseDataSource dataSource3 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource3 = newBalancedDataSourceWithSuffix( "default", properties, hostAddr, @@ -255,8 +252,7 @@ public void testConnectionWithAuth() throws SQLException { assertTrue(dataSource3.getConnection().createStatement().execute("SELECT 1")); properties.setPassword("bar"); - final BalancedClickhouseDataSource dataSource4 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource4 = newBalancedDataSourceWithSuffix( "default?user=oof", properties, hostAddr, @@ -273,8 +269,7 @@ public void testConnectionWithAuth() throws SQLException { // it is not allowed to have query parameters per host try { - ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + newBalancedDataSourceWithSuffix( "default?user=oof", properties, hostAddr + "/default?user=foo&password=bar", @@ -286,8 +281,7 @@ public void testConnectionWithAuth() throws SQLException { // the following behavior is quite unexpected, honestly // but query params always have precedence over properties - final BalancedClickhouseDataSource dataSource5 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource5 = newBalancedDataSourceWithSuffix( "default?user=foo&password=bar", properties, hostAddr, @@ -296,8 +290,7 @@ public void testConnectionWithAuth() throws SQLException { dataSource5.getConnection("broken", "hacker").createStatement().execute("SELECT 1")); // now the other way round, also strange - final BalancedClickhouseDataSource dataSource6 = ClickHouseContainerForTest - .newBalancedDataSourceWithSuffix( + final BalancedClickhouseDataSource dataSource6 = newBalancedDataSourceWithSuffix( "default?user=broken&password=hacker", properties, hostAddr, @@ -313,7 +306,7 @@ public void testConnectionWithAuth() throws SQLException { } } - @Test + @Test(groups = "integration") public void testIPv6() throws Exception { // dedup is not supported at all :< assertEquals(Arrays.asList("jdbc:clickhouse://[::1]:12345", "jdbc:clickhouse://[0:0:0:0:0:0:0:1]:12345"), @@ -322,9 +315,17 @@ public void testIPv6() throws Exception { BalancedClickhouseDataSource.splitUrl("jdbc:clickhouse://[192:168:0:0:0:0:0:1]:12345,[192:168:0:0:0:0:0:2]:12345")); ClickHouseProperties properties = new ClickHouseProperties(); - String hostAddr = ClickHouseContainerForTest.getClickHouseHttpAddress(); - String ipAddr = ClickHouseContainerForTest.getClickHouseHttpAddress("[::1]"); - assertEquals(ClickHouseContainerForTest.newBalancedDataSource(properties, ipAddr).getConnection().getServerVersion(), - ClickHouseContainerForTest.newBalancedDataSource(properties, hostAddr).getConnection().getServerVersion()); + String hostAddr = getClickHouseHttpAddress(); + String ipAddr = getClickHouseHttpAddress("[::1]"); + + try { + assertEquals(newBalancedDataSource(properties, ipAddr).getConnection().getServerVersion(), + newBalancedDataSource(properties, hostAddr).getConnection().getServerVersion()); + } catch (SQLException e) { + // acceptable if IPv6 is not enabled + Throwable cause = e.getCause(); + assertTrue(cause instanceof SocketException); + assertTrue("Protocol family unavailable".equals(cause.getMessage()) || cause.getMessage().contains("Connection refused")); + } } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseConnectionTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseConnectionTest.java index 67631c27e..7fad97fd0 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseConnectionTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseConnectionTest.java @@ -14,12 +14,12 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; -public class ClickHouseConnectionTest { - @Test +public class ClickHouseConnectionTest extends JdbcIntegrationTest { + @Test(groups = "integration") public void testGetSetCatalog() throws SQLException { - String address = ClickHouseContainerForTest.getClickHouseHttpAddress(); + String address = getClickHouseHttpAddress(); String url = "jdbc:clickhouse://" + address + "/default?option1=one%20two&option2=y"; - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(url); + ClickHouseDataSource dataSource = newDataSource(url); String[] dbNames = new String[]{"get_set_catalog_test1", "get_set_catalog_test2"}; try { ClickHouseConnectionImpl connection = (ClickHouseConnectionImpl) dataSource.getConnection(); @@ -51,10 +51,9 @@ public void testGetSetCatalog() throws SQLException { } } - @Test + @Test(groups = "integration") public void testSetCatalogAndStatements() throws SQLException { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource( - "default?option1=one%20two&option2=y"); + ClickHouseDataSource dataSource = newDataSource("default?option1=one%20two&option2=y"); ClickHouseConnectionImpl connection = (ClickHouseConnectionImpl) dataSource.getConnection(); final String sql = "SELECT currentDatabase()"; @@ -71,10 +70,9 @@ public void testSetCatalogAndStatements() throws SQLException { assertEquals(resultSet.getString(1), "default"); } - @Test + @Test(groups = "integration") public void testSetCatalogAndPreparedStatements() throws SQLException { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource( - "default?option1=one%20two&option2=y"); + ClickHouseDataSource dataSource = newDataSource("default?option1=one%20two&option2=y"); ClickHouseConnectionImpl connection = (ClickHouseConnectionImpl) dataSource.getConnection(); final String sql = "SELECT currentDatabase() FROM system.tables WHERE name = ? LIMIT 1"; @@ -93,10 +91,9 @@ public void testSetCatalogAndPreparedStatements() throws SQLException { assertEquals(resultSet.getString(1), "default"); } - @Test + @Test(groups = "integration") public void testScrollableResultSetOnPreparedStatements() throws SQLException { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource( - "default?option1=one%20two&option2=y"); + ClickHouseDataSource dataSource = newDataSource("default?option1=one%20two&option2=y"); ClickHouseConnectionImpl connection = (ClickHouseConnectionImpl) dataSource.getConnection(); final String sql = "SELECT currentDatabase() FROM system.tables WHERE name = ? LIMIT 1"; @@ -120,10 +117,9 @@ public void testScrollableResultSetOnPreparedStatements() throws SQLException { assertEquals(resultSet.getString(1), "default"); } - @Test + @Test(groups = "integration") public void testScrollableResultSetOnStatements() throws SQLException { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource( - "default?option1=one%20two&option2=y"); + ClickHouseDataSource dataSource = newDataSource("default?option1=one%20two&option2=y"); ClickHouseConnectionImpl connection = (ClickHouseConnectionImpl) dataSource.getConnection(); final String sql = "SELECT currentDatabase()"; @@ -145,10 +141,10 @@ public void testScrollableResultSetOnStatements() throws SQLException { assertEquals(resultSet.getString(1), "default"); } - @Test + @Test(groups = "integration") public void testCreateArrayOf() throws Exception { // TODO: more - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource("default"); + ClickHouseDataSource dataSource = newDataSource("default"); ClickHouseConnectionImpl connection = (ClickHouseConnectionImpl) dataSource.getConnection(); for (ClickHouseDataType dataType : ClickHouseDataType.values()) { if (dataType == ClickHouseDataType.Array) { diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseContainerForTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseContainerForTest.java deleted file mode 100644 index 9aae3ebcc..000000000 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseContainerForTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package ru.yandex.clickhouse; - -import static java.time.temporal.ChronoUnit.SECONDS; - -import java.time.Duration; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.images.builder.ImageFromDockerfile; -import org.testng.annotations.AfterSuite; -import org.testng.annotations.BeforeSuite; -import ru.yandex.clickhouse.settings.ClickHouseProperties; -import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; - -public class ClickHouseContainerForTest { - private static final int HTTP_PORT = 8123; - private static final int NATIVE_PORT = 9000; - private static final int MYSQL_PORT = 3306; - - private static final String clickhouseVersion; - private static final GenericContainer clickhouseContainer; - - static { - String timezone = System.getProperty("clickhouseTimezone"); - if (timezone == null || timezone.isEmpty()) { - timezone = "UTC"; - } - String imageTag = System.getProperty("clickhouseVersion"); - - if (imageTag == null || (imageTag = imageTag.trim()).isEmpty()) { - clickhouseVersion = imageTag = ""; - } else { - if (ClickHouseVersionNumberUtil.getMajorVersion(imageTag) == 0) { - clickhouseVersion = ""; - } else { - clickhouseVersion = imageTag; - } - imageTag = ":" + imageTag; - } - - final String imageNameWithTag = "yandex/clickhouse-server" + imageTag; - - clickhouseContainer = new GenericContainer<>(new ImageFromDockerfile() - .withDockerfileFromBuilder(builder -> - builder - .from(imageNameWithTag) - .run("apt-get update && apt-get install tzdata") - )) - .withEnv("TZ", timezone) - .withExposedPorts(HTTP_PORT, NATIVE_PORT, MYSQL_PORT) - .withClasspathResourceMapping( - "ru/yandex/clickhouse/users.d", - "/etc/clickhouse-server/users.d", - BindMode.READ_ONLY) - .waitingFor(Wait.forHttp("/ping").forPort(HTTP_PORT).forStatusCode(200) - .withStartupTimeout(Duration.of(60, SECONDS))); - - } - - public static String getClickHouseVersion() { - return clickhouseVersion; - } - - public static GenericContainer getClickHouseContainer() { - return clickhouseContainer; - } - - public static String getClickHouseHttpAddress() { - return getClickHouseHttpAddress(false); - } - - public static String getClickHouseHttpAddress(boolean useIPaddress) { - return new StringBuilder() - .append(useIPaddress ? clickhouseContainer.getContainerIpAddress() : clickhouseContainer.getHost()) - .append(':').append(clickhouseContainer.getMappedPort(HTTP_PORT)).toString(); - } - - public static String getClickHouseHttpAddress(String customHostOrIp) { - return new StringBuilder() - .append(customHostOrIp == null || customHostOrIp.isEmpty() ? clickhouseContainer.getContainerIpAddress() : customHostOrIp) - .append(':').append(clickhouseContainer.getMappedPort(HTTP_PORT)).toString(); - } - - public static ClickHouseDataSource newDataSource() { - return newDataSource(new ClickHouseProperties()); - } - - public static ClickHouseDataSource newDataSource(ClickHouseProperties properties) { - return newDataSource("jdbc:clickhouse://" + getClickHouseHttpAddress(), properties); - } - - public static ClickHouseDataSource newDataSource(String url) { - return newDataSource(url, new ClickHouseProperties()); - } - - public static ClickHouseDataSource newDataSource(String url, ClickHouseProperties properties) { - String baseUrl = "jdbc:clickhouse://" + getClickHouseHttpAddress(); - if (url == null) { - url = baseUrl; - } else if (!url.startsWith("jdbc:")) { - url = baseUrl + "/" + url; - } - - return new ClickHouseDataSource(url, properties); - } - - public static BalancedClickhouseDataSource newBalancedDataSource(String... addresses) { - return newBalancedDataSource(new ClickHouseProperties(), addresses); - } - - public static BalancedClickhouseDataSource newBalancedDataSource(ClickHouseProperties properties, - String... addresses) { - return newBalancedDataSourceWithSuffix(null, properties, addresses); - } - - public static BalancedClickhouseDataSource newBalancedDataSourceWithSuffix(String urlSuffix, - ClickHouseProperties properties, String... addresses) { - StringBuilder url = new StringBuilder().append("jdbc:clickhouse://"); - if (addresses == null || addresses.length == 0) { - url.append(getClickHouseHttpAddress()); - } else { - int position = url.length(); - for (int i = 0; i < addresses.length; i++) { - url.append(',').append(addresses[i]); - } - url.deleteCharAt(position); - } - - if (urlSuffix != null) { - url.append('/').append(urlSuffix); - } - - return new BalancedClickhouseDataSource(url.toString(), properties); - } - - @BeforeSuite() - public static void beforeSuite() { - clickhouseContainer.start(); - } - - @AfterSuite() - public static void afterSuite() { - clickhouseContainer.stop(); - } -} diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseDataSourceTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseDataSourceTest.java index 927790512..ea0fa671b 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseDataSourceTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseDataSourceTest.java @@ -6,7 +6,7 @@ import static org.testng.Assert.fail; public class ClickHouseDataSourceTest { - @Test + @Test(groups = "unit") public void testConstructor() throws Exception { ClickHouseDataSource ds = new ClickHouseDataSource("jdbc:clickhouse://localhost:1234/ppc"); assertEquals("localhost", ds.getHost()); @@ -40,7 +40,7 @@ public void testConstructor() throws Exception { } } - @Test + @Test(groups = "unit") public void testIPv6Constructor() throws Exception { ClickHouseDataSource ds = new ClickHouseDataSource("jdbc:clickhouse://[::1]:5324"); assertEquals(ds.getHost(), "[::1]"); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementParameterTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementParameterTest.java index 7b167aa4a..aba79e44e 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementParameterTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementParameterTest.java @@ -10,7 +10,7 @@ public class ClickHousePreparedStatementParameterTest { - @Test + @Test(groups = "unit") public void testNullParam() { ClickHousePreparedStatementParameter p0 = ClickHousePreparedStatementParameter.nullParameter(); @@ -23,7 +23,7 @@ public void testNullParam() { assertSame(p1, p0); } - @Test + @Test(groups = "unit") public void testArrayAndCollectionParam() { ClickHousePreparedStatementParameter p0 = ClickHousePreparedStatementParameter.fromObject(Arrays.asList("A", "B", "C"), TimeZone.getDefault(), TimeZone.getDefault()); @@ -33,7 +33,7 @@ public void testArrayAndCollectionParam() { assertEquals(p0.getBatchValue(), p1.getBatchValue()); } - @Test + @Test(groups = "unit") public void testBooleanParam() { assertEquals(ClickHousePreparedStatementParameter.fromObject(Boolean.TRUE, TimeZone.getDefault(), TimeZone.getDefault()).getRegularValue(), "1"); @@ -41,7 +41,7 @@ public void testBooleanParam() { TimeZone.getDefault(), TimeZone.getDefault()).getRegularValue(), "0"); } - @Test + @Test(groups = "unit") public void testNumberParam() { assertEquals(ClickHousePreparedStatementParameter.fromObject(10, TimeZone.getDefault(), TimeZone.getDefault()).getRegularValue(), "10"); @@ -49,7 +49,7 @@ public void testNumberParam() { TimeZone.getDefault(), TimeZone.getDefault()).getRegularValue(), "10.5"); } - @Test + @Test(groups = "unit") public void testStringParam() { assertEquals(ClickHousePreparedStatementParameter.fromObject("someString", TimeZone.getDefault(), TimeZone.getDefault()).getRegularValue(), "'someString'"); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java index 2b19cf1ba..7098f94b4 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java @@ -22,28 +22,28 @@ public class ClickHousePreparedStatementTest { private static final String SQL_STATEMENT= "INSERT INTO foo (bar) VALUES ("; - @Test + @Test(groups = "unit") public void testSetBytesNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBytes(1, null); assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetBytesNormal() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBytes(1, "foo".getBytes("UTF-8")); assertParamMatches(s, "'\\x66\\x6F\\x6F'"); } - @Test + @Test(groups = "unit") public void testSetBytesEmpty() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBytes(1, "".getBytes("UTF-8")); assertParamMatches(s, "''"); } - @Test + @Test(groups = "unit") public void testSetNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setNull(1, Types.ARRAY); @@ -52,56 +52,56 @@ public void testSetNull() throws Exception { assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetBooleanTrue() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBoolean(1, true); assertParamMatches(s, "1"); } - @Test + @Test(groups = "unit") public void testSetBooleanFalse() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBoolean(1, false); assertParamMatches(s, "0"); } - @Test + @Test(groups = "unit") public void testSetByte() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setByte(1, (byte) -127); assertParamMatches(s, "-127"); } - @Test + @Test(groups = "unit") public void testSetShort() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setShort(1, (short) 42); assertParamMatches(s, "42"); } - @Test + @Test(groups = "unit") public void testSetInt() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setInt(1, 0); assertParamMatches(s, "0"); } - @Test + @Test(groups = "unit") public void testSetLong() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setLong(1, 1337L); assertParamMatches(s, "1337"); } - @Test + @Test(groups = "unit") public void testSetFloat() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setFloat(1, -23.42f); assertParamMatches(s, "-23.42"); } - @Test + @Test(groups = "unit") public void testSetDouble() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setDouble(1, Double.MIN_VALUE); @@ -109,49 +109,49 @@ public void testSetDouble() throws Exception { // but parsing is OK } - @Test + @Test(groups = "unit") public void testSetBigDecimalNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBigDecimal(1, null); assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetBigDecimalNormal() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setBigDecimal(1, BigDecimal.valueOf(-0.2342)); assertParamMatches(s, "-0.2342"); } - @Test + @Test(groups = "unit") public void testSetStringNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setString(1, null); assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetStringSimple() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setString(1, "foo"); assertParamMatches(s, "'foo'"); } - @Test + @Test(groups = "unit") public void testSetStringEvil() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setString(1, "\"'\\x32"); assertParamMatches(s, "'\"\\'\\\\x32'"); } - @Test + @Test(groups = "unit") public void testSetDateNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setDate(1, null); assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetDateNormal() throws Exception { ClickHousePreparedStatement s = createStatement(); Date d = new Date(1557168043000L); @@ -159,7 +159,7 @@ public void testSetDateNormal() throws Exception { assertParamMatches(s, "'" + d.toLocalDate().toString() + "'"); } - @Test + @Test(groups = "unit") public void testSetDateOtherTimeZone() throws Exception { ClickHouseProperties p = new ClickHouseProperties(); ClickHousePreparedStatement s = createStatement( @@ -178,7 +178,7 @@ public void testSetDateOtherTimeZone() throws Exception { */ } - @Test + @Test(groups = "unit") public void testSetDateOtherTimeZoneServerTime() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setUseServerTimeZoneForDates(true); @@ -189,7 +189,7 @@ public void testSetDateOtherTimeZoneServerTime() throws Exception { assertParamMatches(s, "'2019-05-07'"); } - @Test + @Test(groups = "unit") public void testSetDateCalendar() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setUseServerTimeZoneForDates(true); @@ -201,7 +201,7 @@ public void testSetDateCalendar() throws Exception { assertParamMatches(s, "'2019-05-06'"); } - @Test + @Test(groups = "unit") public void testSetDateCalendarSameTimeZone() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setUseServerTimeZoneForDates(true); @@ -213,7 +213,7 @@ public void testSetDateCalendarSameTimeZone() throws Exception { assertParamMatches(s, "'2019-05-07'"); } - @Test + @Test(groups = "unit") public void testSetDateCalendarNull() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setUseServerTimeZoneForDates(true); @@ -224,21 +224,21 @@ public void testSetDateCalendarNull() throws Exception { assertParamMatches(s, "'2019-05-07'"); } - @Test + @Test(groups = "unit") public void testSetTimeNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setTime(1, null); assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetTimeNormal() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setTime(1, new Time(1557168043000L)); assertParamMatches(s, "'21:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimeNormalOtherTimeZone() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -247,7 +247,7 @@ public void testSetTimeNormalOtherTimeZone() throws Exception { assertParamMatches(s, "'11:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimeCalendar() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -257,7 +257,7 @@ public void testSetTimeCalendar() throws Exception { assertParamMatches(s, "'02:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimeCalendarNull() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -266,7 +266,7 @@ public void testSetTimeCalendarNull() throws Exception { assertParamMatches(s, "'11:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimeCalendarSameTimeZone() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -276,21 +276,21 @@ public void testSetTimeCalendarSameTimeZone() throws Exception { assertParamMatches(s, "'11:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimestampNull() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setTimestamp(1, null); assertParamMatches(s, "null"); } - @Test + @Test(groups = "unit") public void testSetTimestampNormal() throws Exception { ClickHousePreparedStatement s = createStatement(); s.setTimestamp(1, new Timestamp(1557168043000L)); assertParamMatches(s, "'2019-05-06 21:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimestampNormalOtherTimeZone() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -299,7 +299,7 @@ public void testSetTimestampNormalOtherTimeZone() throws Exception { assertParamMatches(s, "'2019-05-06 11:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimestampCalendar() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -309,7 +309,7 @@ public void testSetTimestampCalendar() throws Exception { assertParamMatches(s, "'2019-05-07 02:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimestampCalendarSameTimeZone() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), @@ -319,7 +319,7 @@ public void testSetTimestampCalendarSameTimeZone() throws Exception { assertParamMatches(s, "'2019-05-06 11:40:43'"); } - @Test + @Test(groups = "unit") public void testSetTimestampCalendarNull() throws Exception { ClickHousePreparedStatement s = createStatement( TimeZone.getTimeZone("America/Los_Angeles"), diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseStatementImplTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementImplTest.java similarity index 89% rename from clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseStatementImplTest.java rename to clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementImplTest.java index cb41694ee..268b8005d 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseStatementImplTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementImplTest.java @@ -1,4 +1,4 @@ -package ru.yandex.clickhouse.integration; +package ru.yandex.clickhouse; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -27,13 +27,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.mockito.internal.util.reflection.Whitebox; import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHouseExternalData; import ru.yandex.clickhouse.ClickHouseStatement; @@ -41,26 +39,20 @@ import ru.yandex.clickhouse.settings.ClickHouseQueryParam; import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; -public class ClickHouseStatementImplTest { - - private ClickHouseDataSource dataSource; +public class ClickHouseStatementImplTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { - if (connection != null) { - connection.close(); - } + closeConnection(connection); } - @Test + @Test(groups = "integration") public void testUpdateCountForSelect() throws Exception { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT dummy FROM system.one"); @@ -71,7 +63,7 @@ public void testUpdateCountForSelect() throws Exception { stmt.close(); } - @Test + @Test(groups = "integration") public void testSingleColumnResultSet() throws SQLException { ResultSet rs = connection.createStatement().executeQuery("select c from (\n" + " select 'a' as c, 1 as rn\n" + @@ -86,7 +78,7 @@ public void testSingleColumnResultSet() throws SQLException { Assert.assertEquals(sb.toString(), "a\nb\n\nd\n"); } - @Test + @Test(groups = "integration") public void readsPastLastAreSafe() throws SQLException { ResultSet rs = connection.createStatement().executeQuery("select c from (\n" + " select 'a' as c, 1 as rn\n" + @@ -104,7 +96,7 @@ public void readsPastLastAreSafe() throws SQLException { Assert.assertEquals(sb.toString(), "a\nb\n\nd\n"); } - @Test + @Test(groups = "integration") public void testSelectUInt32() throws SQLException { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select toUInt32(10), toUInt32(-10)"); @@ -117,7 +109,7 @@ public void testSelectUInt32() throws SQLException { Assert.assertEquals(((Long)bigUInt32).longValue(), 4294967286L); } - @Test + @Test(groups = "integration") public void testSelectUInt64() throws SQLException { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select toUInt64(10), toUInt64(-10)"); @@ -130,7 +122,7 @@ public void testSelectUInt64() throws SQLException { Assert.assertEquals(bigUInt64, new BigInteger("18446744073709551606")); } - @Test + @Test(groups = "integration") public void testExternalData() throws SQLException, UnsupportedEncodingException { String serverVersion = connection.getServerVersion(); ClickHouseStatement stmt = connection.createStatement(); @@ -159,7 +151,7 @@ public void testExternalData() throws SQLException, UnsupportedEncodingException } // reproduce issue #634 - @Test + @Test(groups = "integration") public void testLargeQueryWithExternalData() throws Exception { String serverVersion = connection.getServerVersion(); String[] rows = ClickHouseVersionNumberUtil.getMajorVersion(serverVersion) >= 21 @@ -223,18 +215,18 @@ public int read() { } - @Test + @Test(groups = "integration") public void testExternalDataStream() throws SQLException, UnsupportedEncodingException { if ("21.3.3.14".equals(connection.getServerVersion())) { return; } final ClickHouseStatement statement = connection.createStatement(); - statement.execute("DROP TABLE IF EXISTS test.testExternalData"); + statement.execute("DROP TABLE IF EXISTS testExternalData"); statement.execute( - "CREATE TABLE test.testExternalData (id UInt64, s String) ENGINE = Memory"); + "CREATE TABLE testExternalData (id UInt64, s String) ENGINE = Memory"); statement.execute( - "insert into test.testExternalData select number, toString(number) from numbers(500,100000)"); + "insert into testExternalData select number, toString(number) from numbers(500,100000)"); InputStream inputStream = getTSVStream(100000); @@ -242,7 +234,7 @@ public void testExternalDataStream() throws SQLException, UnsupportedEncodingExc extData.setStructure("id UInt64, s String"); ResultSet rs = statement.executeQuery( - "select count() cnt from test.testExternalData where (id,s) in ext_data", + "select count() cnt from testExternalData where (id,s) in ext_data", null, Collections.singletonList(extData) ); @@ -254,13 +246,13 @@ public void testExternalDataStream() throws SQLException, UnsupportedEncodingExc Assert.assertEquals(cnt, 99500); } - @Test + @Test(groups = "integration") public void testQueryWithMultipleExternalTables() throws SQLException { int tables = 30; int rows = 10; - String ddl = "drop table if exists test.test_ext_data_query;\n" - + "create table test.test_ext_data_query (\n" + String ddl = "drop table if exists test_ext_data_query;\n" + + "create table test_ext_data_query (\n" + " Cb String,\n" + " CREATETIME DateTime64(3),\n" + " TIMESTAMP UInt64,\n" @@ -274,7 +266,7 @@ public void testQueryWithMultipleExternalTables() throws SQLException { String template = "avgIf(Ca1, Cb in L%1$d) as avgCa1%2$d, sumIf(Ca1, Cb in L%1$d) as sumCa1%2$d, minIf(Ca1, Cb in L%1$d) as minCa1%2$d, maxIf(Ca1, Cb in L%1$d) as maxCa1%2$d, anyIf(Ca1, Cb in L%1$d) as anyCa1%2$d, avgIf(Ca2, Cb in L%1$d) as avgCa2%2$d, sumIf(Ca2, Cb in L%1$d) as sumCa2%2$d, minIf(Ca2, Cb in L%1$d) as minCa2%2$d, maxIf(Ca2, Cb in L%1$d) as maxCa2%2$d, anyIf(Ca2, Cb in L%1$d) as anyCa2%2$d, avgIf(Ca3, Cb in L%1$d) as avgCa3%2$d, sumIf(Ca3, Cb in L%1$d) as sumCa3%2$d, minIf(Ca3, Cb in L%1$d) as minCa3%2$d, maxIf(Ca3, Cb in L%1$d) as maxCa3%2$d, anyIf(Ca3, Cb in L%1$d) as anyCa3%2$d"; ClickHouseProperties properties = new ClickHouseProperties(); - properties.setDatabase("test"); + properties.setDatabase(dbName); properties.setSocketTimeout(300000); properties.setMaxAstElements(Long.MAX_VALUE); properties.setMaxTotal(20); @@ -303,9 +295,9 @@ public void testQueryWithMultipleExternalTables() throws SQLException { } else { sql.append('*'); } - sql.append(" from test.test_ext_data_query where TIMESTAMP >= 1625796480 and TIMESTAMP < 1625796540 and Cc = 'eth0'"); + sql.append(" from test_ext_data_query where TIMESTAMP >= 1625796480 and TIMESTAMP < 1625796540 and Cc = 'eth0'"); - try (ClickHouseConnection c = ClickHouseContainerForTest.newDataSource(properties).getConnection(); + try (ClickHouseConnection c = newDataSource(properties).getConnection(); ClickHouseStatement s = c.createStatement();) { s.execute(ddl); ResultSet rs = s.executeQuery(sql.toString(), paramMap, extDataList); @@ -313,11 +305,11 @@ public void testQueryWithMultipleExternalTables() throws SQLException { } } - @Test + @Test(groups = "integration") public void testResultSetWithExtremes() throws SQLException { ClickHouseProperties properties = new ClickHouseProperties(); properties.setExtremes(true); - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(properties); + ClickHouseDataSource dataSource = newDataSource(properties); Connection connection = dataSource.getConnection(); try { @@ -334,7 +326,7 @@ public void testResultSetWithExtremes() throws SQLException { } } - @Test + @Test(groups = "integration") public void testSelectOne() throws SQLException { try (Statement stmt = connection.createStatement()) { ResultSet rs = stmt.executeQuery("select\n1"); @@ -344,7 +336,7 @@ public void testSelectOne() throws SQLException { } } - @Test + @Test(groups = "integration") public void testSelectManyRows() throws SQLException { Statement stmt = connection.createStatement(); int limit = 10000; @@ -359,7 +351,7 @@ public void testSelectManyRows() throws SQLException { Assert.assertEquals(i, limit); } - @Test + @Test(groups = "integration") public void testMoreResultsWithResultSet() throws SQLException { Statement stmt = connection.createStatement(); @@ -372,7 +364,7 @@ public void testMoreResultsWithResultSet() throws SQLException { Assert.assertEquals(stmt.getUpdateCount(), -1); } - @Test + @Test(groups = "integration") public void testMoreResultsWithUpdateCount() throws SQLException { Statement stmt = connection.createStatement(); @@ -385,7 +377,7 @@ public void testMoreResultsWithUpdateCount() throws SQLException { Assert.assertEquals(stmt.getUpdateCount(), -1); } - @Test + @Test(groups = "integration") public void testSelectQueryStartingWithWith() throws SQLException { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("WITH 2 AS two SELECT two * two;"); @@ -398,9 +390,9 @@ public void testSelectQueryStartingWithWith() throws SQLException { stmt.close(); } - @Test + @Test(groups = "integration") public void cancelTest_queryId_is_not_set() throws Exception { - final ClickHouseStatement firstStatement = dataSource.getConnection().createStatement(); + final ClickHouseStatement firstStatement = newConnection().createStatement(); final AtomicReference exceptionAtomicReference = new AtomicReference<>(); Thread thread = new Thread() { @@ -420,7 +412,8 @@ public void run() { final long timeout = 10; - String queryId = (String) readField(firstStatement, "queryId", timeout); + assertTrue(firstStatement instanceof ClickHouseStatementImpl); + String queryId = readQueryId((ClickHouseStatementImpl) firstStatement, timeout); assertNotNull( String.format("it's actually very strange. It seems the query hasn't been executed in %s seconds", timeout), queryId); @@ -436,10 +429,10 @@ public void run() { } - @Test + @Test(groups = "integration") public void cancelTest_queryId_is_set() throws Exception { final String queryId = UUID.randomUUID().toString(); - final ClickHouseStatement firstStatement = dataSource.getConnection().createStatement(); + final ClickHouseStatement firstStatement = newConnection().createStatement(); final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference exceptionAtomicReference = new AtomicReference<>(); @@ -473,7 +466,7 @@ public void run() { thread.interrupt(); } - @Test + @Test(groups = "integration") public void testArrayMetaActualExecutiom() throws SQLException { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT array(42, 23)"); @@ -488,26 +481,26 @@ public void testArrayMetaActualExecutiom() throws SQLException { } - @Test + @Test(groups = "integration") public void testInsertQueryUUIDArray() throws SQLException { // issue #569 connection.createStatement().execute( - "DROP TABLE IF EXISTS test.uuidArray"); + "DROP TABLE IF EXISTS uuidArray"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.uuidArray" + "CREATE TABLE IF NOT EXISTS uuidArray" + "(id UInt8, arr Array(UUID)) " + "ENGINE = TinyLog"); connection.createStatement().execute( - "INSERT INTO test.uuidArray VALUES(1, ['5ff22319-793d-4e6c-bdc1-916095a5a496'])"); + "INSERT INTO uuidArray VALUES(1, ['5ff22319-793d-4e6c-bdc1-916095a5a496'])"); ResultSet rs = connection.createStatement().executeQuery( - "SELECT * FROM test.uuidArray"); + "SELECT * FROM uuidArray"); rs.next(); assertEquals( rs.getArray(2).getArray(), new UUID[] {UUID.fromString("5ff22319-793d-4e6c-bdc1-916095a5a496")}); } - @Test + @Test(groups = "integration") public void testMultiStatements() throws SQLException { try (Statement s = connection.createStatement()) { String sql = "select 1; select 2"; @@ -529,7 +522,7 @@ public void testMultiStatements() throws SQLException { } } - @Test + @Test(groups = "integration") public void testBatchProcessing() throws SQLException { try (Statement s = connection.createStatement()) { int[] results = s.executeBatch(); @@ -549,11 +542,11 @@ public void testBatchProcessing() throws SQLException { } } - private static Object readField(Object object, String fieldName, long timeoutSecs) { + private static String readQueryId(ClickHouseStatementImpl stmt, long timeoutSecs) { long start = System.currentTimeMillis(); - Object value; + String value; do { - value = Whitebox.getInternalState(object, fieldName); + value = stmt.getQueryId(); } while (value == null && TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - start) < timeoutSecs); return value; diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementTest.java index bc9058fde..aa9784ade 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseStatementTest.java @@ -21,7 +21,7 @@ import static org.testng.Assert.assertTrue; public class ClickHouseStatementTest { - @Test + @Test(groups = "unit") public void testClickhousify() throws Exception { ClickHouseStatementImpl s = new ClickHouseStatementImpl(null, null, null, ResultSet.TYPE_FORWARD_ONLY); String sql = "SELECT ololo FROM ololoed;"; @@ -74,7 +74,7 @@ public void testClickhousify() throws Exception { } - @Test + @Test(groups = "unit") public void testCredentials() throws SQLException, URISyntaxException { ClickHouseProperties properties = new ClickHouseProperties(new Properties()); ClickHouseProperties withCredentials = properties.withCredentials("test_user", "test_password"); @@ -95,7 +95,7 @@ public void testCredentials() throws SQLException, URISyntaxException { assertFalse(query.contains("user=test_user")); } - @Test + @Test(groups = "unit") public void testMaxExecutionTime() throws Exception { ClickHouseProperties properties = new ClickHouseProperties(); properties.setMaxExecutionTime(20); @@ -111,7 +111,7 @@ public void testMaxExecutionTime() throws Exception { assertTrue(query.contains("max_execution_time=10"), "max_execution_time param is missing in URL"); } - @Test + @Test(groups = "unit") public void testMaxMemoryUsage() throws Exception { ClickHouseProperties properties = new ClickHouseProperties(); properties.setMaxMemoryUsage(41L); @@ -123,7 +123,7 @@ public void testMaxMemoryUsage() throws Exception { assertTrue(query.contains("max_memory_usage=41"), "max_memory_usage param is missing in URL"); } - @Test + @Test(groups = "unit") public void testAdditionalRequestParams() { ClickHouseProperties properties = new ClickHouseProperties(); ClickHouseStatementImpl statement = new ClickHouseStatementImpl( @@ -158,7 +158,7 @@ public void testAdditionalRequestParams() { assertEquals(statement.write().getRequestParams().get("cache_namespace"), "aaaa"); } - @Test + @Test(groups = "unit") public void testAdditionalDBParams() { ClickHouseProperties properties = new ClickHouseProperties(); properties.setMaxThreads(1); @@ -187,7 +187,7 @@ public void testAdditionalDBParams() { assertEquals(statement.write().getAdditionalDBParams().get(ClickHouseQueryParam.MAX_THREADS), "2"); } - @Test + @Test(groups = "unit") public void testIsSelect() throws SQLException { ClickHouseStatementImpl s = new ClickHouseStatementImpl(null, null, null, ResultSet.TYPE_FORWARD_ONLY); assertTrue(s.parseSqlStatements("SELECT 42")[0].isQuery()); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseUtilTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseUtilTest.java index 8d6f1cec8..1c3a0b817 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseUtilTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickHouseUtilTest.java @@ -9,14 +9,14 @@ public class ClickHouseUtilTest { - @Test + @Test(groups = "unit") public void testQuote() throws Exception { assertEquals("\\N", ClickHouseUtil.escape(null)); assertEquals("test", ClickHouseUtil.escape("test")); assertEquals("t\\n\\0\\r\\test\\'", ClickHouseUtil.escape("t\n\0\r\test'")); } - @Test + @Test(groups = "unit") public void testQuoteIdentifier() throws Exception { assertEquals("`z`", ClickHouseUtil.quoteIdentifier("z")); assertEquals("`a\\`\\' `", ClickHouseUtil.quoteIdentifier("a`' ")); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseJdbcUrlParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseJdbcUrlParserTest.java index 69e3e1e81..7a6aa99c1 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseJdbcUrlParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseJdbcUrlParserTest.java @@ -9,7 +9,7 @@ public class ClickhouseJdbcUrlParserTest { - @Test + @Test(groups = "unit") public void testParseDashes() throws Exception { Properties props = new Properties(); ClickHouseProperties chProps = ClickhouseJdbcUrlParser.parse( @@ -17,7 +17,7 @@ public void testParseDashes() throws Exception { Assert.assertEquals(chProps.getDatabase(), "db-name-with-dash"); } - @Test + @Test(groups = "unit") public void testParseTrailingSlash() throws Exception { Properties props = new Properties(); ClickHouseProperties chProps = ClickhouseJdbcUrlParser.parse( @@ -25,7 +25,7 @@ public void testParseTrailingSlash() throws Exception { Assert.assertEquals(chProps.getDatabase(), "default"); } - @Test + @Test(groups = "unit") public void testParseDbInPathAndProps() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setDatabase("database-name"); @@ -35,7 +35,7 @@ public void testParseDbInPathAndProps() throws Exception { Assert.assertEquals(chProps.getPath(), "/"); } - @Test + @Test(groups = "unit") public void testParseDbInPathAndProps2() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setDatabase("database-name"); @@ -46,7 +46,7 @@ public void testParseDbInPathAndProps2() throws Exception { Assert.assertEquals(chProps.getPath(), "/database-name"); } - @Test + @Test(groups = "unit") public void testParsePathDefaultDb() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setPath("/path"); @@ -56,7 +56,7 @@ public void testParsePathDefaultDb() throws Exception { Assert.assertEquals(chProps.getPath(), "/path"); } - @Test + @Test(groups = "unit") public void testParsePathDefaultDb2() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setPath("/path"); @@ -67,7 +67,7 @@ public void testParsePathDefaultDb2() throws Exception { Assert.assertEquals(chProps.getPath(), "/"); //uri takes priority } - @Test + @Test(groups = "unit") public void testParsePathAndDb() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); ClickHouseProperties chProps = ClickhouseJdbcUrlParser.parse( @@ -76,7 +76,7 @@ public void testParsePathAndDb() throws Exception { Assert.assertEquals(chProps.getPath(), "/"); } - @Test + @Test(groups = "unit") public void testParsePathAndDb2() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setUsePathAsDb(false); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseLZ4StreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseLZ4StreamTest.java index 3d92de916..8b39ec224 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseLZ4StreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/ClickhouseLZ4StreamTest.java @@ -10,7 +10,7 @@ import java.io.IOException; public class ClickhouseLZ4StreamTest { - @Test + @Test(groups = "unit") public void testLZ4Stream() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ClickHouseLZ4OutputStream outputStream = new ClickHouseLZ4OutputStream(baos, 1024*1024); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/JdbcIntegrationTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/JdbcIntegrationTest.java new file mode 100644 index 000000000..b5511fdc0 --- /dev/null +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/JdbcIntegrationTest.java @@ -0,0 +1,122 @@ +package ru.yandex.clickhouse; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import ru.yandex.clickhouse.settings.ClickHouseProperties; +import com.clickhouse.client.BaseIntegrationTest; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; + +public abstract class JdbcIntegrationTest extends BaseIntegrationTest { + protected final String dbName; + + public JdbcIntegrationTest() { + String className = getClass().getSimpleName(); + this.dbName = className.endsWith("Test") ? className.substring(0, className.length() - 5) : className; + } + + public String getClickHouseHttpAddress() { + return getClickHouseHttpAddress(false); + } + + public String getClickHouseHttpAddress(boolean useIPaddress) { + ClickHouseNode server = getServer(ClickHouseProtocol.HTTP); + + String ipAddress = server.getHost(); + try { + ipAddress = InetAddress.getByName(ipAddress).getHostAddress(); + } catch (UnknownHostException e) { + throw new IllegalArgumentException( + String.format("Not able to resolve %s to get its IP address", server.getHost()), e); + } + return new StringBuilder().append(useIPaddress ? ipAddress : server.getHost()).append(':') + .append(server.getPort()).toString(); + } + + public String getClickHouseHttpAddress(String customHostOrIp) { + ClickHouseNode server = getServer(ClickHouseProtocol.HTTP); + return new StringBuilder() + .append(customHostOrIp == null || customHostOrIp.isEmpty() ? server.getHost() : customHostOrIp) + .append(':').append(server.getPort()).toString(); + } + + public ClickHouseDataSource newDataSource() { + return newDataSource(new ClickHouseProperties()); + } + + public ClickHouseDataSource newDataSource(ClickHouseProperties properties) { + return newDataSource("jdbc:clickhouse://" + getClickHouseHttpAddress(), properties); + } + + public ClickHouseDataSource newDataSource(String url) { + return newDataSource(url, new ClickHouseProperties()); + } + + public ClickHouseDataSource newDataSource(String url, ClickHouseProperties properties) { + String baseUrl = "jdbc:clickhouse://" + getClickHouseHttpAddress(); + if (url == null) { + url = baseUrl; + } else if (!url.startsWith("jdbc:")) { + url = baseUrl + "/" + url; + } + + return new ClickHouseDataSource(url, properties); + } + + public BalancedClickhouseDataSource newBalancedDataSource(String... addresses) { + return newBalancedDataSource(new ClickHouseProperties(), addresses); + } + + public BalancedClickhouseDataSource newBalancedDataSource(ClickHouseProperties properties, String... addresses) { + return newBalancedDataSourceWithSuffix(null, properties, addresses); + } + + public BalancedClickhouseDataSource newBalancedDataSourceWithSuffix(String urlSuffix, + ClickHouseProperties properties, String... addresses) { + StringBuilder url = new StringBuilder().append("jdbc:clickhouse://"); + if (addresses == null || addresses.length == 0) { + url.append(getClickHouseHttpAddress()); + } else { + int position = url.length(); + for (int i = 0; i < addresses.length; i++) { + url.append(',').append(addresses[i]); + } + url.deleteCharAt(position); + } + + if (urlSuffix != null) { + url.append('/').append(urlSuffix); + } + + return new BalancedClickhouseDataSource(url.toString(), properties); + } + + public ClickHouseConnection newConnection() throws SQLException { + return newConnection(null); + } + + public ClickHouseConnection newConnection(ClickHouseProperties properties) throws SQLException { + try (ClickHouseConnection conn = newDataSource().getConnection(); + ClickHouseStatement stmt = conn.createStatement();) { + stmt.execute("CREATE DATABASE IF NOT EXISTS " + dbName); + } + + return newDataSource(dbName, properties == null ? new ClickHouseProperties() : properties).getConnection(); + } + + public void closeConnection(Connection conn) throws SQLException { + if (conn == null) { + return; + } + + try (Statement stmt = conn.createStatement()) { + stmt.execute("DROP DATABASE IF EXISTS " + dbName); + } finally { + conn.close(); + } + } +} diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/PreparedStatementParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/PreparedStatementParserTest.java index 72cf8a5e0..cc489da27 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/PreparedStatementParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/PreparedStatementParserTest.java @@ -11,7 +11,7 @@ */ public class PreparedStatementParserTest { - @Test + @Test(groups = "unit") public void testNullSafety() { try { PreparedStatementParser.parse(null); @@ -19,98 +19,98 @@ public void testNullSafety() { } catch (IllegalArgumentException iae) { /* expected */ } } - @Test + @Test(groups = "unit") public void testParseSimple() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, ?)"); assertMatchParams(new String[][] {{"?", "?"}}, s); } - @Test + @Test(groups = "unit") public void testParseConstantSimple() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 'foo')"); assertMatchParams(new String[][] {{"?", "'foo'"}}, s); } - @Test + @Test(groups = "unit") public void testParseSimpleWhitespaceValueMode() { PreparedStatementParser s = PreparedStatementParser.parse( " INSERT\t INTO t(a, b) VALUES(?, ?)"); assertMatchParams(new String[][] {{"?", "?"}}, s); } - @Test + @Test(groups = "unit") public void testParseConstantSimpleInt() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 42)"); assertMatchParams(new String[][] {{"?", "42"}}, s); } - @Test + @Test(groups = "unit") public void testParseConstantSimpleIntTrailingWhitespace() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?,42 )"); assertMatchParams(new String[][] {{"?", "42"}}, s); } - @Test + @Test(groups = "unit") public void testParseConstantSimpleIntTrailingLeadingWhitespace() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 42 )"); assertMatchParams(new String[][] {{"?", "42"}}, s); } - @Test + @Test(groups = "unit") public void testParseParentheses() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES ((?), ('foo'))"); assertMatchParams(new String[][] {{"?", "'foo'"}}, s); } - @Test + @Test(groups = "unit") public void testParseParenthesesInQuotes() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES ((?), ('foo)))'))"); assertMatchParams(new String[][] {{"?", "'foo)))'"}}, s); } - @Test + @Test(groups = "unit") public void testParseEscapedQuote() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 'foo\\'bar')"); assertMatchParams(new String[][] {{"?", "'foo\\'bar'"}}, s); } - @Test + @Test(groups = "unit") public void testParseEscapedQuoteBroken() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 'foo\'bar')"); Assert.assertTrue(s.getParameters().isEmpty()); // Is this expected? } - @Test + @Test(groups = "unit") public void testParseQuestionMarkInQuotes() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES ('?', 'foo')"); assertMatchParams(new String[][] {{"'?'", "'foo'"}}, s); } - @Test + @Test(groups = "unit") public void testParseQuestionMarkAndMoreInQuotes() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES ('? foo ?', 'bar')"); assertMatchParams(new String[][] {{"'? foo ?'", "'bar'"}}, s); } - @Test + @Test(groups = "unit") public void testParseEscapedQuestionMark() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (\\?, 'foo')"); assertMatchParams(new String[][] {{"'foo'"}}, s); } - @Test + @Test(groups = "unit") public void testNoCommasQuestionMarks() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (foo ? bar ?)"); @@ -119,42 +119,42 @@ public void testNoCommasQuestionMarks() { Assert.assertEquals(matrix.get(0).size(), 1); } - @Test + @Test(groups = "unit") public void testParseIgnoreInsert() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (foo, ?) VALUES (?, 'bar')"); assertMatchParams(new String[][] {{"?", "'bar'"}}, s); } - @Test + @Test(groups = "unit") public void testDoubleComma() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 'bar',, ?, , ?)"); assertMatchParams(new String[][] {{"?", "'bar'", "?", "?"}}, s); } - @Test + @Test(groups = "unit") public void testDoubleSingleQuote() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a) VALUES ('')"); assertMatchParams(new String[][] {{"''"}}, s); } - @Test + @Test(groups = "unit") public void testInsertNumbers() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (foo, bar, baz) VALUES (42, 23, '42')"); assertMatchParams(new String[][] {{"42", "23", "'42'"}}, s); } - @Test + @Test(groups = "unit") public void testInsertBoolean() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (foo, bar) VALUES (TRUE, false)"); assertMatchParams(new String[][] {{"1", "0"}}, s); } - @Test + @Test(groups = "unit") public void testMultiParams() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, ?), (?, ?)"); @@ -166,7 +166,7 @@ public void testMultiParams() { s); } - @Test + @Test(groups = "unit") public void testMultiParamsWithConstants() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) VALUES (?, 'foo'), ('bar', ?)"); @@ -178,7 +178,7 @@ public void testMultiParamsWithConstants() { s); } - @Test + @Test(groups = "unit") public void testValuesModeDoubleQuotes() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (\"foo.bar\") VALUES (?)"); @@ -186,7 +186,7 @@ public void testValuesModeDoubleQuotes() { Assert.assertEquals(s.getParts().get(0), "INSERT INTO t (\"foo.bar\") VALUES ("); } - @Test + @Test(groups = "unit") public void testValuesModeDoubleQuotesValues() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (\"foo.bar\") VALUES (\"baz\")"); @@ -194,7 +194,7 @@ public void testValuesModeDoubleQuotesValues() { Assert.assertEquals(s.getParts().get(0), "INSERT INTO t (\"foo.bar\") VALUES ("); } - @Test + @Test(groups = "unit") public void testValuesModeSingleQuotes() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t ('foo.bar') VALUES (?)"); @@ -202,7 +202,7 @@ public void testValuesModeSingleQuotes() { Assert.assertEquals(s.getParts().get(0), "INSERT INTO t ('foo.bar') VALUES ("); } - @Test + @Test(groups = "unit") public void testValuesModeSingleQuotesValues() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t ('foo.bar') VALUES ('baz')"); @@ -210,7 +210,7 @@ public void testValuesModeSingleQuotesValues() { Assert.assertEquals(s.getParts().get(0), "INSERT INTO t ('foo.bar') VALUES ("); } - @Test + @Test(groups = "unit") public void testParseInsertSelect() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) SELECT x, y"); @@ -218,7 +218,7 @@ public void testParseInsertSelect() { Assert.assertTrue(s.getParameters().isEmpty()); } - @Test + @Test(groups = "unit") public void testParseInsertSelectParams() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO t (a, b) SELECT x FROM u WHERE y = ? AND z = ?"); @@ -228,7 +228,7 @@ public void testParseInsertSelectParams() { assertMatchParams(new String[][] {{"?", "?"}}, s); } - @Test + @Test(groups = "unit") public void testParseSelectGroupBy() { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT SUM(x) FROM t WHERE y = ? GROUP BY ? HAVING COUNT(z) > ? ORDER BY z DESC"); @@ -240,7 +240,7 @@ public void testParseSelectGroupBy() { assertMatchParams(new String[][] {{"?", "?", "?"}}, s); } - @Test + @Test(groups = "unit") public void testParseWithComment1() { PreparedStatementParser s = PreparedStatementParser.parse( "select a --what is it?\nfrom t where a = ? and b = 1"); @@ -249,7 +249,7 @@ public void testParseWithComment1() { assertMatchParams(new String[][] {{"?"}}, s); } - @Test + @Test(groups = "unit") public void testParseWithComment2() { PreparedStatementParser s = PreparedStatementParser.parse( "select a /*what is it?*/ from t where a = ? and b = 1"); @@ -260,7 +260,7 @@ public void testParseWithComment2() { assertMatchParams(new String[][] {{"?"}}, s); } - @Test + @Test(groups = "unit") public void testParseSelectStar() { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT * FROM tbl"); @@ -268,7 +268,7 @@ public void testParseSelectStar() { Assert.assertTrue(s.getParameters().isEmpty()); } - @Test + @Test(groups = "unit") public void testParseSelectStarParam() { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT * FROM tbl WHERE t = ?"); @@ -276,7 +276,7 @@ public void testParseSelectStarParam() { assertMatchParams(new String[][] {{"?"}}, s); } - @Test + @Test(groups = "unit") public void testParseSelectEscapedGarbage() { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT 'a\\'\\\\sdfasdf?adsf\\\\' as `sadf\\`?` FROM tbl WHERE t = ? AND r = ? ORDER BY 1"); @@ -288,7 +288,7 @@ public void testParseSelectEscapedGarbage() { assertMatchParams(new String[][] {{"?", "?"}}, s); } - @Test + @Test(groups = "unit") public void testRegularParam() throws Exception { // Test inspired by MetaBase test cases PreparedStatementParser s = PreparedStatementParser.parse( @@ -306,7 +306,7 @@ public void testRegularParam() throws Exception { "))"); } - @Test + @Test(groups = "unit") public void testRegularParamWhitespace() throws Exception { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT count(*) AS `count` FROM `foo`.`bar` " @@ -323,7 +323,7 @@ public void testRegularParamWhitespace() throws Exception { " ))"); } - @Test + @Test(groups = "unit") public void testRegularParamInFunction() throws Exception { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT count(*) AS `count` FROM `foo`.`bar` " @@ -338,7 +338,7 @@ public void testRegularParamInFunction() throws Exception { ")"); } - @Test + @Test(groups = "unit") public void testNullValuesSelect() throws Exception { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT 1 FROM foo WHERE bar IN (?, NULL)"); @@ -348,7 +348,7 @@ public void testNullValuesSelect() throws Exception { Assert.assertEquals(params.get(0).get(0), "?"); } - @Test + @Test(groups = "unit") public void testNullValuesInsert() throws Exception { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO test.prep_nullable_value (s, i, f) VALUES " @@ -359,7 +359,7 @@ public void testNullValuesInsert() throws Exception { s); } - @Test + @Test(groups = "unit") public void testParamLastCharacter() throws Exception { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT * FROM decisions " @@ -377,7 +377,7 @@ public void testParamLastCharacter() throws Exception { Assert.assertEquals(s.getParts().get(5), ""); } - @Test + @Test(groups = "unit") public void testSingleAndBackMixedQuotes() { PreparedStatementParser s = PreparedStatementParser.parse( "SELECT '`' as `'` WHERE 0 = ?"); @@ -386,7 +386,7 @@ public void testSingleAndBackMixedQuotes() { } - @Test + @Test(groups = "unit") public void testInsertValuesFunctions() throws Exception { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO foo(id, src, dst) " @@ -399,7 +399,7 @@ public void testInsertValuesFunctions() throws Exception { ")))"}, s); } - @Test + @Test(groups = "unit") public void testMultiLineValues() { PreparedStatementParser s = PreparedStatementParser.parse( "INSERT INTO table1\n" diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/WriterTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/WriterTest.java index 98eaea540..834f4de97 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/WriterTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/WriterTest.java @@ -1,7 +1,7 @@ package ru.yandex.clickhouse; import org.mockito.Mockito; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.domain.ClickHouseFormat; import ru.yandex.clickhouse.util.ClickHouseStreamCallback; @@ -13,28 +13,28 @@ public class WriterTest { private ClickHouseStatementImpl statement; - @BeforeTest + @BeforeClass(groups = "unit") public void setUp() throws SQLException { statement = Mockito.mock(ClickHouseStatementImpl.class); Mockito.when(statement.write()).thenReturn(new Writer(statement)); } - @Test(expectedExceptions = SQLException.class, expectedExceptionsMessageRegExp = ".*No input data.*") + @Test(groups = "unit", expectedExceptions = SQLException.class, expectedExceptionsMessageRegExp = ".*No input data.*") public void testNonConfigured() throws SQLException { statement.write().send(); } - @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "Format can not be null") + @Test(groups = "unit", expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "Format can not be null") public void testNullFormatGiven() { statement.write().format(null); } - @Test(expectedExceptions = SQLException.class, expectedExceptionsMessageRegExp = "Wrong binary format.*") + @Test(groups = "unit", expectedExceptions = SQLException.class, expectedExceptionsMessageRegExp = "Wrong binary format.*") public void testWrongBinaryFormat() throws SQLException { statement.write().send("INSERT", (ClickHouseStreamCallback)null, ClickHouseFormat.CSV); } - @Test + @Test(groups = "unit") public void testWhitePath() throws SQLException { statement .write() @@ -44,7 +44,7 @@ public void testWhitePath() throws SQLException { .send(); } - @Test + @Test(groups = "unit") public void testSendToTable() throws SQLException { statement.write().sendToTable("table", new ByteArrayInputStream(new byte[1]), ClickHouseFormat.CSV); } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTest.java index 7e642462a..a9dce3dca 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTest.java @@ -7,22 +7,10 @@ import static org.testng.Assert.assertEquals; public class ClickHouseDataTypeTest { - - @Test( - dataProvider = "clickHouseDataTypeStringsProvider", - dataProviderClass = ClickHouseDataTypeTestDataProvider.class - ) + @Test(groups = "unit", dataProvider = "clickHouseDataTypeStringsProvider", dataProviderClass = ClickHouseDataTypeTestDataProvider.class) public void testFromDataTypeStringSimpleTypes(String typeName, ClickHouseDataType result) { - assertEquals( - ClickHouseDataType.fromTypeString(typeName), - result, - typeName); - assertEquals( - ClickHouseDataType.fromTypeString(typeName.toUpperCase(Locale.ROOT)), - result); - assertEquals( - ClickHouseDataType.fromTypeString(typeName.toLowerCase(Locale.ROOT)), - result); + assertEquals(ClickHouseDataType.fromTypeString(typeName), result, typeName); + assertEquals(ClickHouseDataType.fromTypeString(typeName.toUpperCase(Locale.ROOT)), result); + assertEquals(ClickHouseDataType.fromTypeString(typeName.toLowerCase(Locale.ROOT)), result); } - } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseFormatTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseFormatTest.java index 7668775d8..febba8fd0 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseFormatTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/domain/ClickHouseFormatTest.java @@ -7,24 +7,24 @@ public class ClickHouseFormatTest { - @Test + @Test(groups = "unit") public void testNull() { assertFalse(ClickHouseFormat.containsFormat(null)); } - @Test + @Test(groups = "unit") public void testEmpty() { assertFalse(ClickHouseFormat.containsFormat(" \t \r\n")); } - @Test + @Test(groups = "unit") public void testTrailingWhitespace() { assertFalse(ClickHouseFormat.containsFormat("Phantasy ")); assertTrue(ClickHouseFormat.containsFormat("TabSeparatedWithNamesAndTypes ")); assertTrue(ClickHouseFormat.containsFormat("TabSeparatedWithNamesAndTypes \t \n")); } - @Test + @Test(groups = "unit") public void testTrailingSemicolon() { assertFalse(ClickHouseFormat.containsFormat("Phantasy ;")); assertTrue(ClickHouseFormat.containsFormat("TabSeparatedWithNamesAndTypes ; ")); @@ -32,11 +32,10 @@ public void testTrailingSemicolon() { assertTrue(ClickHouseFormat.containsFormat("TabSeparatedWithNamesAndTypes \t ; \n")); } - @Test + @Test(groups = "unit") public void testAllFormats() { for (ClickHouseFormat format : ClickHouseFormat.values()) { assertTrue(ClickHouseFormat.containsFormat(format.name())); } } - } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java index 684522ac3..4a607c429 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java @@ -13,33 +13,34 @@ import java.util.Arrays; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseArray; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHousePreparedStatement; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.domain.ClickHouseDataType; -import static org.testng.Assert.assertEquals; - /** * Here it is assumed the connection to a ClickHouse instance with flights example data it available at localhost:8123 * For ClickHouse quickstart and example dataset see https://clickhouse.yandex/tutorial.html */ -public class ArrayTest { - - private ClickHouseDataSource dataSource; +public class ArrayTest extends JdbcIntegrationTest { private Connection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); + connection = newConnection(); + } + + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); } - @Test + @Test(groups = "integration") public void testStringArray() throws SQLException { String[] array = {"a'','sadf',aa", "", ",", "юникод,'юникод'", ",2134,saldfk"}; @@ -57,17 +58,17 @@ public void testStringArray() throws SQLException { Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("select array(" + arrayString + ")"); while (rs.next()) { - assertEquals(rs.getArray(1).getBaseType(), Types.VARCHAR); + Assert.assertEquals(rs.getArray(1).getBaseType(), Types.VARCHAR); String[] stringArray = (String[]) rs.getArray(1).getArray(); - assertEquals(stringArray.length, array.length); + Assert.assertEquals(stringArray.length, array.length); for (int i = 0; i < stringArray.length; i++) { - assertEquals(stringArray[i], array[i]); + Assert.assertEquals(stringArray[i], array[i]); } } statement.close(); } - @Test + @Test(groups = "integration") public void testLongArray() throws SQLException { Long[] array = {-12345678987654321L, 23325235235L, -12321342L}; StringBuilder sb = new StringBuilder(); @@ -82,17 +83,17 @@ public void testLongArray() throws SQLException { Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("select array(" + arrayString + ")"); while (rs.next()) { - assertEquals(rs.getArray(1).getBaseType(), Types.BIGINT); + Assert.assertEquals(rs.getArray(1).getBaseType(), Types.BIGINT); long[] longArray = (long[]) rs.getArray(1).getArray(); - assertEquals(longArray.length, array.length); + Assert.assertEquals(longArray.length, array.length); for (int i = 0; i < longArray.length; i++) { - assertEquals(longArray[i], array[i].longValue()); + Assert.assertEquals(longArray[i], array[i].longValue()); } } statement.close(); } - @Test + @Test(groups = "integration") public void testDecimalArray() throws SQLException { BigDecimal[] array = {BigDecimal.valueOf(-12.345678987654321), BigDecimal.valueOf(23.325235235), BigDecimal.valueOf(-12.321342)}; StringBuilder sb = new StringBuilder(); @@ -107,25 +108,25 @@ public void testDecimalArray() throws SQLException { Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("select array(" + arrayString + ")"); while (rs.next()) { - assertEquals(rs.getArray(1).getBaseType(), Types.DECIMAL); + Assert.assertEquals(rs.getArray(1).getBaseType(), Types.DECIMAL); BigDecimal[] deciamlArray = (BigDecimal[]) rs.getArray(1).getArray(); - assertEquals(deciamlArray.length, array.length); + Assert.assertEquals(deciamlArray.length, array.length); for (int i = 0; i < deciamlArray.length; i++) { - assertEquals(0, deciamlArray[i].compareTo(array[i])); + Assert.assertEquals(0, deciamlArray[i].compareTo(array[i])); } } statement.close(); } - @Test + @Test(groups = "integration") public void testInsertUIntArray() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.unsigned_array"); + connection.createStatement().execute("DROP TABLE IF EXISTS unsigned_array"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.unsigned_array" + "CREATE TABLE IF NOT EXISTS unsigned_array" + " (ua32 Array(UInt32), ua64 Array(UInt64), f64 Array(Float64), a32 Array(Int32)) ENGINE = TinyLog" ); - String insertSql = "INSERT INTO test.unsigned_array (ua32, ua64, f64, a32) VALUES (?, ?, ?, ?)"; + String insertSql = "INSERT INTO unsigned_array (ua32, ua64, f64, a32) VALUES (?, ?, ?, ?)"; PreparedStatement statement = connection.prepareStatement(insertSql); @@ -146,7 +147,7 @@ public void testInsertUIntArray() throws SQLException { statement.execute(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select ua32, ua64, f64, a32 from test.unsigned_array"); + ResultSet rs = select.executeQuery("select ua32, ua64, f64, a32 from unsigned_array"); for (int i = 0; i < 2; ++i) { rs.next(); Array bigUInt32 = rs.getArray(1); @@ -172,13 +173,13 @@ public void testInsertUIntArray() throws SQLException { } } - @Test + @Test(groups = "integration") public void testInsertStringArray() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.string_array"); + connection.createStatement().execute("DROP TABLE IF EXISTS string_array"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.string_array (foo Array(String)) ENGINE = TinyLog"); + "CREATE TABLE IF NOT EXISTS string_array (foo Array(String)) ENGINE = TinyLog"); - String insertSQL = "INSERT INTO test.string_array (foo) VALUES (?)"; + String insertSQL = "INSERT INTO string_array (foo) VALUES (?)"; PreparedStatement statement = connection.prepareStatement(insertSQL); statement.setArray(1, connection.createArrayOf( String.class.getCanonicalName(), @@ -186,30 +187,49 @@ public void testInsertStringArray() throws Exception { statement.executeUpdate(); ResultSet r = connection.createStatement().executeQuery( - "SELECT foo FROM test.string_array"); + "SELECT foo FROM string_array"); r.next(); String[] s = (String[]) r.getArray(1).getArray(); Assert.assertEquals(s[0], "23"); Assert.assertEquals(s[1], "42"); } - @Test + @Test(groups = "integration") public void testInsertStringArrayViaUnwrap() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.string_array"); + connection.createStatement().execute("DROP TABLE IF EXISTS string_array"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.string_array (foo Array(String)) ENGINE = TinyLog"); + "CREATE TABLE IF NOT EXISTS string_array (foo Array(String)) ENGINE = TinyLog"); - String insertSQL = "INSERT INTO test.string_array (foo) VALUES (?)"; + String insertSQL = "INSERT INTO string_array (foo) VALUES (?)"; ClickHousePreparedStatement statement = connection.prepareStatement(insertSQL) .unwrap(ClickHousePreparedStatement.class); statement.setArray(1, new String[] {"23", "42"}); statement.executeUpdate(); ResultSet r = connection.createStatement().executeQuery( - "SELECT foo FROM test.string_array"); + "SELECT foo FROM string_array"); r.next(); String[] s = (String[]) r.getArray(1).getArray(); Assert.assertEquals(s[0], "23"); Assert.assertEquals(s[1], "42"); } + + // @Test(groups = "integration") + public void testInsertByteArray() throws Exception { + connection.createStatement().execute("DROP TABLE IF EXISTS int8_array"); + connection.createStatement().execute( + "CREATE TABLE IF NOT EXISTS int8_array (foo Array(Int8)) ENGINE = Memory"); + + String insertSQL = "INSERT INTO int8_array (foo) VALUES (?)"; + PreparedStatement statement = connection.prepareStatement(insertSQL); + statement.setArray(1, new ClickHouseArray(ClickHouseDataType.Int8, new byte[]{12,34})); + statement.executeUpdate(); + + ResultSet r = connection.createStatement().executeQuery( + "SELECT foo FROM int8_array"); + r.next(); + int[] bytes = (int[]) r.getArray(1).getArray(); + Assert.assertEquals(bytes[0], 12); + Assert.assertEquals(bytes[1], 34); + } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/BatchInsertsTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/BatchInsertsTest.java index 2065d7295..978e9dc1c 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/BatchInsertsTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/BatchInsertsTest.java @@ -16,44 +16,42 @@ import java.util.stream.IntStream; import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHousePreparedStatement; import ru.yandex.clickhouse.ClickHousePreparedStatementImpl; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.settings.ClickHouseQueryParam; -public class BatchInsertsTest { +public class BatchInsertsTest extends JdbcIntegrationTest { private Connection connection; private DateFormat dateFormat; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setTimeZone(TimeZone.getDefault()); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { - connection.createStatement().execute("DROP DATABASE test"); + closeConnection(connection); } - @Test + @Test(groups = "integration") public void batchInsert() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_insert"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.batch_insert (i Int32, s String) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS batch_insert (i Int32, s String) ENGINE = TinyLog" ); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.batch_insert (s, i) VALUES (?, ?)"); + PreparedStatement statement = connection.prepareStatement("INSERT INTO batch_insert (s, i) VALUES (?, ?)"); statement.setString(1, "string1"); statement.setInt(2, 21); @@ -65,7 +63,7 @@ public void batchInsert() throws Exception { statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.batch_insert"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from batch_insert"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), 2); @@ -74,11 +72,11 @@ public void batchInsert() throws Exception { } - @Test + @Test(groups = "integration") public void testBatchInsert2() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_insert2"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_insert2"); connection.createStatement().execute( - "CREATE TABLE test.batch_insert2 (" + + "CREATE TABLE batch_insert2 (" + "date Date," + "date_time DateTime," + "string String," + @@ -94,7 +92,7 @@ public void testBatchInsert2() throws Exception { double float64 = 42.21; PreparedStatement statement = connection.prepareStatement( - "INSERT INTO test.batch_insert2 (date, date_time, string, int32, float64) VALUES (?, ?, ?, ?, ?)" + "INSERT INTO batch_insert2 (date, date_time, string, int32, float64) VALUES (?, ?, ?, ?, ?)" ); statement.setDate(1, date); @@ -105,7 +103,7 @@ public void testBatchInsert2() throws Exception { statement.addBatch(); statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT * from test.batch_insert2"); + ResultSet rs = connection.createStatement().executeQuery("SELECT * from batch_insert2"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getDate("date"), date); @@ -117,14 +115,14 @@ public void testBatchInsert2() throws Exception { Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testBatchInsert3() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_insert3"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_insert3"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.batch_insert3 (i Int32, s String) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS batch_insert3 (i Int32, s String) ENGINE = TinyLog" ); - ClickHousePreparedStatementImpl statement = (ClickHousePreparedStatementImpl) connection.prepareStatement("INSERT INTO test.batch_insert3 (s, i) VALUES (?, ?), (?, ?)"); + ClickHousePreparedStatementImpl statement = (ClickHousePreparedStatementImpl) connection.prepareStatement("INSERT INTO batch_insert3 (s, i) VALUES (?, ?), (?, ?)"); statement.setString(1, "firstParam"); statement.setInt(2, 1); statement.setString(3, "thirdParam"); @@ -133,42 +131,42 @@ public void testBatchInsert3() throws Exception { int[] result = statement.executeBatch(); Assert.assertEquals(result, new int[]{1, 1}); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.batch_insert3"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from batch_insert3"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void batchInsert4() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_insert4"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_insert4"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.batch_insert4 (i Int32, s String) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS batch_insert4 (i Int32, s String) ENGINE = TinyLog" ); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.batch_insert4 (i, s) VALUES (?, 'hello'), (?, ?)"); + PreparedStatement statement = connection.prepareStatement("INSERT INTO batch_insert4 (i, s) VALUES (?, 'hello'), (?, ?)"); statement.setInt(1, 42); statement.setInt(2, 43); statement.setString(3, "first_param"); statement.execute(); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.batch_insert4"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from batch_insert4"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testBatchInsert5() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_insert5"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_insert5"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.batch_insert5 (i Int32, s String) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS batch_insert5 (i Int32, s String) ENGINE = TinyLog" ); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.batch_insert5 (i, s) VALUES (?, 'hello'), (?, ?)"); + PreparedStatement statement = connection.prepareStatement("INSERT INTO batch_insert5 (i, s) VALUES (?, 'hello'), (?, ?)"); statement.setInt(1, 42); statement.setInt(2, 43); statement.setString(3, "first_param"); @@ -179,18 +177,18 @@ public void testBatchInsert5() throws Exception { statement.addBatch(); statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.batch_insert5"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from batch_insert5"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), 4); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testSimpleInsert() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS insert"); connection.createStatement().execute( - "CREATE TABLE test.insert (" + + "CREATE TABLE insert (" + "date Date," + "date_time DateTime," + "string String," + @@ -206,7 +204,7 @@ public void testSimpleInsert() throws Exception { double float64 = 42.21; PreparedStatement statement = connection.prepareStatement( - "INSERT INTO test.insert (date, date_time, string, int32, float64) VALUES (?, ?, ?, ?, ?)" + "INSERT INTO insert (date, date_time, string, int32, float64) VALUES (?, ?, ?, ?, ?)" ); statement.setDate(1, date); @@ -217,7 +215,7 @@ public void testSimpleInsert() throws Exception { statement.execute(); - ResultSet rs = connection.createStatement().executeQuery("SELECT * from test.insert"); + ResultSet rs = connection.createStatement().executeQuery("SELECT * from insert"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getDate("date").getTime(), date.getTime()); @@ -229,11 +227,11 @@ public void testSimpleInsert() throws Exception { Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void batchInsertNulls() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_insert_nulls"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_insert_nulls"); connection.createStatement().execute( - "CREATE TABLE test.batch_insert_nulls (" + + "CREATE TABLE batch_insert_nulls (" + "date Date," + "date_time Nullable(DateTime)," + "string Nullable(String)," + @@ -243,7 +241,7 @@ public void batchInsertNulls() throws Exception { ); ClickHousePreparedStatement statement = (ClickHousePreparedStatement) connection.prepareStatement( - "INSERT INTO test.batch_insert_nulls (date, date_time, string, int32, float64) VALUES (?, ?, ?, ?, ?)" + "INSERT INTO batch_insert_nulls (date, date_time, string, int32, float64) VALUES (?, ?, ?, ?, ?)" ); Date date = new Date(dateFormat.parse("1989-01-30").getTime()); @@ -255,7 +253,7 @@ public void batchInsertNulls() throws Exception { statement.addBatch(); statement.executeBatch(Collections.singletonMap(ClickHouseQueryParam.CONNECT_TIMEOUT, "1000")); - ResultSet rs = connection.createStatement().executeQuery("SELECT date, date_time, string, int32, float64 from test.batch_insert_nulls"); + ResultSet rs = connection.createStatement().executeQuery("SELECT date, date_time, string, int32, float64 from batch_insert_nulls"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getDate("date"), date); @@ -267,17 +265,17 @@ public void batchInsertNulls() throws Exception { Assert.assertNull(rs.getObject("float64")); Assert.assertFalse(rs.next()); - connection.createStatement().execute("DROP TABLE test.batch_insert_nulls"); + connection.createStatement().execute("DROP TABLE batch_insert_nulls"); } - @Test + @Test(groups = "integration") public void testBatchValuesColumn() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.batch_single_test"); + connection.createStatement().execute("DROP TABLE IF EXISTS batch_single_test"); connection.createStatement().execute( - "CREATE TABLE test.batch_single_test(date Date, values String) ENGINE = StripeLog" + "CREATE TABLE batch_single_test(date Date, values String) ENGINE = StripeLog" ); - PreparedStatement st = connection.prepareStatement("INSERT INTO test.batch_single_test (date, values) VALUES (?, ?)"); + PreparedStatement st = connection.prepareStatement("INSERT INTO batch_single_test (date, values) VALUES (?, ?)"); st.setDate(1, new Date(System.currentTimeMillis())); st.setString(2, "test"); @@ -285,22 +283,22 @@ public void testBatchValuesColumn() throws SQLException { st.executeBatch(); } - @Test(expectedExceptions = SQLException.class) + @Test(groups = "integration", expectedExceptions = SQLException.class) public void testNullParameters() throws SQLException { - PreparedStatement st = connection.prepareStatement("INSERT INTO test.batch_single_test (date, values) VALUES (?, ?)"); + PreparedStatement st = connection.prepareStatement("INSERT INTO batch_single_test (date, values) VALUES (?, ?)"); st.setString(2, "test"); st.addBatch(); } - @Test + @Test(groups = "integration") public void testBatchInsertWithLongQuery() throws SQLException { int columnCount = 200; try (Statement s = connection.createStatement()) { String createColumns = IntStream.range(0, columnCount).mapToObj( i -> "`looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongnaaaaameeeeeeee" + i + "` String " ).collect(Collectors.joining(",")); - s.execute("DROP TABLE IF EXISTS test.batch_insert_with_long_query"); - s.execute("CREATE TABLE test.batch_insert_with_long_query (" + createColumns + ") ENGINE = Memory"); + s.execute("DROP TABLE IF EXISTS batch_insert_with_long_query"); + s.execute("CREATE TABLE batch_insert_with_long_query (" + createColumns + ") ENGINE = Memory"); } String values = IntStream.range(0, columnCount).mapToObj(i -> "?").collect(Collectors.joining(",")); @@ -308,7 +306,7 @@ public void testBatchInsertWithLongQuery() throws SQLException { i -> "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongnaaaaameeeeeeee" + i ).collect(Collectors.joining(",")); int index = 1; - try (PreparedStatement s = connection.prepareStatement("INSERT INTO test.batch_insert_with_long_query (" + columns + ") VALUES (" + values + ")")) { + try (PreparedStatement s = connection.prepareStatement("INSERT INTO batch_insert_with_long_query (" + columns + ") VALUES (" + values + ")")) { for (int i = 0; i < columnCount; i++) { s.setString(index++, "12345"); } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/CSVStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/CSVStreamTest.java index 439b22f28..df6fff62f 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/CSVStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/CSVStreamTest.java @@ -1,45 +1,45 @@ package ru.yandex.clickhouse.integration; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.io.StringBufferInputStream; import java.nio.charset.Charset; import java.sql.ResultSet; import java.sql.SQLException; -public class CSVStreamTest { - private ClickHouseDataSource dataSource; +public class CSVStreamTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); } - @Test + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); + } + + @Test(groups = "integration") public void simpleInsert() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.csv_stream"); + connection.createStatement().execute("DROP TABLE IF EXISTS csv_stream"); connection.createStatement().execute( - "CREATE TABLE test.csv_stream (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE csv_stream (value Int32, string_value String) ENGINE = Log()" ); String string = "5,6\n1,6"; InputStream inputStream = new ByteArrayInputStream(string.getBytes(Charset.forName("UTF-8"))); - inputStream = new StringBufferInputStream(string); - connection.createStatement().sendCSVStream(inputStream, "test.csv_stream"); + connection.createStatement().sendCSVStream(inputStream, dbName + ".csv_stream"); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.csv_stream"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM csv_stream"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertEquals(rs.getLong("sum"), 6); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseBitmapTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseBitmapTest.java index 7a5c3a4ec..b6c8452df 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseBitmapTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseBitmapTest.java @@ -6,34 +6,35 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import org.junit.Assert; import org.roaringbitmap.RoaringBitmap; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MutableRoaringBitmap; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import ru.yandex.clickhouse.ClickHouseContainerForTest; +import ru.yandex.clickhouse.ClickHouseConnection; import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.domain.ClickHouseDataType; import ru.yandex.clickhouse.util.ClickHouseBitmap; -public class ClickHouseBitmapTest { - private Connection conn; +public class ClickHouseBitmapTest extends JdbcIntegrationTest { + private ClickHouseConnection conn; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(); + ClickHouseDataSource dataSource = newDataSource(); conn = dataSource.getConnection(); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { if (conn != null) { conn.close(); @@ -66,7 +67,7 @@ private void checkBitmaps(ResultSet rs, ClickHouseBitmap empty, ClickHouseBitmap assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testRoaringBitmap() throws Exception { if (conn == null) { return; @@ -167,4 +168,34 @@ public void testRoaringBitmap() throws Exception { s.execute("truncate table test_roaring_bitmap"); } } + + @Test(groups = "integration") + public void testRoaringBitmap64() throws Exception { + if (conn == null) { + return; + } + + try (Statement s = conn.createStatement()) { + s.execute( + "drop table if exists string_labels; create table string_labels (label String, value String, uv AggregateFunction(groupBitmap, UInt64)) ENGINE = AggregatingMergeTree() partition by label order by (label, value);"); + s.execute( + "drop table if exists user_string_properties; create table user_string_properties (uid UInt64, label String, value String) ENGINE=MergeTree() order by uid"); + s.execute( + "drop table if exists string_labels_mv; create materialized view string_labels_mv to string_labels as select label, value, groupBitmapState(uid) as uv from user_string_properties group by (label, value)"); + s.execute( + "insert into user_string_properties select number as uid, 'gender' as label, multiIf((number % 3) = 0, 'f', 'm') as value from numbers(20000000)"); + + try (ResultSet rs = s.executeQuery( + "select groupBitmapMergeState(uv) as uv from string_labels where label = 'gender' and value = 'f'")) { + Assert.assertTrue(rs.next()); + + ClickHouseBitmap bitmap = rs.getObject(1, ClickHouseBitmap.class); + Assert.assertNotNull(bitmap); + + Assert.assertFalse(rs.next()); + } + + // s.execute("truncate table test_roaring_bitmap"); + } + } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseConnectionImplTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseConnectionImplTest.java index d1aa4e3a7..1b3cc0ca1 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseConnectionImplTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseConnectionImplTest.java @@ -9,8 +9,8 @@ import org.testng.annotations.Test; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.except.ClickHouseException; import ru.yandex.clickhouse.settings.ClickHouseProperties; @@ -19,66 +19,66 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; -public class ClickHouseConnectionImplTest { +public class ClickHouseConnectionImplTest extends JdbcIntegrationTest { - @Test + @Test(groups = "integration") public void testDefaultEmpty() throws Exception { assertSuccess(createDataSource(null, null)); } - @Test + @Test(groups = "integration") public void testDefaultUserOnly() throws Exception { assertSuccess(createDataSource("default", null)); } - @Test + @Test(groups = "integration") public void testDefaultUserEmptyPassword() throws Exception { assertSuccess(createDataSource("default", "")); } - @Test + @Test(groups = "integration") public void testDefaultUserPass() throws Exception { assertFailure(createDataSource("default", "bar")); } - @Test + @Test(groups = "integration") public void testDefaultPass() throws Exception { assertFailure(createDataSource(null, "bar")); } - @Test + @Test(groups = "integration") public void testFooEmpty() throws Exception { assertFailure(createDataSource("foo", null)); } - @Test + @Test(groups = "integration") public void testFooWrongPass() throws Exception { assertFailure(createDataSource("foo", "baz")); } - @Test + @Test(groups = "integration") public void testFooPass() throws Exception { assertSuccess(createDataSource("foo", "bar")); } - @Test + @Test(groups = "integration") public void testFooWrongUser() throws Exception { assertFailure(createDataSource("baz", "bar")); } - @Test + @Test(groups = "integration") public void testOofNoPassword() throws Exception { assertSuccess(createDataSource("oof", null)); } - @Test + @Test(groups = "integration") public void testOofWrongPassword() throws Exception { assertFailure(createDataSource("oof", "baz")); } - @Test + @Test(groups = "integration") public void testDefaultDatabase() throws Exception { - ClickHouseDataSource ds = ClickHouseContainerForTest.newDataSource(); + ClickHouseDataSource ds = newDataSource(); String currentDbQuery = "select currentDatabase()"; try (Connection conn = ds.getConnection(); Statement s = conn.createStatement()) { try (ResultSet rs = s.executeQuery(currentDbQuery)) { @@ -93,10 +93,10 @@ public void testDefaultDatabase() throws Exception { assertEquals(rs.getString(1), "default"); assertFalse(rs.next()); } - s.execute("create database if not exists tdb1; create database if not exists tdb2"); + s.execute("drop database if exists tdb1; drop database if exists tdb2; create database tdb1; create database tdb2"); } - ds = ClickHouseContainerForTest.newDataSource("tdb2"); + ds = newDataSource("tdb2"); try (Connection conn = ds.getConnection(); Statement s = conn.createStatement()) { try (ResultSet rs = s.executeQuery(currentDbQuery)) { assertTrue(rs.next()); @@ -155,10 +155,10 @@ private static void assertFailure(DataSource dataSource) throws Exception { } } - private static DataSource createDataSource(String user, String password) { + private DataSource createDataSource(String user, String password) { ClickHouseProperties props = new ClickHouseProperties(); props.setUser(user); props.setPassword(password); - return ClickHouseContainerForTest.newDataSource(props); + return newDataSource(props); } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java index 4a02f4300..a2b66be8a 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java @@ -27,21 +27,21 @@ import java.util.TimeZone; import java.util.UUID; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseArray; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.except.ClickHouseException; import ru.yandex.clickhouse.response.ClickHouseColumnInfo; import ru.yandex.clickhouse.response.parser.ClickHouseValueParser; import ru.yandex.clickhouse.settings.ClickHouseProperties; -public class ClickHouseDataTypeTest { +public class ClickHouseDataTypeTest extends JdbcIntegrationTest { private ClickHouseConnection conn; private LocalDate instantToLocalDate(Instant instant, ZoneId zone) { @@ -64,13 +64,13 @@ private LocalTime instantToLocalTime(Instant instant, ZoneId zone) { return LocalTime.ofNanoOfDay(secsOfDay * 1000_000_000L + instant.getNano()); } - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(); + ClickHouseDataSource dataSource = newDataSource(); conn = (ClickHouseConnection) dataSource.getConnection(); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { conn.close(); } @@ -92,7 +92,7 @@ public static Object[][] provieTestTimeZones() { new String[] { "Asia/Chongqing", "Asia/Chongqing", "Asia/Chongqing" } }; } - @Test(groups = { "sit", "timezone" }, dataProvider = "testTimeZones") + @Test(groups = { "integration", "timezone" }, dataProvider = "testTimeZones") public void testDateTimeWithTimeZone(String d1TimeZone, String d2TimeZone, String testTimeZone) throws Exception { try (Statement s = conn.createStatement()) { s.execute("DROP TABLE IF EXISTS test_datetime_with_timezone"); @@ -118,12 +118,9 @@ public void testDateTimeWithTimeZone(String d1TimeZone, String d2TimeZone, Strin DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); long timestamp = 1546300800L; // '2019-01-01 00:00:00' in GMT - try (ClickHouseConnection connDefaultTz = (ClickHouseConnection) ClickHouseContainerForTest - .newDataSource(props1).getConnection(); - ClickHouseConnection connServerTz = (ClickHouseConnection) ClickHouseContainerForTest - .newDataSource(props2).getConnection(); - ClickHouseConnection connCustomTz = (ClickHouseConnection) ClickHouseContainerForTest - .newDataSource(props3).getConnection(); + try (ClickHouseConnection connDefaultTz = (ClickHouseConnection) newDataSource(props1).getConnection(); + ClickHouseConnection connServerTz = (ClickHouseConnection) newDataSource(props2).getConnection(); + ClickHouseConnection connCustomTz = (ClickHouseConnection) newDataSource(props3).getConnection(); Statement stmt = conn.createStatement()) { stmt.execute("insert into test_datetime_with_timezone values (" + timestamp + ", " + timestamp + ", " + timestamp + ")"); @@ -583,7 +580,7 @@ public void testDateTimeWithTimeZone(String d1TimeZone, String d2TimeZone, Strin } } - @Test(groups = { "sit", "timezone" }, dataProvider = "uniqTimeZones") + @Test(groups = { "integration", "timezone" }, dataProvider = "uniqTimeZones") public void testDateWithTimeZone(String testTimeZone) throws Exception { try (Statement s = conn.createStatement()) { s.execute("DROP TABLE IF EXISTS test_date_with_timezone"); @@ -608,12 +605,9 @@ public void testDateWithTimeZone(String testTimeZone) throws Exception { long timestamp = 1546300800L; // '2019-01-01 00:00:00' in GMT int date = (int) timestamp / 24 / 3600; // '2019-01-01' in GMT - try (ClickHouseConnection connDefaultTz = (ClickHouseConnection) ClickHouseContainerForTest - .newDataSource(props1).getConnection(); - ClickHouseConnection connServerTz = (ClickHouseConnection) ClickHouseContainerForTest - .newDataSource(props2).getConnection(); - ClickHouseConnection connCustomTz = (ClickHouseConnection) ClickHouseContainerForTest - .newDataSource(props3).getConnection(); + try (ClickHouseConnection connDefaultTz = (ClickHouseConnection) newDataSource(props1).getConnection(); + ClickHouseConnection connServerTz = (ClickHouseConnection) newDataSource(props2).getConnection(); + ClickHouseConnection connCustomTz = (ClickHouseConnection) newDataSource(props3).getConnection(); Statement stmt = conn.createStatement()) { stmt.execute("insert into test_date_with_timezone values (" + date + ")"); @@ -881,7 +875,7 @@ public void testDateWithTimeZone(String testTimeZone) throws Exception { } } - @Test + @Test(groups = "integration") public void testUUID() throws Exception { try (Statement s = conn.createStatement()) { s.execute("DROP TABLE IF EXISTS test_uuid"); @@ -922,7 +916,7 @@ public void testUUID() throws Exception { } } - @Test + @Test(groups = "integration") public void testDateTime64() throws Exception { try (Statement s = conn.createStatement()) { s.execute("DROP TABLE IF EXISTS test_datetime64"); @@ -952,7 +946,7 @@ public void testDateTime64() throws Exception { } } - @Test + @Test(groups = "integration") public void testDateTimes() throws Exception { try (Statement s = conn.createStatement()) { s.execute("DROP TABLE IF EXISTS test_datetimes"); @@ -1060,7 +1054,7 @@ public void testDateTimes() throws Exception { } } - @Test + @Test(groups = "integration") public void testIPs() throws Exception { try (Statement s = conn.createStatement()) { s.execute("DROP TABLE IF EXISTS test_ips"); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDatabaseMetadataTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDatabaseMetadataTest.java index e4e593f63..60bdd816f 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDatabaseMetadataTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDatabaseMetadataTest.java @@ -1,6 +1,5 @@ package ru.yandex.clickhouse.integration; -import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -10,45 +9,38 @@ import org.mockito.Mockito; import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHouseDatabaseMetadata; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; -public class ClickHouseDatabaseMetadataTest { - - private ClickHouseDataSource dataSource; +public class ClickHouseDatabaseMetadataTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { - if (connection != null) { - connection.close(); - } + closeConnection(connection); } - @Test + @Test(groups = "integration") public void testMetadata() throws Exception { connection.createStatement().executeQuery( - "DROP TABLE IF EXISTS test.testMetadata"); + "DROP TABLE IF EXISTS testMetadata"); connection.createStatement().executeQuery( - "CREATE TABLE test.testMetadata(" + "CREATE TABLE testMetadata(" + "foo Nullable(UInt32), bar UInt64) ENGINE = TinyLog"); ResultSet columns = connection.getMetaData().getColumns( - null, "test", "testMetaData", null); + null, dbName, "testMetaData", null); while (columns.next()) { String colName = columns.getString("COLUMN_NAME"); if ("foo".equals(colName)) { @@ -62,21 +54,21 @@ public void testMetadata() throws Exception { } } - @Test + @Test(groups = "integration") public void testMetadataColumns() throws Exception { boolean supportComment = ClickHouseVersionNumberUtil.compare(connection.getServerVersion(), "18.16") >= 0; connection.createStatement().executeQuery( - "DROP TABLE IF EXISTS test.testMetadata"); + "DROP TABLE IF EXISTS testMetadata"); connection.createStatement().executeQuery( - "CREATE TABLE test.testMetadata(" + "CREATE TABLE testMetadata(" + "foo Float32, bar UInt8" + (supportComment ? " DEFAULT 42 COMMENT 'baz'" : "") + ") ENGINE = TinyLog"); ResultSet columns = connection.getMetaData().getColumns( - null, "test", "testMetadata", null); + null, dbName, "testMetadata", null); columns.next(); Assert.assertEquals(columns.getString("TABLE_CAT"), "default"); - Assert.assertEquals(columns.getString("TABLE_SCHEM"), "test"); + Assert.assertEquals(columns.getString("TABLE_SCHEM"), dbName); Assert.assertEquals(columns.getString("TABLE_NAME"), "testMetadata"); Assert.assertEquals(columns.getString("COLUMN_NAME"), "foo"); Assert.assertEquals(columns.getInt("DATA_TYPE"), Types.REAL); @@ -106,7 +98,7 @@ public void testMetadataColumns() throws Exception { } } - @Test + @Test(groups = "integration") public void testDriverVersion() throws Exception { DatabaseMetaData metaData = new ClickHouseDatabaseMetadata( "url", Mockito.mock(ClickHouseConnection.class)); @@ -115,7 +107,7 @@ public void testDriverVersion() throws Exception { Assert.assertEquals(metaData.getDriverMinorVersion(), 1); } - @Test + @Test(groups = "integration") public void testDatabaseVersion() throws Exception { String dbVersion = connection.getMetaData().getDatabaseProductVersion(); Assert.assertFalse(dbVersion == null || dbVersion.isEmpty()); @@ -127,41 +119,41 @@ public void testDatabaseVersion() throws Exception { Assert.assertEquals(connection.getMetaData().getDatabaseMinorVersion(), dbMinor); } - @Test(dataProvider = "tableEngines") + @Test(groups = "integration", dataProvider = "tableEngines") public void testGetTablesEngines(String engine) throws Exception { connection.createStatement().executeQuery( - "DROP TABLE IF EXISTS test.testMetadata"); + "DROP TABLE IF EXISTS testMetadata"); connection.createStatement().executeQuery( - "CREATE TABLE test.testMetadata(" + "CREATE TABLE testMetadata(" + "foo Date) ENGINE = " + engine); - ResultSet tableMeta = connection.getMetaData().getTables(null, "test", "testMetadata", null); + ResultSet tableMeta = connection.getMetaData().getTables(null, dbName, "testMetadata", null); tableMeta.next(); Assert.assertEquals("TABLE", tableMeta.getString("TABLE_TYPE")); } - @Test + @Test(groups = "integration") public void testGetTablesViews() throws Exception { connection.createStatement().executeQuery( - "DROP TABLE IF EXISTS test.testMetadataView"); + "DROP TABLE IF EXISTS testMetadataView"); connection.createStatement().executeQuery( - "CREATE VIEW test.testMetadataView AS SELECT 1 FROM system.tables"); + "CREATE VIEW testMetadataView AS SELECT 1 FROM system.tables"); ResultSet tableMeta = connection.getMetaData().getTables( - null, "test", "testMetadataView", null); + null, dbName, "testMetadataView", null); tableMeta.next(); Assert.assertEquals("VIEW", tableMeta.getString("TABLE_TYPE")); } - @Test + @Test(groups = "integration") public void testToDateTimeTZ() throws Exception { connection.createStatement().executeQuery( - "DROP TABLE IF EXISTS test.testDateTimeTZ"); + "DROP TABLE IF EXISTS testDateTimeTZ"); connection.createStatement().executeQuery( - "CREATE TABLE test.testDateTimeTZ (foo DateTime) Engine = Memory"); + "CREATE TABLE testDateTimeTZ (foo DateTime) Engine = Memory"); connection.createStatement().execute( - "INSERT INTO test.testDateTimeTZ (foo) VALUES('2019-04-12 13:37:00')"); + "INSERT INTO testDateTimeTZ (foo) VALUES('2019-04-12 13:37:00')"); ResultSet rs = connection.createStatement().executeQuery( - "SELECT toDateTime(foo) FROM test.testDateTimeTZ"); + "SELECT toDateTime(foo) FROM testDateTimeTZ"); ResultSetMetaData meta = rs.getMetaData(); Assert.assertEquals(meta.getColumnClassName(1), Timestamp.class.getCanonicalName()); TimeZone timezone = ((ClickHouseConnection) connection).getTimeZone(); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseLargeNumberTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseLargeNumberTest.java index e9a393fe8..d955747ea 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseLargeNumberTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseLargeNumberTest.java @@ -16,27 +16,27 @@ import java.util.Map; import java.util.UUID; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHouseStatement; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.except.ClickHouseException; import ru.yandex.clickhouse.settings.ClickHouseProperties; import ru.yandex.clickhouse.settings.ClickHouseQueryParam; import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; -public class ClickHouseLargeNumberTest { +public class ClickHouseLargeNumberTest extends JdbcIntegrationTest { private ClickHouseConnection conn; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setSessionId(UUID.randomUUID().toString()); - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(props); + ClickHouseDataSource dataSource = newDataSource(props); conn = dataSource.getConnection(); try (Statement s = conn.createStatement()) { s.execute("SET allow_experimental_bigint_types=1"); @@ -45,7 +45,7 @@ public void setUp() throws Exception { } } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { if (conn == null) { return; @@ -56,7 +56,7 @@ public void tearDown() throws Exception { } } - @Test + @Test(groups = "integration") public void testBigIntSupport() throws SQLException { if (conn == null || ClickHouseVersionNumberUtil.compare(conn.getServerVersion(), "21.7") >= 0) { return; @@ -64,20 +64,18 @@ public void testBigIntSupport() throws SQLException { String testSql = "create table if not exists system.test_bigint_support(i Int256) engine=Memory;" + "drop table if exists system.test_bigint_support;"; - try (Connection conn = ClickHouseContainerForTest.newDataSource().getConnection(); - Statement s = conn.createStatement()) { + try (Connection conn = newDataSource().getConnection(); Statement s = conn.createStatement()) { s.execute("set allow_experimental_bigint_types=0;" + testSql); fail("Should fail without enabling bigint support"); } catch (SQLException e) { assertEquals(e.getErrorCode(), 44); } - try (Connection conn = ClickHouseContainerForTest.newDataSource().getConnection(); - Statement s = conn.createStatement()) { + try (Connection conn = newDataSource().getConnection(); Statement s = conn.createStatement()) { assertFalse(s.execute("set allow_experimental_bigint_types=1;" + testSql)); } - try (ClickHouseConnection conn = ClickHouseContainerForTest.newDataSource().getConnection(); + try (ClickHouseConnection conn = newDataSource().getConnection(); ClickHouseStatement s = conn.createStatement()) { Map params = new EnumMap<>(ClickHouseQueryParam.class); params.put(ClickHouseQueryParam.ALLOW_EXPERIMENTAL_BIGINT_TYPES, "1"); @@ -91,7 +89,7 @@ public void testBigIntSupport() throws SQLException { } } - @Test + @Test(groups = "integration") public void testSignedIntegers() throws Exception { if (conn == null) { return; @@ -123,7 +121,7 @@ public void testSignedIntegers() throws Exception { } } - @Test + @Test(groups = "integration") public void testUnsignedIntegers() throws Exception { if (conn == null) { return; @@ -148,7 +146,7 @@ public void testUnsignedIntegers() throws Exception { } } - @Test + @Test(groups = "integration") public void testDecimal256() throws Exception { if (conn == null) { return; @@ -169,13 +167,16 @@ public void testDecimal256() throws Exception { } // check max scale + if (ClickHouseVersionNumberUtil.compare(conn.getServerVersion(), "21.9") >= 0) { + s.execute("set output_format_decimal_trailing_zeros=1"); + } try (ResultSet rs = s.executeQuery("select d from test_decimal256 order by d")) { assertTrue(rs.next()); - assertEquals(new BigDecimal("-123456789.123456789").setScale(20), rs.getObject("d")); + assertEquals(rs.getObject("d"), new BigDecimal("-123456789.123456789").setScale(20)); assertTrue(rs.next()); assertTrue(rs.next()); - assertEquals(new BigDecimal("123456789.123456789").setScale(20), rs.getObject("d")); + assertEquals(rs.getObject("d"), new BigDecimal("123456789.123456789").setScale(20)); } } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseMapTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseMapTest.java index fcbdaa60e..97aa36786 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseMapTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseMapTest.java @@ -16,28 +16,28 @@ import java.util.Map; import java.util.UUID; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHouseStatement; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.except.ClickHouseException; import ru.yandex.clickhouse.settings.ClickHouseProperties; import ru.yandex.clickhouse.settings.ClickHouseQueryParam; import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; import ru.yandex.clickhouse.util.Utils; -public class ClickHouseMapTest { +public class ClickHouseMapTest extends JdbcIntegrationTest { private ClickHouseConnection conn; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setSessionId(UUID.randomUUID().toString()); - ClickHouseDataSource dataSource = ClickHouseContainerForTest.newDataSource(props); + ClickHouseDataSource dataSource = newDataSource(props); conn = dataSource.getConnection(); try (Statement s = conn.createStatement()) { s.execute("SET allow_experimental_map_type=1"); @@ -46,7 +46,7 @@ public void setUp() throws Exception { } } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { if (conn == null) { return; @@ -70,19 +70,17 @@ private void assertMap(Object actual, Object expected) { } } - @Test + @Test(groups = "integration") public void testMapSupport() throws SQLException { if (conn == null) { return; } - String testSql = "create table if not exists system.test_map_support(m Map(UInt8, String)) engine=Memory;" + "drop table if exists system.test_map_support;"; - try (ClickHouseConnection conn = ClickHouseContainerForTest.newDataSource().getConnection(); + try (ClickHouseConnection conn = newDataSource().getConnection(); Statement s = conn.createStatement()) { s.execute("set allow_experimental_map_type=0;" + testSql); - String version = conn.getServerVersion(); if (version.compareTo("21.8") < 0) { fail("Should fail without enabling map support"); @@ -91,12 +89,12 @@ public void testMapSupport() throws SQLException { assertEquals(e.getErrorCode(), 44); } - try (Connection conn = ClickHouseContainerForTest.newDataSource().getConnection(); + try (Connection conn = newDataSource().getConnection(); Statement s = conn.createStatement()) { assertFalse(s.execute("set allow_experimental_map_type=1;" + testSql)); } - try (ClickHouseConnection conn = ClickHouseContainerForTest.newDataSource().getConnection(); + try (ClickHouseConnection conn = newDataSource().getConnection(); ClickHouseStatement s = conn.createStatement()) { Map params = new EnumMap<>(ClickHouseQueryParam.class); params.put(ClickHouseQueryParam.ALLOW_EXPERIMENTAL_MAP_TYPE, "1"); @@ -113,7 +111,7 @@ public void testMapSupport() throws SQLException { } } - @Test + @Test(groups = "integration") public void testMaps() throws Exception { if (conn == null) { return; diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHousePreparedStatementTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHousePreparedStatementTest.java index c70dddf7f..f950f82b2 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHousePreparedStatementTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHousePreparedStatementTest.java @@ -17,16 +17,15 @@ import java.util.UUID; import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseArray; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHousePreparedStatement; import ru.yandex.clickhouse.ClickHousePreparedStatementImpl; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.domain.ClickHouseDataType; import ru.yandex.clickhouse.response.ClickHouseResponse; import ru.yandex.clickhouse.settings.ClickHouseProperties; @@ -37,34 +36,28 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -public class ClickHousePreparedStatementTest { - - private ClickHouseDataSource dataSource; +public class ClickHousePreparedStatementTest extends JdbcIntegrationTest { private Connection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { - if (connection != null) { - connection.close(); - } + closeConnection(connection); } - @Test + @Test(groups = "integration") public void testArrayTest() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.array_test"); + connection.createStatement().execute("DROP TABLE IF EXISTS array_test"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.array_test (i Int32, a Array(Int32)) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS array_test (i Int32, a Array(Int32)) ENGINE = TinyLog" ); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.array_test (i, a) VALUES (?, ?)"); + PreparedStatement statement = connection.prepareStatement("INSERT INTO array_test (i, a) VALUES (?, ?)"); statement.setInt(1, 1); statement.setArray(2, new ClickHouseArray(ClickHouseDataType.Int32, new int[]{1, 2, 3})); @@ -75,18 +68,18 @@ public void testArrayTest() throws Exception { statement.addBatch(); statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.array_test"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from array_test"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testArrayOfNullable() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.array_of_nullable"); + connection.createStatement().execute("DROP TABLE IF EXISTS array_of_nullable"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.array_of_nullable (" + + "CREATE TABLE IF NOT EXISTS array_of_nullable (" + "str Nullable(String), " + "int Nullable(Int32), " + "strs Array(Nullable(String)), " + @@ -94,7 +87,7 @@ public void testArrayOfNullable() throws Exception { ); PreparedStatement statement = connection.prepareStatement( - "INSERT INTO test.array_of_nullable (str, int, strs, ints) VALUES (?, ?, ?, ?)" + "INSERT INTO array_of_nullable (str, int, strs, ints) VALUES (?, ?, ?, ?)" ); statement.setObject(1, null); @@ -104,7 +97,7 @@ public void testArrayOfNullable() throws Exception { statement.addBatch(); statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM test.array_of_nullable"); + ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM array_of_nullable"); Assert.assertTrue(rs.next()); Assert.assertNull(rs.getObject("str")); @@ -115,11 +108,10 @@ public void testArrayOfNullable() throws Exception { ClickHouseProperties properties = new ClickHouseProperties(); properties.setUseObjectsInArrays(true); - ClickHouseDataSource configuredDataSource = new ClickHouseDataSource(dataSource.getUrl(), properties); - ClickHouseConnection configuredConnection = configuredDataSource.getConnection(); + ClickHouseConnection configuredConnection = newConnection(properties); try { - rs = configuredConnection.createStatement().executeQuery("SELECT * FROM test.array_of_nullable"); + rs = configuredConnection.createStatement().executeQuery("SELECT * FROM array_of_nullable"); rs.next(); Assert.assertEquals(rs.getArray("ints").getArray(), new Integer[]{1, null, 3}); @@ -128,14 +120,14 @@ public void testArrayOfNullable() throws Exception { } } - @Test + @Test(groups = "integration") public void testArrayFixedStringTest() throws Exception { - connection.createStatement().execute("DROP TABLE IF EXISTS test.array_fixed_string_test"); + connection.createStatement().execute("DROP TABLE IF EXISTS array_fixed_string_test"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.array_fixed_string_test (i Int32, a Array(FixedString(16))) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS array_fixed_string_test (i Int32, a Array(FixedString(16))) ENGINE = TinyLog" ); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.array_fixed_string_test (i, a) VALUES (?, ?)"); + PreparedStatement statement = connection.prepareStatement("INSERT INTO array_fixed_string_test (i, a) VALUES (?, ?)"); statement.setInt(1, 1); statement.setArray(2, new ClickHouseArray(ClickHouseDataType.FixedString, new byte[][]{randomEncodedUUID(), randomEncodedUUID()})); @@ -146,25 +138,25 @@ public void testArrayFixedStringTest() throws Exception { statement.addBatch(); statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.array_fixed_string_test"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from array_fixed_string_test"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testInsertUInt() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.unsigned_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS unsigned_insert"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.unsigned_insert (ui32 UInt32, ui64 UInt64) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS unsigned_insert (ui32 UInt32, ui64 UInt64) ENGINE = TinyLog" ); - PreparedStatement stmt = connection.prepareStatement("insert into test.unsigned_insert (ui32, ui64) values (?, ?)"); + PreparedStatement stmt = connection.prepareStatement("insert into unsigned_insert (ui32, ui64) values (?, ?)"); stmt.setObject(1, 4294967286L); stmt.setObject(2, new BigInteger("18446744073709551606")); stmt.execute(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select ui32, ui64 from test.unsigned_insert"); + ResultSet rs = select.executeQuery("select ui32, ui64 from unsigned_insert"); rs.next(); Object bigUInt32 = rs.getObject(1); Assert.assertTrue(bigUInt32 instanceof Long); @@ -174,18 +166,18 @@ public void testInsertUInt() throws SQLException { Assert.assertEquals(bigUInt64, new BigInteger("18446744073709551606")); } - @Test + @Test(groups = "integration") public void testInsertUUID() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.uuid_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS uuid_insert"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.uuid_insert (ui32 UInt32, uuid UUID) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS uuid_insert (ui32 UInt32, uuid UUID) ENGINE = TinyLog" ); - PreparedStatement stmt = connection.prepareStatement("insert into test.uuid_insert (ui32, uuid) values (?, ?)"); + PreparedStatement stmt = connection.prepareStatement("insert into uuid_insert (ui32, uuid) values (?, ?)"); stmt.setObject(1, Long.valueOf(4294967286L)); stmt.setObject(2, UUID.fromString("bef35f40-3b03-45b0-b1bd-8ec6593dcaaa")); stmt.execute(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select ui32, uuid from test.uuid_insert"); + ResultSet rs = select.executeQuery("select ui32, uuid from uuid_insert"); rs.next(); Object bigUInt32 = rs.getObject(1); Assert.assertTrue(bigUInt32 instanceof Long); @@ -195,19 +187,19 @@ public void testInsertUUID() throws SQLException { Assert.assertEquals(uuid, UUID.fromString("bef35f40-3b03-45b0-b1bd-8ec6593dcaaa")); } - @Test + @Test(groups = "integration") public void testInsertUUIDBatch() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.uuid_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS uuid_insert"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.uuid_insert (ui32 UInt32, uuid UUID) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS uuid_insert (ui32 UInt32, uuid UUID) ENGINE = TinyLog" ); - PreparedStatement stmt = connection.prepareStatement("insert into test.uuid_insert (ui32, uuid) values (?, ?)"); + PreparedStatement stmt = connection.prepareStatement("insert into uuid_insert (ui32, uuid) values (?, ?)"); stmt.setObject(1, 4294967286L); stmt.setObject(2, UUID.fromString("bef35f40-3b03-45b0-b1bd-8ec6593dcaaa")); stmt.addBatch(); stmt.executeBatch(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select ui32, uuid from test.uuid_insert"); + ResultSet rs = select.executeQuery("select ui32, uuid from uuid_insert"); rs.next(); Object bigUInt32 = rs.getObject(1); Assert.assertTrue(bigUInt32 instanceof Long); @@ -217,32 +209,32 @@ public void testInsertUUIDBatch() throws SQLException { Assert.assertEquals(uuid, UUID.fromString("bef35f40-3b03-45b0-b1bd-8ec6593dcaaa")); } - @Test + @Test(groups = "integration") public void testInsertStringContainsKeyword() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.keyword_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS keyword_insert"); connection.createStatement().execute( - "CREATE TABLE test.keyword_insert(a String,b String)ENGINE = MergeTree() ORDER BY a SETTINGS index_granularity = 8192" + "CREATE TABLE keyword_insert(a String,b String)ENGINE = MergeTree() ORDER BY a SETTINGS index_granularity = 8192" ); - PreparedStatement stmt = connection.prepareStatement("insert into test.keyword_insert(a,b) values('values(',',')"); + PreparedStatement stmt = connection.prepareStatement("insert into keyword_insert(a,b) values('values(',',')"); stmt.execute(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select * from test.keyword_insert"); + ResultSet rs = select.executeQuery("select * from keyword_insert"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getString(1), "values("); Assert.assertEquals(rs.getString(2), ","); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testInsertNullString() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.null_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS null_insert"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.null_insert (val Nullable(String)) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS null_insert (val Nullable(String)) ENGINE = TinyLog" ); - PreparedStatement stmt = connection.prepareStatement("insert into test.null_insert (val) values (?)"); + PreparedStatement stmt = connection.prepareStatement("insert into null_insert (val) values (?)"); stmt.setNull(1, Types.VARCHAR); stmt.execute(); stmt.setNull(1, Types.VARCHAR); @@ -262,21 +254,21 @@ public void testInsertNullString() throws SQLException { stmt.executeBatch(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select count(*), val from test.null_insert group by val"); + ResultSet rs = select.executeQuery("select count(*), val from null_insert group by val"); rs.next(); Assert.assertEquals(rs.getInt(1), 6); Assert.assertNull(rs.getString(2)); Assert.assertFalse(rs.next()); } - @Test + @Test(groups = "integration") public void testSelectNullableTypes() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.select_nullable"); + connection.createStatement().execute("DROP TABLE IF EXISTS select_nullable"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.select_nullable (idx Int32, i Nullable(Int32), ui Nullable(UInt64), f Nullable(Float32), s Nullable(String)) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS select_nullable (idx Int32, i Nullable(Int32), ui Nullable(UInt64), f Nullable(Float32), s Nullable(String)) ENGINE = TinyLog" ); - PreparedStatement stmt = connection.prepareStatement("insert into test.select_nullable (idx, i, ui, f, s) values (?, ?, ?, ?, ?)"); + PreparedStatement stmt = connection.prepareStatement("insert into select_nullable (idx, i, ui, f, s) values (?, ?, ?, ?, ?)"); stmt.setInt(1, 1); stmt.setObject(2, null); stmt.setObject(3, null); @@ -292,7 +284,7 @@ public void testSelectNullableTypes() throws SQLException { stmt.executeBatch(); Statement select = connection.createStatement(); - ResultSet rs = select.executeQuery("select i, ui, f, s from test.select_nullable order by idx"); + ResultSet rs = select.executeQuery("select i, ui, f, s from select_nullable order by idx"); rs.next(); Assert.assertEquals(rs.getMetaData().getColumnType(1), Types.INTEGER); Assert.assertEquals(rs.getMetaData().getColumnType(2), Types.BIGINT); @@ -322,17 +314,17 @@ public void testSelectNullableTypes() throws SQLException { } - @Test + @Test(groups = "integration") public void testInsertBatchNullValues() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.prep_nullable_value"); + "DROP TABLE IF EXISTS prep_nullable_value"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.prep_nullable_value " + "CREATE TABLE IF NOT EXISTS prep_nullable_value " + "(idx Int32, s Nullable(String), i Nullable(Int32), f Nullable(Float32)) " + "ENGINE = TinyLog" ); PreparedStatement stmt = connection.prepareStatement( - "INSERT INTO test.prep_nullable_value (idx, s, i, f) VALUES " + "INSERT INTO prep_nullable_value (idx, s, i, f) VALUES " + "(1, ?, ?, NULL), (2, NULL, NULL, ?)"); stmt.setString(1, "foo"); stmt.setInt(2, 42); @@ -342,7 +334,7 @@ public void testInsertBatchNullValues() throws Exception { Assert.assertEquals(updateCount.length, 2); ResultSet rs = connection.createStatement().executeQuery( - "SELECT s, i, f FROM test.prep_nullable_value " + "SELECT s, i, f FROM prep_nullable_value " + "ORDER BY idx ASC"); rs.next(); Assert.assertEquals(rs.getString(1), "foo"); @@ -354,7 +346,7 @@ public void testInsertBatchNullValues() throws Exception { Assert.assertEquals(rs.getFloat(3), 42.0f); } - @Test + @Test(groups = "integration") public void testSelectDouble() throws SQLException { Statement select = connection.createStatement(); ResultSet rs = select.executeQuery("select toFloat64(0.1) "); @@ -364,7 +356,7 @@ public void testSelectDouble() throws SQLException { Assert.assertEquals(rs.getDouble(1), 0.1); } - @Test + @Test(groups = "integration") public void testExecuteQueryClickhouseResponse() throws SQLException { ClickHousePreparedStatement sth = (ClickHousePreparedStatement) connection.prepareStatement("select ? limit 5"); sth.setObject(1, 314); @@ -372,7 +364,7 @@ public void testExecuteQueryClickhouseResponse() throws SQLException { Assert.assertEquals(resp.getData(), singletonList(singletonList("314"))); } - @Test + @Test(groups = "integration") public void clickhouseJdbcFailsBecauseOfCommentInStart() throws Exception { String sqlStatement = "/*comment*/ select * from system.numbers limit 3"; Statement stmt = connection.createStatement(); @@ -384,7 +376,7 @@ public void clickhouseJdbcFailsBecauseOfCommentInStart() throws Exception { } } - @Test + @Test(groups = "integration") public void testTrailingParameterOrderBy() throws Exception { String sqlStatement = "SELECT 42 AS foo, 23 AS bar from numbers(100) " @@ -396,7 +388,7 @@ public void testTrailingParameterOrderBy() throws Exception { Assert.assertTrue(rs.next()); } - @Test + @Test(groups = "integration") public void testSetTime() throws Exception { ClickHousePreparedStatement stmt = (ClickHousePreparedStatement) connection.prepareStatement("SELECT ?"); @@ -406,9 +398,9 @@ public void testSetTime() throws Exception { Assert.assertEquals(rs.getTime(1), Time.valueOf("13:37:42")); } - @Test + @Test(groups = "integration") public void testAsSql() throws Exception { - String unbindedStatement = "SELECT test.example WHERE id IN (?, ?)"; + String unbindedStatement = "SELECT example WHERE id IN (?, ?)"; ClickHousePreparedStatement statement = (ClickHousePreparedStatement) connection.prepareStatement(unbindedStatement); Assert.assertEquals(statement.asSql(), unbindedStatement); @@ -417,25 +409,25 @@ public void testAsSql() throws Exception { Assert.assertEquals(statement.asSql(), unbindedStatement); statement.setInt(2, 456); - Assert.assertEquals(statement.asSql(), "SELECT test.example WHERE id IN (123, 456)"); + Assert.assertEquals(statement.asSql(), "SELECT example WHERE id IN (123, 456)"); } - @Test + @Test(groups = "integration") public void testMetadataOnlySelect() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.mymetadata"); + "DROP TABLE IF EXISTS mymetadata"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.mymetadata " + "CREATE TABLE IF NOT EXISTS mymetadata " + "(idx Int32, s String) " + "ENGINE = TinyLog" ); PreparedStatement insertStmt = connection.prepareStatement( - "INSERT INTO test.mymetadata (idx, s) VALUES (?, ?)"); + "INSERT INTO mymetadata (idx, s) VALUES (?, ?)"); insertStmt.setInt(1, 42); insertStmt.setString(2, "foo"); insertStmt.executeUpdate(); PreparedStatement metaStmt = connection.prepareStatement( - "SELECT idx, s FROM test.mymetadata WHERE idx = ?"); + "SELECT idx, s FROM mymetadata WHERE idx = ?"); metaStmt.setInt(1, 42); ResultSetMetaData metadata = metaStmt.getMetaData(); Assert.assertEquals(metadata.getColumnCount(), 2); @@ -443,22 +435,22 @@ public void testMetadataOnlySelect() throws Exception { Assert.assertEquals(metadata.getColumnName(2), "s"); } - @Test + @Test(groups = "integration") public void testMetadataOnlySelectAfterExecution() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.mymetadata"); + "DROP TABLE IF EXISTS mymetadata"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.mymetadata " + "CREATE TABLE IF NOT EXISTS mymetadata " + "(idx Int32, s String) " + "ENGINE = TinyLog" ); PreparedStatement insertStmt = connection.prepareStatement( - "INSERT INTO test.mymetadata (idx, s) VALUES (?, ?)"); + "INSERT INTO mymetadata (idx, s) VALUES (?, ?)"); insertStmt.setInt(1, 42); insertStmt.setString(2, "foo"); insertStmt.executeUpdate(); PreparedStatement metaStmt = connection.prepareStatement( - "SELECT idx, s FROM test.mymetadata WHERE idx = ?"); + "SELECT idx, s FROM mymetadata WHERE idx = ?"); metaStmt.setInt(1, 42); metaStmt.executeQuery(); ResultSetMetaData metadata = metaStmt.getMetaData(); @@ -467,22 +459,22 @@ public void testMetadataOnlySelectAfterExecution() throws Exception { Assert.assertEquals(metadata.getColumnName(2), "s"); } - @Test + @Test(groups = "integration") public void testMetadataExecutionAfterMeta() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.mymetadata"); + "DROP TABLE IF EXISTS mymetadata"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.mymetadata " + "CREATE TABLE IF NOT EXISTS mymetadata " + "(idx Int32, s String) " + "ENGINE = TinyLog" ); PreparedStatement insertStmt = connection.prepareStatement( - "INSERT INTO test.mymetadata (idx, s) VALUES (?, ?)"); + "INSERT INTO mymetadata (idx, s) VALUES (?, ?)"); insertStmt.setInt(1, 42); insertStmt.setString(2, "foo"); insertStmt.executeUpdate(); PreparedStatement metaStmt = connection.prepareStatement( - "SELECT idx, s FROM test.mymetadata WHERE idx = ?"); + "SELECT idx, s FROM mymetadata WHERE idx = ?"); metaStmt.setInt(1, 42); ResultSetMetaData metadata = metaStmt.getMetaData(); Assert.assertEquals(metadata.getColumnCount(), 2); @@ -499,22 +491,22 @@ public void testMetadataExecutionAfterMeta() throws Exception { Assert.assertEquals(metadata.getColumnName(2), "s"); } - @Test + @Test(groups = "integration") public void testMetadataOnlyUpdate() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.mymetadata"); + "DROP TABLE IF EXISTS mymetadata"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.mymetadata " + "CREATE TABLE IF NOT EXISTS mymetadata " + "(idx Int32, s String) " + "ENGINE = TinyLog" ); PreparedStatement insertStmt = connection.prepareStatement( - "INSERT INTO test.mymetadata (idx, s) VALUES (?, ?)"); + "INSERT INTO mymetadata (idx, s) VALUES (?, ?)"); insertStmt.setInt(1, 42); insertStmt.setString(2, "foo"); insertStmt.executeUpdate(); PreparedStatement metaStmt = connection.prepareStatement( - "UPDATE test.mymetadata SET s = ? WHERE idx = ?"); + "UPDATE mymetadata SET s = ? WHERE idx = ?"); metaStmt.setString(1, "foo"); metaStmt.setInt(2, 42); ResultSetMetaData metadata = metaStmt.getMetaData(); @@ -522,16 +514,16 @@ public void testMetadataOnlyUpdate() throws Exception { metaStmt.close(); } - @Test + @Test(groups = "integration") public void testInsertWithFunctions() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.insertfunctions"); + "DROP TABLE IF EXISTS insertfunctions"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.insertfunctions " + "CREATE TABLE IF NOT EXISTS insertfunctions " + "(id UInt32, foo String, bar String) " + "ENGINE = TinyLog"); PreparedStatement stmt = connection.prepareStatement( - "INSERT INTO test.insertfunctions(id, foo, bar) VALUES " + "INSERT INTO insertfunctions(id, foo, bar) VALUES " + "(?, lower(reverse(?)), upper(reverse(?)))"); stmt.setInt(1, 42); stmt.setString(2, "Foo"); @@ -539,12 +531,12 @@ public void testInsertWithFunctions() throws Exception { String sql = stmt.unwrap(ClickHousePreparedStatementImpl.class).asSql(); Assert.assertEquals( sql, - "INSERT INTO test.insertfunctions(id, foo, bar) VALUES " + "INSERT INTO insertfunctions(id, foo, bar) VALUES " + "(42, lower(reverse('Foo')), upper(reverse('Bar')))"); // make sure that there is no exception stmt.execute(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT id, foo, bar FROM test.insertfunctions"); + "SELECT id, foo, bar FROM insertfunctions"); rs.next(); Assert.assertEquals(rs.getInt(1), 42); Assert.assertEquals(rs.getString(2), "oof"); @@ -554,19 +546,19 @@ public void testInsertWithFunctions() throws Exception { public void testBytes() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.strings_versus_bytes"); + "DROP TABLE IF EXISTS strings_versus_bytes"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.strings_versus_bytes" + "CREATE TABLE IF NOT EXISTS strings_versus_bytes" + "(s String, fs FixedString(8)) " + "ENGINE = TinyLog" ); PreparedStatement insertStmt = connection.prepareStatement( - "INSERT INTO test.strings_versus_bytes (s, fs) VALUES (?, ?)"); + "INSERT INTO strings_versus_bytes (s, fs) VALUES (?, ?)"); insertStmt.setBytes(1, "foo".getBytes(Charset.forName("UTF-8"))); insertStmt.setBytes(2, "bar".getBytes(Charset.forName("UTF-8"))); insertStmt.executeUpdate(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT s, fs FROM test.strings_versus_bytes"); + "SELECT s, fs FROM strings_versus_bytes"); rs.next(); Assert.assertEquals(rs.getString(1), "foo"); // TODO: The actual String returned by our ResultSet is rather strange @@ -574,16 +566,16 @@ public void testBytes() throws Exception { Assert.assertEquals(rs.getString(2).trim(), "bar"); } - @Test + @Test(groups = "integration") public void testInsertWithFunctionsAddBatch() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.insertfunctions"); + "DROP TABLE IF EXISTS insertfunctions"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.insertfunctions " + "CREATE TABLE IF NOT EXISTS insertfunctions " + "(id UInt32, foo String, bar String) " + "ENGINE = TinyLog"); PreparedStatement stmt = connection.prepareStatement( - "INSERT INTO test.insertfunctions(id, foo, bar) VALUES " + "INSERT INTO insertfunctions(id, foo, bar) VALUES " + "(?, lower(reverse(?)), upper(reverse(?)))"); stmt.setInt(1, 42); stmt.setString(2, "Foo"); @@ -595,17 +587,17 @@ public void testInsertWithFunctionsAddBatch() throws Exception { } @SuppressWarnings("boxing") - @Test + @Test(groups = "integration") public void testMultiLineValues() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.multiline"); + "DROP TABLE IF EXISTS multiline"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.multiline" + "CREATE TABLE IF NOT EXISTS multiline" + "(foo Int32, bar String) " + "ENGINE = TinyLog" ); PreparedStatement insertStmt = connection.prepareStatement( - "INSERT INTO test.multiline\n" + "INSERT INTO multiline\n" + "\t(foo, bar)\r\n" + "\t\tVALUES\n" + "(?, ?) , \n\r" @@ -622,7 +614,7 @@ public void testMultiLineValues() throws Exception { insertStmt.executeUpdate(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT * FROM test.multiline ORDER BY foo"); + "SELECT * FROM multiline ORDER BY foo"); rs.next(); Assert.assertEquals(rs.getInt(1), 23); Assert.assertEquals(rs.getString(2), "baz"); @@ -638,14 +630,14 @@ public void testMultiLineValues() throws Exception { // Issue 153 public void testArrayDateTime() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.date_time_array"); + "DROP TABLE IF EXISTS date_time_array"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.date_time_array" + "CREATE TABLE IF NOT EXISTS date_time_array" + "(foo Array(DateTime)) " + "ENGINE = TinyLog" ); PreparedStatement stmt = connection.prepareStatement( - "INSERT INTO test.date_time_array (foo) VALUES (?)"); + "INSERT INTO date_time_array (foo) VALUES (?)"); stmt.setArray(1, connection.createArrayOf("DateTime", new Timestamp[] { new Timestamp(1557136800000L), @@ -654,28 +646,28 @@ public void testArrayDateTime() throws Exception { stmt.execute(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT foo FROM test.date_time_array"); + "SELECT foo FROM date_time_array"); rs.next(); Timestamp[] result = (Timestamp[]) rs.getArray(1).getArray(); Assert.assertEquals(result[0].getTime(), 1557136800000L); Assert.assertEquals(result[1].getTime(), 1560698526598L); } - @Test + @Test(groups = "integration") public void testStaticNullValue() throws Exception { connection.createStatement().execute( - "DROP TABLE IF EXISTS test.static_null_value"); + "DROP TABLE IF EXISTS static_null_value"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.static_null_value" + "CREATE TABLE IF NOT EXISTS static_null_value" + "(foo Nullable(String), bar Nullable(String)) " + "ENGINE = TinyLog" ); PreparedStatement ps0 = connection.prepareStatement( - "INSERT INTO test.static_null_value(foo) VALUES (null)"); + "INSERT INTO static_null_value(foo) VALUES (null)"); ps0.executeUpdate(); ps0 = connection.prepareStatement( - "INSERT INTO test.static_null_value(foo, bar) VALUES (null, ?)"); + "INSERT INTO static_null_value(foo, bar) VALUES (null, ?)"); ps0.setNull(1, Types.VARCHAR); ps0.executeUpdate(); } @@ -696,10 +688,10 @@ public void testTernaryOperator() throws Exception { } } - @Test + @Test(groups = "integration") public void testBatchProcess() throws Exception { try (PreparedStatement s = connection.prepareStatement( - "create table if not exists test.batch_update(k UInt8, v String) engine=MergeTree order by k")) { + "create table if not exists batch_update(k UInt8, v String) engine=MergeTree order by k")) { s.execute(); } @@ -710,7 +702,7 @@ public void testBatchProcess() throws Exception { }; // insert - try (PreparedStatement s = connection.prepareStatement("insert into table test.batch_update values(?,?)")) { + try (PreparedStatement s = connection.prepareStatement("insert into table batch_update values(?,?)")) { for (int i = 0; i < data.length; i++) { Object[] row = data[i]; s.setInt(1, (int) row[0]); @@ -724,7 +716,7 @@ public void testBatchProcess() throws Exception { // select try (PreparedStatement s = connection.prepareStatement( - "select * from test.batch_update where k in (?, ?) order by k, v")) { + "select * from batch_update where k in (?, ?) order by k, v")) { s.setInt(1, 1); s.setInt(2, 3); ResultSet rs = s.executeQuery(); @@ -739,7 +731,7 @@ public void testBatchProcess() throws Exception { // update try (PreparedStatement s = connection.prepareStatement( - "alter table test.batch_update update v = ? where k = ?")) { + "alter table batch_update update v = ? where k = ?")) { s.setString(1, "x"); s.setInt(2, 1); s.addBatch(); @@ -752,7 +744,7 @@ public void testBatchProcess() throws Exception { } // delete - try (PreparedStatement s = connection.prepareStatement("alter table test.batch_update delete where k = ?")) { + try (PreparedStatement s = connection.prepareStatement("alter table batch_update delete where k = ?")) { s.setInt(1, 1); s.addBatch(); s.setInt(1, 3); @@ -762,7 +754,7 @@ public void testBatchProcess() throws Exception { assertEquals(results.length, 2); } - try (PreparedStatement s = connection.prepareStatement("drop table if exists test.batch_update")) { + try (PreparedStatement s = connection.prepareStatement("drop table if exists batch_update")) { s.execute(); } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickhouseLZ4StreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickhouseLZ4StreamTest.java index 03b41d54a..6ddf3c21b 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickhouseLZ4StreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickhouseLZ4StreamTest.java @@ -4,38 +4,41 @@ import java.sql.ResultSet; import java.sql.SQLException; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.settings.ClickHouseProperties; -public class ClickhouseLZ4StreamTest { - private ClickHouseDataSource dataSource; +public class ClickhouseLZ4StreamTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { ClickHouseProperties properties = new ClickHouseProperties(); properties.setDecompress(true); - dataSource = ClickHouseContainerForTest.newDataSource(properties); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + + connection = newConnection(properties); } - @Test + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); + } + + @Test(groups = "integration") public void testBigBatchCompressedInsert() throws SQLException { if ("21.3.3.14".equals(connection.getServerVersion())) { return; } - connection.createStatement().execute("DROP TABLE IF EXISTS test.big_batch_insert"); + connection.createStatement().execute("DROP TABLE IF EXISTS big_batch_insert"); connection.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.big_batch_insert (i Int32, s String) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS big_batch_insert (i Int32, s String) ENGINE = TinyLog" ); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.big_batch_insert (s, i) VALUES (?, ?)"); + PreparedStatement statement = connection.prepareStatement("INSERT INTO big_batch_insert (s, i) VALUES (?, ?)"); int cnt = 1000000; for (int i = 0; i < cnt; i++) { @@ -46,7 +49,7 @@ public void testBigBatchCompressedInsert() throws SQLException { statement.executeBatch(); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from test.big_batch_insert"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() as cnt from big_batch_insert"); rs.next(); Assert.assertEquals(rs.getInt("cnt"), cnt); Assert.assertFalse(rs.next()); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ErrorsTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ErrorsTest.java index 1e8b80a06..163730bfe 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ErrorsTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ErrorsTest.java @@ -1,14 +1,16 @@ package ru.yandex.clickhouse.integration; import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import ru.yandex.clickhouse.ClickHouseContainerForTest; +import ru.yandex.clickhouse.ClickHouseConnection; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.except.ClickHouseException; import ru.yandex.clickhouse.settings.ClickHouseProperties; import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; - -import javax.sql.DataSource; +import com.clickhouse.client.ClickHouseServerForTest; import java.sql.Connection; import java.sql.Date; @@ -18,17 +20,26 @@ import java.util.ArrayList; import java.util.List; -public class ErrorsTest { +public class ErrorsTest extends JdbcIntegrationTest { + private ClickHouseConnection connection; + + @BeforeClass(groups = "integration") + public void setUp() throws Exception { + connection = newConnection(); + } + + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); + } - @Test + @Test(groups = "integration") public void testWrongUser() { ClickHouseProperties properties = new ClickHouseProperties(); properties.setUser("not_existing"); - DataSource dataSource = ClickHouseContainerForTest.newDataSource(properties); - try { - Connection connection = dataSource.getConnection(); + try (Connection connection = newConnection(properties)) { } catch (Exception e) { - String version = ClickHouseContainerForTest.getClickHouseVersion(); + String version = ClickHouseServerForTest.getClickHouseVersion(); if (!version.isEmpty() && ClickHouseVersionNumberUtil.getMajorVersion(version) <= 19) { Assert.assertEquals((getClickhouseException(e)).getErrorCode(), 192); } else { @@ -39,36 +50,42 @@ public void testWrongUser() { Assert.assertTrue(false, "didn' find correct error"); } - @Test(expectedExceptions = ClickHouseException.class) + @Test(groups = "integration", expectedExceptions = ClickHouseException.class) public void testTableNotExists() throws SQLException { - DataSource dataSource = ClickHouseContainerForTest.newDataSource(); - Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement(); - statement.execute("select * from table_not_exists"); + try (Statement statement = connection.createStatement()) { + statement.execute("select * from table_not_exists"); + } } - @Test + @Test(groups = "integration") public void testErrorDecompression() throws Exception { ClickHouseProperties properties = new ClickHouseProperties(); properties.setCompress(true); - String[] address = ClickHouseContainerForTest.getClickHouseHttpAddress().split(":"); - DataSource dataSource = ClickHouseContainerForTest.newDataSource(properties); - Connection connection = dataSource.getConnection(); + String[] address = getClickHouseHttpAddress().split(":"); - connection.createStatement().execute("DROP TABLE IF EXISTS test.table_not_exists"); + try (Connection connection = newConnection(properties)) { + connection.createStatement().execute("DROP TABLE IF EXISTS table_not_exists"); - PreparedStatement statement = connection.prepareStatement("INSERT INTO test.table_not_exists (d, s) VALUES (?, ?)"); + PreparedStatement statement = connection + .prepareStatement("INSERT INTO table_not_exists (d, s) VALUES (?, ?)"); - statement.setDate(1, new Date(System.currentTimeMillis())); - statement.setInt(2, 1); - try { - statement.executeBatch(); - } catch (Exception e) { - String exceptionMsg = getClickhouseException(e).getMessage(); - Assert.assertTrue(exceptionMsg.startsWith("ClickHouse exception, code: 60, host: " + address[0] +", port: " + address[1] +"; Code: 60, e.displayText() = DB::Exception: Table test.table_not_exists doesn't exist"), exceptionMsg); - return; + statement.setDate(1, new Date(System.currentTimeMillis())); + statement.setInt(2, 1); + try { + statement.executeBatch(); + } catch (Exception e) { + String exceptionMsg = getClickhouseException(e).getMessage(); + Assert.assertTrue( + exceptionMsg + .startsWith("ClickHouse exception, code: 60, host: " + address[0] + ", port: " + + address[1] + "; Code: 60") + && exceptionMsg.contains( + " DB::Exception: Table " + dbName + ".table_not_exists doesn't exist"), + exceptionMsg); + return; + } + Assert.assertTrue(false, "didn' find correct error"); } - Assert.assertTrue(false, "didn' find correct error"); } private static ClickHouseException getClickhouseException(Throwable t) { @@ -91,13 +108,13 @@ private static ClickHouseException getClickhouseException(Throwable t) { } advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration } - + for (Throwable throwable : causes) { if (throwable instanceof ClickHouseException) { return (ClickHouseException) throwable; } } - + throw new IllegalArgumentException("no ClickHouseException found"); } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/NativeStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/NativeStreamTest.java index 84dc4892a..0f6d460c6 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/NativeStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/NativeStreamTest.java @@ -1,12 +1,12 @@ package ru.yandex.clickhouse.integration; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHouseStatement; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.util.ClickHouseRowBinaryStream; import ru.yandex.clickhouse.util.ClickHouseStreamCallback; @@ -16,24 +16,25 @@ import static org.testng.Assert.assertEquals; -public class NativeStreamTest { - - private ClickHouseDataSource dataSource; +public class NativeStreamTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); + } + + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); } - @Test + @Test(groups = "integration") public void testLowCardinality() throws Exception{ final ClickHouseStatement statement = connection.createStatement(); - connection.createStatement().execute("DROP TABLE IF EXISTS test.low_cardinality"); + connection.createStatement().execute("DROP TABLE IF EXISTS low_cardinality"); connection.createStatement().execute( - "CREATE TABLE test.low_cardinality (date Date, " + + "CREATE TABLE low_cardinality (date Date, " + "lowCardinality LowCardinality(String), " + "string String," + "fixedString FixedString(3)," + @@ -49,7 +50,7 @@ public void testLowCardinality() throws Exception{ final Date date1 = new Date(1497474018000L); statement.sendNativeStream( - "INSERT INTO test.low_cardinality (date, lowCardinality, string, fixedString, fixedStringLC)", + "INSERT INTO low_cardinality (date, lowCardinality, string, fixedString, fixedStringLC)", new ClickHouseStreamCallback() { @Override public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { @@ -79,7 +80,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } ); - ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM test.low_cardinality"); + ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM low_cardinality"); Assert.assertTrue(rs.next()); assertEquals(rs.getString("lowCardinality"), "string"); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/OnTime.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/OnTime.java index 0033c3053..c8cd658fa 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/OnTime.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/OnTime.java @@ -1,10 +1,10 @@ package ru.yandex.clickhouse.integration; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import java.sql.Connection; import java.sql.ResultSet; @@ -15,19 +15,19 @@ * Here it is assumed the connection to a ClickHouse instance with flights example data it available at localhost:8123 * For ClickHouse quickstart and example dataset see https://clickhouse.yandex/tutorial.html */ -public class OnTime { +public class OnTime extends JdbcIntegrationTest { private ClickHouseDataSource dataSource; private Connection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); + dataSource = newDataSource(); connection = dataSource.getConnection(); connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); } - @Test(enabled = false) + @Test(groups = "integration", enabled = false) public void simpleSelect() throws SQLException { Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("select FlightDate, AirlineID, FlightNum from ontime limit 10"); @@ -37,7 +37,7 @@ public void simpleSelect() throws SQLException { statement.close(); } - @Test(enabled = false) + @Test(groups = "integration", enabled = false) public void mostTrendingDestinationTest() throws SQLException { String query = "SELECT \n" + diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ResultSummaryTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ResultSummaryTest.java index 79ad4661a..0b8066228 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ResultSummaryTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ResultSummaryTest.java @@ -1,7 +1,7 @@ package ru.yandex.clickhouse.integration; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.*; import ru.yandex.clickhouse.settings.ClickHouseQueryParam; @@ -15,21 +15,20 @@ import static org.testng.Assert.assertNull; import static org.testng.AssertJUnit.assertTrue; -public class ResultSummaryTest { +public class ResultSummaryTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - connection = ClickHouseContainerForTest.newDataSource().getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { - connection.createStatement().execute("DROP DATABASE IF EXISTS test"); + closeConnection(connection); } - @Test + @Test(groups = "integration") public void select() throws Exception { ClickHouseStatement st = connection.createStatement(); st.executeQuery("SELECT * FROM numbers(10)", Collections.singletonMap(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, "true")); @@ -38,7 +37,7 @@ public void select() throws Exception { assertTrue(st.getResponseSummary().getReadBytes() > 0); } - @Test + @Test(groups = "integration") public void largeSelect() throws Exception { ClickHouseStatement st = connection.createStatement(); st.executeQuery("SELECT * FROM numbers(10000000)", Collections.singletonMap(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, "true")); @@ -47,7 +46,7 @@ public void largeSelect() throws Exception { assertTrue(st.getResponseSummary().getReadBytes() > 0); } - @Test + @Test(groups = "integration") public void largeSelectWaitEndOfQuery() throws Exception { ClickHouseStatement st = connection.createStatement(); st.executeQuery("SELECT * FROM numbers(10000000)", largeSelectWaitEndOfQueryParams()); @@ -63,7 +62,7 @@ private Map largeSelectWaitEndOfQueryParams() { return res; } - @Test + @Test(groups = "integration") public void selectWithoutParam() throws Exception { ClickHouseStatement st = connection.createStatement(); st.executeQuery("SELECT * FROM numbers(10)", Collections.singletonMap(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, "true")); @@ -72,11 +71,11 @@ public void selectWithoutParam() throws Exception { assertTrue(st.getResponseSummary().getReadBytes() > 0); } - @Test + @Test(groups = "integration") public void insertSingle() throws Exception { createInsertTestTable(); - ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO test.insert_test VALUES(?)"); + ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO insert_test VALUES(?)"); ps.setLong(1, 1); ps.executeQuery(Collections.singletonMap(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, "true")); @@ -84,11 +83,11 @@ public void insertSingle() throws Exception { assertTrue(ps.getResponseSummary().getWrittenBytes() > 0); } - @Test + @Test(groups = "integration") public void insertBatch() throws Exception { createInsertTestTable(); - ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO test.insert_test VALUES(?)"); + ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO insert_test VALUES(?)"); for (long i = 0; i < 10; i++) { ps.setLong(1, i); ps.addBatch(); @@ -99,29 +98,29 @@ public void insertBatch() throws Exception { assertTrue(ps.getResponseSummary().getWrittenBytes() > 0); } - @Test + @Test(groups = "integration") public void insertSelect() throws Exception { createInsertTestTable(); - ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO test.insert_test SELECT number FROM numbers(10)"); + ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO insert_test SELECT number FROM numbers(10)"); ps.executeQuery(Collections.singletonMap(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, "true")); assertEquals(ps.getResponseSummary().getWrittenRows(), 10); assertTrue(ps.getResponseSummary().getWrittenBytes() > 0); } - @Test + @Test(groups = "integration") public void insertLargeSelect() throws Exception { createInsertTestTable(); - ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO test.insert_test SELECT number FROM numbers(10000000)"); + ClickHousePreparedStatement ps = (ClickHousePreparedStatement) connection.prepareStatement("INSERT INTO insert_test SELECT number FROM numbers(10000000)"); ps.executeQuery(Collections.singletonMap(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, "true")); assertEquals(ps.getResponseSummary().getWrittenRows(), 10000000); assertTrue(ps.getResponseSummary().getWrittenBytes() > 0); } - @Test + @Test(groups = "integration") public void noSummary() throws Exception { ClickHouseStatement st = connection.createStatement(); st.executeQuery("SELECT * FROM numbers(10)"); @@ -130,7 +129,7 @@ public void noSummary() throws Exception { } private void createInsertTestTable() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.insert_test"); - connection.createStatement().execute("CREATE TABLE IF NOT EXISTS test.insert_test (value UInt32) ENGINE = TinyLog"); + connection.createStatement().execute("DROP TABLE IF EXISTS insert_test"); + connection.createStatement().execute("CREATE TABLE IF NOT EXISTS insert_test (value UInt32) ENGINE = TinyLog"); } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/RowBinaryStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/RowBinaryStreamTest.java index bc4d490c5..1af34d107 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/RowBinaryStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/RowBinaryStreamTest.java @@ -24,12 +24,12 @@ import org.roaringbitmap.longlong.Roaring64Bitmap; import org.roaringbitmap.longlong.Roaring64NavigableMap; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; import ru.yandex.clickhouse.ClickHouseStatement; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.domain.ClickHouseDataType; import ru.yandex.clickhouse.util.ClickHouseBitmap; import ru.yandex.clickhouse.util.ClickHouseRowBinaryInputStream; @@ -40,16 +40,17 @@ /** * @author Dmitry Andreev */ -public class RowBinaryStreamTest { - - private ClickHouseDataSource dataSource; +public class RowBinaryStreamTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); + } + + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); } private void createTable(String table) throws SQLException { @@ -89,18 +90,18 @@ private void createTable(String table) throws SQLException { ); } - @Test + @Test(groups = "integration") public void multiRowTest() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.big_data"); + connection.createStatement().execute("DROP TABLE IF EXISTS big_data"); connection.createStatement().execute( - "CREATE TABLE test.big_data (value Int32) ENGINE = TinyLog()" + "CREATE TABLE big_data (value Int32) ENGINE = TinyLog()" ); final int count = 1000000; final AtomicLong sum = new AtomicLong(); connection.createStatement().sendRowBinaryStream( - "INSERT INTO test.big_data (value)", + "INSERT INTO big_data (value)", new ClickHouseStreamCallback() { @Override public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { @@ -112,7 +113,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } ); - ResultSet rs = connection.createStatement().executeQuery("SELECT count() AS cnt, sum(value) AS sum FROM test.big_data"); + ResultSet rs = connection.createStatement().executeQuery("SELECT count() AS cnt, sum(value) AS sum FROM big_data"); Assert.assertTrue(rs.next()); assertEquals(rs.getInt("cnt"), count); @@ -120,12 +121,12 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } - @Test + @Test(groups = "integration") public void testRowBinaryStream() throws Exception { testRowBinaryStream(false); } - @Test + @Test(groups = "integration") public void testRowBinaryInputStream() throws Exception { testRowBinaryStream(true); } @@ -255,7 +256,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } } - @Test + @Test(groups = "integration") public void testBitmap() throws Exception { // TODO seems Int8, Int16 and Int32 are still not supported in ClickHouse testBitmap(ClickHouseDataType.UInt8, 32); @@ -277,11 +278,11 @@ public void testBitmap() throws Exception { } } - @Test + @Test(groups = "integration") public void testBigDecimals() throws Exception { try (ClickHouseStatement statement = connection.createStatement()) { statement.execute("set allow_experimental_bigint_types=1;" - + "create table if not exists test.test_big_decimals(d128 Decimal128(6), d256 Decimal256(12)) engine=Memory"); + + "create table if not exists test_big_decimals(d128 Decimal128(6), d256 Decimal256(12)) engine=Memory"); } catch (SQLException e) { return; } @@ -292,7 +293,7 @@ public void testBigDecimals() throws Exception { BigDecimal.ZERO, BigDecimal.valueOf(123.123456789D) }; - statement.sendRowBinaryStream("insert into table test.test_big_decimals", new ClickHouseStreamCallback() { + statement.sendRowBinaryStream("insert into table test_big_decimals", new ClickHouseStreamCallback() { @Override public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { for (int i = 0; i < values.length; i++) { @@ -302,7 +303,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } }); - try (ResultSet rs = statement.executeQuery("select * from test.test_big_decimals order by d128")) { + try (ResultSet rs = statement.executeQuery("select * from test_big_decimals order by d128")) { int rowCounter = 0; while (rs.next()) { rowCounter++; @@ -313,12 +314,12 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { assertEquals(rowCounter, values.length); } - statement.execute("drop table if exists test.test_big_decimals"); + statement.execute("drop table if exists test_big_decimals"); } } private void testRowBinaryStream(boolean rowBinaryResult) throws Exception { - createTable("test.raw_binary"); + createTable("raw_binary"); ClickHouseStatement statement = connection.createStatement(); final long timestamp = 1483230102000L; //2017-01-01 03:21:42 final Date date1 = new Date(timestamp); @@ -340,7 +341,7 @@ private void testRowBinaryStream(boolean rowBinaryResult) throws Exception { final UUID uuid2 = UUID.fromString("789e0123-e89b-12d3-a456-426655444444"); statement.sendRowBinaryStream( - "INSERT INTO test.raw_binary " + + "INSERT INTO raw_binary " + "(date, dateTime, string, int8, uInt8, int16, uInt16, int32, uInt32, int64, uInt64, float32, " + "float64, dateArray, dateTimeArray, stringArray, int8Array, uInt8Array, int16Array, uInt16Array, " + "int32Array, uInt32Array, int64Array, uInt64Array, float32Array, float64Array, uuid, lowCardinality, " + @@ -413,7 +414,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { ); if (!rowBinaryResult) { - ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM test.raw_binary ORDER BY date"); + ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM raw_binary ORDER BY date"); Assert.assertTrue(rs.next()); assertEquals(rs.getString("date"), @@ -482,7 +483,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { Assert.assertFalse(rs.next()); } else { ClickHouseRowBinaryInputStream is = connection.createStatement().executeQueryClickhouseRowBinaryStream( - "SELECT * FROM test.raw_binary ORDER BY date"); + "SELECT * FROM raw_binary ORDER BY date"); assertEquals(is.readDate(), withTimeAtStartOfDay(date1)); assertEquals(is.readDateTime(), new Timestamp(timestamp)); @@ -572,19 +573,19 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } - @Test + @Test(groups = "integration") public void testTimeZone() throws Exception{ final ClickHouseStatement statement = connection.createStatement(); - connection.createStatement().execute("DROP TABLE IF EXISTS test.binary_tz"); + connection.createStatement().execute("DROP TABLE IF EXISTS binary_tz"); connection.createStatement().execute( - "CREATE TABLE test.binary_tz (date Date, dateTime DateTime) ENGINE = MergeTree(date, (date), 8192)" + "CREATE TABLE binary_tz (date Date, dateTime DateTime) ENGINE = MergeTree(date, (date), 8192)" ); // final Date date1 = new Date(1497474018000L); statement.sendRowBinaryStream( - "INSERT INTO test.binary_tz (date, dateTime)", + "INSERT INTO binary_tz (date, dateTime)", new ClickHouseStreamCallback() { @Override public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { @@ -595,7 +596,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { ); ResultSet rs = connection.createStatement().executeQuery( - "SELECT date, dateTime from test.binary_tz" + "SELECT date, dateTime from binary_tz" ); Assert.assertTrue(rs.next()); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/StreamSQLTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/StreamSQLTest.java index ea207acb3..73d01100e 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/StreamSQLTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/StreamSQLTest.java @@ -16,19 +16,19 @@ import java.time.format.DateTimeFormatter; import java.util.zip.GZIPOutputStream; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.domain.ClickHouseCompression; import ru.yandex.clickhouse.domain.ClickHouseFormat; +import ru.yandex.clickhouse.util.ClickHouseVersionNumberUtil; -public class StreamSQLTest { +public class StreamSQLTest extends JdbcIntegrationTest { private static final DateTimeFormatter DATE_TIME_FORMATTER_TZ = DateTimeFormatter.ofPattern("yyyy-MM-dd['T'][ ]HH:mm:ss[.SSS][XXX]"); - private ClickHouseDataSource dataSource; private ClickHouseConnection connection; private Timestamp utcToServerTimezone(String datetime) { @@ -36,18 +36,21 @@ private Timestamp utcToServerTimezone(String datetime) { .atZone(ZoneId.of("UTC")).withZoneSameInstant(connection.getTimeZone().toZoneId()).toInstant()); } - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); } - @Test + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); + } + + @Test(groups = "integration") public void simpleCSVInsert() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.csv_stream_sql"); + connection.createStatement().execute("DROP TABLE IF EXISTS csv_stream_sql"); connection.createStatement().execute( - "CREATE TABLE test.csv_stream_sql (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE csv_stream_sql (value Int32, string_value String) ENGINE = Log()" ); String string = "5,6\n1,6"; @@ -55,12 +58,12 @@ public void simpleCSVInsert() throws SQLException { connection.createStatement() .write() - .sql("insert into test.csv_stream_sql format CSV") + .sql("insert into csv_stream_sql format CSV") .data(inputStream) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.csv_stream_sql"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM csv_stream_sql"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertEquals(rs.getLong("sum"), 6); @@ -92,23 +95,23 @@ public int read() { }; } - @Test + @Test(groups = "integration") public void multiRowTSVInsert() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.tsv_stream_sql"); + connection.createStatement().execute("DROP TABLE IF EXISTS tsv_stream_sql"); connection.createStatement().execute( - "CREATE TABLE test.tsv_stream_sql (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE tsv_stream_sql (value Int32, string_value String) ENGINE = Log()" ); final int rowsCount = 100000; connection.createStatement(). write() - .sql("insert into test.tsv_stream_sql format TSV") + .sql("insert into tsv_stream_sql format TSV") .data(getTSVStream(rowsCount), ClickHouseFormat.TSV) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.tsv_stream_sql"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM tsv_stream_sql"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), rowsCount); Assert.assertEquals(rs.getInt("sum"), rowsCount); @@ -131,11 +134,11 @@ private InputStream gzStream( InputStream is ) throws IOException return new ByteArrayInputStream( os.toByteArray() ); } - @Test + @Test(groups = "integration") public void multiRowTSVInsertCompressed() throws SQLException, IOException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.tsv_compressed_stream_sql"); + connection.createStatement().execute("DROP TABLE IF EXISTS tsv_compressed_stream_sql"); connection.createStatement().execute( - "CREATE TABLE test.tsv_compressed_stream_sql (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE tsv_compressed_stream_sql (value Int32, string_value String) ENGINE = Log()" ); final int rowsCount = 100000; @@ -143,23 +146,23 @@ public void multiRowTSVInsertCompressed() throws SQLException, IOException { InputStream gz = gzStream(getTSVStream(rowsCount)); connection.createStatement() .write() - .sql("insert into test.tsv_compressed_stream_sql format TSV") + .sql("insert into tsv_compressed_stream_sql format TSV") .data(gz, ClickHouseFormat.TSV, ClickHouseCompression.gzip) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.tsv_compressed_stream_sql"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM tsv_compressed_stream_sql"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), rowsCount); Assert.assertEquals(rs.getInt("sum"), rowsCount); Assert.assertEquals(rs.getInt("uniq"), rowsCount); } - @Test + @Test(groups = "integration") public void multiRowTSVInsertNotCompressed() throws SQLException, IOException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.tsv_not_compressed_stream_sql"); + connection.createStatement().execute("DROP TABLE IF EXISTS tsv_not_compressed_stream_sql"); connection.createStatement().execute( - "CREATE TABLE test.tsv_not_compressed_stream_sql (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE tsv_not_compressed_stream_sql (value Int32, string_value String) ENGINE = Log()" ); final int rowsCount = 100000; @@ -167,12 +170,12 @@ public void multiRowTSVInsertNotCompressed() throws SQLException, IOException { InputStream in = getTSVStream(rowsCount); connection.createStatement() .write() - .sql("insert into test.tsv_not_compressed_stream_sql format TSV") + .sql("insert into tsv_not_compressed_stream_sql format TSV") .data(in, ClickHouseFormat.TSV, ClickHouseCompression.none) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.tsv_not_compressed_stream_sql"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM tsv_not_compressed_stream_sql"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), rowsCount); Assert.assertEquals(rs.getInt("sum"), rowsCount); @@ -180,11 +183,11 @@ public void multiRowTSVInsertNotCompressed() throws SQLException, IOException { } - @Test + @Test(groups = "integration") public void JSONEachRowInsert() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.json_stream_sql"); + connection.createStatement().execute("DROP TABLE IF EXISTS json_stream_sql"); connection.createStatement().execute( - "CREATE TABLE test.json_stream_sql (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE json_stream_sql (value Int32, string_value String) ENGINE = Log()" ); String string = "{\"value\":5,\"string_value\":\"6\"}\n{\"value\":1,\"string_value\":\"6\"}"; @@ -192,24 +195,24 @@ public void JSONEachRowInsert() throws SQLException { connection.createStatement(). write() - .sql("insert into test.json_stream_sql") + .sql("insert into json_stream_sql") .data(inputStream, ClickHouseFormat.JSONEachRow) .data(inputStream) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.json_stream_sql"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM json_stream_sql"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertEquals(rs.getLong("sum"), 6); Assert.assertEquals(rs.getLong("uniq"), 1); } - @Test + @Test(groups = "integration") public void JSONEachRowCompressedInsert() throws SQLException, IOException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.json_comressed_stream_sql"); + connection.createStatement().execute("DROP TABLE IF EXISTS json_comressed_stream_sql"); connection.createStatement().execute( - "CREATE TABLE test.json_comressed_stream_sql (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE json_comressed_stream_sql (value Int32, string_value String) ENGINE = Log()" ); String string = "{\"value\":5,\"string_value\":\"6\"}\n{\"value\":1,\"string_value\":\"6\"}"; @@ -217,59 +220,59 @@ public void JSONEachRowCompressedInsert() throws SQLException, IOException { connection.createStatement(). write() - .sql("insert into test.json_comressed_stream_sql") + .sql("insert into json_comressed_stream_sql") .data(inputStream, ClickHouseFormat.JSONEachRow) .data(gzStream(inputStream)) .dataCompression(ClickHouseCompression.gzip) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.json_comressed_stream_sql"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM json_comressed_stream_sql"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertEquals(rs.getLong("sum"), 6); Assert.assertEquals(rs.getLong("uniq"), 1); } - @Test + @Test(groups = "integration") public void CSVInsertCompressedIntoTable() throws SQLException, IOException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.csv_stream_compressed"); + connection.createStatement().execute("DROP TABLE IF EXISTS csv_stream_compressed"); connection.createStatement().execute( - "CREATE TABLE test.csv_stream_compressed (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE csv_stream_compressed (value Int32, string_value String) ENGINE = Log()" ); String string = "5,6\n1,6"; InputStream inputStream = new ByteArrayInputStream(string.getBytes(Charset.forName("UTF-8"))); - connection.createStatement(). - write() - .table("test.csv_stream_compressed") + connection.createStatement() + .write() + .table(dbName + ".csv_stream_compressed") .format(ClickHouseFormat.CSV) .dataCompression(ClickHouseCompression.gzip) .data(gzStream(inputStream)) .send(); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.csv_stream_compressed"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM csv_stream_compressed"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertEquals(rs.getLong("sum"), 6); Assert.assertEquals(rs.getLong("uniq"), 1); } - @Test + @Test(groups = "integration") public void ORCInsertCompressedIntoTable() throws SQLException { // clickhouse-client -q "select number int, toString(number) str, 1/number flt, toDecimal64( 1/(number+1) , 9) dcml, // toDateTime('2020-01-01 00:00:00') + number time from numbers(100) format ORC"|gzip > test_sample.orc.gz String version = connection.getServerVersion(); - if (version.compareTo("20.8") < 0) { + if (ClickHouseVersionNumberUtil.compare(version, "20.8") < 0) { return; } - connection.createStatement().execute("DROP TABLE IF EXISTS test.orc_stream_compressed"); + connection.createStatement().execute("DROP TABLE IF EXISTS orc_stream_compressed"); connection.createStatement().execute( - "CREATE TABLE test.orc_stream_compressed (int Int64, str String, flt Float64, " + + "CREATE TABLE orc_stream_compressed (int Int64, str String, flt Float64, " + "dcml Decimal64(9), time DateTime) ENGINE = Log();" ); @@ -277,7 +280,7 @@ public void ORCInsertCompressedIntoTable() throws SQLException { connection.createStatement(). write() - .table("test.orc_stream_compressed") + .table(dbName + ".orc_stream_compressed") .format(ClickHouseFormat.ORC) .dataCompression(ClickHouseCompression.gzip) .data(inputStream) @@ -291,18 +294,18 @@ public void ORCInsertCompressedIntoTable() throws SQLException { "max(dcml) max_dcml, " + "min(time) min_time, " + "max(time) max_time " + - "FROM test.orc_stream_compressed"); + "FROM orc_stream_compressed"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 100); Assert.assertEquals(rs.getLong("sum_int"), 4950); Assert.assertEquals(rs.getFloat("sum_flt"), Float.POSITIVE_INFINITY); Assert.assertEquals(rs.getLong("uniq_str"), 100); - Assert.assertEquals(rs.getBigDecimal("max_dcml"), new BigDecimal("1.000000000")); + Assert.assertEquals(rs.getBigDecimal("max_dcml").setScale(9), new BigDecimal("1.000000000")); Assert.assertEquals(rs.getTimestamp("min_time"), utcToServerTimezone("2020-01-01 00:00:00")); Assert.assertEquals(rs.getTimestamp("max_time"), utcToServerTimezone("2020-01-01 00:01:39")); } - @Test + @Test(groups = "integration") public void ORCInsertCompressedIntoTable1() throws SQLException { // clickhouse-client -q "select number int, toString(number) str, 1/number flt, toDecimal64( 1/(number+1) , 9) dcml, // toDateTime('2020-01-01 00:00:00') + number time from numbers(100) format ORC"|gzip > test_sample.orc.gz @@ -312,9 +315,9 @@ public void ORCInsertCompressedIntoTable1() throws SQLException { return; } - connection.createStatement().execute("DROP TABLE IF EXISTS test.orc1_stream_compressed"); + connection.createStatement().execute("DROP TABLE IF EXISTS orc1_stream_compressed"); connection.createStatement().execute( - "CREATE TABLE test.orc1_stream_compressed (int Int64, str String, flt Float64, " + + "CREATE TABLE orc1_stream_compressed (int Int64, str String, flt Float64, " + "dcml Decimal64(9), time DateTime) ENGINE = Log();" ); @@ -322,13 +325,13 @@ public void ORCInsertCompressedIntoTable1() throws SQLException { connection.createStatement(). write() - .sql("insert into test.orc1_stream_compressed format ORC") + .sql("insert into orc1_stream_compressed format ORC") .dataCompression(ClickHouseCompression.gzip) .data(inputStream) .send(); ResultSet rs = connection.createStatement().executeQuery( - "select * from test.orc1_stream_compressed where int=42"); + "select * from orc1_stream_compressed where int=42"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("int"), 42); Assert.assertEquals(rs.getString("str"), "42"); @@ -337,7 +340,7 @@ public void ORCInsertCompressedIntoTable1() throws SQLException { Assert.assertEquals(rs.getTimestamp("time"), utcToServerTimezone("2020-01-01 00:00:42")); } - @Test + @Test(groups = "integration") public void ParquetInsertCompressedIntoTable() throws SQLException { // clickhouse-client -q "select number int, toString(number) str, 1/number flt, toDecimal64( 1/(number+1) , 9) dcml, // toDateTime('2020-01-01 00:00:00') + number time from numbers(100) format Parquet"|gzip > test_sample.parquet.gz @@ -347,9 +350,9 @@ public void ParquetInsertCompressedIntoTable() throws SQLException { return; } - connection.createStatement().execute("DROP TABLE IF EXISTS test.parquet_stream_compressed"); + connection.createStatement().execute("DROP TABLE IF EXISTS parquet_stream_compressed"); connection.createStatement().execute( - "CREATE TABLE test.parquet_stream_compressed (int Int64, str String, flt Float64, " + + "CREATE TABLE parquet_stream_compressed (int Int64, str String, flt Float64, " + "dcml Decimal64(9), time DateTime) ENGINE = Log();" ); @@ -357,7 +360,7 @@ public void ParquetInsertCompressedIntoTable() throws SQLException { connection.createStatement(). write() - .table("test.parquet_stream_compressed") + .table(dbName + ".parquet_stream_compressed") .format(ClickHouseFormat.Parquet) .dataCompression(ClickHouseCompression.gzip) .data(inputStream) @@ -371,18 +374,18 @@ public void ParquetInsertCompressedIntoTable() throws SQLException { "max(dcml) max_dcml, " + "min(time) min_time, " + "max(time) max_time " + - "FROM test.parquet_stream_compressed"); + "FROM parquet_stream_compressed"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 100); Assert.assertEquals(rs.getLong("sum_int"), 4950); Assert.assertEquals(rs.getFloat("sum_flt"), Float.POSITIVE_INFINITY); Assert.assertEquals(rs.getLong("uniq_str"), 100); - Assert.assertEquals(rs.getBigDecimal("max_dcml"), new BigDecimal("1.000000000")); + Assert.assertEquals(rs.getBigDecimal("max_dcml").setScale(9), new BigDecimal("1.000000000")); Assert.assertEquals(rs.getTimestamp("min_time"), utcToServerTimezone("2020-01-01 00:00:00")); Assert.assertEquals(rs.getTimestamp("max_time"), utcToServerTimezone("2020-01-01 00:01:39")); } - @Test + @Test(groups = "integration") public void ParquetInsertCompressedIntoTable1() throws SQLException { // clickhouse-client -q "select number int, toString(number) str, 1/number flt, toDecimal64( 1/(number+1) , 9) dcml, // toDateTime('2020-01-01 00:00:00') + number time from numbers(100) format Parquet"|gzip > test_sample.parquet.gz @@ -392,9 +395,9 @@ public void ParquetInsertCompressedIntoTable1() throws SQLException { return; } - connection.createStatement().execute("DROP TABLE IF EXISTS test.parquet1_stream_compressed"); + connection.createStatement().execute("DROP TABLE IF EXISTS parquet1_stream_compressed"); connection.createStatement().execute( - "CREATE TABLE test.parquet1_stream_compressed (int Int64, str String, flt Float64, " + + "CREATE TABLE parquet1_stream_compressed (int Int64, str String, flt Float64, " + "dcml Decimal64(9), time DateTime) ENGINE = Log();" ); @@ -402,13 +405,13 @@ public void ParquetInsertCompressedIntoTable1() throws SQLException { connection.createStatement(). write() - .sql("insert into test.parquet1_stream_compressed format Parquet") + .sql("insert into parquet1_stream_compressed format Parquet") .dataCompression(ClickHouseCompression.gzip) .data(inputStream) .send(); ResultSet rs = connection.createStatement().executeQuery( - "select * from test.parquet1_stream_compressed where int=42"); + "select * from parquet1_stream_compressed where int=42"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("int"), 42); Assert.assertEquals(rs.getString("str"), "42"); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TSVStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TSVStreamTest.java index cf84497bb..54cea5920 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TSVStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TSVStreamTest.java @@ -1,11 +1,11 @@ package ru.yandex.clickhouse.integration; import org.testng.Assert; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -13,32 +13,33 @@ import java.sql.ResultSet; import java.sql.SQLException; -public class TSVStreamTest { - - private ClickHouseDataSource dataSource; +public class TSVStreamTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - dataSource = ClickHouseContainerForTest.newDataSource(); - connection = dataSource.getConnection(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connection = newConnection(); + } + + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); } - @Test + @Test(groups = "integration") public void simpleInsert() throws SQLException { - connection.createStatement().execute("DROP TABLE IF EXISTS test.tsv_stream"); + connection.createStatement().execute("DROP TABLE IF EXISTS tsv_stream"); connection.createStatement().execute( - "CREATE TABLE test.tsv_stream (value Int32, string_value String) ENGINE = Log()" + "CREATE TABLE tsv_stream (value Int32, string_value String) ENGINE = Log()" ); String string = "5\t6\n1\t6"; InputStream inputStream = new ByteArrayInputStream(string.getBytes(Charset.forName("UTF-8"))); - connection.createStatement().sendStream(inputStream, "test.tsv_stream"); + connection.createStatement().sendStream(inputStream, dbName + ".tsv_stream"); ResultSet rs = connection.createStatement().executeQuery( - "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM test.tsv_stream"); + "SELECT count() AS cnt, sum(value) AS sum, uniqExact(string_value) uniq FROM tsv_stream"); Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt("cnt"), 2); Assert.assertEquals(rs.getLong("sum"), 6); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TimeZoneTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TimeZoneTest.java index 858d607d1..b002d6768 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TimeZoneTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/TimeZoneTest.java @@ -17,77 +17,68 @@ import java.util.concurrent.TimeUnit; import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; -import ru.yandex.clickhouse.ClickHouseDataSource; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.settings.ClickHouseProperties; import ru.yandex.clickhouse.util.ClickHouseRowBinaryStream; import ru.yandex.clickhouse.util.ClickHouseStreamCallback; import static org.testng.Assert.fail; -public class TimeZoneTest { +public class TimeZoneTest extends JdbcIntegrationTest { private ClickHouseConnection connectionServerTz; private ClickHouseConnection connectionManualTz; private long currentTime = 1000 * (System.currentTimeMillis() / 1000); - @BeforeTest + @BeforeClass(groups = "integration") public void setUp() throws Exception { - ClickHouseDataSource datasourceServerTz = ClickHouseContainerForTest.newDataSource();; - connectionServerTz = datasourceServerTz.getConnection(); + connectionServerTz = newConnection(); + TimeZone serverTimeZone = connectionServerTz.getTimeZone(); ClickHouseProperties properties = new ClickHouseProperties(); properties.setUseServerTimeZone(false); int serverTimeZoneOffsetHours = (int) TimeUnit.MILLISECONDS.toHours(serverTimeZone.getOffset(currentTime)); int manualTimeZoneOffsetHours = serverTimeZoneOffsetHours - 1; properties.setUseTimeZone("GMT" + (manualTimeZoneOffsetHours > 0 ? "+" : "") + manualTimeZoneOffsetHours + ":00"); - ClickHouseDataSource dataSourceManualTz = ClickHouseContainerForTest.newDataSource(properties); - connectionManualTz = dataSourceManualTz.getConnection(); - connectionServerTz.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); + connectionManualTz = newConnection(properties); } - @AfterTest + @AfterClass(groups = "integration") public void tearDown() throws Exception { - if (connectionServerTz != null) { - connectionServerTz.close(); - connectionServerTz = null; - } - if (connectionManualTz != null) { - connectionManualTz.close(); - connectionManualTz = null; - } + closeConnection(connectionServerTz); + closeConnection(connectionManualTz); } - @Test + @Test(groups = "integration") public void timeZoneTestTimestamp() throws Exception { try (Statement s = connectionServerTz.createStatement()) { - s.execute("DROP TABLE IF EXISTS test.time_zone_test"); + s.execute("DROP TABLE IF EXISTS time_zone_test"); s.execute( - "CREATE TABLE IF NOT EXISTS test.time_zone_test (i Int32, d DateTime) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS time_zone_test (i Int32, d DateTime) ENGINE = TinyLog" ); } try (PreparedStatement statement = - connectionServerTz.prepareStatement("INSERT INTO test.time_zone_test (i, d) VALUES (?, ?)")) { + connectionServerTz.prepareStatement("INSERT INTO time_zone_test (i, d) VALUES (?, ?)")) { statement.setInt(1, 1); statement.setTimestamp(2, new Timestamp(currentTime)); statement.execute(); } try (PreparedStatement statementUtc = - connectionManualTz.prepareStatement("INSERT INTO test.time_zone_test (i, d) VALUES (?, ?)")) { + connectionManualTz.prepareStatement("INSERT INTO time_zone_test (i, d) VALUES (?, ?)")) { statementUtc.setInt(1, 2); statementUtc.setTimestamp(2, new Timestamp(currentTime)); statementUtc.execute(); } - String query = "SELECT i, d as cnt from test.time_zone_test order by i"; + String query = "SELECT i, d as cnt from time_zone_test order by i"; try (Statement s = connectionServerTz.createStatement(); ResultSet rs = s.executeQuery(query)) { // server write, server read rs.next(); @@ -123,129 +114,117 @@ public void timeZoneTestTimestamp() throws Exception { * true TZ_DIFF true (forbidden) */ - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate1() throws Exception { try { - ClickHouseDataSource ds = createDataSource(false, null, false); - ds.getConnection(); + createConnection(false, null, false); fail(); } catch (IllegalArgumentException iae) { // expected } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate2() throws Exception { int suffix = 2; Long offset = Long.valueOf( TimeUnit.MILLISECONDS.toHours( connectionServerTz.getServerTimeZone().getOffset(currentTime))); - ClickHouseDataSource ds = createDataSource(false, offset, false); resetDateTestTable(suffix); - ClickHouseConnection conn = ds.getConnection(); - insertDateTestData(suffix, connectionServerTz, 1); - insertDateTestData(suffix, conn, 1); - - assertDateResult(suffix, conn, Instant.ofEpochMilli(currentTime) - .atOffset(ZoneOffset.ofHours(offset.intValue())) - .truncatedTo(ChronoUnit.DAYS) - .toEpochSecond()); - conn.close(); - ds = null; + try (ClickHouseConnection conn = createConnection(false, offset, false)) { + insertDateTestData(suffix, connectionServerTz, 1); + insertDateTestData(suffix, conn, 1); + + assertDateResult(suffix, conn, Instant.ofEpochMilli(currentTime) + .atOffset(ZoneOffset.ofHours(offset.intValue())) + .truncatedTo(ChronoUnit.DAYS) + .toEpochSecond()); + } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate3() throws Exception { try { - ClickHouseDataSource ds = createDataSource(false, null, false); - ds.getConnection(); + createConnection(false, null, false); fail(); } catch (IllegalArgumentException iae) { // expected } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate4() throws Exception { int suffix = 4; Long offset = Long.valueOf( TimeUnit.MILLISECONDS.toHours( connectionServerTz.getTimeZone().getOffset(currentTime))); - ClickHouseDataSource ds = createDataSource(false, offset, true); resetDateTestTable(suffix); - ClickHouseConnection conn = ds.getConnection(); - insertDateTestData(suffix, connectionServerTz, 1); - insertDateTestData(suffix, conn, 1); - assertDateResult( - suffix, conn, - Instant.ofEpochMilli(currentTime) - .atOffset(ZoneOffset.ofHours(offset.intValue())) - .truncatedTo(ChronoUnit.DAYS) - .toEpochSecond()); - conn.close(); - ds = null; + try (ClickHouseConnection conn = createConnection(false, offset, true)) { + insertDateTestData(suffix, connectionServerTz, 1); + insertDateTestData(suffix, conn, 1); + assertDateResult( + suffix, conn, + Instant.ofEpochMilli(currentTime) + .atOffset(ZoneOffset.ofHours(offset.intValue())) + .truncatedTo(ChronoUnit.DAYS) + .toEpochSecond()); + } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate5() throws Exception { int suffix = 5; - ClickHouseDataSource ds = createDataSource(true, null, false); resetDateTestTable(suffix); - ClickHouseConnection conn = ds.getConnection(); - insertDateTestData(suffix, connectionServerTz, 1); - insertDateTestData(suffix, conn, 1); - - assertDateResult( - suffix, conn, - Instant.ofEpochMilli(currentTime) - .atZone(ZoneId.systemDefault()) - .truncatedTo(ChronoUnit.DAYS) - .toEpochSecond()); - conn.close(); - ds = null; + try (ClickHouseConnection conn = createConnection(true, null, false)) { + insertDateTestData(suffix, connectionServerTz, 1); + insertDateTestData(suffix, conn, 1); + + assertDateResult( + suffix, conn, + Instant.ofEpochMilli(currentTime) + .atZone(ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.DAYS) + .toEpochSecond()); + } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate6() throws Exception { try { - ClickHouseDataSource ds = createDataSource(true, Long.valueOf(1L), false); - ds.getConnection(); + createConnection(true, Long.valueOf(1L), false); fail(); } catch (IllegalArgumentException iae) { // expected } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate7() throws Exception { int suffix = 7; - ClickHouseDataSource ds = createDataSource(true, null, true); resetDateTestTable(suffix); - ClickHouseConnection conn = ds.getConnection(); - insertDateTestData(suffix, connectionServerTz, 1); - insertDateTestData(suffix, conn, 1); - assertDateResult( - suffix, conn, - Instant.ofEpochMilli(currentTime) - .atZone(ZoneId.systemDefault()) - .truncatedTo(ChronoUnit.DAYS) - .toEpochSecond()); - conn.close(); - ds = null; + try (ClickHouseConnection conn = createConnection(true, null, true)) { + insertDateTestData(suffix, connectionServerTz, 1); + insertDateTestData(suffix, conn, 1); + assertDateResult( + suffix, conn, + Instant.ofEpochMilli(currentTime) + .atZone(ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.DAYS) + .toEpochSecond()); + } } - @Test + @Test(groups = "integration") public void testTimeZoneParseSQLDate8() throws Exception { try { - ClickHouseDataSource ds = createDataSource(true, Long.valueOf(1L), true); - ds.getConnection(); + createConnection(true, Long.valueOf(1L), true); fail(); } catch (IllegalArgumentException iae) { // expected } } - @Test + @Test(groups = "integration") public void dateTest() throws SQLException { ResultSet rsMan = connectionManualTz.createStatement().executeQuery("select toDate('2017-07-05')"); ResultSet rsSrv = connectionServerTz.createStatement().executeQuery("select toDate('2017-07-05')"); @@ -258,11 +237,11 @@ public void dateTest() throws SQLException { Assert.assertEquals(rsMan.getDate(1), new Date(117, 6, 5)); } - @Test + @Test(groups = "integration") public void dateInsertTest() throws SQLException { - connectionServerTz.createStatement().execute("DROP TABLE IF EXISTS test.date_insert"); + connectionServerTz.createStatement().execute("DROP TABLE IF EXISTS date_insert"); connectionServerTz.createStatement().execute( - "CREATE TABLE test.date_insert (" + + "CREATE TABLE date_insert (" + "i UInt8," + "d Date" + ") ENGINE = TinyLog" @@ -273,7 +252,7 @@ public void dateInsertTest() throws SQLException { Date localStartOfDay = withTimeAtStartOfDay(date); connectionServerTz.createStatement().sendRowBinaryStream( - "INSERT INTO test.date_insert (i, d)", + "INSERT INTO date_insert (i, d)", new ClickHouseStreamCallback() { @Override public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { @@ -284,7 +263,7 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { ); connectionManualTz.createStatement().sendRowBinaryStream( - "INSERT INTO test.date_insert (i, d)", + "INSERT INTO date_insert (i, d)", new ClickHouseStreamCallback() { @Override public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { @@ -294,8 +273,8 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { } ); - ResultSet rsMan = connectionManualTz.createStatement().executeQuery("select d from test.date_insert order by i"); - ResultSet rsSrv = connectionServerTz.createStatement().executeQuery("select d from test.date_insert order by i"); + ResultSet rsMan = connectionManualTz.createStatement().executeQuery("select d from date_insert order by i"); + ResultSet rsSrv = connectionServerTz.createStatement().executeQuery("select d from date_insert order by i"); rsMan.next(); rsSrv.next(); // inserted in server timezone @@ -309,11 +288,11 @@ public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { Assert.assertEquals(rsSrv.getDate(1), localStartOfDay); } - @Test + @Test(groups = "integration") public void testParseColumnsWithDifferentTimeZones() throws Exception { - connectionServerTz.createStatement().execute("DROP TABLE IF EXISTS test.fun_with_timezones"); + connectionServerTz.createStatement().execute("DROP TABLE IF EXISTS fun_with_timezones"); connectionServerTz.createStatement().execute( - "CREATE TABLE test.fun_with_timezones (" + + "CREATE TABLE fun_with_timezones (" + "i UInt8," + "dt_server DateTime," + "dt_berlin DateTime('Europe/Berlin')," + @@ -321,11 +300,11 @@ public void testParseColumnsWithDifferentTimeZones() throws Exception { ") ENGINE = TinyLog" ); connectionServerTz.createStatement().execute( - "INSERT INTO test.fun_with_timezones (i, dt_server, dt_berlin, dt_lax) " + + "INSERT INTO fun_with_timezones (i, dt_server, dt_berlin, dt_lax) " + "VALUES (42, 1557136800, 1557136800, 1557136800)"); ResultSet rs_timestamps = connectionServerTz.createStatement().executeQuery( "SELECT i, toUnixTimestamp(dt_server), toUnixTimestamp(dt_berlin), toUnixTimestamp(dt_lax) " + - "FROM test.fun_with_timezones"); + "FROM fun_with_timezones"); rs_timestamps.next(); Assert.assertEquals(rs_timestamps.getLong(2), 1557136800); Assert.assertEquals(rs_timestamps.getLong(3), 1557136800); @@ -333,18 +312,18 @@ public void testParseColumnsWithDifferentTimeZones() throws Exception { ResultSet rs_datetimes = connectionServerTz.createStatement().executeQuery( "SELECT i, dt_server, dt_berlin, dt_lax " + - "FROM test.fun_with_timezones"); + "FROM fun_with_timezones"); rs_datetimes.next(); Assert.assertEquals(rs_datetimes.getTimestamp(2).getTime(), 1557136800000L); Assert.assertEquals(rs_datetimes.getTimestamp(3).getTime(), 1557136800000L); Assert.assertEquals(rs_datetimes.getTimestamp(4).getTime(), 1557136800000L); } - @Test + @Test(groups = "integration") public void testParseColumnsWithDifferentTimeZonesArray() throws Exception { - connectionServerTz.createStatement().execute("DROP TABLE IF EXISTS test.fun_with_timezones_array"); + connectionServerTz.createStatement().execute("DROP TABLE IF EXISTS fun_with_timezones_array"); connectionServerTz.createStatement().execute( - "CREATE TABLE test.fun_with_timezones_array (" + + "CREATE TABLE fun_with_timezones_array (" + "i UInt8," + "dt_server Array(DateTime)," + "dt_berlin Array(DateTime('Europe/Berlin'))," + @@ -352,11 +331,11 @@ public void testParseColumnsWithDifferentTimeZonesArray() throws Exception { ") ENGINE = TinyLog" ); connectionServerTz.createStatement().execute( - "INSERT INTO test.fun_with_timezones_array (i, dt_server, dt_berlin, dt_lax) " + + "INSERT INTO fun_with_timezones_array (i, dt_server, dt_berlin, dt_lax) " + "VALUES (42, [1557136800], [1557136800], [1557136800])"); ResultSet rs_timestamps = connectionServerTz.createStatement().executeQuery( "SELECT i, array(toUnixTimestamp(dt_server[1])), array(toUnixTimestamp(dt_berlin[1])), array(toUnixTimestamp(dt_lax[1])) " + - "FROM test.fun_with_timezones_array"); + "FROM fun_with_timezones_array"); rs_timestamps.next(); Assert.assertEquals(((long[]) rs_timestamps.getArray(2).getArray())[0], 1557136800); Assert.assertEquals(((long[]) rs_timestamps.getArray(3).getArray())[0], 1557136800); @@ -364,7 +343,7 @@ public void testParseColumnsWithDifferentTimeZonesArray() throws Exception { ResultSet rs_datetimes = connectionServerTz.createStatement().executeQuery( "SELECT i, dt_server, dt_berlin, dt_lax " + - "FROM test.fun_with_timezones_array"); + "FROM fun_with_timezones_array"); rs_datetimes.next(); Assert.assertEquals(((Timestamp[]) rs_datetimes.getArray(2).getArray())[0].getTime(), 1557136800000L); Assert.assertEquals(((Timestamp[]) rs_datetimes.getArray(3).getArray())[0].getTime(), 1557136800000L); @@ -381,8 +360,8 @@ private static Date withTimeAtStartOfDay(Date date) { return new Date(cal.getTimeInMillis()); } - private static ClickHouseDataSource createDataSource(boolean useServerTimeZone, - Long manualTimeZoneOffsetHours, boolean useServerTimeZoneForParsingDates) + private ClickHouseConnection createConnection(boolean useServerTimeZone, + Long manualTimeZoneOffsetHours, boolean useServerTimeZoneForParsingDates) throws SQLException { ClickHouseProperties props = new ClickHouseProperties(); props.setUseServerTimeZone(useServerTimeZone); @@ -393,20 +372,20 @@ private static ClickHouseDataSource createDataSource(boolean useServerTimeZone, + manualTimeZoneOffsetHours + ":00"); } props.setUseServerTimeZoneForDates(useServerTimeZoneForParsingDates); - return ClickHouseContainerForTest.newDataSource(props); + return newConnection(props); } private void resetDateTestTable(int suffix) throws Exception { connectionServerTz.createStatement().execute( - "DROP TABLE IF EXISTS test.time_zone_test" + suffix); + "DROP TABLE IF EXISTS time_zone_test" + suffix); connectionServerTz.createStatement().execute( - "CREATE TABLE IF NOT EXISTS test.time_zone_test" + suffix + " (i Int32, d DateTime) ENGINE = TinyLog" + "CREATE TABLE IF NOT EXISTS time_zone_test" + suffix + " (i Int32, d DateTime) ENGINE = TinyLog" ); } private void insertDateTestData(int suffix, ClickHouseConnection conn, int id) throws Exception { PreparedStatement statement = conn.prepareStatement( - "INSERT INTO test.time_zone_test" + suffix + " (i, d) VALUES (?, ?)"); + "INSERT INTO time_zone_test" + suffix + " (i, d) VALUES (?, ?)"); statement.setInt(1, id); statement.setTimestamp(2, new Timestamp(currentTime)); statement.execute(); @@ -416,7 +395,7 @@ private static void assertDateResult(int suffix, Connection conn, long expectedS throws Exception { try (Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT i, d as cnt from test.time_zone_test" + suffix + " order by i")) { + ResultSet rs = stmt.executeQuery("SELECT i, d as cnt from time_zone_test" + suffix + " order by i")) { Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getDate(2).getTime(), expectedSecondsEpoch * 1000); Assert.assertTrue(rs.next()); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/WriterTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/WriterTest.java index 6e22b33fc..4ecc706fe 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/WriterTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/WriterTest.java @@ -1,11 +1,12 @@ package ru.yandex.clickhouse.integration; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.testng.internal.Utils; import ru.yandex.clickhouse.ClickHouseConnection; -import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseStatement; +import ru.yandex.clickhouse.JdbcIntegrationTest; import ru.yandex.clickhouse.domain.ClickHouseFormat; import ru.yandex.clickhouse.settings.ClickHouseProperties; import ru.yandex.clickhouse.util.ClickHouseRowBinaryStream; @@ -18,113 +19,125 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; -public class WriterTest { - +public class WriterTest extends JdbcIntegrationTest { private ClickHouseConnection connection; - private ClickHouseStatement statement; - @BeforeMethod + @BeforeClass(groups = "integration") public void setUp() throws Exception { ClickHouseProperties properties = new ClickHouseProperties(); properties.setDecompress(true); properties.setCompress(true); - connection = ClickHouseContainerForTest.newDataSource(properties).getConnection(); - statement = connection.createStatement(); - connection.createStatement().execute("CREATE DATABASE IF NOT EXISTS test"); - connection.createStatement().execute("DROP TABLE IF EXISTS test.writer"); - connection.createStatement().execute("CREATE TABLE test.writer (id Int32, name String) ENGINE = Log"); - connection.createStatement().execute("TRUNCATE TABLE test.writer"); + connection = newConnection(properties); + } + + @AfterClass(groups = "integration") + public void tearDown() throws Exception { + closeConnection(connection); + } + + @BeforeMethod(groups = "integration") + public void createTable() throws Exception { + try (ClickHouseStatement statement = connection.createStatement()) { + statement.execute("DROP TABLE IF EXISTS writer"); + statement.execute("CREATE TABLE writer (id Int32, name String) ENGINE = Log"); + statement.execute("TRUNCATE TABLE writer"); + } } - @Test + @Test(groups = "integration") public void testCSV() throws Exception { String data = "10;Фёдор\n20;Слава"; - statement.write() - .table("test.writer") - .format(ClickHouseFormat.CSV) - .option("format_csv_delimiter", ";") - .data(new ByteArrayInputStream(data.getBytes("UTF-8"))) - .send(); - assertTableRowCount(2); + try (ClickHouseStatement statement = connection.createStatement()) { + statement.write().table(dbName + ".writer").format(ClickHouseFormat.CSV).option("format_csv_delimiter", ";") + .data(new ByteArrayInputStream(data.getBytes("UTF-8"))).send(); + assertTableRowCount(2); + } } - @Test + @Test(groups = "integration") public void testTSV() throws Exception { - File tempFile = Utils.createTempFile(""); + File tempFile = File.createTempFile("tmp-", ".tsv"); + tempFile.deleteOnExit(); FileOutputStream fos = new FileOutputStream(tempFile); for (int i = 0; i < 1000; i++) { fos.write((i + "\tИмя " + i + "\n").getBytes("UTF-8")); } fos.close(); - statement - .write() - .table("test.writer") - .format(ClickHouseFormat.TabSeparated) - .data(tempFile) - .send(); + try (ClickHouseStatement statement = connection.createStatement()) { + statement.write().table(dbName + ".writer").format(ClickHouseFormat.TabSeparated).data(tempFile).send(); - assertTableRowCount(1000); + assertTableRowCount(1000); - ResultSet rs = statement.executeQuery("SELECT count() FROM test.writer WHERE name = concat('Имя ', toString(id))"); - rs.next(); - assertEquals(rs.getInt(1), 1000); + ResultSet rs = statement + .executeQuery("SELECT count() FROM writer WHERE name = concat('Имя ', toString(id))"); + rs.next(); + assertEquals(rs.getInt(1), 1000); + } } - @Test + @Test(groups = "integration") public void testRowBinary() throws Exception { - statement.write().send("INSERT INTO test.writer", new ClickHouseStreamCallback() { - @Override - public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { - for (int i = 0; i < 10; i++) { - stream.writeInt32(i); - stream.writeString("Имя " + i); + try (ClickHouseStatement statement = connection.createStatement()) { + statement.write().send("INSERT INTO writer", new ClickHouseStreamCallback() { + @Override + public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { + for (int i = 0; i < 10; i++) { + stream.writeInt32(i); + stream.writeString("Имя " + i); + } } - } - }, ClickHouseFormat.RowBinary); + }, ClickHouseFormat.RowBinary); - assertTableRowCount(10); - ResultSet rs = statement.executeQuery("SELECT count() FROM test.writer WHERE name = concat('Имя ', toString(id))"); - rs.next(); - assertEquals(rs.getInt(1), 10); + assertTableRowCount(10); + ResultSet rs = statement + .executeQuery("SELECT count() FROM writer WHERE name = concat('Имя ', toString(id))"); + rs.next(); + assertEquals(rs.getInt(1), 10); + } } - @Test + @Test(groups = "integration") public void testNative() throws Exception { - statement.write().send("INSERT INTO test.writer", new ClickHouseStreamCallback() { - @Override - public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { + try (ClickHouseStatement statement = connection.createStatement()) { + statement.write().send("INSERT INTO writer", new ClickHouseStreamCallback() { + @Override + public void writeTo(ClickHouseRowBinaryStream stream) throws IOException { - int numberOfRows = 1000; - stream.writeUnsignedLeb128(2); // 2 columns - stream.writeUnsignedLeb128(numberOfRows); + int numberOfRows = 1000; + stream.writeUnsignedLeb128(2); // 2 columns + stream.writeUnsignedLeb128(numberOfRows); - stream.writeString("id"); - stream.writeString("Int32"); + stream.writeString("id"); + stream.writeString("Int32"); - for (int i = 0; i < numberOfRows; i++) { - stream.writeInt32(i); - } + for (int i = 0; i < numberOfRows; i++) { + stream.writeInt32(i); + } - stream.writeString("name"); - stream.writeString("String"); + stream.writeString("name"); + stream.writeString("String"); - for (int i = 0; i < numberOfRows; i++) { - stream.writeString("Имя " + i); + for (int i = 0; i < numberOfRows; i++) { + stream.writeString("Имя " + i); + } } - } - }, ClickHouseFormat.Native); + }, ClickHouseFormat.Native); - assertTableRowCount(1000); - ResultSet rs = statement.executeQuery("SELECT count() FROM test.writer WHERE name = concat('Имя ', toString(id))"); - rs.next(); - assertEquals(rs.getInt(1), 1000); + assertTableRowCount(1000); + ResultSet rs = statement + .executeQuery("SELECT count() FROM writer WHERE name = concat('Имя ', toString(id))"); + rs.next(); + assertEquals(rs.getInt(1), 1000); + } } private void assertTableRowCount(int expected) throws SQLException { - ResultSet rs = statement.executeQuery("SELECT count() from test.writer"); - assertTrue(rs.next()); - assertEquals(rs.getInt(1), expected); + try (ClickHouseStatement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery("SELECT count() from writer");) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), expected); + } } } diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java index e95ef61d1..77abc0503 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java @@ -60,7 +60,7 @@ private void checkSingleStatement(ClickHouseSqlStatement[] stmts, String sql, St assertEquals(s.getTable(), table); } - @Test + @Test(groups = "unit") public void testParseNonSql() throws ParseException { String sql; @@ -81,7 +81,7 @@ public void testParseNonSql() throws ParseException { // StatementType.UNKNOWN); } - @Test + @Test(groups = "unit") public void testAlterStatement() { String sql; @@ -95,7 +95,7 @@ public void testAlterStatement() { checkSingleStatement(parse(sql = "ALTER USER user DEFAULT ROLE role1, role2"), sql, StatementType.ALTER); } - @Test + @Test(groups = "unit") public void testAttachStatement() { String sql; @@ -103,7 +103,7 @@ public void testAttachStatement() { StatementType.ATTACH); } - @Test + @Test(groups = "unit") public void testCheckStatement() { String sql; @@ -111,14 +111,14 @@ public void testCheckStatement() { checkSingleStatement(parse(sql = "check table a.a"), sql, StatementType.CHECK); } - @Test + @Test(groups = "unit") public void testCreateStatement() { String sql; checkSingleStatement(parse(sql = "create table a(a String) engine=Memory"), sql, StatementType.CREATE); } - @Test + @Test(groups = "unit") public void testDeleteStatement() { String sql; @@ -127,7 +127,7 @@ public void testDeleteStatement() { "c", "a"); } - @Test + @Test(groups = "unit") public void testDescribeStatement() { String sql; @@ -141,7 +141,7 @@ public void testDescribeStatement() { checkSingleStatement(parse(sql = "desc table t1 as `t2`"), sql, StatementType.DESCRIBE, "system", "columns"); } - @Test + @Test(groups = "unit") public void testDetachStatement() { String sql; @@ -149,7 +149,7 @@ public void testDetachStatement() { checkSingleStatement(parse(sql = "detach TABLE if exists t.t on cluster 'cc'"), sql, StatementType.DETACH); } - @Test + @Test(groups = "unit") public void testDropStatement() { String sql; @@ -157,7 +157,7 @@ public void testDropStatement() { checkSingleStatement(parse(sql = "drop TABLE if exists t.t on cluster 'cc'"), sql, StatementType.DROP); } - @Test + @Test(groups = "unit") public void testExistsStatement() { String sql; @@ -166,7 +166,7 @@ public void testExistsStatement() { checkSingleStatement(parse(sql = "EXISTS DICTIONARY c"), sql, StatementType.EXISTS); } - @Test + @Test(groups = "unit") public void testExplainStatement() { String sql; @@ -179,7 +179,7 @@ public void testExplainStatement() { sql, StatementType.EXPLAIN); } - @Test + @Test(groups = "unit") public void testGrantStatement() { String sql; @@ -188,7 +188,7 @@ public void testGrantStatement() { checkSingleStatement(parse(sql = "GRANT INSERT(x,y) ON db.table TO john"), sql, StatementType.GRANT); } - @Test + @Test(groups = "unit") public void testInsertStatement() throws ParseException { String sql; @@ -222,7 +222,7 @@ public void testInsertStatement() throws ParseException { } - @Test + @Test(groups = "unit") public void testKillStatement() { String sql; @@ -233,7 +233,7 @@ public void testKillStatement() { sql, StatementType.KILL); } - @Test + @Test(groups = "unit") public void testOptimizeStatement() { String sql; @@ -241,7 +241,7 @@ public void testOptimizeStatement() { StatementType.OPTIMIZE); } - @Test + @Test(groups = "unit") public void testRenameStatement() { String sql; @@ -252,7 +252,7 @@ public void testRenameStatement() { sql, StatementType.RENAME); } - @Test + @Test(groups = "unit") public void testRevokeStatement() { String sql; @@ -260,7 +260,7 @@ public void testRevokeStatement() { checkSingleStatement(parse(sql = "REVOKE SELECT(wage) ON accounts.staff FROM mira"), sql, StatementType.REVOKE); } - @Test + @Test(groups = "unit") public void testSelectStatement() { String sql; @@ -319,7 +319,7 @@ public void testSelectStatement() { new ClickHouseSqlStatement(sql, StatementType.SELECT, null, null, "unknown", null, null, null, null) }); } - @Test + @Test(groups = "unit") public void testSetStatement() { String sql; @@ -327,7 +327,7 @@ public void testSetStatement() { checkSingleStatement(parse(sql = "SET DEFAULT ROLE role1, role2, role3 TO user"), sql, StatementType.SET); } - @Test + @Test(groups = "unit") public void testShowStatement() { String sql; @@ -337,7 +337,7 @@ public void testShowStatement() { "dictionaries"); } - @Test + @Test(groups = "unit") public void testSystemStatement() { String sql; @@ -347,14 +347,14 @@ public void testSystemStatement() { StatementType.SYSTEM); } - @Test + @Test(groups = "unit") public void testTruncateStatement() { String sql; checkSingleStatement(parse(sql = "truncate table a.b"), sql, StatementType.TRUNCATE, "a", "b"); } - @Test + @Test(groups = "unit") public void testUpdateStatement() { String sql; @@ -363,19 +363,19 @@ public void testUpdateStatement() { StatementType.UPDATE); } - @Test + @Test(groups = "unit") public void testUseStatement() throws ParseException { String sql; checkSingleStatement(parse(sql = "use system"), sql, StatementType.USE); } - @Test + @Test(groups = "unit") public void testWatchStatement() throws ParseException { String sql; checkSingleStatement(parse(sql = "watch system.processes"), sql, StatementType.WATCH); } - @Test + @Test(groups = "unit") public void testComments() throws ParseException { String sql; checkSingleStatement(parse(sql = "select\n--something\n//else\n1/*2*/ from a.b"), sql, StatementType.SELECT, @@ -388,7 +388,7 @@ public void testComments() throws ParseException { checkSingleStatement(parse(sql = "SELECT /*ab/*cd*/ef*/ 1 from a.b"), sql, StatementType.SELECT, "a", "b"); } - @Test + @Test(groups = "unit") public void testMultipleStatements() throws ParseException { assertEquals(parse("use ab;;;select 1; ;\t;\r;\n"), new ClickHouseSqlStatement[] { new ClickHouseSqlStatement("use ab", StatementType.USE, null, "ab", null, null, null, null, null), @@ -400,7 +400,7 @@ public void testMultipleStatements() throws ParseException { new ClickHouseSqlStatement("select 1 as `a ; a`", StatementType.SELECT) }); } - @Test + @Test(groups = "unit") public void testAlias() throws ParseException { String sql; checkSingleStatement(parse(sql = "select 1 as c, 2 b"), sql, StatementType.SELECT); @@ -412,7 +412,7 @@ public void testAlias() throws ParseException { checkSingleStatement(parse(sql = "select 1 from a.b c1, b.a c2"), sql, StatementType.SELECT, "a", "b"); } - @Test + @Test(groups = "unit") public void testExpression() throws ParseException { String sql; checkSingleStatement(parse(sql = "SELECT a._ from a.b"), sql, StatementType.SELECT, "a", "b"); @@ -450,7 +450,7 @@ public void testExpression() throws ParseException { "a", "b"); } - @Test + @Test(groups = "unit") public void testFormat() throws ParseException { String sql = "select 1 as format, format csv"; ClickHouseSqlStatement[] stmts = parse(sql); @@ -474,7 +474,7 @@ public void testFormat() throws ParseException { assertEquals(stmts[0].getFormat(), "CSVWithNames"); } - @Test + @Test(groups = "unit") public void testOutfile() throws ParseException { String sql = "select 1 into outfile '1.txt'"; ClickHouseSqlStatement[] stmts = parse(sql); @@ -491,7 +491,7 @@ public void testOutfile() throws ParseException { assertEquals(stmts[0].getOutfile(), null); } - @Test + @Test(groups = "unit") public void testWithTotals() throws ParseException { String sql = "select 1 as with totals"; ClickHouseSqlStatement[] stmts = parse(sql); @@ -506,7 +506,7 @@ public void testWithTotals() throws ParseException { assertEquals(stmts[0].hasWithTotals(), true); } - @Test + @Test(groups = "unit") public void testParameterHandling() throws ParseException { String sql = "insert into table d.t(a1, a2, a3) values(?,?,?)"; ClickHouseSqlStatement[] stmts = parse(sql); @@ -523,7 +523,7 @@ public String handleParameter(String cluster, String database, String table, int assertEquals(stmts[0].getSQL(), "insert into table d.t(a1, a2, a3) values(1,2,3)"); } - @Test + @Test(groups = "unit") public void testMacroHandling() throws ParseException { String sql = "select #listOfColumns #ignored from (#subQuery('1','2','3'))"; ClickHouseSqlStatement[] stmts = parse(sql); @@ -546,7 +546,7 @@ public String handleMacro(String name, List parameters) { assertEquals(stmts[0].getSQL(), "select a, b from (select 1+2+3)"); } - @Test + @Test(groups = "unit") public void testExtractDBAndTableName() { String sql; diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlUtilsTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlUtilsTest.java index a0bf980e1..f6347b685 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlUtilsTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/jdbc/parser/ClickHouseSqlUtilsTest.java @@ -4,7 +4,7 @@ import org.testng.annotations.Test; public class ClickHouseSqlUtilsTest { - @Test + @Test(groups = "unit") public void testIsQuote() { Assert.assertFalse(ClickHouseSqlUtils.isQuote('\0')); @@ -13,7 +13,7 @@ public void testIsQuote() { Assert.assertTrue(ClickHouseSqlUtils.isQuote('`')); } - @Test + @Test(groups = "unit") public void testEscape() { char[] quotes = new char[] { '"', '\'', '`' }; String str; @@ -39,7 +39,7 @@ public void testEscape() { } } - @Test + @Test(groups = "unit") public void testUnescape() { String str; Assert.assertEquals(ClickHouseSqlUtils.unescape(str = null), str); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ByteFragmentTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ByteFragmentTest.java index 57ef07dd6..6dcc1bbd6 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ByteFragmentTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ByteFragmentTest.java @@ -24,21 +24,21 @@ public Object[][] stringEscape() { }; } - @Test(dataProvider = "stringEscape") + @Test(groups = "unit", dataProvider = "stringEscape") public void testEscape(String str, String escapedStr) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteFragment.escape(str.getBytes(StandardCharsets.UTF_8), out); assertEquals(out.toString(StandardCharsets.UTF_8.name()), escapedStr); } - @Test(dataProvider = "stringEscape") + @Test(groups = "unit", dataProvider = "stringEscape") public void testUnescape(String str, String escapedStr) throws IOException { byte[] bytes = escapedStr.getBytes(StandardCharsets.UTF_8); ByteFragment byteFragment = new ByteFragment(bytes, 0, bytes.length); assertEquals(new String(byteFragment.unescape(), StandardCharsets.UTF_8.name()), str); } - @Test + @Test(groups = "unit") public void testIsEmpty() { ByteFragment byteFragment = new ByteFragment(new byte[0], 0, 0); assertTrue(byteFragment.isEmpty()); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseColumnInfoTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseColumnInfoTest.java index 1dc5d9479..b6eec9a62 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseColumnInfoTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseColumnInfoTest.java @@ -12,7 +12,7 @@ public class ClickHouseColumnInfoTest { - @Test + @Test(groups = "unit") public void testParseNull() { try { ClickHouseColumnInfo.parse(null, null, null); @@ -20,21 +20,21 @@ public void testParseNull() { } catch (NullPointerException npe) { /* expected */ } } - @Test + @Test(groups = "unit") public void testDateTimeWithoutTimeZone() { ClickHouseColumnInfo info = ClickHouseColumnInfo.parse( "DateTime", "column", null); assertEquals(info.getClickHouseDataType(), ClickHouseDataType.DateTime); } - @Test + @Test(groups = "unit") public void testDateTimeWithEmptyTimeZone() { ClickHouseColumnInfo info = ClickHouseColumnInfo.parse( "DateTime()", "column", null); assertEquals(info.getClickHouseDataType(), ClickHouseDataType.DateTime); } - @Test + @Test(groups = "unit") public void testDateTimeArrayWithTimeZonee() { assertEquals( ClickHouseColumnInfo.parse( @@ -42,14 +42,14 @@ public void testDateTimeArrayWithTimeZonee() { TimeZone.getTimeZone("America/Los_Angeles")); } - @Test + @Test(groups = "unit") public void testSpuriousArguments() { ClickHouseColumnInfo info = ClickHouseColumnInfo.parse( "Decimal(12, 3), 42)", "column", null); assertEquals(info.getScale(), 3); } - @Test(dataProvider = "columnInfoParsingDataProvider") + @Test(groups = "unit", dataProvider = "columnInfoParsingDataProvider") public void testParser(String input, ClickHouseDataType dataType, ClickHouseDataType arrayBaseType, boolean nullable, boolean lowCardinality, int precision, int scale, TimeZone timeZone, int arrayLevel) @@ -101,7 +101,7 @@ public Object[][] provideRegularParsingTestData() { }; } - @Test(dataProvider = "columnInfoParsingUnknownDataProvider") + @Test(groups = "unit", dataProvider = "columnInfoParsingUnknownDataProvider") public void testParserUnknownDataTypes(String input, boolean nullable, boolean lowCardinality) { @@ -111,7 +111,7 @@ public void testParserUnknownDataTypes(String input, boolean nullable, assertEquals(info.isLowCardinality(), lowCardinality); } - @Test(dataProvider = "columnInfoParsingUnknownDataProvider") + @Test(groups = "unit", dataProvider = "columnInfoParsingUnknownDataProvider") public void testParserUnknownArrayDataTypes(String input, boolean nullable, boolean lowCardinality) { @@ -133,7 +133,7 @@ public Object[][] provideUnknownDataTypeParserInput() { }; } - @Test + @Test(groups = "unit") public void testCleanTypeName() { assertEquals( ClickHouseColumnInfo.parse("Decimal(12,3)", "col", null).getCleanTypeName(), @@ -155,7 +155,7 @@ public void testCleanTypeName() { "Array(Array(Decimal(12,3)))"); } - @Test(dataProvider = "columnInfoNullableTypeDefinitions") + @Test(groups = "unit", dataProvider = "columnInfoNullableTypeDefinitions") public void testTypeIsNullable(String typeDef, Boolean nullable) throws Exception { assertEquals( nullable.booleanValue(), @@ -172,7 +172,7 @@ public Object[][] provideNullableTypeDefinitions() { }; } - @Test(dataProvider = "columnInfoScales") + @Test(groups = "unit", dataProvider = "columnInfoScales") public void testGetScale(String typeDef, int scale) { assertEquals( ClickHouseColumnInfo.parse(typeDef, "foo", null).getScale(), @@ -199,7 +199,7 @@ public Object[][] provideScaleTypeDefinitions() { }; } - @Test(dataProvider = "columnInfoPrecisions") + @Test(groups = "unit", dataProvider = "columnInfoPrecisions") public void testGetPrecision(String typeDef, int precision) { assertEquals( ClickHouseColumnInfo.parse(typeDef, "foo", null).getPrecision(), diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultBuilderTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultBuilderTest.java index 9bccb0303..016fe9ed4 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultBuilderTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultBuilderTest.java @@ -8,7 +8,7 @@ public class ClickHouseResultBuilderTest { - @Test + @Test(groups = "unit") public void testBuild() throws Exception { ClickHouseResultSet resultSet = ClickHouseResultBuilder.builder(2) .names("string", "int") diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetMetaDataTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetMetaDataTest.java index cd22ff57a..5114751c4 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetMetaDataTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetMetaDataTest.java @@ -15,7 +15,7 @@ public class ClickHouseResultSetMetaDataTest { - @Test + @Test(groups = "unit") public void testIsNullable() throws SQLException { ClickHouseResultSet resultSet = mock(ClickHouseResultSet.class); @@ -29,7 +29,7 @@ public void testIsNullable() throws SQLException { Assert.assertEquals(resultSetMetaData.isNullable(2), ResultSetMetaData.columnNullable); } - @Test + @Test(groups = "unit") public void testIsNullableColumnTypeName() throws SQLException { ClickHouseResultSet resultSet = mock(ClickHouseResultSet.class); @@ -39,7 +39,7 @@ public void testIsNullableColumnTypeName() throws SQLException { Assert.assertEquals(resultSetMetaData.getColumnTypeName(1), "Float64"); } - @Test + @Test(groups = "unit") public void testIsNullableSigned() throws SQLException { ClickHouseResultSet resultSet = mock(ClickHouseResultSet.class); ClickHouseColumnInfo[] types = new ClickHouseColumnInfo[]{ @@ -55,7 +55,7 @@ public void testIsNullableSigned() throws SQLException { Assert.assertFalse(resultSetMetaData.isSigned(3)); } - @Test + @Test(groups = "unit") public void testDateTimeWithTimeZone() throws SQLException { ClickHouseResultSet resultSet = mock(ClickHouseResultSet.class); when(resultSet.getColumns()).thenReturn(Collections.singletonList( diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetTest.java index e760946ff..c62f0df1e 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetTest.java @@ -55,7 +55,7 @@ public class ClickHouseResultSetTest { private ClickHouseProperties props; - @BeforeMethod + @BeforeMethod(groups = "unit") public void setUp() { props = Mockito.mock(ClickHouseProperties.class); } @@ -69,7 +69,7 @@ public Object[][] longArrays() { }; } - @Test(dataProvider = "longArrays") + @Test(groups = "unit", dataProvider = "longArrays") public void toLongArrayTest(String str, long[] expected) throws Exception { Assert.assertEquals( ClickHouseResultSet.toLongArray( @@ -80,7 +80,7 @@ public void toLongArrayTest(String str, long[] expected) throws Exception { } - @Test + @Test(groups = "unit") public void withoutTotals() throws Exception { String response = "SiteName\tcount()\n" + @@ -103,7 +103,7 @@ public void withoutTotals() throws Exception { assertFalse(rs.next()); } - @Test + @Test(groups = "unit") public void withoutTotalsSingleColumn() throws Exception { String response = "SiteName\n" + @@ -139,7 +139,7 @@ public void withoutTotalsSingleColumn() throws Exception { assertFalse(rs.next()); } - @Test + @Test(groups = "unit") public void withTotals() throws Exception { String response = "SiteName\tcount()\n" + "String\tUInt64\n" + @@ -205,7 +205,7 @@ public void withTotalsAndEmptyStrings() throws Exception { assertEquals(70511139L, rs.getLong(2)); } - @Test + @Test(groups = "unit") public void withTotalsSingleColumn() throws Exception { String response = "SiteName\n" + @@ -236,7 +236,7 @@ public void withTotalsSingleColumn() throws Exception { assertFalse(rs.next()); } - @Test + @Test(groups = "unit") public void withTotalsSingleIntColumn() throws Exception { String response = "Code\n" + @@ -266,7 +266,7 @@ public void withTotalsSingleIntColumn() throws Exception { assertEquals(0L, rs.getLong(1)); } - @Test + @Test(groups = "unit") public void withTotalsSingleNullableColumn() throws Exception { String response = "SiteName\n" + @@ -304,7 +304,7 @@ public void withTotalsSingleNullableColumn() throws Exception { assertNull(rs.getString(1)); } - @Test + @Test(groups = "unit") public void testIsLast() throws Exception { String response = "SiteName\tcount()\n" + @@ -324,7 +324,7 @@ public void testIsLast() throws Exception { assertFalse(rs.next()); } - @Test + @Test(groups = "unit") public void testIsFirst() throws Exception { String response = "SiteName\tcount()\n" + @@ -343,7 +343,7 @@ public void testIsFirst() throws Exception { assertFalse(rs.isFirst()); } - @Test + @Test(groups = "unit") public void testBeforeFirst() throws Exception { String response = "SiteName\tcount()\n" + @@ -361,7 +361,7 @@ public void testBeforeFirst() throws Exception { is.close(); } - @Test + @Test(groups = "unit") public void testIsAfterLast() throws Exception { String response = "SiteName\tcount()\n" + @@ -382,7 +382,7 @@ public void testIsAfterLast() throws Exception { assertTrue(rs.isAfterLast()); } - @Test + @Test(groups = "unit") public void testDecimalMetadata() throws Exception { String response = "sum(myMoney)\n" + @@ -399,7 +399,7 @@ public void testDecimalMetadata() throws Exception { assertEquals(rs.getMetaData().getPrecision(1), 38); } - @Test + @Test(groups = "unit") public void testArrayString() throws Exception { String response = "FOO\n" @@ -418,7 +418,7 @@ public void testArrayString() throws Exception { assertEquals("bar", s[1]); } - @Test + @Test(groups = "unit") public void test3dArrayString() throws Exception { String response = "FOO\n" @@ -446,7 +446,7 @@ public void test3dArrayString() throws Exception { } } - @Test + @Test(groups = "unit") public void testClassNamesObjects() throws Exception { String testData = ClickHouseTypesTestData.buildTestString(); ByteArrayInputStream is = new ByteArrayInputStream(testData.getBytes("UTF-8")); @@ -480,7 +480,7 @@ public void testClassNamesObjects() throws Exception { } } - @Test + @Test(groups = "unit") public void testGetColumnNames() throws Exception { String response = "SiteName\tCountry\n" + "String\tString\n" + @@ -521,7 +521,7 @@ public void testGetColumnNames() throws Exception { * {@link java.sql.ResultSet#getURL(int)} unsupported now * {@link java.sql.ResultSet#getAsciiStream(int)} unsupported now */ - @Test + @Test(groups = "unit") public void testNulls() throws Exception { String response = "Type\n" + @@ -556,7 +556,7 @@ public void testNulls() throws Exception { // this test checks mapping of SQL type to Java class // according to spec appendix table B-3 - @Test + @Test(groups = "unit") public void testJDBCTableB3() throws Exception { String testData = ClickHouseTypesTestData.buildTestString(); ByteArrayInputStream is = new ByteArrayInputStream(testData.getBytes("UTF-8")); @@ -584,7 +584,7 @@ public void testJDBCTableB3() throws Exception { } } - @Test + @Test(groups = "unit") public void testFindColumn() throws Exception { /* @@ -628,7 +628,7 @@ public void testFindColumn() throws Exception { // this test checks mapping of SQL type to Java class // according to spec appendix table B-1 - @Test + @Test(groups = "unit") public void testJDBCTableB1() throws Exception { String testData = ClickHouseTypesTestData.buildTestString(); ByteArrayInputStream is = new ByteArrayInputStream(testData.getBytes("UTF-8")); @@ -674,7 +674,7 @@ public void testJDBCTableB1() throws Exception { } } - @Test + @Test(groups = "unit") public void testGetDateCalendarJVMTime() throws Exception { String testData = "column\nDateTime\n2020-02-08 01:02:03"; ByteArrayInputStream is = new ByteArrayInputStream(testData.getBytes("UTF-8")); @@ -697,7 +697,7 @@ public void testGetDateCalendarJVMTime() throws Exception { .toEpochSecond()); } - @Test + @Test(groups = "unit") public void testGetDateCalendarServerTime() throws Exception { Mockito .when(props.isUseServerTimeZoneForDates()) @@ -723,7 +723,7 @@ public void testGetDateCalendarServerTime() throws Exception { .toEpochSecond()); } - @Test + @Test(groups = "unit") public void testLocalDateTime() throws Exception { String response = "SiteName\n" + diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseScrollableResultSetTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseScrollableResultSetTest.java index b47268226..e2649c024 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseScrollableResultSetTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseScrollableResultSetTest.java @@ -34,7 +34,7 @@ public Object[][] longArrays() { } @Override - @Test + @Test(groups = "unit") public void testIsLast() throws Exception { String response = "SiteName\tcount()\n" + @@ -53,7 +53,7 @@ public void testIsLast() throws Exception { assertFalse(rs.next()); } - @Test + @Test(groups = "unit") public void testPrevious() throws Exception { String response = "SiteName\tcount()\n" + @@ -78,7 +78,7 @@ public void testPrevious() throws Exception { assertEquals(21209048L, rs.getLong(2)); } - @Test + @Test(groups = "unit") public void testAbsolute() throws Exception { String response = "SiteName\tcount()\n" + @@ -147,7 +147,7 @@ public void testAbsoluteWithTotal() throws Exception { } - @Test + @Test(groups = "unit") public void testRelative() throws Exception { String response = "SiteName\tcount()\n" + @@ -180,7 +180,7 @@ public void testRelative() throws Exception { } - @Test + @Test(groups = "unit") public void testBeforeFirst() throws Exception { String response = "SiteName\tcount()\n" + diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseBigDecimalParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseBigDecimalParserTest.java index fce72feb6..425a19771 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseBigDecimalParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseBigDecimalParserTest.java @@ -19,13 +19,13 @@ public class ClickHouseBigDecimalParserTest { private ClickHouseValueParser parser; private ClickHouseColumnInfo columnInfo; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() throws Exception { parser = ClickHouseValueParser.getParser(BigDecimal.class); columnInfo = ClickHouseColumnInfo.parse("Float64", "column_name", null); } - @Test + @Test(groups = "unit") public void testParseBigDecimal() throws Exception { assertNull(parser.parse(ByteFragment.fromString("\\N"), columnInfo, null)); assertNull(parser.parse(ByteFragment.fromString(""), columnInfo, null)); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseDoubleParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseDoubleParserTest.java index 5f667255f..551e772d0 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseDoubleParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseDoubleParserTest.java @@ -14,13 +14,13 @@ public class ClickHouseDoubleParserTest { private ClickHouseDoubleParser parser; private ClickHouseColumnInfo columnInfo; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { parser = ClickHouseDoubleParser.getInstance(); columnInfo = ClickHouseColumnInfo.parse("Float64", "columnName", null); } - @Test + @Test(groups = "unit") public void testParseDouble() throws Exception { assertNull(parse("\\N")); assertEquals(parse("0"), Double.valueOf(0.0)); @@ -33,7 +33,7 @@ public void testParseDouble() throws Exception { assertEquals(parse("-inf"), Double.valueOf(Double.NEGATIVE_INFINITY)); } - @Test + @Test(groups = "unit") public void testParseDefault() throws Exception { assertEquals(parseWithDefault("\\N"), Double.valueOf(0)); assertEquals(parseWithDefault("nan"), Double.valueOf(Double.NaN)); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseInstantParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseInstantParserTest.java index 23efb00ee..2e09b0eed 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseInstantParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseInstantParserTest.java @@ -21,14 +21,14 @@ public class ClickHouseInstantParserTest { private TimeZone tzBerlin; private ClickHouseInstantParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { parser = ClickHouseInstantParser.getInstance(); tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); } - @Test + @Test(groups = "unit") public void testParseInstantDateTime() throws Exception { Instant inst = parser.parse( ByteFragment.fromString("2020-01-20 22:23:24"), @@ -44,7 +44,7 @@ public void testParseInstantDateTime() throws Exception { 1579587804); } - @Test + @Test(groups = "unit") public void testParseInstantDateTimeColumnOverride() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "DateTime(Europe/Berlin)", "col", TimeZone.getTimeZone("Asia/Chongqing")); @@ -55,7 +55,7 @@ public void testParseInstantDateTimeColumnOverride() throws Exception { 1579555404); } - @Test + @Test(groups = "unit") public void testParseInstantDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "Date", "col", null); @@ -67,6 +67,7 @@ public void testParseInstantDate() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseInstantTimestampSeconds(ClickHouseDataType dataType) throws Exception { @@ -85,6 +86,7 @@ public void testParseInstantTimestampSeconds(ClickHouseDataType dataType) throws } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseInstantTimestampMillis(ClickHouseDataType dataType) throws Exception { @@ -102,7 +104,7 @@ public void parseInstantTimestampMillis(ClickHouseDataType dataType) throws Exce 1579507200); } - @Test + @Test(groups = "unit") public void testParseInstantString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "String", "col", null); @@ -118,7 +120,7 @@ public void testParseInstantString() throws Exception { 1579555404); } - @Test + @Test(groups = "unit") public void testParseInstantUInt64Overflow() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "UInt64", "col", null); @@ -135,7 +137,7 @@ public void testParseInstantUInt64Overflow() throws Exception { 2262, 4, 11, 23, 47, 16, 988000000, ZoneId.of("UTC"))); } - @Test + @Test(groups = "unit") public void testParseInstantUInt64Millis() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "UInt64", "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateParserTest.java index 582c5d21b..4992d8894 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateParserTest.java @@ -23,14 +23,14 @@ public class ClickHouseLocalDateParserTest { private TimeZone tzBerlin; private ClickHouseLocalDateParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); parser = ClickHouseLocalDateParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseLocalDateNull() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); @@ -42,7 +42,7 @@ public void testParseLocalDateNull() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseLocalDateDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); @@ -64,7 +64,7 @@ public void testParseLocalDateDate() throws Exception { ByteFragment.fromString("0000-00-00"), columnInfo, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseLocalDateDateNullable() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Nullable(Date)", "col", null); @@ -73,7 +73,7 @@ public void testParseLocalDateDateNullable() throws Exception { ByteFragment.fromString("0000-00-00"), columnInfo, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseLocalDateDateTime() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime", "col", null); @@ -94,7 +94,7 @@ public void testParseLocalDateDateTime() throws Exception { ByteFragment.fromString("0000-00-00 00:00:00"), columnInfo, null)); } - @Test + @Test(groups = "unit") public void testParseLocalDateDateTimeTZColumn() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime(Europe/Berlin)", "col", null); @@ -116,6 +116,7 @@ public void testParseLocalDateDateTimeTZColumn() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseLocalDateNumber(ClickHouseDataType dataType) throws Exception { @@ -164,7 +165,7 @@ public void testParseLocalDateNumber(ClickHouseDataType dataType) throws Excepti ByteFragment.fromString(String.valueOf(0)), columnInfo, tzBerlin)); } - @Test + @Test(groups = "unit") public void testParseLocalDateNumberNegative() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Int64.name(), "col", null); @@ -175,7 +176,7 @@ public void testParseLocalDateNumberNegative() throws Exception { LocalDate.of(1957, 10, 4)); } - @Test + @Test(groups = "unit") public void testParseLocalDateOtherLikeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Unknown.name(), "col", null); @@ -199,7 +200,7 @@ public void testParseLocalDateOtherLikeDate() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseLocalDateOtherLikeDateTime() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Unknown.name(), "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateTimeParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateTimeParserTest.java index 3937888a6..8ebef8beb 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateTimeParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalDateTimeParserTest.java @@ -24,14 +24,14 @@ public class ClickHouseLocalDateTimeParserTest { private TimeZone tzBerlin; private ClickHouseLocalDateTimeParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); parser = ClickHouseLocalDateTimeParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeNull() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); @@ -44,7 +44,7 @@ public void testParseLocalDateTimeNull() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); @@ -66,7 +66,7 @@ public void testParseLocalDateTimeDate() throws Exception { ByteFragment.fromString("0000-00-00"), columnInfo, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeDateTimeNullable() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Nullable(DateTime)", "col", null); @@ -75,7 +75,7 @@ public void testParseLocalDateTimeDateTimeNullable() throws Exception { ByteFragment.fromString("0000-00-00 00:00:00"), columnInfo, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeDateTime() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime", "col", null); @@ -99,7 +99,7 @@ public void testParseLocalDateTimeDateTime() throws Exception { /* * No automatic conversion into any time zone, simply local date time */ - @Test + @Test(groups = "unit") public void testParseLocalDateTimeDateTimeTZColumn() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime(Europe/Berlin)", "col", null); @@ -121,6 +121,7 @@ public void testParseLocalDateTimeDateTimeTZColumn() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseLocalDateTimeNumber(ClickHouseDataType dataType) throws Exception { @@ -180,7 +181,7 @@ public void testParseLocalDateTimeNumber(ClickHouseDataType dataType) throws Exc ByteFragment.fromString(String.valueOf(0)), columnInfo, tzBerlin)); } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeNumberNegative() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Int64.name(), "col", null); @@ -190,7 +191,7 @@ public void testParseLocalDateTimeNumberNegative() throws Exception { LocalDate.of(1957, 10, 4).atStartOfDay()); } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeOtherLikeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Unknown.name(), "col", null); @@ -214,7 +215,7 @@ public void testParseLocalDateTimeOtherLikeDate() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseLocalDateTimeOtherLikeDateTime() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Unknown.name(), "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalTimeParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalTimeParserTest.java index 75fd2d782..7f3b15757 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalTimeParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseLocalTimeParserTest.java @@ -21,14 +21,14 @@ public class ClickHouseLocalTimeParserTest { private TimeZone tzLosAngeles; private ClickHouseLocalTimeParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); parser = ClickHouseLocalTimeParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseLocalTimeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); assertEquals( @@ -41,7 +41,7 @@ public void testParseLocalTimeDate() throws Exception { LocalTime.MIDNIGHT); } - @Test + @Test(groups = "unit") public void testParseLocalTimeDateTime() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime", "col", null); assertEquals( @@ -57,7 +57,7 @@ public void testParseLocalTimeDateTime() throws Exception { /* * No automatic conversion into any time zone, simply local time */ - @Test + @Test(groups = "unit") public void testParseLocalTimeDateTimeColumnTimeZone() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime(Asia/Vladivostok)", "col", null); @@ -72,6 +72,7 @@ public void testParseLocalTimeDateTimeColumnTimeZone() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseLocalTimeNumber(ClickHouseDataType dataType) throws Exception { @@ -113,7 +114,7 @@ public void testParseLocalTimeNumber(ClickHouseDataType dataType) throws Excepti } } - @Test + @Test(groups = "unit") public void testParseLocalTimeString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.String.name(), "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseMapParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseMapParserTest.java index 73681ec79..0d2368ba9 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseMapParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseMapParserTest.java @@ -14,7 +14,7 @@ import static org.testng.Assert.assertEquals; public class ClickHouseMapParserTest { - @Test + @Test(groups = "unit") public void testReadPart() throws Exception { ClickHouseMapParser parser = ClickHouseMapParser.getInstance(); String s; @@ -101,7 +101,7 @@ public void testReadPart() throws Exception { assertEquals(sb.toString(), s); } - @Test + @Test(groups = "unit") public void testParse() throws Exception { ClickHouseMapParser parser = ClickHouseMapParser.getInstance(); Map result = parser.parse(ByteFragment.fromString("{'a': 1, 'a''\\\\\\'b':2}"), diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetDateTimeParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetDateTimeParserTest.java index 6310440a2..9b62dcc8c 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetDateTimeParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetDateTimeParserTest.java @@ -18,14 +18,14 @@ public class ClickHouseOffsetDateTimeParserTest { private TimeZone tzBerlin; private ClickHouseOffsetDateTimeParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); parser = ClickHouseOffsetDateTimeParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseOffsetDateTimeDateTime() throws Exception { OffsetDateTime inst = parser.parse( ByteFragment.fromString("2020-01-20 22:23:24"), @@ -41,7 +41,7 @@ public void testParseOffsetDateTimeDateTime() throws Exception { 1579587804); } - @Test + @Test(groups = "unit") public void testParseOffsetDateTimeDateTimeColumnOverride() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "DateTime(Europe/Berlin)", "col", null); @@ -52,7 +52,7 @@ public void testParseOffsetDateTimeDateTimeColumnOverride() throws Exception { 1579555404); } - @Test + @Test(groups = "unit") public void testParseOffsetDateTimeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "Date", "col", null); @@ -64,6 +64,7 @@ public void testParseOffsetDateTimeDate() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseOffsetDateTimeTimestampSeconds(ClickHouseDataType dataType) throws Exception { @@ -82,6 +83,7 @@ public void parseOffsetDateTimeTimestampSeconds(ClickHouseDataType dataType) thr } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseOffsetDateTimeTimestampMillis(ClickHouseDataType dataType) throws Exception { @@ -99,7 +101,7 @@ public void parseOffsetDateTimeTimestampMillis(ClickHouseDataType dataType) thro 1579507200); } - @Test + @Test(groups = "unit") public void testParseOffsetDateTimeString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "String", "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetTimeParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetTimeParserTest.java index d5fd005d9..7d89e1b60 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetTimeParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseOffsetTimeParserTest.java @@ -30,7 +30,7 @@ public class ClickHouseOffsetTimeParserTest { private ZoneOffset offsetJVM; private ClickHouseOffsetTimeParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); offsetBerlin = tzBerlin.toZoneId().getRules().getOffset( @@ -42,7 +42,7 @@ public void setUp() { parser = ClickHouseOffsetTimeParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseOffsetTimeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); assertEquals( @@ -55,7 +55,7 @@ public void testParseOffsetTimeDate() throws Exception { LocalTime.MIDNIGHT.atOffset(offsetLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseOffsetTimeDateTime() throws Exception { assertEquals( parser.parse( @@ -69,7 +69,7 @@ public void testParseOffsetTimeDateTime() throws Exception { LocalTime.of(22, 23, 24).atOffset(offsetLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseOffsetTimeRegular() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("String", "col", null); assertEquals( @@ -80,7 +80,7 @@ public void testParseOffsetTimeRegular() throws Exception { ZoneOffset.ofHours(7))); } - @Test + @Test(groups = "unit") public void testParseOffsetTimeDateTimeColumnTimeZone() throws Exception { LocalDateTime localDateTime = LocalDateTime.of(LocalDate.of(2020,01,17), LocalTime.MIDNIGHT); ZoneOffset offsetVladivostok = ZoneId.of("Asia/Vladivostok").getRules().getOffset(localDateTime); @@ -99,6 +99,7 @@ public void testParseOffsetTimeDateTimeColumnTimeZone() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseOffsetTimeNumber(ClickHouseDataType dataType) throws Exception { @@ -140,7 +141,7 @@ public void testParseOffsetTimeNumber(ClickHouseDataType dataType) throws Except } } - @Test + @Test(groups = "unit") public void testParseOffsetTimeString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.String.name(), "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLDateParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLDateParserTest.java index 63e2c92b7..319f71940 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLDateParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLDateParserTest.java @@ -26,14 +26,14 @@ public class ClickHouseSQLDateParserTest { private TimeZone tzBerlin; private ClickHouseSQLDateParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); parser = ClickHouseSQLDateParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseSQLDateNull() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); @@ -46,7 +46,7 @@ public void testParseSQLDateNull() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseSQLDateDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); @@ -80,7 +80,7 @@ public void testParseSQLDateDate() throws Exception { ByteFragment.fromString("0000-00-00"), columnInfo, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseSQLDateDateNullable() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Nullable(Date)", "col", null); @@ -89,7 +89,7 @@ public void testParseSQLDateDateNullable() throws Exception { ByteFragment.fromString("0000-00-00"), columnInfo, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseSQLDateDateTime() throws Exception { assertEquals( parser.parse( @@ -124,7 +124,7 @@ public void testParseSQLDateDateTime() throws Exception { ClickHouseColumnInfo.parse("DateTime", "col", TimeZone.getDefault()), null)); } - @Test + @Test(groups = "unit") public void testParseSQLDateDateTimeTZColumn() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime(Europe/Berlin)", "col", TimeZone.getTimeZone("Asia/Chongqing")); @@ -162,7 +162,7 @@ public void testParseSQLDateDateTimeTZColumn() throws Exception { ByteFragment.fromString("0000-00-00 00:00:00"), columnInfo, null)); } - @Test + @Test(groups = "unit") public void testParseSQLDateDateTimeOtherTZColumn() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime(Asia/Vladivostok)", "col", TimeZone.getTimeZone("Europe/Moscow")); @@ -196,6 +196,7 @@ public void testParseSQLDateDateTimeOtherTZColumn() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseSQLDateNumber(ClickHouseDataType dataType) throws Exception { @@ -251,7 +252,7 @@ public void testParseSQLDateNumber(ClickHouseDataType dataType) throws Exception ByteFragment.fromString(String.valueOf(0)), columnInfo, tzBerlin)); } - @Test + @Test(groups = "unit") public void testParseSQLDateNumberNegative() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Int64.name(), "col", null); @@ -268,7 +269,7 @@ public void testParseSQLDateNumberNegative() throws Exception { .toEpochMilli())); } - @Test + @Test(groups = "unit") public void testParseSQLDateOtherLikeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Unknown.name(), "col", null); @@ -296,7 +297,7 @@ public void testParseSQLDateOtherLikeDate() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseSQLDateOtherLikeDateTime() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.Unknown.name(), "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimeParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimeParserTest.java index 7d452edcb..b0eaec32a 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimeParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimeParserTest.java @@ -24,14 +24,14 @@ public class ClickHouseSQLTimeParserTest { private TimeZone tzLosAngeles; private ClickHouseSQLTimeParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); parser = ClickHouseSQLTimeParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseTimeTimeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Date", "col", null); assertEquals( @@ -44,7 +44,7 @@ public void testParseTimeTimeDate() throws Exception { createExpectedTime(LocalTime.MIDNIGHT, tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseOffsetTimeDateTime() throws Exception { assertEquals( parser.parse( @@ -58,7 +58,7 @@ public void testParseOffsetTimeDateTime() throws Exception { createExpectedTime(LocalTime.of(22, 23, 24), tzLosAngeles)); } - @Test + @Test(groups = "unit") public void testParseOffsetTimeDateTimeColumnTimeZone() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("DateTime(Asia/Vladivostok)", "col", null); @@ -78,6 +78,7 @@ public void testParseOffsetTimeDateTimeColumnTimeZone() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void testParseOffsetTimeNumber(ClickHouseDataType dataType) throws Exception { @@ -119,7 +120,7 @@ public void testParseOffsetTimeNumber(ClickHouseDataType dataType) throws Except } } - @Test + @Test(groups = "unit") public void testParseOffsetTimeString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(ClickHouseDataType.String.name(), "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimestampParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimestampParserTest.java index 49417c54e..537f617ba 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimestampParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseSQLTimestampParserTest.java @@ -18,14 +18,14 @@ public class ClickHouseSQLTimestampParserTest { private TimeZone tzBerlin; private ClickHouseSQLTimestampParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); parser = ClickHouseSQLTimestampParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseTimestampDateTime() throws Exception { Timestamp inst = parser.parse( ByteFragment.fromString("2020-01-20 22:23:24"), @@ -41,7 +41,7 @@ public void testParseTimestampDateTime() throws Exception { 1579587804000L); } - @Test + @Test(groups = "unit") public void testParseTimestampDateTimeColumnOverride() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "DateTime(Europe/Berlin)", "col", TimeZone.getTimeZone("Asia/Chongqing")); @@ -52,7 +52,7 @@ public void testParseTimestampDateTimeColumnOverride() throws Exception { 1579555404000L); } - @Test + @Test(groups = "unit") public void testParseTimestampDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "Date", "col", null); @@ -64,6 +64,7 @@ public void testParseTimestampDate() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseTimestampTimestampSeconds(ClickHouseDataType dataType) throws Exception { @@ -82,6 +83,7 @@ public void parseTimestampTimestampSeconds(ClickHouseDataType dataType) throws E } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseTimestampTimestampMillis(ClickHouseDataType dataType) throws Exception { @@ -99,7 +101,7 @@ public void parseTimestampTimestampMillis(ClickHouseDataType dataType) throws Ex 1579507200000L); } - @Test + @Test(groups = "unit") public void testParseTimestampString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "String", "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseStringParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseStringParserTest.java index 52e8a5115..43f3e5c05 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseStringParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseStringParserTest.java @@ -14,13 +14,13 @@ public class ClickHouseStringParserTest { private ClickHouseStringParser parser; private ClickHouseColumnInfo columnInfo; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { parser = ClickHouseStringParser.getInstance(); columnInfo = ClickHouseColumnInfo.parse("String", "column_name", null); } - @Test + @Test(groups = "unit") public void testParseString() throws Exception { assertNull(parser.parse(ByteFragment.fromString("\\N"), columnInfo, null)); assertEquals(parser.parse(ByteFragment.fromString(""), columnInfo, null), ""); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseUUIDParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseUUIDParserTest.java index 27770a17e..2ac6b5747 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseUUIDParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseUUIDParserTest.java @@ -17,24 +17,24 @@ public class ClickHouseUUIDParserTest { private ClickHouseValueParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() throws Exception { parser = ClickHouseValueParser.getParser(UUID.class); } - @Test + @Test(groups = "unit") public void testNullValue() throws Exception { assertNull(parser.parse( ByteFragment.fromString("\\N"), null, null)); } - @Test + @Test(groups = "unit") public void testEmptyValue() throws Exception { assertNull(parser.parse( ByteFragment.fromString(""), null, null)); } - @Test + @Test(groups = "unit") public void testSimpleUUID() throws Exception { UUID uuid = UUID.randomUUID(); assertEquals( @@ -47,7 +47,7 @@ public void testSimpleUUID() throws Exception { UUID.randomUUID()); } - @Test + @Test(groups = "unit") public void testBrokenUUID() throws Exception { try { parser.parse( diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseValueParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseValueParserTest.java index 3ff78f943..8a5eaf11f 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseValueParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseValueParserTest.java @@ -14,7 +14,7 @@ public class ClickHouseValueParserTest { - @Test + @Test(groups = "unit") public void testParseInt() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Int64", "columnName", null); assertEquals(ClickHouseValueParser.parseInt(ByteFragment.fromString("42"), columnInfo), 42); @@ -57,7 +57,7 @@ public void testParseInt() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseLong() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Int64", "columnName", null); assertEquals(ClickHouseValueParser.parseLong(ByteFragment.fromString("42"), columnInfo), 42); @@ -101,7 +101,7 @@ public void testParseLong() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseShort() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("UInt16", "columnName", null); assertEquals(ClickHouseValueParser.parseShort(ByteFragment.fromString("42"), columnInfo), 42); @@ -144,7 +144,7 @@ public void testParseShort() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseBoolean() throws SQLException { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("UInt8", "columnName", null); assertFalse(ClickHouseValueParser.parseBoolean(ByteFragment.fromString(""), columnInfo)); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParserTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParserTest.java index e75b0260b..62772b069 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParserTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParserTest.java @@ -18,14 +18,14 @@ public class ClickHouseZonedDateTimeParserTest { private TimeZone tzBerlin; private ClickHouseZonedDateTimeParser parser; - @BeforeClass + @BeforeClass(groups = "unit") public void setUp() { tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); parser = ClickHouseZonedDateTimeParser.getInstance(); } - @Test + @Test(groups = "unit") public void testParseZonedDateTimeDateTime() throws Exception { ZonedDateTime inst = parser.parse( ByteFragment.fromString("2020-01-20 22:23:24"), @@ -41,7 +41,7 @@ public void testParseZonedDateTimeDateTime() throws Exception { 1579587804); } - @Test + @Test(groups = "unit") public void testParseZonedDateTimeDateTimeColumnOverride() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "DateTime(Europe/Berlin)", "col", TimeZone.getTimeZone("Asia/Chongqing")); @@ -52,7 +52,7 @@ public void testParseZonedDateTimeDateTimeColumnOverride() throws Exception { 1579555404); } - @Test + @Test(groups = "unit") public void testParseZonedDateTimeDate() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "Date", "col", null); @@ -64,6 +64,7 @@ public void testParseZonedDateTimeDate() throws Exception { } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseZonedDateTimeTimestampSeconds(ClickHouseDataType dataType) throws Exception { @@ -82,6 +83,7 @@ public void parseZonedDateTimeTimestampSeconds(ClickHouseDataType dataType) thro } @Test( + groups = "unit", dataProvider = ClickHouseTimeParserTestDataProvider.OTHER_DATA_TYPES, dataProviderClass = ClickHouseTimeParserTestDataProvider.class) public void parseZonedDateTimeTimestampMillis(ClickHouseDataType dataType) throws Exception { @@ -99,7 +101,7 @@ public void parseZonedDateTimeTimestampMillis(ClickHouseDataType dataType) throw 1579507200); } - @Test + @Test(groups = "unit") public void testParseZonedDateTimeString() throws Exception { ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse( "String", "col", null); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/settings/ClickHousePropertiesTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/settings/ClickHousePropertiesTest.java index 7818fa969..11e3f5f1d 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/settings/ClickHousePropertiesTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/settings/ClickHousePropertiesTest.java @@ -21,7 +21,7 @@ public class ClickHousePropertiesTest { * of {@link Properties} with defaults. These defaults may be missed if method * {@link java.util.Hashtable#get(Object)} is used for {@code Properties}. */ - @Test + @Test(groups = "unit") public void constructorShouldNotIgnoreDefaults() { Properties defaults = new Properties(); String expectedUsername = "superuser"; @@ -32,7 +32,7 @@ public void constructorShouldNotIgnoreDefaults() { Assert.assertEquals(clickHouseProperties.getUser(), expectedUsername); } - @Test + @Test(groups = "unit") public void constructorShouldNotIgnoreClickHouseProperties() { int expectedConnectionTimeout = 1000; boolean isCompress = false; @@ -95,7 +95,7 @@ public void constructorShouldNotIgnoreClickHouseProperties() { ); } - @Test + @Test(groups = "unit") public void additionalParametersTest_clickhouse_datasource() { ClickHouseDataSource clickHouseDataSource = new ClickHouseDataSource("jdbc:clickhouse://localhost:1234/ppc?compress=1&decompress=1&user=root"); @@ -104,7 +104,7 @@ public void additionalParametersTest_clickhouse_datasource() { assertEquals("root", clickHouseDataSource.getProperties().getUser()); } - @Test + @Test(groups = "unit") public void additionalParametersTest_balanced_clickhouse_datasource() { BalancedClickhouseDataSource clickHouseDataSource = new BalancedClickhouseDataSource("jdbc:clickhouse://localhost:1234,another.host.com:4321/ppc?compress=1&decompress=1&user=root"); @@ -113,21 +113,21 @@ public void additionalParametersTest_balanced_clickhouse_datasource() { assertEquals("root", clickHouseDataSource.getProperties().getUser()); } - @Test + @Test(groups = "unit") public void booleanParamCanBeParsedAsZeroAndOne() throws Exception { Assert.assertTrue(new ClickHouseProperties().isCompress()); Assert.assertFalse(new ClickHouseProperties(new Properties(){{setProperty("compress", "0");}}).isCompress()); Assert.assertTrue(new ClickHouseProperties(new Properties(){{setProperty("compress", "1");}}).isCompress()); } - @Test + @Test(groups = "unit") public void clickHouseQueryParamContainsMaxMemoryUsage() throws Exception { final ClickHouseProperties clickHouseProperties = new ClickHouseProperties(); clickHouseProperties.setMaxMemoryUsage(43L); Assert.assertEquals(clickHouseProperties.asProperties().getProperty("max_memory_usage"), "43"); } - @Test + @Test(groups = "unit") public void maxMemoryUsageParamShouldBeParsed() throws Exception { final Properties driverProperties = new Properties(); driverProperties.setProperty("max_memory_usage", "42"); @@ -136,7 +136,7 @@ public void maxMemoryUsageParamShouldBeParsed() throws Exception { Assert.assertEquals(ds.getProperties().getMaxMemoryUsage(), Long.valueOf(42L), "max_memory_usage is missing"); } - @Test + @Test(groups = "unit") public void buildQueryParamsTest() { ClickHouseProperties clickHouseProperties = new ClickHouseProperties(); clickHouseProperties.setInsertQuorumTimeout(1000L); @@ -159,7 +159,7 @@ public void buildQueryParamsTest() { assertFalse(clickHouseQueryParams.containsKey(ClickHouseQueryParam.PASSWORD)); } - @Test + @Test(groups = "unit") public void mergeClickHousePropertiesTest() { ClickHouseProperties clickHouseProperties1 = new ClickHouseProperties(); ClickHouseProperties clickHouseProperties2 = new ClickHouseProperties(); @@ -176,7 +176,7 @@ public void mergeClickHousePropertiesTest() { Assert.assertEquals(merged.getUser(),"readonly"); // using properties2 } - @Test + @Test(groups = "unit") public void mergePropertiesTest() { ClickHouseProperties clickHouseProperties1 = new ClickHouseProperties(); Properties properties2 = new Properties(); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseArrayUtilTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseArrayUtilTest.java index d64a2052a..be83b1a60 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseArrayUtilTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseArrayUtilTest.java @@ -27,7 +27,7 @@ */ public class ClickHouseArrayUtilTest { - @Test + @Test(groups = "unit") public void testArrayToString() throws Exception { assertEquals( ClickHouseArrayUtil.arrayToString(new String[]{"a", "b"}), @@ -101,7 +101,7 @@ public void testArrayToString() throws Exception { } - @Test + @Test(groups = "unit") public void testCollectionToString() throws Exception { assertEquals( ClickHouseArrayUtil.toString(new ArrayList(Arrays.asList("a", "b"))), @@ -215,7 +215,7 @@ public void testCollectionToString() throws Exception { ); } - @Test + @Test(groups = "unit") public void testArrayDateTimeDefaultTimeZone() { Timestamp ts0 = new Timestamp(1557136800000L); Timestamp ts1 = new Timestamp(1560698526598L); @@ -227,7 +227,7 @@ public void testArrayDateTimeDefaultTimeZone() { "['" + sdf.format(ts0) + "',NULL,'" + sdf.format(ts1) + "']"); } - @Test + @Test(groups = "unit") public void testArrayDateTimeOtherTimeZone() { TimeZone tzTokyo = TimeZone.getTimeZone("Asia/Tokyo"); Timestamp ts0 = new Timestamp(1557136800000L); @@ -241,7 +241,7 @@ public void testArrayDateTimeOtherTimeZone() { } - @Test(dataProvider = "doubleArrayWithNan") + @Test(groups = "unit", dataProvider = "doubleArrayWithNan") public void testDoubleNan(String[] source, double[] expected) throws Exception { StringBuilder sb = new StringBuilder(); @@ -269,7 +269,7 @@ public void testDoubleNan(String[] source, double[] expected) throws Exception } } - @Test(dataProvider = "floatArrayWithNan") + @Test(groups = "unit", dataProvider = "floatArrayWithNan") public void testFloatNan(String[] source, float[] expected) throws Exception { StringBuilder sb = new StringBuilder(); @@ -296,7 +296,7 @@ public void testFloatNan(String[] source, float[] expected) throws Exception } } - @Test(dataProvider = "stringArray") + @Test(groups = "unit", dataProvider = "stringArray") public void testParseArray(String[] array) throws Exception { StringBuilder sb = new StringBuilder(); for (String s : array) { @@ -319,7 +319,7 @@ public void testParseArray(String[] array) throws Exception { } } - @Test(dataProvider = "intBoxedArray") + @Test(groups = "unit", dataProvider = "intBoxedArray") public void testParseBoxedArray(Integer[] array) throws Exception { StringBuilder sb = new StringBuilder(); for (Integer i : array) { @@ -341,7 +341,7 @@ public void testParseBoxedArray(Integer[] array) throws Exception { } } - @Test(dataProvider = "longArray") + @Test(groups = "unit", dataProvider = "longArray") public void testParseArray(long[] array) throws Exception { StringBuilder sb = new StringBuilder(); for (long l : array) { @@ -363,7 +363,7 @@ public void testParseArray(long[] array) throws Exception { } } - @Test(dataProvider = "floatArray") + @Test(groups = "unit", dataProvider = "floatArray") public void testParseArray(float[] array) throws Exception { StringBuilder sb = new StringBuilder(); for (float f : array) { @@ -385,7 +385,7 @@ public void testParseArray(float[] array) throws Exception { } } - @Test(dataProvider = "doubleArray") + @Test(groups = "unit", dataProvider = "doubleArray") public void testParseArray(double[] array) throws Exception { StringBuilder sb = new StringBuilder(); for (double d : array) { @@ -407,7 +407,7 @@ public void testParseArray(double[] array) throws Exception { } } - @Test(dataProvider = "booleanArray") + @Test(groups = "unit", dataProvider = "booleanArray") public void testParseArray(String[] input, boolean[] array) throws Exception { String sourceString = "[" + String.join(",", input) + "]"; byte[] bytes = sourceString.getBytes(StandardCharsets.UTF_8); @@ -421,7 +421,7 @@ public void testParseArray(String[] input, boolean[] array) throws Exception { } - @Test(dataProvider = "dateArray") + @Test(groups = "unit", dataProvider = "dateArray") public void testParseArray(Date[] array) throws Exception { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -445,7 +445,7 @@ public void testParseArray(Date[] array) throws Exception { } } - @Test(dataProvider = "decimalArray") + @Test(groups = "unit", dataProvider = "decimalArray") public void testParseArray(BigDecimal[] array) throws Exception { StringBuilder sb = new StringBuilder(); for (BigDecimal d : array) { @@ -467,7 +467,7 @@ public void testParseArray(BigDecimal[] array) throws Exception { } } - @Test + @Test(groups = "unit") public void testParseArrayThreeLevels() throws Exception { int[][][] expected = {{{10,11,12},{13,14,15}},{{20,21,22},{23,24,25}},{{30,31,32},{33,34,35}}}; String sourceString = "[[[10,11,12],[13,14,15]],[[20,21,22],[23,24,25]],[[30,31,32],[33,34,35]]]"; @@ -486,7 +486,7 @@ public void testParseArrayThreeLevels() throws Exception { } } - @Test + @Test(groups = "unit") public void testParseArrayTwoLevelsEmpty() throws Exception { String sourceString = "[[]]"; byte[] bytes = sourceString.getBytes(StandardCharsets.UTF_8); @@ -497,7 +497,7 @@ public void testParseArrayTwoLevelsEmpty() throws Exception { assertEquals(0, actual[0].length); } - @Test + @Test(groups = "unit") public void testParseSparseArray() throws Exception { String sourceString = "[[],[NULL],['a','b',NULL]]"; byte[] bytes = sourceString.getBytes(StandardCharsets.UTF_8); @@ -514,7 +514,7 @@ public void testParseSparseArray() throws Exception { assertNull(actual[2][2]); } - @Test + @Test(groups = "unit") public void testParseArrayOf32Levels() throws Exception { String sourceString = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[32]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"; byte[] bytes = sourceString.getBytes(StandardCharsets.UTF_8); @@ -526,7 +526,7 @@ public void testParseArrayOf32Levels() throws Exception { assertEquals(actual[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0], 32); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Maximum parse depth exceeded") + @Test(groups = "unit", expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Maximum parse depth exceeded") public void testParseArrayMaximumDepthExceeded() throws SQLException { String sourceString = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[33]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"; byte[] bytes = sourceString.getBytes(StandardCharsets.UTF_8); @@ -536,6 +536,7 @@ public void testParseArrayMaximumDepthExceeded() throws SQLException { @Test( + groups = "unit", dataProvider = "invalidArray", expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "not an array.*" diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBitmapTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBitmapTest.java index 87b045c2c..6be49e923 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBitmapTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBitmapTest.java @@ -10,7 +10,7 @@ import ru.yandex.clickhouse.domain.ClickHouseDataType; public class ClickHouseBitmapTest { - @Test + @Test(groups = "unit") public void testEmptyRoaringBitmap() { byte[] expectedBytes = new byte[] { 0, 0 }; diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBlockChecksumTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBlockChecksumTest.java index dded8e3cb..9dd04715f 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBlockChecksumTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseBlockChecksumTest.java @@ -13,7 +13,7 @@ public class ClickHouseBlockChecksumTest { private static final int HEADER_SIZE_BYTES = 9; - @Test + @Test(groups = "unit") public void trickyBlock() { byte[] compressedData = DatatypeConverter.parseHexBinary("1F000100078078000000B4000000"); int uncompressedSizeBytes = 35; @@ -32,7 +32,7 @@ public void trickyBlock() { ); } - @Test + @Test(groups = "unit") public void anotherTrickyBlock() { byte[] compressedData = DatatypeConverter.parseHexBinary("80D9CEF753E3A59B3F"); int uncompressedSizeBytes = 8; diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseCookieStoreProviderTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseCookieStoreProviderTest.java index 41500a9ae..a21258e39 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseCookieStoreProviderTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseCookieStoreProviderTest.java @@ -11,7 +11,7 @@ public class ClickHouseCookieStoreProviderTest { ClickHouseCookieStoreProvider cookieStoreProvider = new ClickHouseCookieStoreProvider(); - @Test + @Test(groups = "unit") public void testCookieStoreProviderWithNullHost() { ClickHouseProperties props = new ClickHouseProperties(); props.setUseSharedCookieStore(true); @@ -20,7 +20,7 @@ public void testCookieStoreProviderWithNullHost() { assertNull(cookieStoreProvider.getCookieStore(props)); } - @Test + @Test(groups = "unit") public void testCookieStoreProviderWithInvalidPort() { ClickHouseProperties props = new ClickHouseProperties(); props.setUseSharedCookieStore(true); @@ -30,7 +30,7 @@ public void testCookieStoreProviderWithInvalidPort() { assertNull(cookieStoreProvider.getCookieStore(props)); } - @Test + @Test(groups = "unit") public void testCookieStoreProviderWithNullDBName() { ClickHouseProperties props = new ClickHouseProperties(); props.setUseSharedCookieStore(true); @@ -39,7 +39,7 @@ public void testCookieStoreProviderWithNullDBName() { assertNull(cookieStoreProvider.getCookieStore(props)); } - @Test + @Test(groups = "unit") public void testCookieStoreProviderWithSameDBAndSharedCookieStore() { ClickHouseProperties props = new ClickHouseProperties(); props.setUseSharedCookieStore(true); @@ -50,7 +50,7 @@ public void testCookieStoreProviderWithSameDBAndSharedCookieStore() { assertEquals(cookieStoreProvider.getCookieStore(props), cookieStoreProvider.getCookieStore(props)); } - @Test + @Test(groups = "unit") public void testCookieStoreProviderWithPrivateCookieStore() { ClickHouseProperties props = new ClickHouseProperties(); props.setUseSharedCookieStore(false); @@ -60,7 +60,7 @@ public void testCookieStoreProviderWithPrivateCookieStore() { assertNull(cookieStoreProvider.getCookieStore(props)); } - @Test + @Test(groups = "unit") public void testCookieStoreProviderWithDiffDB() { ClickHouseProperties props1 = new ClickHouseProperties(); props1.setUseSharedCookieStore(true); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilderTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilderTest.java index 8ecbcd1f8..a04bfada5 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilderTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilderTest.java @@ -34,36 +34,36 @@ public class ClickHouseHttpClientBuilderTest { private static WireMockServer mockServer; - @BeforeClass + @BeforeClass(groups = "unit") public static void beforeAll() { mockServer = new WireMockServer( WireMockConfiguration.wireMockConfig().dynamicPort()); mockServer.start(); } - @AfterMethod + @AfterMethod(groups = "unit") public void afterTest() { mockServer.resetAll(); } - @AfterClass + @AfterClass(groups = "unit") public static void afterAll() { mockServer.stop(); mockServer = null; } - @Test + @Test(groups = "unit") public void testCreateClientContextNull() { assertNull(ClickHouseHttpClientBuilder.createClientContext(null).getAuthCache()); } - @Test + @Test(groups = "unit") public void testCreateClientContextNoUserNoPass() { assertNull(ClickHouseHttpClientBuilder.createClientContext(new ClickHouseProperties()) .getAuthCache()); } - @Test + @Test(groups = "unit") public void testCreateClientContextNoHost() { ClickHouseProperties props = new ClickHouseProperties(); props.setUser("myUser"); @@ -71,7 +71,7 @@ public void testCreateClientContextNoHost() { assertNull(ClickHouseHttpClientBuilder.createClientContext(props).getAuthCache()); } - @Test + @Test(groups = "unit") public void testCreateClientContextUserPass() { ClickHouseProperties props = new ClickHouseProperties(); props.setUser("myUser"); @@ -83,7 +83,7 @@ public void testCreateClientContextUserPass() { "basic"); } - @Test + @Test(groups = "unit") public void testCreateClientContextOnlyUser() { ClickHouseProperties props = new ClickHouseProperties(); props.setUser("myUser"); @@ -94,7 +94,7 @@ public void testCreateClientContextOnlyUser() { "basic"); } - @Test + @Test(groups = "unit") public void testCreateClientContextOnlyPass() { ClickHouseProperties props = new ClickHouseProperties(); props.setPassword("myPass"); @@ -105,7 +105,7 @@ public void testCreateClientContextOnlyPass() { "basic"); } - @Test + @Test(groups = "unit") public void testHttpClientsWithSharedCookie() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setHost("localhost"); @@ -131,7 +131,7 @@ public void testHttpClientsWithSharedCookie() throws Exception { mockServer.verify(getRequestedFor(WireMock.urlEqualTo("/cookie/check")).withHeader("cookie", equalTo(cookie))); } - @Test(dataProvider = "authUserPassword") + @Test(groups = "unit", dataProvider = "authUserPassword") public void testHttpAuthParametersCombination(String authorization, String user, String password, String expectedAuthHeader) throws Exception { @@ -203,7 +203,7 @@ public void run() { }.start(); } - // @Test(dependsOnMethods = { "testWithRetry" }, expectedExceptions = { NoHttpResponseException.class }) + // @Test(groups = "unit", dependsOnMethods = { "testWithRetry" }, expectedExceptions = { NoHttpResponseException.class }) public void testWithoutRetry() throws Exception { final WireMockServer server = newServer(); @@ -222,7 +222,7 @@ public void testWithoutRetry() throws Exception { } } - // @Test(expectedExceptions = { HttpHostConnectException.class }) + // @Test(groups = "unit", expectedExceptions = { HttpHostConnectException.class }) public void testWithRetry() throws Exception { final WireMockServer server = newServer(); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseLZ4OutputStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseLZ4OutputStreamTest.java index 8746941da..2a89a2c3c 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseLZ4OutputStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseLZ4OutputStreamTest.java @@ -21,7 +21,7 @@ private byte[] genCompressedByts(int b, int length, int blockSize) throws IOExce return bytes; } - @Test + @Test(groups = "unit") public void testWrite() throws IOException { ByteArrayOutputStream bas = new ByteArrayOutputStream(64); @@ -49,7 +49,7 @@ public void testWrite() throws IOException { } } - @Test + @Test(groups = "unit") public void testWriteBytes() throws IOException { Assert.assertThrows(NullPointerException.class, new Assert.ThrowingRunnable() { @Override @@ -118,7 +118,7 @@ public void run() throws Throwable { } } - @Test + @Test(groups = "unit") public void testWriteBytesWithOffset() throws IOException { Assert.assertThrows(NullPointerException.class, new Assert.ThrowingRunnable() { @Override diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryInputStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryInputStreamTest.java index 1da6d70a5..89affb342 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryInputStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryInputStreamTest.java @@ -13,7 +13,7 @@ public class ClickHouseRowBinaryInputStreamTest { - @Test + @Test(groups = "unit") public void testUInt8() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{1, 0, 1, 0, -1, -128}); @@ -25,7 +25,7 @@ public void testUInt8() throws Exception { assertEquals(input.readUInt8(), 128); } - @Test + @Test(groups = "unit") public void testUInt8AsByte() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{1, 0, 1, 0, -1, -128}); @@ -37,7 +37,7 @@ public void testUInt8AsByte() throws Exception { assertEquals(input.readUInt8AsByte(), (byte) 128); } - @Test + @Test(groups = "unit") public void testUInt16() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{0, 0, -1, -1, 0, -128}); @@ -46,7 +46,7 @@ public void testUInt16() throws Exception { assertEquals(input.readUInt16(), 32768); } - @Test + @Test(groups = "unit") public void testUInt16AsShort() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{0, 0, -1, -1, 0, -128}); @@ -56,14 +56,14 @@ public void testUInt16AsShort() throws Exception { } - @Test + @Test(groups = "unit") public void testFloat64() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{0, 0, 0, 0, 0, 0, -8, 127}); assertEquals(input.readFloat64(), Double.NaN); } - @Test + @Test(groups = "unit") public void testUInt64() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1}); @@ -71,7 +71,7 @@ public void testUInt64() throws Exception { assertEquals(input.readUInt64(), new BigInteger("18446744073709551615")); } - @Test + @Test(groups = "unit") public void testUInt64AsLong() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1}); @@ -79,7 +79,7 @@ public void testUInt64AsLong() throws Exception { assertEquals(input.readUInt64AsLong(), new BigInteger("18446744073709551615").longValue()); } - @Test + @Test(groups = "unit") public void testDecimal128() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{-10, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); assertEquals(input.readDecimal128(3), new BigDecimal("10.230")); @@ -87,19 +87,19 @@ public void testDecimal128() throws Exception { assertEquals(input2.readDecimal128(2), new BigDecimal("9999999999999.98")); } - @Test + @Test(groups = "unit") public void testDecimal64() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{-10, 39, 0, 0, 0, 0, 0, 0}); assertEquals(input.readDecimal64(3), new BigDecimal("10.23")); } - @Test + @Test(groups = "unit") public void testDecimal32() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{-10, 39, 0, 0}); assertEquals(input.readDecimal32(3), new BigDecimal("10.23")); } - @Test + @Test(groups = "unit") public void testFixedString() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{48, 49, 48, 49, 55, 49, 50, 50, 48, 48}); @@ -110,7 +110,7 @@ public void testFixedString() throws Exception { assertEquals(inputZeroPaddedString.readFixedString(10), "hello\0\0\0\0\0"); } - @Test + @Test(groups = "unit") public void testOne() throws Exception { ClickHouseRowBinaryInputStream input = prepareStream(new byte[]{5, 97, 46, 98, 46, 99, 123, 20, -82, 71, -31, 26, 69, 64, 34, 87, -13, 88, 120, 67, 48, 116, -13, 88}); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryStreamTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryStreamTest.java index fe0dd8b9f..1a9c9499d 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryStreamTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseRowBinaryStreamTest.java @@ -16,7 +16,7 @@ */ public class ClickHouseRowBinaryStreamTest { - @Test + @Test(groups = "unit") public void testUInt8() throws Exception { check( new StreamWriter() { @@ -34,7 +34,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testUInt16() throws Exception { check( new StreamWriter() { @@ -49,7 +49,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testFloat64() throws Exception { check( new StreamWriter() { @@ -63,7 +63,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { } - @Test + @Test(groups = "unit") public void testUInt64() throws Exception { check( new StreamWriter() { @@ -77,7 +77,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testDecimal32() throws Exception { check( new StreamWriter() { @@ -91,7 +91,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testDecimal64() throws Exception { check( new StreamWriter() { @@ -105,7 +105,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testDecimal128() throws Exception { check( new StreamWriter() { @@ -122,7 +122,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testDecimal256() throws Exception { check( new StreamWriter() { @@ -141,7 +141,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testOne() throws Exception { check( @@ -162,7 +162,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { } - @Test + @Test(groups = "unit") public void testUnsignedLeb128() throws Exception { check( new StreamWriter() { @@ -182,7 +182,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { } - @Test + @Test(groups = "unit") public void testNative() throws Exception { check( new StreamWriter() { @@ -213,7 +213,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { } - @Test + @Test(groups = "unit") public void testStringArray() throws Exception { check( new StreamWriter() { @@ -228,7 +228,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testUInt64Array() throws Exception { check( new StreamWriter() { @@ -243,7 +243,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testString() throws Exception { check( new StreamWriter() { @@ -262,7 +262,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testFixedString() throws Exception { check( new StreamWriter() { @@ -281,7 +281,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testFixedStringLen() throws Exception { check( new StreamWriter() { @@ -300,7 +300,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testFixedStringLen1() throws Exception { check( new StreamWriter() { @@ -318,7 +318,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testFixedStringLen2() throws Exception { check( new StreamWriter() { @@ -336,7 +336,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testUUID() throws Exception { check( new StreamWriter() { @@ -351,7 +351,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testUUIDArray() throws Exception { check( new StreamWriter() { @@ -370,7 +370,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { ); } - @Test + @Test(groups = "unit") public void testWriteNullableInt32() throws Exception { check( new StreamWriter() { @@ -387,7 +387,7 @@ public void write(ClickHouseRowBinaryStream stream) throws Exception { // clickhouse-client -q "SELECT CAST(1 AS Nullable(Int32)) Format RowBinary" | od -vAn -td1 } - @Test + @Test(groups = "unit") public void testWriteNull() throws Exception { check( new StreamWriter() { diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseValueFormatterTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseValueFormatterTest.java index 1fb3d49eb..b2a03bb76 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseValueFormatterTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseValueFormatterTest.java @@ -31,7 +31,7 @@ public class ClickHouseValueFormatterTest { - @Test + @Test(groups = "unit") public void testFormatBytesUUID() { UUID uuid = UUID.randomUUID(); byte[] bytes = ByteBuffer.allocate(16) @@ -48,7 +48,7 @@ public void testFormatBytesUUID() { assertEquals(reparsedBytes, bytes); } - @Test + @Test(groups = "unit") public void testFormatBytesHelloWorld() throws Exception { byte[] bytes = "HELLO WORLD".getBytes("UTF-8"); String formattedBytes = ClickHouseValueFormatter.formatBytes(bytes); @@ -63,23 +63,23 @@ public void testFormatBytesHelloWorld() throws Exception { "HELLO WORLD"); } - @Test + @Test(groups = "unit") public void testFormatBytesNull() { assertNull(ClickHouseValueFormatter.formatBytes(null)); } - @Test + @Test(groups = "unit") public void testFormatBytesEmpty() { assertEquals(ClickHouseValueFormatter.formatBytes(new byte[0]), ""); } - @Test + @Test(groups = "unit") public void testFormatLocalTime() { LocalTime l = LocalTime.parse("13:37:42.023"); assertEquals(ClickHouseValueFormatter.formatLocalTime(l), "13:37:42"); } - @Test + @Test(groups = "unit") public void testFormatOffsetTime() { OffsetTime o = OffsetTime.of( LocalTime.parse("13:37:42.023"), @@ -89,19 +89,19 @@ public void testFormatOffsetTime() { "13:37:42.023+01:07"); } - @Test + @Test(groups = "unit") public void testFormatLocalDate() { LocalDate l = LocalDate.of(2020, 1, 7); assertEquals(ClickHouseValueFormatter.formatLocalDate(l), "2020-01-07"); } - @Test + @Test(groups = "unit") public void testFormatLocalDateTime() { LocalDateTime l = LocalDateTime.of(2020, 1, 7, 13, 37, 42, 107); assertEquals(ClickHouseValueFormatter.formatLocalDateTime(l), "2020-01-07 13:37:42"); } - @Test + @Test(groups = "unit") public void testFormatOffsetDateTime() { OffsetDateTime o = OffsetDateTime.of( LocalDateTime.of(2020, 1, 7, 13, 37, 42, 107), @@ -111,7 +111,7 @@ public void testFormatOffsetDateTime() { "2020-01-07 14:07:42"); } - @Test + @Test(groups = "unit") public void testFormatZonedDateTime() { ZonedDateTime z = ZonedDateTime.of( LocalDateTime.of(2020, 1, 7, 13, 37, 42, 107), @@ -121,7 +121,7 @@ public void testFormatZonedDateTime() { "2020-01-08 00:37:42"); } - @Test + @Test(groups = "unit") public void testFormatObject() throws Exception { TimeZone tzUTC = TimeZone.getTimeZone("UTC"); assertEquals(ClickHouseValueFormatter.formatObject(Byte.valueOf("42"), tzUTC, tzUTC), "42"); @@ -199,7 +199,7 @@ public void testFormatObject() throws Exception { "2020-01-07 21:37:42"); } - @Test + @Test(groups = "unit") public void testRoundTripLocalDate() throws Exception { LocalDate l0 = LocalDate.of(1957, 10, 4); LocalDate l1 = (LocalDate) ClickHouseValueParser.getParser(LocalDate.class).parse( @@ -212,7 +212,7 @@ public void testRoundTripLocalDate() throws Exception { assertEquals(l1, l0); } - @Test + @Test(groups = "unit") public void testRoundTripLocalTime() throws Exception { LocalTime l0 = LocalTime.of(13, 37, 42); LocalTime l1 = (LocalTime) ClickHouseValueParser.getParser(LocalTime.class).parse( @@ -221,7 +221,7 @@ public void testRoundTripLocalTime() throws Exception { assertEquals(l1, l0); } - @Test + @Test(groups = "unit") public void testRoundTripLocalDateTime() throws Exception { LocalDateTime l0 = LocalDateTime.of(1957, 10, 4, 13, 37, 42); LocalDateTime l1 = (LocalDateTime) ClickHouseValueParser.getParser(LocalDateTime.class) @@ -234,7 +234,7 @@ public void testRoundTripLocalDateTime() throws Exception { assertEquals(l1, l0); } - @Test + @Test(groups = "unit") public void testRoundTripOffsetTime() throws Exception { ZoneOffset offset = ZoneId.of("Asia/Vladivostok") .getRules().getOffset(Instant.now()); @@ -245,7 +245,7 @@ public void testRoundTripOffsetTime() throws Exception { assertEquals(ot1, ot0); } - @Test + @Test(groups = "unit") public void testRoundTripOffsetDateTime() throws Exception { TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); TimeZone tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); @@ -277,7 +277,7 @@ public void testRoundTripOffsetDateTime() throws Exception { tzLosAngeles.toZoneId().getRules().getOffset(ldt))); } - @Test + @Test(groups = "unit") public void testRoundTripZonedDateTime() throws Exception { TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); TimeZone tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); @@ -309,7 +309,7 @@ public void testRoundTripZonedDateTime() throws Exception { tzLosAngeles.toZoneId())); } - @Test + @Test(groups = "unit") public void testRoundTripSQLTimestamp() throws Exception { TimeZone tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); @@ -336,7 +336,7 @@ public void testRoundTripSQLTimestamp() throws Exception { assertEquals(t1, t2); } - @Test + @Test(groups = "unit") public void testRoundTripSQLTime() throws Exception { TimeZone tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); @@ -354,7 +354,7 @@ public void testRoundTripSQLTime() throws Exception { Instant.ofEpochMilli((t0.getTime() + (24 - 9) * 3600 * 1000) % (24 * 3600 * 1000))); } - @Test + @Test(groups = "unit") public void testRoundTripSQLDate() throws Exception { TimeZone tzLosAngeles = TimeZone.getTimeZone("America/Los_Angeles"); TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseVersionNumberUtilTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseVersionNumberUtilTest.java index 50d152ec3..fb218c84f 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseVersionNumberUtilTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/ClickHouseVersionNumberUtilTest.java @@ -5,7 +5,7 @@ public class ClickHouseVersionNumberUtilTest { - @Test + @Test(groups = "unit") public void testMajorNull() { try { ClickHouseVersionNumberUtil.getMajorVersion(null); @@ -14,7 +14,7 @@ public void testMajorNull() { /* expected */ } } - @Test + @Test(groups = "unit") public void testMinorNull() { try { ClickHouseVersionNumberUtil.getMinorVersion(null); @@ -23,7 +23,7 @@ public void testMinorNull() { /* expected */ } } - @Test + @Test(groups = "unit") public void testMajorGarbage() { Assert.assertEquals(0, ClickHouseVersionNumberUtil.getMajorVersion("")); Assert.assertEquals(0, ClickHouseVersionNumberUtil.getMajorVersion(" \t")); @@ -34,7 +34,7 @@ public void testMajorGarbage() { Assert.assertEquals(0, ClickHouseVersionNumberUtil.getMajorVersion("42.foo")); } - @Test + @Test(groups = "unit") public void testMajorSimple() { Assert.assertEquals(ClickHouseVersionNumberUtil.getMajorVersion("1.0"), 1); Assert.assertEquals(ClickHouseVersionNumberUtil.getMajorVersion("1.0.42"), 1); @@ -44,7 +44,7 @@ public void testMajorSimple() { Assert.assertEquals(ClickHouseVersionNumberUtil.getMajorVersion("1.0-SNAPSHOT"), 1); } - @Test + @Test(groups = "unit") public void testMinorGarbage() { Assert.assertEquals(0, ClickHouseVersionNumberUtil.getMinorVersion("")); Assert.assertEquals(0, ClickHouseVersionNumberUtil.getMinorVersion(" \t")); @@ -55,7 +55,7 @@ public void testMinorGarbage() { Assert.assertEquals(0, ClickHouseVersionNumberUtil.getMinorVersion("42.foo")); } - @Test + @Test(groups = "unit") public void testMinorSimple() { Assert.assertEquals(ClickHouseVersionNumberUtil.getMinorVersion("0.1"), 1); Assert.assertEquals(ClickHouseVersionNumberUtil.getMinorVersion("42.1.42"), 1); @@ -64,7 +64,7 @@ public void testMinorSimple() { Assert.assertEquals(ClickHouseVersionNumberUtil.getMinorVersion("1.1-SNAPSHOT"), 1); } - @Test + @Test(groups = "unit") public void testCompare() { Assert.assertEquals(ClickHouseVersionNumberUtil.compare("1", "1"), 0); Assert.assertEquals(ClickHouseVersionNumberUtil.compare("21.3", "21.12"), -1); diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/UtilsTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/UtilsTest.java index 09b7efed5..6f76c7a25 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/UtilsTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/util/UtilsTest.java @@ -11,7 +11,7 @@ import org.testng.annotations.Test; public class UtilsTest { - @Test + @Test(groups = "unit") public void testUnsignedLeb128() throws Exception { DataInputStream input = prepareStream(new byte[] { 0 }); assertEquals(Utils.readUnsignedLeb128(input), 0); @@ -23,7 +23,7 @@ public void testUnsignedLeb128() throws Exception { assertEquals(Utils.readUnsignedLeb128(input), 100000000); } - @Test + @Test(groups = "unit") public void testString() { Charset charset = Charset.forName("ISO-8859-15"); byte[] b1 = new byte[] { (byte) 127, (byte) 128 }; @@ -32,7 +32,7 @@ public void testString() { assertEquals(b2, b1); } - @Test + @Test(groups = "unit") public void testVarInt() { ByteBuffer buffer; for (int i : new int[] { 0, 128, 255, 65535, 1023 * 1024 }) { diff --git a/clickhouse-jdbc/src/test/resources/log4j.properties b/clickhouse-jdbc/src/test/resources/log4j.properties index b6c544007..04d4e6be0 100644 --- a/clickhouse-jdbc/src/test/resources/log4j.properties +++ b/clickhouse-jdbc/src/test/resources/log4j.properties @@ -1,5 +1,5 @@ log4j.rootLogger=WARN, STDOUT -#log4j.category.ru.yandex.clickhouse=DEBUG +log4j.category.ru.yandex.clickhouse=DEBUG #log4j.logger.org.apache.http=DEBUG log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout diff --git a/clickhouse-jdbc/src/test/resources/ru/yandex/clickhouse/users.d/foo.xml b/clickhouse-jdbc/src/test/resources/ru/yandex/clickhouse/users.d/foo.xml deleted file mode 100644 index 5083e230b..000000000 --- a/clickhouse-jdbc/src/test/resources/ru/yandex/clickhouse/users.d/foo.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - default - - ::/0 - - bar - default - - - diff --git a/clickhouse-jdbc/src/test/resources/ru/yandex/clickhouse/users.d/oof.xml b/clickhouse-jdbc/src/test/resources/ru/yandex/clickhouse/users.d/oof.xml deleted file mode 100644 index 24c239f08..000000000 --- a/clickhouse-jdbc/src/test/resources/ru/yandex/clickhouse/users.d/oof.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - default - - ::/0 - - default - - - - diff --git a/clickhouse-mysql-client/pom.xml b/clickhouse-mysql-client/pom.xml deleted file mode 100644 index 87145a149..000000000 --- a/clickhouse-mysql-client/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - 4.0.0 - - - tech.clickhouse - clickhouse-java - ${revision} - - - ${parent.groupId} - clickhouse-mysql-client - ${revision} - jar - - ${project.artifactId} - MySQL client for ClickHouse - /~https://github.com/ClickHouse/clickhouse-jdbc/tree/master/clickhouse-mysql-client - - - 2.7.2 - 8.0.23 - - - - - ${parent.groupId} - clickhouse-client - ${revision} - - - org.mariadb.jdbc - mariadb-java-client - ${mariadb-driver.version} - - - * - * - - - - - mysql - mysql-connector-java - ${mysql-driver.version} - - - * - * - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - true - - -Xlint:all - -Werror - - - - - - diff --git a/clickhouse-native-client/pom.xml b/clickhouse-native-client/pom.xml deleted file mode 100644 index 5969700e6..000000000 --- a/clickhouse-native-client/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - 4.0.0 - - - tech.clickhouse - clickhouse-java - ${revision} - - - ${parent.groupId} - clickhouse-native-client - ${revision} - jar - - ${project.artifactId} - Native client for ClickHouse - /~https://github.com/ClickHouse/clickhouse-jdbc/tree/master/clickhouse-native-client - - - - ${parent.groupId} - clickhouse-client - ${revision} - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${jdk.version} - ${jdk.version} - true - - -Xlint:all - -Werror - - - - - - diff --git a/clickhouse-tcp-client/pom.xml b/clickhouse-tcp-client/pom.xml new file mode 100644 index 000000000..9098716d6 --- /dev/null +++ b/clickhouse-tcp-client/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + + com.clickhouse + clickhouse-java + ${revision} + + + ${project.parent.groupId} + clickhouse-tcp-client + ${revision} + jar + + ${project.artifactId} + TCP/Native client for ClickHouse + /~https://github.com/ClickHouse/clickhouse-jdbc/tree/master/clickhouse-tcp-client + + + + ${project.parent.groupId} + clickhouse-client + ${revision} + + + + ${project.parent.groupId} + clickhouse-client + ${revision} + test-jar + test + + + dnsjava + dnsjava + test + + + org.slf4j + slf4j-log4j12 + test + + + org.testcontainers + testcontainers + test + + + org.testng + testng + test + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6a2728a88..d81991b77 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 - tech.clickhouse + com.clickhouse clickhouse-java ${revision} pom @@ -13,8 +13,8 @@ 2015 - ClickHouse - https://clickhouse.tech/ + ClickHouse, Inc. + https://clickhouse.com/ @@ -38,7 +38,7 @@ clickhouse-client clickhouse-grpc-client clickhouse-http-client - clickhouse-native-client + clickhouse-tcp-client clickhouse-jdbc clickhouse-benchmark @@ -76,32 +76,49 @@ 2021 UTF-8 UTF-8 - 1.8 - 3.0.0 - 3.4.2 - 3.3.1 + + 6.0.53 + 9.2 + 3.0.3 + 3.4.4 + 3.4.2 + 8.5.4 + 1.40.1 + 2.8.8 + 4.5.13 + 3.17.3 1.8.0 2.12.3 - 0.9.15 - 1.7.31 - 1.10.19 - 2.27.2 - 1.15.3 - 6.14.3 + 0.9.21 + 1.8.0-beta4 + 3.12.4 + 2.31.0 + 1.16.0 + 7.4.0 + + 1.0.0 + 3.3.0 + 3.8.1 3.0.0-M1 + 3.0.0-M3 + 3.0.0-M5 1.2.7 - 1.6.8 1.6 - 3.8.1 - 3.2.4 - 3.2.1 + 3.2.0 0.8.6 3.2.0 3.2.0 + 1.7.0 + 0.6.1 + 3.2.4 + 3.2.1 + 1.6.8 3.0.0-M5 - 3.0.0-M5 - 3.3.0 + 3.0.0 + 2.8.1 + + 1.8 false ${skipTests} ${skipTests} @@ -115,6 +132,17 @@ + + ${project.groupId} + io.grpc + ${repackaged.version} + + + ${project.groupId} + org.roaringbitmap + ${repackaged.version} + + com.fasterxml.jackson.core jackson-core @@ -125,6 +153,11 @@ jackson-databind ${jackson.version} + + com.google.code.gson + gson + ${gson.version} + com.github.ben-manes.caffeine caffeine @@ -140,11 +173,63 @@ dnsjava ${dnsjava.version} + + io.grpc + grpc-netty-shaded + ${grpc.version} + + + + io.grpc + grpc-okhttp + ${grpc.version} + + + io.grpc + + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + it.unimi.dsi + fastutil + ${fastutil.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + org.apache.tomcat + annotations-api + ${annotations-api.version} + org.lz4 lz4-java ${lz4.version} + + org.ow2.asm + asm + ${asm.version} + + + org.ow2.asm + asm-util + ${asm.version} + org.roaringbitmap RoaringBitmap @@ -162,7 +247,7 @@ org.mockito - mockito-all + mockito-core ${mockito.version} @@ -186,6 +271,11 @@ + + kr.motd.maven + os-maven-plugin + ${os-plugin.version} + org.apache.maven.plugins maven-assembly-plugin @@ -195,23 +285,17 @@ org.apache.maven.plugins maven-compiler-plugin ${compiler-plugin.version} - org.apache.maven.plugins maven-deploy-plugin ${deploy-plugin.version} + + org.apache.maven.plugins + maven-enforcer-plugin + ${enforcer-plugin.version} + org.apache.maven.plugins maven-gpg-plugin @@ -238,9 +322,9 @@ ${source-plugin.version} - org.sonatype.plugins - nexus-staging-maven-plugin - ${staging-plugin.version} + org.apache.maven.plugins + maven-toolchains-plugin + ${toolchains-plugin.version} org.apache.maven.plugins @@ -248,10 +332,11 @@ ${failsafe-plugin.version} **/*.java - + integration ${skipTests} ${skipITs} - false + true + false @@ -269,12 +354,17 @@ maven-surefire-plugin ${surefire-plugin.version} - - + ${excludedGroups} + unit ${skipUTs} - false + false + + org.codehaus.mojo + build-helper-maven-plugin + ${helper-plugin.version} + org.codehaus.mojo flatten-maven-plugin @@ -300,9 +390,107 @@ + + org.codehaus.mojo + versions-maven-plugin + ${versions-plugin.version} + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${staging-plugin.version} + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-plugin.version} + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${minJdk} + ${minJdk} + true + + -Xlint:all + + + + + + java9 + none + + compile + + + 9 + + 9 + + + ${project.basedir}/src/main/java9 + + true + + + + java11 + none + + compile + + + 11 + + 11 + + + ${project.basedir}/src/main/java11 + + true + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + (,2.1.0),(2.1.0,2.2.0),(2.2.0,) + + Maven 2.1.0 and 2.2.0 produce incorrect GPG signatures and checksums respectively. + + + + + + org.codehaus.mojo flatten-maven-plugin @@ -361,10 +549,101 @@ + + compile-java9 + + [9,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + java9 + compile + + + + + + + + compile-java11 + + [11,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + java11 + compile + + + + + + release + + org.apache.maven.plugins + maven-toolchains-plugin + + + + toolchain + + + + + + + 9 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + java9 + compile + + + java11 + compile + + + + + org.apache.maven.plugins + maven-jar-plugin + + + default-jar + + + + true + + + + + + org.apache.maven.plugins maven-source-plugin @@ -380,6 +659,9 @@ org.apache.maven.plugins maven-javadoc-plugin + + ${minJdk} + attach-javadocs @@ -436,5 +718,144 @@ + + update + + + + org.codehaus.mojo + versions-maven-plugin + + false + + org.slf4j:* + + + + + initialize + + update-properties + + + + + + + + + build8 + + + !release + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${minJdk} + ${minJdk} + true + + -Xlint:all + + + + + maven-surefire-plugin + + ${env.JAVA_HOME}/bin/java + + + + maven-failsafe-plugin + + ${env.JAVA_HOME}/bin/java + + + + + + + build9 + + + release + + + + + + org.apache.maven.plugins + maven-toolchains-plugin + + + + toolchain + + + + + + + 9 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + java9 + compile + + + java11 + compile + + + + + org.apache.maven.plugins + maven-jar-plugin + + + default-jar + + + + true + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${minJdk} + + + + attach-javadocs + + jar + + + + + + + - + \ No newline at end of file diff --git a/third-party-libraries/README.md b/third-party-libraries/README.md new file mode 100644 index 000000000..301defdb0 --- /dev/null +++ b/third-party-libraries/README.md @@ -0,0 +1,9 @@ +Repack third party libraries for JPMS support. + +Enable MRJAR(multi-release jar) +* Parent POM: /~https://github.com/meterware/multirelease-parent + Note: another example /~https://github.com/apache/maven-compiler-plugin/blob/master/src/it/multirelease-patterns/singleproject-runtime/pom.xml#L102-L108 +* Basics: https://www.baeldung.com/java-multi-release-jar +* Maven: https://maven.apache.org/plugins/maven-compiler-plugin/multirelease.html +* Gradle: https://blog.gradle.org/mrjars +* More to read at https://in.relation.to/2017/02/13/building-multi-release-jars-with-maven/