blob: 851ce9d21817a1e9fdaacf6af8a20e64a6e77d31 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.iotdb.web.grafana.dao.impl;
import org.apache.iotdb.web.grafana.bean.TimeValues;
import org.apache.iotdb.web.grafana.dao.BasicDao;
import org.apache.tsfile.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Repository
@PropertySource("classpath:application.properties")
public class BasicDaoImpl implements BasicDao {
private static final Logger logger = LoggerFactory.getLogger(BasicDaoImpl.class);
private final JdbcTemplate jdbcTemplate;
private static long timestampRadioX = -1L;
@Value("${timestamp_precision}")
private String timestampPrecision = "ms";
@Value("${isDownSampling}")
private boolean isDownSampling;
@Value("${continuous_data_function}")
private String continuousDataFunction;
@Value("${discrete_data_function}")
private String discreteDataFunction;
@Value("${interval}")
private String interval;
@Autowired
public BasicDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<String> getMetaData() {
return jdbcTemplate.execute(
(ConnectionCallback<List<String>>)
connection -> {
try (Statement statement = connection.createStatement()) {
statement.execute("show timeseries root.**");
logger.info("Start to get timeseries");
try (ResultSet resultSet = statement.getResultSet()) {
List<String> columnsName = new ArrayList<>();
while (resultSet.next()) {
columnsName.add(resultSet.getString(1).substring(5));
}
return columnsName;
}
}
});
}
public static void setTimestampRadioX(String timestampPrecision) {
switch (timestampPrecision) {
case "us":
timestampRadioX = 1000L;
break;
case "ns":
timestampRadioX = 1000_000L;
break;
default:
timestampRadioX = 1L;
}
timestampPrecision = timestampPrecision.replaceAll("[\n\r\t]", "_");
logger.info("Use timestamp precision {}", timestampPrecision);
}
/**
* Note: If the query fails this could be due to AGGREGATION like AVG on boolean field. Thus, we
* then do a retry with FIRST aggregation. This should be solved better in the long run.
*/
@Override
public List<TimeValues> querySeries(String s, Pair<ZonedDateTime, ZonedDateTime> timeRange) {
if (timestampRadioX == -1L) {
setTimestampRadioX(timestampPrecision);
}
try {
return querySeriesInternal(s, timeRange, continuousDataFunction);
} catch (Exception e) {
// Try it with discreteDataFunction
try {
return querySeriesInternal(s, timeRange, discreteDataFunction);
} catch (Exception e2) {
logger.warn("Even {} query did not succeed, returning NULL now", discreteDataFunction, e2);
return Collections.emptyList();
}
}
}
public List<TimeValues> querySeriesInternal(
String s, Pair<ZonedDateTime, ZonedDateTime> timeRange, String function) {
long from = zonedCovertToLong(timeRange.left);
long to = zonedCovertToLong(timeRange.right);
final long hours = Duration.between(timeRange.left, timeRange.right).toHours();
String sql = "SELECT ? FROM root.? WHERE time > ? and time < ?";
Object[] params =
new Object[] {
s.substring(s.lastIndexOf('.') + 1),
s.substring(0, s.lastIndexOf('.')),
from * timestampRadioX,
to * timestampRadioX,
};
String columnName = "root." + s;
String intervalLocal = getInterval(hours);
if (!"".equals(intervalLocal)) {
sql = "SELECT ?(?) FROM root.? WHERE time > ? and time < ? group by ([?, ?),?)";
params =
new Object[] {
function,
s.substring(s.lastIndexOf('.') + 1),
s.substring(0, s.lastIndexOf('.')),
from * timestampRadioX,
to * timestampRadioX,
from * timestampRadioX,
to * timestampRadioX,
intervalLocal
};
columnName = function + "(root." + s + ")";
}
logger.info("SQL: {}, Params: {}", sql, params);
return jdbcTemplate.query(sql, params, new TimeValuesRowMapper(columnName));
}
public String getInterval(final long hours) {
if (!isDownSampling || hours <= 1) {
return "";
}
if (hours < 30 * 24 && hours > 24) {
return "1h";
} else if (hours > 30 * 24) {
return "1d";
}
return this.interval;
}
private long zonedCovertToLong(ZonedDateTime time) {
return time.toInstant().toEpochMilli();
}
static class TimeValuesRowMapper implements RowMapper<TimeValues> {
String columnName;
TimeValuesRowMapper(String columnName) {
this.columnName = columnName;
}
@Override
public TimeValues mapRow(ResultSet resultSet, int i) throws SQLException {
TimeValues tv = new TimeValues();
tv.setTime(resultSet.getLong("Time") / timestampRadioX);
String valueString = resultSet.getString(columnName);
if (valueString != null) {
try {
tv.setValue(Float.parseFloat(resultSet.getString(columnName)));
} catch (Exception e) {
tv.setValue(resultSet.getString(columnName));
}
}
return tv;
}
}
}