com.axibase.tsd.driver.jdbc.ext.AtsdMeta Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of atsd-jdbc Show documentation
Show all versions of atsd-jdbc Show documentation
JDBC driver for SQL API using
/*
* Copyright 2016 Axibase Corporation or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* https://www.axibase.com/atsd/axibase-apache-2.0.pdf
*
* or in the "license" file accompanying this file. This file 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.axibase.tsd.driver.jdbc.ext;
import com.axibase.tsd.driver.jdbc.DriverConstants;
import com.axibase.tsd.driver.jdbc.content.*;
import com.axibase.tsd.driver.jdbc.content.json.Metric;
import com.axibase.tsd.driver.jdbc.content.json.Series;
import com.axibase.tsd.driver.jdbc.converter.AtsdSqlConverterFactory;
import com.axibase.tsd.driver.jdbc.enums.AtsdType;
import com.axibase.tsd.driver.jdbc.enums.DefaultColumn;
import com.axibase.tsd.driver.jdbc.enums.Location;
import com.axibase.tsd.driver.jdbc.intf.IContentProtocol;
import com.axibase.tsd.driver.jdbc.intf.IDataProvider;
import com.axibase.tsd.driver.jdbc.intf.IStoreStrategy;
import com.axibase.tsd.driver.jdbc.intf.MetadataColumnDefinition;
import com.axibase.tsd.driver.jdbc.logging.LoggingFacade;
import com.axibase.tsd.driver.jdbc.protocol.SdkProtocolImpl;
import com.axibase.tsd.driver.jdbc.util.EnumUtil;
import com.axibase.tsd.driver.jdbc.util.JsonMappingUtil;
import com.axibase.tsd.driver.jdbc.util.WildcardsUtil;
import lombok.SneakyThrows;
import org.apache.calcite.avatica.*;
import org.apache.calcite.avatica.remote.TypedValue;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import static com.axibase.tsd.driver.jdbc.DriverConstants.DEFAULT_CHARSET;
import static com.axibase.tsd.driver.jdbc.DriverConstants.DEFAULT_TABLE_NAME;
import static org.apache.calcite.avatica.Meta.StatementType.SELECT;
public class AtsdMeta extends MetaImpl {
private static final LoggingFacade log = LoggingFacade.getLogger(AtsdMeta.class);
public static final FastDateFormat TIMESTAMP_FORMATTER = prepareFormatter("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
public static final FastDateFormat TIMESTAMP_SHORT_FORMATTER = prepareFormatter("yyyy-MM-dd'T'HH:mm:ss'Z'");
private final AtomicInteger idGenerator = new AtomicInteger(1);
private final Map metaCache = new ConcurrentHashMap<>();
private final Map providerCache = new ConcurrentHashMap<>();
private final Map contextMap = new ConcurrentHashMap<>();
private final Map> queryPartsMap = new ConcurrentHashMap<>();
private final AtsdConnectionInfo atsdConnectionInfo;
public AtsdMeta(final AvaticaConnection conn) {
super(conn);
this.connProps.setAutoCommit(true);
this.connProps.setReadOnly(true);
this.connProps.setTransactionIsolation(Connection.TRANSACTION_NONE);
this.connProps.setDirty(false);
this.atsdConnectionInfo = ((AtsdConnection) conn).getConnectionInfo();
}
private static FastDateFormat prepareFormatter(final String pattern) {
return FastDateFormat.getInstance(pattern, TimeZone.getTimeZone("UTC"), Locale.US);
}
StatementContext getContextFromMap(StatementHandle statementHandle) {
return contextMap.get(statementHandle.id);
}
@Override
@SneakyThrows(SQLException.class)
public StatementHandle prepare(ConnectionHandle connectionHandle, String query, long maxRowCount) {
final int statementHandleId = idGenerator.getAndIncrement();
log.trace("[prepare] handle: {} query: {}", statementHandleId, query);
if (StringUtils.isBlank(query)) {
throw new SQLException("Failed to prepare statement with blank query");
}
final List queryParts = splitQueryByPlaceholder(query);
queryPartsMap.put(statementHandleId, queryParts);
final StatementType statementType = EnumUtil.getStatementTypeByQuery(query);
Signature signature = new Signature(new ArrayList(), query, Collections.emptyList(), null,
statementType == SELECT ? CursorFactory.LIST : null, statementType);
return new StatementHandle(connectionHandle.id, statementHandleId, signature);
}
public void updatePreparedStatementResultSetMetaData(Signature signature, StatementHandle handle) throws SQLException {
if (signature.columns.isEmpty()) {
final String metaEndpoint = Location.SQL_META_ENDPOINT.getUrl(atsdConnectionInfo);
final ContentDescription contentDescription = new ContentDescription(
metaEndpoint, atsdConnectionInfo, signature.sql, new StatementContext(handle));
try (final IContentProtocol protocol = new SdkProtocolImpl(contentDescription)) {
final List columnMetaData = ContentMetadata.buildMetadataList(protocol.readContent(0),
atsdConnectionInfo.catalog(), atsdConnectionInfo.assignColumnNames(), atsdConnectionInfo.odbc2Compatibility());
signature.columns.addAll(columnMetaData);
} catch (AtsdJsonException e) {
final Object jsonError = e.getJson().get("error");
if (jsonError != null) {
log.error("[updatePreparedStatementResultSetMetaData] error: {}", jsonError);
throw new SQLException(jsonError.toString());
} else {
throw new SQLException(e);
}
} catch (AtsdRuntimeException e) {
log.error("[updatePreparedStatementResultSetMetaData] error: {}", e.getMessage());
throw new SQLDataException(e);
} catch (Exception e) {
log.error("[updatePreparedStatementResultSetMetaData] error", e);
throw new SQLException(e);
}
}
}
@Override
public ExecuteResult execute(StatementHandle statementHandle, List parameterValues, long maxRowsCount)
throws NoSuchStatementException {
return execute(statementHandle, parameterValues, AvaticaUtils.toSaturatedInt(maxRowsCount));
}
@Override
@SneakyThrows(SQLDataException.class)
public ExecuteResult execute(StatementHandle statementHandle, List parameterValues, int maxRowsInFirstFrame) throws NoSuchStatementException {
if (log.isTraceEnabled()) {
log.trace("[execute] maxRowsInFirstFrame: {} parameters: {} handle: {}", maxRowsInFirstFrame, parameterValues.size(),
statementHandle.toString());
}
final List queryParts = queryPartsMap.get(statementHandle.id);
if (queryParts == null) {
throw new NoSuchStatementException(statementHandle);
}
final String query = substitutePlaceholders(queryParts, parameterValues);
final AvaticaStatement statement = connection.statementMap.get(statementHandle.id);
if (statement == null) {
throw new NoSuchStatementException(statementHandle);
}
final StatementType statementType = statement.getStatementType();
try {
IDataProvider provider = createDataProvider(statementHandle, query, statementType);
final int timeoutMillis = statement.getQueryTimeout() * 1000;
final ExecuteResult result;
if (SELECT == statementType) {
final int maxRows = statement.getMaxRows();
provider.fetchData(maxRows, timeoutMillis);
final ContentMetadata contentMetadata = createMetadata(query, statementHandle.connectionId, statementHandle.id);
result = new ExecuteResult(contentMetadata.getList());
} else {
String content = AtsdSqlConverterFactory.getConverter(statementType, atsdConnectionInfo.timestampTz()).convertToCommand(query);
provider.getContentDescription().setPostContent(content);
long updateCount = provider.sendData(timeoutMillis);
MetaResultSet metaResultSet = MetaResultSet.count(statementHandle.connectionId, statementHandle.id, updateCount);
List resultSets = Collections.singletonList(metaResultSet);
result = new ExecuteResult(resultSets);
}
return result;
} catch (SQLDataException e) {
log.error("[execute] error", e.getMessage());
throw e;
} catch (final RuntimeException e) {
log.error("[execute] error", e);
throw e;
} catch (final Exception e) {
log.error("[execute] error", e);
throw new AtsdRuntimeException(e.getMessage(), e);
}
}
static List splitQueryByPlaceholder(String query) {
final List queryParts = new ArrayList<>();
final int length = query.length();
boolean quoted = false;
boolean singleQuoted = false;
int startOfQueryPart = 0;
for (int i = 0; i < length; i++) {
char currentChar = query.charAt(i);
switch (currentChar) {
case '?':
if (!quoted && !singleQuoted) {
queryParts.add(query.substring(startOfQueryPart, i));
startOfQueryPart = i + 1;
}
break;
case '\'':
if (!quoted) {
singleQuoted = !singleQuoted;
}
break;
case '"':
if (!singleQuoted) {
quoted = !quoted;
}
break;
}
}
queryParts.add(StringUtils.substring(query, startOfQueryPart));
return queryParts;
}
private static String substitutePlaceholders(List queryParts, List parameterValues) {
final int parametersSize = parameterValues.size();
final int queryPartsSize = queryParts.size();
if (queryPartsSize - 1 != parametersSize) {
throw new AtsdRuntimeException(String.format("Number of specified values [%d] does not match the number of placeholder occurrences [%d]",
parametersSize, queryPartsSize - 1));
}
if (queryPartsSize == 1) {
return queryParts.get(0);
}
final StringBuilder buffer = new StringBuilder();
for (int i = 0; i < parametersSize; i++) {
buffer.append(queryParts.get(i));
appendTypedValue(parameterValues.get(i), buffer);
}
buffer.append(queryParts.get(parametersSize));
final String result = buffer.toString();
log.debug("[substitutePlaceholders] {}", result);
return result;
}
private static void appendTypedValue(TypedValue parameterValue, StringBuilder buffer) {
Object value = parameterValue.value;
if (value == null) {
buffer.append("NULL");
return;
}
switch(parameterValue.type) {
case STRING:
buffer.append('\'').append(value).append('\'');
break;
case JAVA_SQL_TIMESTAMP:
case JAVA_UTIL_DATE:
buffer.append('\'').append(TIMESTAMP_FORMATTER.format(value)).append('\'');
break;
case OBJECT:
appendObjectValue(value, buffer);
break;
default:
buffer.append(value);
}
}
private static void appendObjectValue(Object value, StringBuilder buffer) {
if (value instanceof String) {
buffer.append('\'').append(value).append('\'');
} else if (value instanceof Date) {
buffer.append('\'').append(TIMESTAMP_FORMATTER.format((Date) value)).append('\'');
} else {
buffer.append(value);
}
}
@Override
public ExecuteResult prepareAndExecute(StatementHandle statementHandle, String query, long maxRowCount,
PrepareCallback callback) throws NoSuchStatementException {
return prepareAndExecute(statementHandle, query, maxRowCount, 0, callback);
}
@Override
@SneakyThrows(SQLDataException.class)
public ExecuteResult prepareAndExecute(StatementHandle statementHandle, String query, long maxRowCount,
int maxRowsInFrame, PrepareCallback callback) throws NoSuchStatementException {
final long limit = maxRowCount < 0 ? 0 : maxRowCount;
log.trace("[prepareAndExecute] handle: {} maxRowCount: {} query: {}", statementHandle, limit, query);
try {
final AvaticaStatement statement = (AvaticaStatement) callback.getMonitor();
final StatementType statementType = statement.getStatementType() == null ? EnumUtil.getStatementTypeByQuery(query) : statement.getStatementType();
final IDataProvider provider = createDataProvider(statementHandle, query, statementType);
final long updateCount;
if (SELECT == statementType) {
provider.fetchData(limit, statement.getQueryTimeout());
updateCount = -1;
} else {
String content = AtsdSqlConverterFactory.getConverter(statementType, atsdConnectionInfo.timestampTz()).convertToCommand(query);
provider.getContentDescription().setPostContent(content);
updateCount = provider.sendData(statement.getQueryTimeout());
}
final ContentMetadata contentMetadata = createMetadata(query, statementHandle.connectionId, statementHandle.id);
synchronized (callback.getMonitor()) {
callback.clear();
callback.assign(contentMetadata.getSign(), null, updateCount);
}
final ExecuteResult result = new ExecuteResult(contentMetadata.getList());
callback.execute();
return result;
} catch (final AtsdRuntimeException e) {
log.error("[prepareAndExecute] error", e);
throw new SQLDataException(e.getMessage(), e);
} catch (final RuntimeException e) {
log.error("[prepareAndExecute] error", e);
throw e;
} catch (final Exception e) {
log.error("[prepareAndExecute] error", e);
throw new AtsdRuntimeException(e.getMessage(), e);
}
}
@Override
public ExecuteBatchResult prepareAndExecuteBatch(StatementHandle statementHandle, List queries) throws NoSuchStatementException {
log.trace("[prepareAndExecuteBatch] handle: {} queries: {}", statementHandle.toString(), queries);
try {
final AvaticaStatement statement = connection.statementMap.get(statementHandle.id);
long[] updateCounts = new long[queries.size()];
int count = 0;
for (String query : queries) {
final StatementType statementType = statement.getStatementType() == null ? EnumUtil.getStatementTypeByQuery(query) : statement.getStatementType();
if (SELECT == statementType) {
throw new IllegalArgumentException("Invalid statement type: " + statementType);
}
final IDataProvider provider = createDataProvider(statementHandle, query, statementType);
String content = AtsdSqlConverterFactory.getConverter(statementType, atsdConnectionInfo.timestampTz()).convertToCommand(query);
provider.getContentDescription().setPostContent(content);
long updateCount = provider.sendData(statement.getQueryTimeout());
updateCounts[count++] = updateCount;
}
final ExecuteBatchResult result = new ExecuteBatchResult(updateCounts);
return result;
} catch (final RuntimeException e) {
log.error("[prepareAndExecuteBatch] error", e);
throw e;
} catch (final Exception e) {
log.error("[prepareAndExecuteBatch] error", e);
throw new AtsdRuntimeException(e.getMessage(), e);
}
}
@Override
public ExecuteBatchResult executeBatch(StatementHandle statementHandle, List> parameterValueBatch) throws NoSuchStatementException {
log.trace("[executeBatch] parameters: {} handle: {}", parameterValueBatch.size(), statementHandle.toString());
final AvaticaStatement statement = connection.statementMap.get(statementHandle.id);
final StatementType statementType = statement.getStatementType();
if (SELECT == statementType) {
throw new IllegalArgumentException("Invalid statement type: " + statementType);
}
final String query = ((AtsdPreparedStatement) statement).getSql();
final List> preparedValueBatch = prepareValueBatch(parameterValueBatch);
try {
IDataProvider provider = createDataProvider(statementHandle, query, statementType);
final int timeoutMillis = statement.getQueryTimeout();
String content = AtsdSqlConverterFactory
.getConverter(statementType, atsdConnectionInfo.timestampTz())
.convertBatchToCommands(query, preparedValueBatch);
provider.getContentDescription().setPostContent(content);
long updateCount = provider.sendData(timeoutMillis);
ExecuteBatchResult result = new ExecuteBatchResult(generateExecuteBatchResult(parameterValueBatch.size(), updateCount == 0 ? 0 : 1));
return result;
} catch (final RuntimeException e) {
log.error("[executeBatch] error", e);
throw e;
} catch (final Exception e) {
log.error("[executeBatch] error", e);
throw new AtsdRuntimeException(e.getMessage(), e);
}
}
@Override
public Frame fetch(final StatementHandle statementHandle, long loffset, int fetchMaxRowCount)
throws NoSuchStatementException, MissingResultsException {
final int offset = (int) loffset;
log.trace("[fetch] statement: {} fetchMaxRowCount: {}, offset: {}", statementHandle.id, fetchMaxRowCount, offset);
IDataProvider provider = providerCache.get(statementHandle.id);
if (provider == null) {
throw new MissingResultsException(statementHandle);
}
final IStoreStrategy strategy = provider.getStrategy();
final ContentMetadata contentMetadata = metaCache.get(statementHandle.id);
if (contentMetadata == null) {
throw new MissingResultsException(statementHandle);
}
try {
if (offset == 0) {
final String[] headers = strategy.openToRead(contentMetadata.getMetadataList());
if (ArrayUtils.isEmpty(headers)) {
throw new MissingResultsException(statementHandle);
}
}
@SuppressWarnings("unchecked")
final List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy