/* * Copyright 2016 KairosDB Authors * * 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 org.kairosdb.datastore.h2; import com.google.common.collect.ImmutableSortedMap; import com.google.inject.Inject; import com.google.inject.name.Named; import com.mchange.v2.c3p0.DataSources; import org.agileclick.genorm.runtime.GenOrmQueryResultSet; import org.h2.jdbcx.JdbcDataSource; import org.kairosdb.core.KairosDataPointFactory; import org.kairosdb.core.datastore.*; import org.kairosdb.core.exception.DatastoreException; import org.kairosdb.datastore.h2.orm.*; import org.kairosdb.util.KDataInput; import org.kairosdb.util.KDataOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.*; public class H2Datastore implements Datastore { public static final Logger logger = LoggerFactory.getLogger(H2Datastore.class); public static final String DATABASE_PATH_PROPERTY = "kairosdb.datastore.h2.database_path"; private Connection m_holdConnection; //Connection that holds the database open private KairosDataPointFactory m_dataPointFactory; @Inject public H2Datastore(@Named(DATABASE_PATH_PROPERTY) String dbPath, KairosDataPointFactory dataPointFactory) throws DatastoreException { m_dataPointFactory = dataPointFactory; boolean createDB = false; File dataDir = new File(dbPath); if (!dataDir.exists()) createDB = true; dbPath = dbPath.replace('\\', '/'); //newer H2 is more strict about relative paths String jdbcPath = (dataDir.isAbsolute() || dbPath.startsWith("./") ? "" : "./") + dbPath; logger.info("Starting H2 database in " + jdbcPath); JdbcDataSource ds = new JdbcDataSource(); ds.setURL("jdbc:h2:" + jdbcPath + "/kairosdb"); ds.setUser("sa"); try { GenOrmDataSource.setDataSource(new DSEnvelope(DataSources.pooledDataSource(ds))); } catch (SQLException e) { e.printStackTrace(); } try { if (createDB) createDatabase(ds); } catch (SQLException e) { //TODO System.out.println("Oh Crap"); e.printStackTrace(); } catch (IOException e) { //TODO System.out.println("double oh crap"); e.printStackTrace(); } } private void createDatabase(DataSource ds) throws IOException, SQLException { logger.info("Creating DB"); m_holdConnection = ds.getConnection(); m_holdConnection.setAutoCommit(false); StringBuilder sb = new StringBuilder(); try(InputStreamReader reader = new InputStreamReader(getClass().getClassLoader() .getResourceAsStream("create.sql"))) { int ch; while ((ch = reader.read()) != -1) sb.append((char) ch); } String[] tableCommands = sb.toString().split(";"); Statement s = m_holdConnection.createStatement(); for (String command : tableCommands) s.execute(command); m_holdConnection.commit(); } @Override public void close() { try { if (m_holdConnection != null) m_holdConnection.close(); } catch (SQLException e) { logger.error("Failed closing last connection:", e); } } @Override public synchronized void putDataPoint(String metricName, ImmutableSortedMap<String, String> tags, org.kairosdb.core.DataPoint dataPoint, int ttl) throws DatastoreException { GenOrmDataSource.attachAndBegin(); try { String key = createMetricKey(metricName, tags, dataPoint.getDataStoreDataType()); Metric m = Metric.factory.findOrCreate(key); if (m.isNew()) { m.setName(metricName); m.setType(dataPoint.getDataStoreDataType()); for (String name : tags.keySet()) { String value = tags.get(name); Tag.factory.findOrCreate(name, value); MetricTag.factory.findOrCreate(key, name, value); } GenOrmDataSource.flush(); } KDataOutput dataOutput = new KDataOutput(); dataPoint.writeValueToBuffer(dataOutput); new InsertDataPointQuery(m.getId(), new Timestamp(dataPoint.getTimestamp()), dataOutput.getBytes()).runUpdate(); GenOrmDataSource.commit(); } catch (IOException e) { throw new DatastoreException(e); } finally { GenOrmDataSource.close(); } } @Override public Iterable<String> getMetricNames() { MetricNamesQuery query = new MetricNamesQuery(); MetricNamesQuery.ResultSet results = query.runQuery(); List<String> metricNames = new ArrayList<String>(); while (results.next()) { metricNames.add(results.getRecord().getName()); } results.close(); return (metricNames); } @Override public Iterable<String> getTagNames() { TagNamesQuery.ResultSet results = new TagNamesQuery().runQuery(); List<String> tagNames = new ArrayList<String>(); while (results.next()) tagNames.add(results.getRecord().getName()); results.close(); return (tagNames); } @Override public Iterable<String> getTagValues() { TagValuesQuery.ResultSet results = new TagValuesQuery().runQuery(); List<String> tagValues = new ArrayList<String>(); while (results.next()) tagValues.add(results.getRecord().getValue()); results.close(); return (tagValues); } private GenOrmQueryResultSet<? extends MetricIdResults> getMetricIdsForQuery(DatastoreMetricQuery query) { StringBuilder sb = new StringBuilder(); GenOrmQueryResultSet<? extends MetricIdResults> idQuery = null; //Manually build the where clause for the tags //This is subject to sql injection Set<String> filterTags = query.getTags().keySet(); if (filterTags.size() != 0) { sb.append(" and ("); boolean first = true; for (String tag : filterTags) { if (!first) sb.append(" or "); first = false; sb.append(" (mt.\"tag_name\" = '").append(tag); sb.append("' and ("); Set<String> values = query.getTags().get(tag); boolean firstValue = true; for (String value : values) { if (!firstValue) sb.append(" or "); firstValue = false; sb.append("mt.\"tag_value\" = '").append(value); sb.append("' "); } sb.append(")) "); } sb.append(") "); idQuery = new MetricIdsWithTagsQuery(query.getName(), filterTags.size(), sb.toString()).runQuery(); } else { idQuery = new MetricIdsQuery(query.getName()).runQuery(); } return (idQuery); } @Override public void queryDatabase(DatastoreMetricQuery query, QueryCallback queryCallback) throws DatastoreException { GenOrmQueryResultSet<? extends MetricIdResults> idQuery = getMetricIdsForQuery(query); try { while (idQuery.next()) { MetricIdResults result = idQuery.getRecord(); String metricId = result.getMetricId(); String type = result.getType(); //Collect the tags in the results MetricTag.ResultSet tags = MetricTag.factory.getByMetric(metricId); Map<String, String> tagMap = new TreeMap<String, String>(); while (tags.next()) { MetricTag mtag = tags.getRecord(); tagMap.put(mtag.getTagName(), mtag.getTagValue()); } tags.close(); Timestamp startTime = new Timestamp(query.getStartTime()); Timestamp endTime = new Timestamp(query.getEndTime()); DataPoint.ResultSet resultSet; if (query.getLimit() == 0) { resultSet = DataPoint.factory.getForMetricId(metricId, startTime, endTime, query.getOrder().getText()); } else { resultSet = DataPoint.factory.getForMetricIdWithLimit(metricId, startTime, endTime, query.getLimit(), query.getOrder().getText()); } try { boolean startedDataPointSet = false; while (resultSet.next()) { if (!startedDataPointSet) { queryCallback.startDataPointSet(type, tagMap); startedDataPointSet = true; } DataPoint record = resultSet.getRecord(); queryCallback.addDataPoint(m_dataPointFactory.createDataPoint(type, record.getTimestamp().getTime(), KDataInput.createInput(record.getValue()))); } } finally { resultSet.close(); } } queryCallback.endDataPoints(); } catch (IOException e) { throw new DatastoreException(e); } finally { idQuery.close(); } } @Override public void deleteDataPoints(DatastoreMetricQuery deleteQuery) throws DatastoreException { GenOrmDataSource.attachAndBegin(); try { GenOrmQueryResultSet<? extends MetricIdResults> idQuery = getMetricIdsForQuery(deleteQuery); while (idQuery.next()) { String metricId = idQuery.getRecord().getMetricId(); new DeleteMetricsQuery(metricId, new Timestamp(deleteQuery.getStartTime()), new Timestamp(deleteQuery.getEndTime())).runUpdate(); if (DataPoint.factory.getWithMetricId(metricId) == null) { Metric.factory.find(metricId).delete(); } } idQuery.close(); GenOrmDataSource.commit(); } finally { GenOrmDataSource.close(); } } @Override public TagSet queryMetricTags(DatastoreMetricQuery query) throws DatastoreException { GenOrmQueryResultSet<? extends MetricIdResults> idQuery = getMetricIdsForQuery(query); TagSetImpl tagSet = new TagSetImpl(); try { while (idQuery.next()) { String metricId = idQuery.getRecord().getMetricId(); //Collect the tags in the results MetricTag.ResultSet tags = MetricTag.factory.getByMetric(metricId); while (tags.next()) { MetricTag mtag = tags.getRecord(); tagSet.addTag(mtag.getTagName(), mtag.getTagValue()); } tags.close(); } } finally { idQuery.close(); } return tagSet; } private String createMetricKey(String metricName, SortedMap<String, String> tags, String type) { StringBuilder sb = new StringBuilder(); sb.append(metricName).append(":"); sb.append(type).append(":"); for (String name : tags.keySet()) { sb.append(name).append("="); sb.append(tags.get(name)).append(":"); } return (sb.toString()); } }