Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.axibase.tsd.driver.jdbc.converter.AtsdSqlConverter Maven / Gradle / Ivy
package com.axibase.tsd.driver.jdbc.converter;
import com.axibase.tsd.driver.jdbc.ext.AtsdMeta;
import com.axibase.tsd.driver.jdbc.logging.LoggingFacade;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.calcite.avatica.com.fasterxml.jackson.databind.util.ISO8601Utils;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDynamicParam;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.util.NlsString;
import org.apache.commons.lang3.StringUtils;
import static com.axibase.tsd.driver.jdbc.DriverConstants.DEFAULT_TABLE_NAME;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
public abstract class AtsdSqlConverter {
protected final LoggingFacade logger = LoggingFacade.getLogger(getClass());
private static final Pattern DATETIME_ISO_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z$");
private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?$");
private static final char TAGS_DELIMETER = ';';
private static final String ENTITY = "entity";
private static final String TIME = "time";
private static final String DATETIME = "datetime";
private static final String METRIC = "metric";
protected static final String VALUE = "value";
private static final String TEXT = "text";
protected static final String TAGS = "tags";
protected static final String PREFIX_TAGS = "tags.";
protected T rootNode;
private final boolean timestampTz;
protected AtsdSqlConverter(boolean timestampTz) {
this.timestampTz = timestampTz;
}
public String convertToCommand(String sql) throws SQLException {
return convertToCommand(sql, null);
}
public String convertToCommand(String sql, List parameterValues) throws SQLException {
logger.debug("[convertToCommand] parameterCount: {}", getSize(parameterValues));
try {
sql = prepareSql(sql);
} catch (RuntimeException e) {
throw new SQLException("SQL prepare error: " + sql, e);
}
this.rootNode = parseSql(sql);
final String result = createSeriesCommand(parameterValues);
logger.trace("[convertToCommand] result: {}", result);
return result;
}
public String convertBatchToCommands(String sql, List> parameterValuesBatch) throws SQLException {
logger.debug("[convertBatchToCommands] batchSize: {}", getSize(parameterValuesBatch));
try {
sql = prepareSql(sql);
} catch (RuntimeException e) {
throw new SQLException("SQL prepare error: " + sql, e);
}
this.rootNode = parseSql(sql);
final String result =createSeriesCommandBatch(parameterValuesBatch);
logger.trace("[convertBatchToCommands] result: {}", result);
return result;
}
protected abstract String prepareSql(String sql) throws SQLException;
protected abstract String getTargetTableName();
protected abstract List getColumnNames();
protected abstract List getColumnValues(List parameterValues);
protected abstract List> getColumnValuesBatch(List> parameterValuesBatch);
private T parseSql(String sql) throws SQLException {
logger.debug("[parseSql]");
try {
SqlParser sqlParser = SqlParser.create(sql, SqlParser.configBuilder()
.setParserFactory(SqlParserImpl.FACTORY)
.setUnquotedCasing(Casing.TO_LOWER)
.setQuoting(Quoting.DOUBLE_QUOTE)
.build());
return (T) sqlParser.parseStmt();
} catch (SqlParseException exc) {
throw new SQLException("Could not parse sql: " + sql, exc);
}
}
private String createSeriesCommand(List parameterValues) throws SQLDataException {
logger.debug("[createSeriesCommand]");
final String tableName = getTargetTableName();
final List columnNames = getColumnNames();
logger.debug("[createSeriesCommand] tableName: {} columnCount: {}", tableName, columnNames.size());
logger.trace("[createSeriesCommand] columnNames: {}", columnNames);
final List values = getColumnValues(parameterValues);
logger.trace("[createSeriesCommand] values: {}", values);
return DEFAULT_TABLE_NAME.equals(tableName) ? composeSeriesCommand(logger, columnNames, values, timestampTz) : composeSeriesCommand(logger, tableName,
columnNames, values, timestampTz);
}
private String createSeriesCommandBatch(List> parameterValueBatch) throws SQLDataException {
logger.debug("[createSeriesCommandBatch]");
final String tableName = getTargetTableName();
final List columnNames = getColumnNames();
logger.debug("[createSeriesCommandBatch] tableName: {} columnCount: {}", tableName, columnNames.size());
logger.trace("[createSeriesCommandBatch] columnNames: {}", columnNames);
final List> valueBatch = getColumnValuesBatch(parameterValueBatch);
StringBuilder buffer = new StringBuilder();
if (DEFAULT_TABLE_NAME.equals(tableName)) {
for (List values : valueBatch) {
buffer.append(composeSeriesCommand(logger, columnNames, values, timestampTz));
}
} else {
for (List values : valueBatch) {
buffer.append(composeSeriesCommand(logger, tableName, columnNames, values, timestampTz));
}
}
return buffer.toString();
}
private static String composeSeriesCommand(LoggingFacade logger, final String metricName, final List columnNames, final List values,
boolean timestampTz)
throws SQLDataException {
if (columnNames.size() != values.size()) {
throw new IndexOutOfBoundsException(
String.format("Number of values [%d] does not match to number of columns [%d]",
values.size(), columnNames.size()));
}
SeriesCommand command = new SeriesCommand();
String columnName;
Object value;
for (int i = 0; i columnNames, final List values, boolean timestampTz) throws
SQLDataException {
if (columnNames.size() != values.size()) {
throw new IndexOutOfBoundsException(
String.format("Number of values [%d] does not match to number of columns [%d]",
values.size(), columnNames.size()));
}
SeriesCommand command = new SeriesCommand();
String columnName;
Object value;
String metricName = null;
Double metricValue = null;
String metricText = null;
for (int i = 0; i T validate(Object value, Class targetClass) throws SQLDataException {
if (targetClass.isInstance(value)) {
return (T) value;
}
throw new SQLDataException("Invalid value: " + value + ". Current type: " + value.getClass().getSimpleName()
+ ", expected type: " + targetClass.getSimpleName());
}
private static String validateDateTime(Object value, boolean timestampTz) throws SQLDataException {
if (value instanceof Number) {
return AtsdMeta.TIMESTAMP_FORMATTER.format(((Number) value).longValue());
} else if (!(value instanceof String)) {
throw new SQLDataException("Invalid value: " + value + ". Current type: " + value.getClass().getSimpleName()
+ ", expected type: " + Timestamp.class.getSimpleName());
}
final String dateTime = value.toString();
Matcher matcher = DATETIME_ISO_PATTERN.matcher(dateTime);
if (matcher.matches()) {
return dateTime;
}
matcher = TIMESTAMP_PATTERN.matcher(dateTime);
if (matcher.matches()) {
final Timestamp timestamp = Timestamp.valueOf(dateTime);
final Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp.getTime());
if (timestampTz) {
calendar.set(Calendar.ZONE_OFFSET, TimeZone.getTimeZone("UTC").getRawOffset());
}
return ISO8601Utils.format(calendar.getTime(), true);
}
throw new SQLDataException("Invalid datetime value: " + value + ". Expected formats: yyyy-MM-dd'T'HH:mm:ss[.SSS]'Z', yyyy-MM-dd HH:mm:ss[.fffffffff]");
}
private static Map parseTags(String value) throws SQLDataException {
if (StringUtils.isBlank(value)) {
return Collections.emptyMap();
}
String[] tags = StringUtils.split(value, TAGS_DELIMETER);
Map result = new LinkedHashMap<>();
Pair nameAndValue;
for (String tag : tags) {
nameAndValue = parseTag(StringUtils.trim(tag));
if (nameAndValue != null) {
result.put(nameAndValue.getKey(), nameAndValue.getValue());
}
}
return result;
}
private static Pair parseTag(String value) throws SQLDataException {
if (StringUtils.isBlank(value)) {
return null;
}
final int idx = value.indexOf('=');
if (idx < 1) {
throw new SQLDataException("Invalid tags value: " + value);
}
String tagName = StringUtils.trim(value.substring(0, idx));
String tagValue = StringUtils.trim(value.substring(idx + 1));
return StringUtils.isBlank(tagValue) ? null : new ImmutablePair<>(tagName, tagValue);
}
public static final class DynamicParam {
final int index;
private DynamicParam(int index) {
this.index = index;
}
private static DynamicParam create(int index) {
return new DynamicParam(index);
}
}
}