com.clickhouse.client.ClickHouseRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of clickhouse-client Show documentation
Show all versions of clickhouse-client Show documentation
Unified Java client for ClickHouse
package com.clickhouse.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
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.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import com.clickhouse.client.config.ClickHouseClientOption;
import com.clickhouse.config.ClickHouseConfigChangeListener;
import com.clickhouse.config.ClickHouseOption;
import com.clickhouse.data.ClickHouseChecker;
import com.clickhouse.data.ClickHouseCompression;
import com.clickhouse.data.ClickHouseDeferredValue;
import com.clickhouse.data.ClickHouseExternalTable;
import com.clickhouse.data.ClickHouseFile;
import com.clickhouse.data.ClickHouseFormat;
import com.clickhouse.data.ClickHouseFreezableMap;
import com.clickhouse.data.ClickHouseInputStream;
import com.clickhouse.data.ClickHouseOutputStream;
import com.clickhouse.data.ClickHousePassThruStream;
import com.clickhouse.data.ClickHouseUtils;
import com.clickhouse.data.ClickHouseValue;
import com.clickhouse.data.ClickHouseValues;
import com.clickhouse.data.ClickHouseWriter;
/**
* 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 Set SPECIAL_SETTINGS;
static final String PARAM_SETTING = "setting";
static {
Set set = new HashSet<>();
set.add(ClickHouseClientOption.QUERY_ID.getKey());
set.add(ClickHouseClientOption.SESSION_ID.getKey());
set.add(ClickHouseClientOption.SESSION_CHECK.getKey());
set.add(ClickHouseClientOption.SESSION_TIMEOUT.getKey());
SPECIAL_SETTINGS = Collections.unmodifiableSet(set);
}
static class PipedWriter implements ClickHouseWriter {
private final ClickHouseDeferredValue input;
PipedWriter(ClickHouseDeferredValue input) {
this.input = input;
}
@Override
public void write(ClickHouseOutputStream output) throws IOException {
ClickHouseInputStream in = input.get();
if (in != null) {
in.pipe(output);
}
}
}
/**
* Mutation request.
*/
public static class Mutation extends ClickHouseRequest {
protected Mutation(ClickHouseRequest> request, boolean sealed) {
super(request.getClient(), request.server, request.serverRef, request.options, sealed);
if (request.options.isFreezed()) {
this.options.freeze();
}
// use headless format if possible
if (!sealed) {
format(request.getFormat().defaultInputFormat());
}
this.settings.putAll(request.settings);
if (request.settings.isFreezed()) {
this.settings.freeze();
}
this.txRef.set(request.txRef.get());
this.changeListener = request.changeListener;
this.serverListener = request.serverListener;
}
@Override
protected String getQuery() {
if (input != null && sql != null) {
int tmp = 0;
int len = sql.length();
int index = len;
while ((tmp = ClickHouseUtils.skipContentsUntil(sql, tmp, sql.length(), "format", false)) < len) {
index = tmp;
}
StringBuilder builder = new StringBuilder();
boolean spaces = false;
for (; index < len; index++) {
char ch = sql.charAt(index);
if (ClickHouseUtils.isQuote(ch) || ClickHouseUtils.isOpenBracket(ch)) {
break;
} else if (Character.isWhitespace(ch)) {
if (builder.length() > 0) {
spaces = true;
}
} else if (index + 1 < len) {
char nextCh = sql.charAt(index + 1);
if (ch == '-' && nextCh == '-') {
index = ClickHouseUtils.skipSingleLineComment(sql, index + 2, len) - 1;
} else if (ch == '/' && nextCh == '*') {
index = ClickHouseUtils.skipMultiLineComment(sql, index + 2, len) - 1;
} else if (ch == '\\') {
index++;
} else {
if (spaces) {
break;
} else {
builder.append(ch);
}
}
} else {
if (spaces) {
break;
} else {
builder.append(ch);
}
}
}
return builder.length() > 0 && index == len ? sql
: new StringBuilder().append(sql).append("\n 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);
}
/**
* Sets custom writer for writing uncompressed data, use
* {@link #data(ClickHousePassThruStream)} when the data is compressed. This
* will remove input stream set by other {@code data(...)} methods.
*
* @param writer writer
* @return mutation request
*/
public Mutation data(ClickHouseWriter writer) {
checkSealed();
this.writer = changeProperty(PROP_WRITER, this.writer,
ClickHouseChecker.nonNull(writer, ClickHouseWriter.TYPE_NAME));
this.input = changeProperty(PROP_DATA, this.input,
ClickHouseDeferredValue.of(() -> ClickHouseInputStream.of(getConfig(), writer)));
return this;
}
/**
* Loads data from the given pass-thru stream which may or may not be
* compressed. This will not only remove custom writer set by
* {@link #data(ClickHouseWriter)}, but may also update compression and format.
*
* @param stream pass-thru stream, could be a file and may or may not be
* compressed
* @return mutation request
*/
public Mutation data(ClickHousePassThruStream stream) {
checkSealed();
if (!ClickHouseChecker.nonNull(stream, ClickHousePassThruStream.TYPE_NAME).hasInput()) {
throw new IllegalArgumentException(ClickHousePassThruStream.ERROR_NO_INPUT);
}
final ClickHouseConfig c = getConfig();
if (stream.hasCompression()) {
decompressClientRequest(stream.isCompressed(), stream.getCompressionAlgorithm(),
stream.getCompressionLevel());
} else if (c.isRequestCompressed()) {
decompressClientRequest(false);
}
if (stream.hasFormat()) {
format(stream.getFormat());
}
final int bufferSize = c.getReadBufferSize();
this.input = changeProperty(PROP_DATA, this.input, ClickHouseDeferredValue
.of(() -> ClickHouseInputStream.of(stream, bufferSize, null)));
this.writer = changeProperty(PROP_WRITER, this.writer, new PipedWriter(this.input));
return this;
}
/**
* Loads data from the given file. It's same as
* {@code data(ClickHouseFile.of(file))} if the file name implies compression
* algorithm and/or format(e.g. {@code some_file.csv.gz}). It fall back to
* {@link #data(InputStream)} if no clue. This will remove custom writer set by
* {@link #data(ClickHouseWriter)}.
*
* @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) {
ClickHouseFile f = ClickHouseFile.of(file);
return f.isRecognized() ? data(f) : data(f.getInputStream());
}
/**
* Loads compressed data from the given file. This will remove custom writer set
* by {@link #data(ClickHouseWriter)}.
*
* @param file absolute or relative path of the file
* @param compression compression algorithm, null or
* {@link ClickHouseCompression#NONE} means no compression
* @return mutation request
*/
public Mutation data(String file, ClickHouseCompression compression) {
return data(ClickHouseFile.of(file, compression,
(int) ClickHouseClientOption.DECOMPRESS_LEVEL.getDefaultValue(), null));
}
/**
* Loads compressed data from the given file. This will remove custom writer set
* by {@link #data(ClickHouseWriter)}.
*
* @param file absolute or relative path of the file
* @param compression compression algorithm, null or
* {@link ClickHouseCompression#NONE} means no
* compression
* @param compressionLevel compression level, use
* {@code com.clickhouse.data.ClickHouseDataConfig#DEFAULT_READ_COMPRESS_LEVEL}
* to use recommended level for the algorithm
* @return mutation request
*/
public Mutation data(String file, ClickHouseCompression compression, int compressionLevel) {
return data(ClickHouseFile.of(file, compression, compressionLevel, null));
}
/**
* Loads data from input stream. This will remove custom writer set by
* {@link #data(ClickHouseWriter)}.
*
* @param input input stream
* @return mutation request
*/
public Mutation data(InputStream input) {
return data(ClickHouseInputStream.of(input));
}
/**
* Loads data from input stream. This will remove custom writer set by
* {@link #data(ClickHouseWriter)}.
*
* @param input input stream
* @return mutation request
*/
public Mutation data(ClickHouseInputStream input) {
if (ClickHouseChecker.nonNull(input, ClickHouseInputStream.TYPE_NAME).hasUnderlyingStream()) {
return data(input.getUnderlyingStream());
}
checkSealed();
this.input = changeProperty(PROP_DATA, this.input,
ClickHouseDeferredValue.of(input, ClickHouseInputStream.class));
this.writer = changeProperty(PROP_WRITER, this.writer, new PipedWriter(this.input));
return this;
}
/**
* Loads data from deferred input stream. This will remove custom writer set by
* {@link #data(ClickHouseWriter)}.
*
* @param input input stream
* @return mutation request
*/
public Mutation data(ClickHouseDeferredValue input) {
checkSealed();
this.input = changeProperty(PROP_DATA, this.input, input);
this.writer = changeProperty(PROP_WRITER, this.writer, input != null ? new PipedWriter(input) : null);
return this;
}
@Override
public Mutation table(String table, String queryId) {
checkSealed();
return super.query("INSERT INTO " + ClickHouseChecker.nonBlank(table, "table"), queryId);
}
@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.externalTables.addAll(externalTables);
req.options.putAll(options);
if (options.isFreezed()) {
req.options.freeze();
}
req.settings.putAll(settings);
if (settings.isFreezed()) {
req.settings.freeze();
}
req.namedParameters.putAll(namedParameters);
req.input = input;
req.writer = writer;
req.queryId = queryId;
req.sql = sql;
req.preparedQuery = preparedQuery;
req.managerRef.set(managerRef.get());
req.txRef.set(txRef.get());
}
return req;
}
}
private static final long serialVersionUID = 4990313525960702287L;
static final String PROP_DATA = "data";
static final String PROP_MANAGER = "manager";
static final String PROP_OUTPUT = "output";
static final String PROP_PREPARED_QUERY = "preparedQuery";
static final String PROP_QUERY = "query";
static final String PROP_QUERY_ID = "queryId";
static final String PROP_TRANSACTION = "transaction";
static final String PROP_WRITER = "writer";
private final boolean sealed;
private final ClickHouseClient client;
protected final AtomicReference managerRef;
protected final Function server;
protected final AtomicReference serverRef;
protected final AtomicReference txRef;
protected final List externalTables;
protected final ClickHouseFreezableMap options;
protected final ClickHouseFreezableMap settings;
protected final Map namedParameters;
protected transient ClickHouseWriter writer;
protected transient ClickHouseDeferredValue input;
protected transient ClickHouseDeferredValue output;
protected String queryId;
protected String sql;
protected ClickHouseParameterizedQuery preparedQuery;
protected transient ClickHouseConfigChangeListener> changeListener;
protected transient BiConsumer serverListener;
// cache
protected transient ClickHouseConfig config;
protected transient List statements;
@SuppressWarnings("squid:S1905")
protected ClickHouseRequest(ClickHouseClient client, Function server,
AtomicReference ref, Map options, boolean sealed) {
if (client == null || server == null) {
throw new IllegalArgumentException("Non-null client and server are required");
}
this.client = client;
this.managerRef = new AtomicReference<>(null);
this.server = (Function & Serializable) server;
this.serverRef = ref == null ? new AtomicReference<>(null) : ref;
this.txRef = new AtomicReference<>(null);
this.externalTables = new LinkedList<>();
// TODO configurable whitelist? maybe later
this.options = ClickHouseFreezableMap.of(new HashMap<>(), ClickHouseClientOption.SESSION_ID);
this.settings = ClickHouseFreezableMap.of(new LinkedHashMap<>(client.getConfig().getCustomSettings()),
ClickHouseTransaction.SETTING_IMPLICIT_TRANSACTION);
options(options);
this.namedParameters = new HashMap<>();
this.sealed = sealed;
}
protected T changeProperty(String property, T oldValue, T newValue) {
if (changeListener != null && !Objects.equals(oldValue, newValue)) {
changeListener.propertyChanged(this, property, oldValue, newValue);
}
return newValue;
}
protected ClickHouseNode changeServer(ClickHouseNode currentServer, ClickHouseNode newServer) {
if (!serverRef.compareAndSet(currentServer, newServer)) {
newServer = getServer();
} else if (serverListener != null) {
serverListener.accept(currentServer, newServer);
}
return newServer;
}
protected void checkSealed() {
if (sealed) {
throw new IllegalStateException("Sealed request is immutable");
}
}
/**
* Gets client associated with the request.
*
* @return non-null client
*/
protected ClickHouseClient getClient() {
if (client != null) {
return client;
}
ClickHouseNode node = getServer();
return ClickHouseClient.newInstance(node.getCredentials().orElse(null), node.getProtocol());
}
/**
* 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;
}
}
/**
* Creates a copy of this request including listeners. Please pay attention that
* the same node reference (returned from {@link #getServer()}) will be copied
* to the new request as well, meaning failover will change node for both
* requests.
*
* @return copy of this request
*/
public ClickHouseRequest copy() {
ClickHouseRequest req = new ClickHouseRequest<>(getClient(), server, serverRef, options, false);
if (options.isFreezed()) {
req.options.freeze();
}
req.externalTables.addAll(externalTables);
req.settings.putAll(settings);
if (settings.isFreezed()) {
req.settings.freeze();
}
req.namedParameters.putAll(namedParameters);
req.input = input;
req.writer = writer;
req.output = output;
req.queryId = queryId;
req.sql = sql;
req.preparedQuery = preparedQuery;
req.managerRef.set(managerRef.get());
req.txRef.set(txRef.get());
req.changeListener = changeListener;
req.serverListener = serverListener;
return req;
}
/**
* Gets manager for the request, which defaults to
* {@link ClickHouseRequestManager#getInstance()}.
*
* @return non-null request manager
*/
public ClickHouseRequestManager getManager() {
ClickHouseRequestManager m = managerRef.get();
if (m == null) {
m = ClickHouseRequestManager.getInstance();
if (!managerRef.compareAndSet(null, m)) {
m = managerRef.get();
}
}
return m;
}
/**
* 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 is bounded with a transaction.
*
* @return true if the request is bounded with a transaction; false otherwise
*/
public boolean isTransactional() {
return txRef.get() != null;
}
/**
* Checks if the request contains any input stream or custom writer.
*
* @return true if there's input stream or customer writer; false otherwise
*/
public boolean hasInputStream() {
return this.input != null || this.writer != null || !this.externalTables.isEmpty();
}
/**
* Checks if the response should be redirected to an output stream, which was
* defined by one of {@code output(*)} methods.
*
* @return true if response should be redirected to an output stream; false
* otherwise
*/
public boolean hasOutputStream() {
return this.output != null;
}
/**
* Gets the server currently connected to. The initial value was determined by
* the {@link java.util.function.Function} passed to constructor, and it may be
* changed over time when failover is enabled.
*
* @return non-null node
*/
public final ClickHouseNode getServer() {
ClickHouseNode node = serverRef.get();
if (node == null) {
node = server.apply(getClient().getConfig().getNodeSelector());
if (!serverRef.compareAndSet(null, node)) {
node = serverRef.get();
}
}
return node;
}
/**
* Gets request configuration.
*
* @return request configuration
*/
public ClickHouseConfig getConfig() {
if (config == null) {
ClickHouseConfig clientConfig = getClient().getConfig();
ClickHouseNode node = getServer();
if (options.isEmpty()
&& clientConfig.getDefaultCredentials().equals(node.getCredentials(clientConfig))) {
config = clientConfig;
} else {
Map merged = new HashMap<>();
merged.putAll(clientConfig.getAllOptions());
merged.putAll(options);
config = new ClickHouseConfig(merged, node.getCredentials(clientConfig),
clientConfig.getNodeSelector(), clientConfig.getMetricRegistry().orElse(null));
}
}
return config;
}
public ClickHouseTransaction getTransaction() {
return txRef.get();
}
public final ClickHouseConfigChangeListener> getChangeListener() {
return this.changeListener;
}
public final BiConsumer getServerListener() {
return this.serverListener;
}
/**
* Gets input stream.
*
* @return input stream
*/
public Optional getInputStream() {
return input != null ? input.getOptional() : Optional.empty();
}
/**
* Gets custom writer for writing raw request.
*
* @return custom writer
*/
public Optional getWriter() {
return Optional.ofNullable(this.writer);
}
/**
* Gets output stream.
*
* @return output stream
*/
public Optional getOutputStream() {
return output != null ? output.getOptional() : Optional.empty();
}
/**
* 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 prepared query, which is a loosely parsed query with the origianl query
* and list of parameters.
*
* @return prepared query
*/
public ClickHouseParameterizedQuery getPreparedQuery() {
if (preparedQuery == null) {
preparedQuery = changeProperty(PROP_PREPARED_QUERY, preparedQuery,
ClickHouseParameterizedQuery.of(getConfig(), getQuery()));
}
return preparedQuery;
}
/**
* Freezes settings to discard future changes.
*
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT freezeSettings() {
settings.freeze();
return (SelfT) this;
}
/**
* Unfreezes settings to accept future changes.
*
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT unfreezeSettings() {
settings.unfreeze();
return (SelfT) this;
}
/**
* Gets typed setting value.
*
* @param type of the setting value
* @param setting non-null setting key
* @param valueType non-null value type
* @return non-null value
*/
public T getSetting(String setting, Class valueType) {
Serializable value = settings.get(ClickHouseChecker.nonBlank(setting, PARAM_SETTING));
return ClickHouseOption.fromString(value == null ? "" : value.toString(), valueType);
}
/**
* Gets typed setting value.
*
* @param type of the setting value
* @param setting non-null setting key
* @param defaultValue non-null default value
* @return non-null value
*/
public T getSetting(String setting, T defaultValue) {
Serializable value = settings.get(ClickHouseChecker.nonBlank(setting, PARAM_SETTING));
ClickHouseChecker.nonNull(defaultValue, "defaultValue");
if (value == null) {
return defaultValue;
}
return (T) ClickHouseOption.fromString(value.toString(), defaultValue.getClass());
}
/**
* Checks if a setting has been defined or not.
*
* @param setting setting
* @return true if the setting has been defined; false otherwise
*/
public boolean hasSetting(String setting) {
return settings.containsKey(setting);
}
/**
* Gets immutable settings.
*
* @return immutable settings
*/
public Map getSettings() {
return Collections.unmodifiableMap(settings);
}
/**
* Gets session id.
*
* @return session id
*/
public Optional getSessionId() {
String sessionId = getConfig().getStrOption(ClickHouseClientOption.SESSION_ID);
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)) {
StringBuilder builder = new StringBuilder();
if (preparedQuery == null) {
ClickHouseParameterizedQuery.apply(builder, stmt, namedParameters);
} else {
preparedQuery.apply(builder, namedParameters);
}
statements.add(builder.toString());
}
}
return Collections.unmodifiableList(statements);
}
/**
* Enable or disable compression of server response. Pay attention that
* {@link ClickHouseClientOption#COMPRESS_ALGORITHM} and
* {@link ClickHouseClientOption#COMPRESS_LEVEL} will be used.
*
* @param enable true to enable compression of server response; false otherwise
* @return the request itself
*/
public SelfT compressServerResponse(boolean enable) {
return compressServerResponse(enable, null,
(int) ClickHouseClientOption.COMPRESS_LEVEL.getEffectiveDefaultValue());
}
/**
* Enable or disable compression of server response. Pay attention that
* {@link ClickHouseClientOption#COMPRESS_ALGORITHM} and
* {@link ClickHouseClientOption#COMPRESS_LEVEL} will be used.
*
* @param compressAlgorithm compression algorihtm, null or
* {@link ClickHouseCompression#NONE} means no
* compression
* @return the request itself
*/
public SelfT compressServerResponse(ClickHouseCompression compressAlgorithm) {
return compressServerResponse(compressAlgorithm != null && compressAlgorithm != ClickHouseCompression.NONE,
compressAlgorithm, (int) ClickHouseClientOption.COMPRESS_LEVEL.getEffectiveDefaultValue());
}
/**
* Enable or disable compression of server response. Pay attention that
* {@link ClickHouseClientOption#COMPRESS_LEVEL} will be used.
*
* @param enable true to enable compression of server response; false
* otherwise
* @param compressAlgorithm compression algorithm, null is treated as
* {@link ClickHouseCompression#NONE} or
* {@link ClickHouseClientOption#COMPRESS_ALGORITHM}
* depending on whether enabled
* @return the request itself
*/
public SelfT compressServerResponse(boolean enable, ClickHouseCompression compressAlgorithm) {
return compressServerResponse(enable, compressAlgorithm,
(int) ClickHouseClientOption.COMPRESS_LEVEL.getEffectiveDefaultValue());
}
/**
* Enable or disable compression of server response.
*
* @param enable true to enable compression of server response; false
* otherwise
* @param compressAlgorithm compression algorithm, null is treated as
* {@link ClickHouseCompression#NONE} or
* {@link ClickHouseClientOption#COMPRESS_ALGORITHM}
* depending on whether enabled
* @param compressLevel compression level
* @return the request itself
*/
public SelfT compressServerResponse(boolean enable, ClickHouseCompression compressAlgorithm, int compressLevel) {
checkSealed();
if (compressAlgorithm == null) {
compressAlgorithm = enable
? (ClickHouseCompression) ClickHouseClientOption.COMPRESS_ALGORITHM.getEffectiveDefaultValue()
: ClickHouseCompression.NONE;
}
return option(ClickHouseClientOption.COMPRESS, enable)
.option(ClickHouseClientOption.COMPRESS_ALGORITHM, compressAlgorithm)
.option(ClickHouseClientOption.COMPRESS_LEVEL, compressLevel);
}
/**
* Enable or disable compression of client request. Pay attention that
* {@link ClickHouseClientOption#DECOMPRESS_ALGORITHM} and
* {@link ClickHouseClientOption#DECOMPRESS_LEVEL} will be used.
*
* @param enable true to enable compression of client request; false otherwise
* @return the request itself
*/
public SelfT decompressClientRequest(boolean enable) {
return decompressClientRequest(enable, null,
(int) ClickHouseClientOption.DECOMPRESS_LEVEL.getEffectiveDefaultValue());
}
/**
* Enable or disable compression of client request. Pay attention that
* {@link ClickHouseClientOption#DECOMPRESS_LEVEL} will be used.
*
* @param compressAlgorithm compression algorithm, null is treated as
* {@link ClickHouseCompression#NONE} or
* {@link ClickHouseClientOption#DECOMPRESS_ALGORITHM}
* depending on whether enabled
* @return the request itself
*/
public SelfT decompressClientRequest(ClickHouseCompression compressAlgorithm) {
return decompressClientRequest(compressAlgorithm != null && compressAlgorithm != ClickHouseCompression.NONE,
compressAlgorithm, (int) ClickHouseClientOption.DECOMPRESS_LEVEL.getEffectiveDefaultValue());
}
/**
* Enable or disable compression of client request. Pay attention that
* {@link ClickHouseClientOption#DECOMPRESS_LEVEL} will be used.
*
* @param enable true to enable compression of client request; false
* otherwise
* @param compressAlgorithm compression algorithm, null is treated as
* {@link ClickHouseCompression#NONE} or
* {@link ClickHouseClientOption#DECOMPRESS_ALGORITHM}
* depending on whether enabled
* @return the request itself
*/
public SelfT decompressClientRequest(boolean enable, ClickHouseCompression compressAlgorithm) {
return decompressClientRequest(enable, compressAlgorithm,
(int) ClickHouseClientOption.DECOMPRESS_LEVEL.getEffectiveDefaultValue());
}
/**
* Enable or disable compression of client request.
*
* @param enable true to enable compression of client request; false
* otherwise
* @param compressAlgorithm compression algorithm, null is treated as
* {@link ClickHouseCompression#NONE}
* @param compressLevel compression level
* @return the request itself
*/
public SelfT decompressClientRequest(boolean enable, ClickHouseCompression compressAlgorithm, int compressLevel) {
checkSealed();
if (compressAlgorithm == null) {
compressAlgorithm = enable
? (ClickHouseCompression) ClickHouseClientOption.DECOMPRESS_ALGORITHM.getEffectiveDefaultValue()
: ClickHouseCompression.NONE;
}
return option(ClickHouseClientOption.DECOMPRESS, enable)
.option(ClickHouseClientOption.DECOMPRESS_ALGORITHM, compressAlgorithm)
.option(ClickHouseClientOption.DECOMPRESS_LEVEL, compressLevel);
}
/**
* Adds an external table.
*
* @param table non-null external table
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT addExternal(ClickHouseExternalTable table) {
checkSealed();
if (externalTables.add(ClickHouseChecker.nonNull(table, ClickHouseExternalTable.TYPE_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, ClickHouseExternalTable.TYPE_NAME));
if (more != null) {
for (ClickHouseExternalTable e : more) {
externalTables.add(ClickHouseChecker.nonNull(e, ClickHouseExternalTable.TYPE_NAME));
}
}
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, ClickHouseExternalTable.TYPE_NAME));
}
}
return (SelfT) this;
}
/**
* Sets format to be used for communication between server and client.
*
* @param format preferred format, {@code null} means reset to default
* @return the request itself
*/
public SelfT format(ClickHouseFormat format) {
checkSealed();
return option(ClickHouseClientOption.FORMAT, format);
}
/**
* Sets request manager which is responsible for generating query ID and session
* ID, as well as transaction creation.
*
* @param manager request manager
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT manager(ClickHouseRequestManager manager) {
checkSealed();
ClickHouseRequestManager current = managerRef.get();
if (!Objects.equals(current, manager) && managerRef.compareAndSet(current, manager)) {
changeProperty(PROP_MANAGER, current, manager);
}
return (SelfT) this;
}
/**
* Freezes options to discard future changes.
*
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT freezeOptions() {
options.freeze();
return (SelfT) this;
}
/**
* Unfreezes options to accept future changes.
*
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT unfreezeOptions() {
options.unfreeze();
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(ClickHouseOption option, Serializable value) {
checkSealed();
if (value == null) {
return removeOption(option);
} else if (option != null && option.getValueType() != value.getClass()) {
value = ClickHouseOption.fromString(value.toString(), option.getValueType());
}
if (option == ClickHouseClientOption.CUSTOM_SETTINGS) {
for (Entry entry : ClickHouseOption.toKeyValuePairs(value.toString()).entrySet()) {
set(entry.getKey(), entry.getValue());
}
} else {
Serializable oldValue = options.put(ClickHouseChecker.nonNull(option, ClickHouseConfig.PARAM_OPTION),
value); // NOSONAR
if (oldValue == null || !oldValue.equals(value)) {
if (changeListener != null) {
changeListener.optionChanged(this, option, oldValue, value);
}
resetCache();
}
}
return (SelfT) this;
}
/**
* Sets all options. {@code option} is for configuring client's behaviour, while
* {@code setting} is for server.
*
* @param options options
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT options(Map options) {
checkSealed();
if (changeListener == null) {
this.options.clear();
if (options != null) {
for (Entry e : options.entrySet()) {
option(e.getKey(), e.getValue());
}
}
resetCache();
} else {
Map m = new HashMap<>();
m.putAll(this.options);
if (options != null) {
for (Entry e : options.entrySet()) {
option(e.getKey(), e.getValue());
m.remove(e.getKey());
}
}
for (ClickHouseOption o : m.keySet()) {
removeOption(o);
}
}
return (SelfT) this;
}
/**
* Sets all options. {@code option} is for configuring client's behaviour, while
* {@code setting} is for server.
*
* @param options options
* @return the request itself
*/
@SuppressWarnings("unchecked")
public SelfT options(Properties options) {
checkSealed();
Map m = new HashMap<>();
m.putAll(this.options);
if (options != null) {
for (Entry