/* * 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 gobblin.metastore.database; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.AbstractMap; import java.util.Arrays; import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.linkedin.data.template.StringMap; import gobblin.metastore.DatabaseJobHistoryStore; import gobblin.metastore.JobHistoryStore; import gobblin.rest.JobExecutionInfo; import gobblin.rest.JobExecutionQuery; import gobblin.rest.JobStateEnum; import gobblin.rest.LauncherTypeEnum; import gobblin.rest.Metric; import gobblin.rest.MetricArray; import gobblin.rest.MetricTypeEnum; import gobblin.rest.QueryListType; import gobblin.rest.Table; import gobblin.rest.TableTypeEnum; import gobblin.rest.TaskExecutionInfo; import gobblin.rest.TaskExecutionInfoArray; import gobblin.rest.TaskStateEnum; import gobblin.rest.TimeRange; /** * An implementation of {@link JobHistoryStore} backed by MySQL. * * <p> * The DDLs for the MySQL job history store can be found under metastore/src/main/resources. * </p> * * @author Yinan Li */ @SupportedDatabaseVersion(isDefault = true, version = "1.0.0") public class DatabaseJobHistoryStoreV100 implements VersionedDatabaseJobHistoryStore { private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseJobHistoryStore.class); private static final String JOB_EXECUTION_INSERT_STATEMENT_TEMPLATE = "INSERT INTO gobblin_job_executions (job_name,job_id,start_time,end_time,duration,state," + "launched_tasks,completed_tasks,launcher_type,tracking_url) VALUES(?,?,?,?,?,?,?,?,?,?)"; private static final String TASK_EXECUTION_INSERT_STATEMENT_TEMPLATE = "INSERT INTO gobblin_task_executions (task_id,job_id,start_time,end_time,duration," + "state,failure_exception,low_watermark,high_watermark,table_namespace,table_name,table_type) " + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"; private static final String JOB_METRIC_INSERT_STATEMENT_TEMPLATE = "INSERT INTO gobblin_job_metrics (job_id,metric_group,metric_name," + "metric_type,metric_value) VALUES(?,?,?,?,?)"; private static final String TASK_METRIC_INSERT_STATEMENT_TEMPLATE = "INSERT INTO gobblin_task_metrics (task_id,metric_group,metric_name," + "metric_type,metric_value) VALUES(?,?,?,?,?)"; private static final String JOB_PROPERTY_INSERT_STATEMENT_TEMPLATE = "INSERT INTO gobblin_job_properties (job_id,property_key,property_value) VALUES(?,?,?)"; private static final String TASK_PROPERTY_INSERT_STATEMENT_TEMPLATE = "INSERT INTO gobblin_task_properties (task_id,property_key,property_value) VALUES(?,?,?)"; private static final String JOB_EXECUTION_UPDATE_STATEMENT_TEMPLATE = "UPDATE gobblin_job_executions SET start_time=?,end_time=?,duration=?," + "state=?,launched_tasks=?,completed_tasks=?,launcher_type=?,tracking_url=? WHERE job_id=?"; private static final String TASK_EXECUTION_UPDATE_STATEMENT_TEMPLATE = "UPDATE gobblin_task_executions SET start_time=?,end_time=?,duration=?,state=?,failure_exception=?," + "low_watermark=?,high_watermark=?,table_namespace=?,table_name=?,table_type=? WHERE task_id=?"; private static final String JOB_METRIC_UPDATE_STATEMENT_TEMPLATE = "UPDATE gobblin_job_metrics SET metric_value=? WHERE job_id=? AND " + "metric_group=? AND metric_name=? AND metric_type=?"; private static final String TASK_METRIC_UPDATE_STATEMENT_TEMPLATE = "UPDATE gobblin_task_metrics SET metric_value=? WHERE task_id=? AND " + "metric_group=? AND metric_name=? AND metric_type=?"; private static final String JOB_PROPERTY_UPDATE_STATEMENT_TEMPLATE = "UPDATE gobblin_job_properties SET property_value=? WHERE job_id=? AND property_key=?"; private static final String TASK_PROPERTY_UPDATE_STATEMENT_TEMPLATE = "UPDATE gobblin_task_properties SET property_value=? WHERE task_id=? AND property_key=?"; private static final String LIST_DISTINCT_JOB_EXECUTION_QUERY_TEMPLATE = "SELECT j.job_id FROM gobblin_job_executions j, " + "(SELECT MAX(last_modified_ts) AS most_recent_ts, job_name " + "FROM gobblin_job_executions GROUP BY job_name) max_results " + "WHERE j.job_name = max_results.job_name AND j.last_modified_ts = max_results.most_recent_ts"; private static final String LIST_RECENT_JOB_EXECUTION_QUERY_TEMPLATE = "SELECT job_id FROM gobblin_job_executions"; private static final String JOB_NAME_QUERY_BY_TABLE_STATEMENT_TEMPLATE = "SELECT j.job_name FROM gobblin_job_executions j, gobblin_task_executions t " + "WHERE j.job_id=t.job_id AND %s GROUP BY j.job_name"; private static final String JOB_ID_QUERY_BY_JOB_NAME_STATEMENT_TEMPLATE = "SELECT job_id FROM gobblin_job_executions WHERE job_name=?"; private static final String JOB_EXECUTION_QUERY_BY_JOB_ID_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_job_executions WHERE job_id=?"; private static final String TASK_EXECUTION_EXIST_QUERY_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_task_executions WHERE task_id=?"; private static final String TASK_EXECUTION_QUERY_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_task_executions WHERE job_id=?"; private static final String JOB_METRIC_EXIST_QUERY_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_job_metrics " + "WHERE job_id=? AND metric_group=? AND metric_name=? AND metric_type=?"; private static final String TASK_METRIC_EXIST_QUERY_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_task_metrics " + "WHERE task_id=? AND metric_group=? AND metric_name=? AND metric_type=?"; private static final String JOB_METRIC_QUERY_STATEMENT_TEMPLATE = "SELECT metric_group,metric_name,metric_type,metric_value FROM gobblin_job_metrics WHERE job_id=?"; private static final String TASK_METRIC_QUERY_STATEMENT_TEMPLATE = "SELECT metric_group,metric_name,metric_type,metric_value FROM gobblin_task_metrics WHERE task_id=?"; private static final String JOB_PROPERTY_EXIST_QUERY_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_job_properties WHERE job_id=? AND property_key=?"; private static final String TASK_PROPERTY_EXIST_QUERY_STATEMENT_TEMPLATE = "SELECT * FROM gobblin_task_properties WHERE task_id=? AND property_key=?"; private static final String JOB_PROPERTY_QUERY_STATEMENT_TEMPLATE = "SELECT property_key, property_value FROM gobblin_job_properties WHERE job_id=?"; private static final String TASK_PROPERTY_QUERY_STATEMENT_TEMPLATE = "SELECT property_key, property_value FROM gobblin_task_properties WHERE task_id=?"; private static final Timestamp DEFAULT_TIMESTAMP = new Timestamp(1000L); private DataSource dataSource; @Override public void init(DataSource dataSource) { this.dataSource = dataSource; } @Override public synchronized void put(JobExecutionInfo jobExecutionInfo) throws IOException { Optional<Connection> connectionOptional = Optional.absent(); try { connectionOptional = Optional.of(getConnection()); Connection connection = connectionOptional.get(); connection.setAutoCommit(false); // Insert or update job execution information if (existsJobExecutionInfo(connection, jobExecutionInfo)) { updateJobExecutionInfo(connection, jobExecutionInfo); } else { insertJobExecutionInfo(connection, jobExecutionInfo); } // Insert or update job metrics if (jobExecutionInfo.hasMetrics()) { for (Metric metric : jobExecutionInfo.getMetrics()) { boolean insert = !existsMetric(connection, JOB_METRIC_EXIST_QUERY_STATEMENT_TEMPLATE, jobExecutionInfo.getJobId(), metric); updateMetric(connection, insert ? JOB_METRIC_INSERT_STATEMENT_TEMPLATE : JOB_METRIC_UPDATE_STATEMENT_TEMPLATE, jobExecutionInfo.getJobId(), metric, insert); } } // Insert or update job properties if (jobExecutionInfo.hasJobProperties()) { for (Map.Entry<String, String> entry : jobExecutionInfo.getJobProperties().entrySet()) { boolean insert = !existsProperty(connection, JOB_PROPERTY_EXIST_QUERY_STATEMENT_TEMPLATE, jobExecutionInfo.getJobId(), entry.getKey()); updateProperty(connection, insert ? JOB_PROPERTY_INSERT_STATEMENT_TEMPLATE : JOB_PROPERTY_UPDATE_STATEMENT_TEMPLATE, jobExecutionInfo.getJobId(), entry.getKey(), entry.getValue(), insert); } } // Insert or update task execution information if (jobExecutionInfo.hasTaskExecutions()) { for (TaskExecutionInfo info : jobExecutionInfo.getTaskExecutions()) { // Insert or update task execution information if (existsTaskExecutionInfo(connection, info)) { updateTaskExecutionInfo(connection, info); } else { insertTaskExecutionInfo(connection, info); } // Insert or update task metrics if (info.hasMetrics()) { for (Metric metric : info.getMetrics()) { boolean insert = !existsMetric(connection, TASK_METRIC_EXIST_QUERY_STATEMENT_TEMPLATE, info.getTaskId(), metric); updateMetric(connection, insert ? TASK_METRIC_INSERT_STATEMENT_TEMPLATE : TASK_METRIC_UPDATE_STATEMENT_TEMPLATE, info.getTaskId(), metric, insert); } } // Insert or update task properties if (info.hasTaskProperties()) { for (Map.Entry<String, String> entry : info.getTaskProperties().entrySet()) { boolean insert = !existsProperty(connection, TASK_PROPERTY_EXIST_QUERY_STATEMENT_TEMPLATE, info.getTaskId(), entry.getKey()); updateProperty(connection, insert ? TASK_PROPERTY_INSERT_STATEMENT_TEMPLATE : TASK_PROPERTY_UPDATE_STATEMENT_TEMPLATE, info.getTaskId(), entry.getKey(), entry.getValue(), insert); } } } } connection.commit(); } catch (SQLException se) { LOGGER.error("Failed to put a new job execution information record", se); if (connectionOptional.isPresent()) { try { connectionOptional.get().rollback(); } catch (SQLException se1) { LOGGER.error("Failed to rollback", se1); } } throw new IOException(se); } finally { if (connectionOptional.isPresent()) { try { connectionOptional.get().close(); } catch (SQLException se) { LOGGER.error("Failed to close connection", se); } } } } @Override public synchronized List<JobExecutionInfo> get(JobExecutionQuery query) throws IOException { Preconditions.checkArgument(query.hasId() && query.hasIdType()); Optional<Connection> connectionOptional = Optional.absent(); try { connectionOptional = Optional.of(getConnection()); Connection connection = connectionOptional.get(); switch (query.getIdType()) { case JOB_ID: List<JobExecutionInfo> jobExecutionInfos = Lists.newArrayList(); JobExecutionInfo jobExecutionInfo = processQueryById(connection, query.getId().getString(), query, Filter.MISSING); if (jobExecutionInfo != null) { jobExecutionInfos.add(jobExecutionInfo); } return jobExecutionInfos; case JOB_NAME: return processQueryByJobName(connection, query.getId().getString(), query, Filter.MISSING); case TABLE: return processQueryByTable(connection, query); case LIST_TYPE: return processListQuery(connection, query); default: throw new IOException("Unsupported query ID type: " + query.getIdType().name()); } } catch (SQLException se) { LOGGER.error("Failed to execute query: " + query, se); throw new IOException(se); } finally { if (connectionOptional.isPresent()) { try { connectionOptional.get().close(); } catch (SQLException se) { LOGGER.error("Failed to close connection", se); } } } } @Override public void close() throws IOException { // Nothing to do } private Connection getConnection() throws SQLException { return this.dataSource.getConnection(); } private static boolean existsJobExecutionInfo(Connection connection, JobExecutionInfo info) throws SQLException { Preconditions.checkArgument(info.hasJobId()); try (PreparedStatement queryStatement = connection .prepareStatement(JOB_EXECUTION_QUERY_BY_JOB_ID_STATEMENT_TEMPLATE)) { queryStatement.setString(1, info.getJobId()); try (ResultSet resultSet = queryStatement.executeQuery()) { return resultSet.next(); } } } private static void insertJobExecutionInfo(Connection connection, JobExecutionInfo info) throws SQLException { Preconditions.checkArgument(info.hasJobName()); Preconditions.checkArgument(info.hasJobId()); try (PreparedStatement insertStatement = connection.prepareStatement(JOB_EXECUTION_INSERT_STATEMENT_TEMPLATE)) { int index = 0; insertStatement.setString(++index, info.getJobName()); insertStatement.setString(++index, info.getJobId()); insertStatement .setTimestamp(++index, info.hasStartTime() ? new Timestamp(info.getStartTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); insertStatement.setTimestamp(++index, info.hasEndTime() ? new Timestamp(info.getEndTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); insertStatement.setLong(++index, info.hasDuration() ? info.getDuration() : -1); insertStatement.setString(++index, info.hasState() ? info.getState().name() : null); insertStatement.setInt(++index, info.hasLaunchedTasks() ? info.getLaunchedTasks() : -1); insertStatement.setInt(++index, info.hasCompletedTasks() ? info.getCompletedTasks() : -1); insertStatement.setString(++index, info.hasLauncherType() ? info.getLauncherType().name() : null); insertStatement.setString(++index, info.hasTrackingUrl() ? info.getTrackingUrl() : null); insertStatement.executeUpdate(); } } private static void updateJobExecutionInfo(Connection connection, JobExecutionInfo info) throws SQLException { Preconditions.checkArgument(info.hasJobId()); try (PreparedStatement updateStatement = connection.prepareStatement(JOB_EXECUTION_UPDATE_STATEMENT_TEMPLATE)) { int index = 0; updateStatement .setTimestamp(++index, info.hasStartTime() ? new Timestamp(info.getStartTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); updateStatement.setTimestamp(++index, info.hasEndTime() ? new Timestamp(info.getEndTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); updateStatement.setLong(++index, info.hasDuration() ? info.getDuration() : -1); updateStatement.setString(++index, info.hasState() ? info.getState().name() : null); updateStatement.setInt(++index, info.hasLaunchedTasks() ? info.getLaunchedTasks() : -1); updateStatement.setInt(++index, info.hasCompletedTasks() ? info.getCompletedTasks() : -1); updateStatement.setString(++index, info.hasLauncherType() ? info.getLauncherType().name() : null); updateStatement.setString(++index, info.hasTrackingUrl() ? info.getTrackingUrl() : null); updateStatement.setString(++index, info.getJobId()); updateStatement.executeUpdate(); } } private static boolean existsTaskExecutionInfo(Connection connection, TaskExecutionInfo info) throws SQLException { Preconditions.checkArgument(info.hasTaskId()); try (PreparedStatement queryStatement = connection.prepareStatement(TASK_EXECUTION_EXIST_QUERY_STATEMENT_TEMPLATE)) { queryStatement.setString(1, info.getTaskId()); try (ResultSet resultSet = queryStatement.executeQuery()) { return resultSet.next(); } } } private static void insertTaskExecutionInfo(Connection connection, TaskExecutionInfo info) throws SQLException { Preconditions.checkArgument(info.hasTaskId()); Preconditions.checkArgument(info.hasJobId()); try (PreparedStatement insertStatement = connection.prepareStatement(TASK_EXECUTION_INSERT_STATEMENT_TEMPLATE)) { int index = 0; insertStatement.setString(++index, info.getTaskId()); insertStatement.setString(++index, info.getJobId()); insertStatement .setTimestamp(++index, info.hasStartTime() ? new Timestamp(info.getStartTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); insertStatement.setTimestamp(++index, info.hasEndTime() ? new Timestamp(info.getEndTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); insertStatement.setLong(++index, info.hasDuration() ? info.getDuration() : -1); insertStatement.setString(++index, info.hasState() ? info.getState().name() : null); insertStatement.setString(++index, info.hasFailureException() ? info.getFailureException() : null); insertStatement.setLong(++index, info.hasLowWatermark() ? info.getLowWatermark() : -1); insertStatement.setLong(++index, info.hasHighWatermark() ? info.getHighWatermark() : -1); insertStatement.setString(++index, info.hasTable() && info.getTable().hasNamespace() ? info.getTable().getNamespace() : null); insertStatement .setString(++index, info.hasTable() && info.getTable().hasName() ? info.getTable().getName() : null); insertStatement .setString(++index, info.hasTable() && info.getTable().hasType() ? info.getTable().getType().name() : null); insertStatement.executeUpdate(); } } private static void updateTaskExecutionInfo(Connection connection, TaskExecutionInfo info) throws SQLException { Preconditions.checkArgument(info.hasTaskId()); try (PreparedStatement updateStatement = connection.prepareStatement(TASK_EXECUTION_UPDATE_STATEMENT_TEMPLATE)) { int index = 0; updateStatement .setTimestamp(++index, info.hasStartTime() ? new Timestamp(info.getStartTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); updateStatement.setTimestamp(++index, info.hasEndTime() ? new Timestamp(info.getEndTime()) : DEFAULT_TIMESTAMP, getCalendarUTCInstance()); updateStatement.setLong(++index, info.hasDuration() ? info.getDuration() : -1); updateStatement.setString(++index, info.hasState() ? info.getState().name() : null); updateStatement.setString(++index, info.hasFailureException() ? info.getFailureException() : null); updateStatement.setLong(++index, info.hasLowWatermark() ? info.getLowWatermark() : -1); updateStatement.setLong(++index, info.hasHighWatermark() ? info.getHighWatermark() : -1); updateStatement.setString(++index, info.hasTable() && info.getTable().hasNamespace() ? info.getTable().getNamespace() : null); updateStatement .setString(++index, info.hasTable() && info.getTable().hasName() ? info.getTable().getName() : null); updateStatement .setString(++index, info.hasTable() && info.getTable().hasType() ? info.getTable().getType().name() : null); updateStatement.setString(++index, info.getTaskId()); updateStatement.executeUpdate(); } } private static boolean existsMetric(Connection connection, String template, String id, Metric metric) throws SQLException { Preconditions.checkArgument(!Strings.isNullOrEmpty(id)); Preconditions.checkArgument(metric.hasGroup()); Preconditions.checkArgument(metric.hasName()); Preconditions.checkArgument(metric.hasType()); try (PreparedStatement queryStatement = connection.prepareStatement(template)) { int index = 0; queryStatement.setString(++index, id); queryStatement.setString(++index, metric.getGroup()); queryStatement.setString(++index, metric.getName()); queryStatement.setString(++index, metric.getType().name()); try (ResultSet resultSet = queryStatement.executeQuery()) { return resultSet.next(); } } } private static void updateMetric(Connection connection, String template, String id, Metric metric, boolean insert) throws SQLException { Preconditions.checkArgument(!Strings.isNullOrEmpty(id)); Preconditions.checkArgument(metric.hasGroup()); Preconditions.checkArgument(metric.hasName()); Preconditions.checkArgument(metric.hasType()); Preconditions.checkArgument(metric.hasValue()); try (PreparedStatement updateStatement = connection.prepareStatement(template)) { int index = 0; if (insert) { updateStatement.setString(++index, id); updateStatement.setString(++index, metric.getGroup()); updateStatement.setString(++index, metric.getName()); updateStatement.setString(++index, metric.getType().name()); updateStatement.setString(++index, metric.getValue()); } else { updateStatement.setString(++index, metric.getValue()); updateStatement.setString(++index, id); updateStatement.setString(++index, metric.getGroup()); updateStatement.setString(++index, metric.getName()); updateStatement.setString(++index, metric.getType().name()); } updateStatement.executeUpdate(); } } private static boolean existsProperty(Connection connection, String template, String id, String key) throws SQLException { Preconditions.checkArgument(!Strings.isNullOrEmpty(id)); Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); try (PreparedStatement queryStatement = connection.prepareStatement(template)) { int index = 0; queryStatement.setString(++index, id); queryStatement.setString(++index, key); try (ResultSet resultSet = queryStatement.executeQuery()) { return resultSet.next(); } } } private static void updateProperty(Connection connection, String template, String id, String key, String value, boolean insert) throws SQLException { Preconditions.checkArgument(!Strings.isNullOrEmpty(id)); Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); Preconditions.checkArgument(!Strings.isNullOrEmpty(value)); try (PreparedStatement updateStatement = connection.prepareStatement(template)) { int index = 0; if (insert) { updateStatement.setString(++index, id); updateStatement.setString(++index, key); updateStatement.setString(++index, value); } else { updateStatement.setString(++index, value); updateStatement.setString(++index, id); updateStatement.setString(++index, key); } updateStatement.executeUpdate(); } } private JobExecutionInfo processQueryById(Connection connection, String jobId, JobExecutionQuery query, Filter tableFilter) throws SQLException { Preconditions.checkArgument(!Strings.isNullOrEmpty(jobId)); // Query job execution information try (PreparedStatement jobExecutionQueryStatement = connection .prepareStatement(JOB_EXECUTION_QUERY_BY_JOB_ID_STATEMENT_TEMPLATE)) { jobExecutionQueryStatement.setString(1, jobId); try (ResultSet jobRs = jobExecutionQueryStatement.executeQuery()) { if (!jobRs.next()) { return null; } JobExecutionInfo jobExecutionInfo = resultSetToJobExecutionInfo(jobRs); // Query job metrics if (query.isIncludeJobMetrics()) { try (PreparedStatement jobMetricQueryStatement = connection .prepareStatement(JOB_METRIC_QUERY_STATEMENT_TEMPLATE)) { jobMetricQueryStatement.setString(1, jobRs.getString(2)); try (ResultSet jobMetricRs = jobMetricQueryStatement.executeQuery()) { MetricArray jobMetrics = new MetricArray(); while (jobMetricRs.next()) { jobMetrics.add(resultSetToMetric(jobMetricRs)); } // Add job metrics jobExecutionInfo.setMetrics(jobMetrics); } } } // Query job properties Set<String> requestedJobPropertyKeys = null; if (query.hasJobProperties()) { requestedJobPropertyKeys = new HashSet<>(Arrays.asList(query.getJobProperties().split(","))); } try (PreparedStatement jobPropertiesQueryStatement = connection .prepareStatement(JOB_PROPERTY_QUERY_STATEMENT_TEMPLATE)) { jobPropertiesQueryStatement.setString(1, jobExecutionInfo.getJobId()); try (ResultSet jobPropertiesRs = jobPropertiesQueryStatement.executeQuery()) { Map<String, String> jobProperties = Maps.newHashMap(); while (jobPropertiesRs.next()) { Map.Entry<String, String> property = resultSetToProperty(jobPropertiesRs); if (requestedJobPropertyKeys == null || requestedJobPropertyKeys.contains(property.getKey())) { jobProperties.put(property.getKey(), property.getValue()); } } // Add job properties jobExecutionInfo.setJobProperties(new StringMap(jobProperties)); } } // Query task execution information if (query.isIncludeTaskExecutions()) { TaskExecutionInfoArray taskExecutionInfos = new TaskExecutionInfoArray(); String taskExecutionQuery = TASK_EXECUTION_QUERY_STATEMENT_TEMPLATE; // Add table filter if applicable if (tableFilter.isPresent()) { taskExecutionQuery += " AND " + tableFilter; } try (PreparedStatement taskExecutionQueryStatement = connection.prepareStatement(taskExecutionQuery)) { taskExecutionQueryStatement.setString(1, jobId); if (tableFilter.isPresent()) { tableFilter.addParameters(taskExecutionQueryStatement, 2); } try (ResultSet taskRs = taskExecutionQueryStatement.executeQuery()) { while (taskRs.next()) { TaskExecutionInfo taskExecutionInfo = resultSetToTaskExecutionInfo(taskRs); // Query task metrics for each task execution record if (query.isIncludeTaskMetrics()) { try (PreparedStatement taskMetricQueryStatement = connection .prepareStatement(TASK_METRIC_QUERY_STATEMENT_TEMPLATE)) { taskMetricQueryStatement.setString(1, taskExecutionInfo.getTaskId()); try (ResultSet taskMetricRs = taskMetricQueryStatement.executeQuery()) { MetricArray taskMetrics = new MetricArray(); while (taskMetricRs.next()) { taskMetrics.add(resultSetToMetric(taskMetricRs)); } // Add task metrics taskExecutionInfo.setMetrics(taskMetrics); } } } taskExecutionInfos.add(taskExecutionInfo); // Query task properties Set<String> queryTaskPropertyKeys = null; if (query.hasTaskProperties()) { queryTaskPropertyKeys = new HashSet<>(Arrays.asList(query.getTaskProperties().split(","))); } try (PreparedStatement taskPropertiesQueryStatement = connection .prepareStatement(TASK_PROPERTY_QUERY_STATEMENT_TEMPLATE)) { taskPropertiesQueryStatement.setString(1, taskExecutionInfo.getTaskId()); try (ResultSet taskPropertiesRs = taskPropertiesQueryStatement.executeQuery()) { Map<String, String> taskProperties = Maps.newHashMap(); while (taskPropertiesRs.next()) { Map.Entry<String, String> property = resultSetToProperty(taskPropertiesRs); if (queryTaskPropertyKeys == null || queryTaskPropertyKeys.contains(property.getKey())) { taskProperties.put(property.getKey(), property.getValue()); } } // Add job properties taskExecutionInfo.setTaskProperties(new StringMap(taskProperties)); } } // Add task properties } // Add task execution information jobExecutionInfo.setTaskExecutions(taskExecutionInfos); } } } return jobExecutionInfo; } } } private List<JobExecutionInfo> processQueryByJobName(Connection connection, String jobName, JobExecutionQuery query, Filter tableFilter) throws SQLException { Preconditions.checkArgument(!Strings.isNullOrEmpty(jobName)); // Construct the query for job IDs by a given job name Filter timeRangeFilter = Filter.MISSING; String jobIdByNameQuery = JOB_ID_QUERY_BY_JOB_NAME_STATEMENT_TEMPLATE; if (query.hasTimeRange()) { // Add time range filter if applicable try { timeRangeFilter = constructTimeRangeFilter(query.getTimeRange()); if (timeRangeFilter.isPresent()) { jobIdByNameQuery += " AND " + timeRangeFilter; } } catch (ParseException pe) { LOGGER.error("Failed to parse the query time range", pe); throw new SQLException(pe); } } // Add ORDER BY jobIdByNameQuery += " ORDER BY created_ts DESC"; List<JobExecutionInfo> jobExecutionInfos = Lists.newArrayList(); // Query job IDs by the given job name try (PreparedStatement queryStatement = connection.prepareStatement(jobIdByNameQuery)) { int limit = query.getLimit(); if (limit > 0) { queryStatement.setMaxRows(limit); } queryStatement.setString(1, jobName); if (timeRangeFilter.isPresent()) { timeRangeFilter.addParameters(queryStatement, 2); } try (ResultSet rs = queryStatement.executeQuery()) { while (rs.next()) { jobExecutionInfos.add(processQueryById(connection, rs.getString(1), query, tableFilter)); } } } return jobExecutionInfos; } private List<JobExecutionInfo> processQueryByTable(Connection connection, JobExecutionQuery query) throws SQLException { Preconditions.checkArgument(query.getId().isTable()); Filter tableFilter = constructTableFilter(query.getId().getTable()); // Construct the query for job names by table definition String jobNameByTableQuery = String.format(JOB_NAME_QUERY_BY_TABLE_STATEMENT_TEMPLATE, tableFilter); List<JobExecutionInfo> jobExecutionInfos = Lists.newArrayList(); // Query job names by table definition try (PreparedStatement queryStatement = connection.prepareStatement(jobNameByTableQuery)) { if (tableFilter.isPresent()) { tableFilter.addParameters(queryStatement, 1); } try (ResultSet rs = queryStatement.executeQuery()) { while (rs.next()) { jobExecutionInfos.addAll(processQueryByJobName(connection, rs.getString(1), query, tableFilter)); } } } return jobExecutionInfos; } private List<JobExecutionInfo> processListQuery(Connection connection, JobExecutionQuery query) throws SQLException { Preconditions.checkArgument(query.getId().isQueryListType()); Filter timeRangeFilter = Filter.MISSING; QueryListType queryType = query.getId().getQueryListType(); String listJobExecutionsQuery = ""; if (queryType == QueryListType.DISTINCT) { listJobExecutionsQuery = LIST_DISTINCT_JOB_EXECUTION_QUERY_TEMPLATE; if (query.hasTimeRange()) { try { timeRangeFilter = constructTimeRangeFilter(query.getTimeRange()); if (timeRangeFilter.isPresent()) { listJobExecutionsQuery += " AND " + timeRangeFilter; } } catch (ParseException pe) { LOGGER.error("Failed to parse the query time range", pe); throw new SQLException(pe); } } } else { listJobExecutionsQuery = LIST_RECENT_JOB_EXECUTION_QUERY_TEMPLATE; } listJobExecutionsQuery += " ORDER BY last_modified_ts DESC"; try (PreparedStatement queryStatement = connection.prepareStatement(listJobExecutionsQuery)) { int limit = query.getLimit(); if (limit > 0) { queryStatement.setMaxRows(limit); } if (timeRangeFilter.isPresent()) { timeRangeFilter.addParameters(queryStatement, 1); } try (ResultSet rs = queryStatement.executeQuery()) { List<JobExecutionInfo> jobExecutionInfos = Lists.newArrayList(); while (rs.next()) { jobExecutionInfos.add(processQueryById(connection, rs.getString(1), query, Filter.MISSING)); } return jobExecutionInfos; } } } private JobExecutionInfo resultSetToJobExecutionInfo(ResultSet rs) throws SQLException { JobExecutionInfo jobExecutionInfo = new JobExecutionInfo(); jobExecutionInfo.setJobName(rs.getString("job_name")); jobExecutionInfo.setJobId(rs.getString("job_id")); try { jobExecutionInfo.setStartTime(rs.getTimestamp("start_time").getTime()); } catch (SQLException se) { jobExecutionInfo.setStartTime(0); } try { jobExecutionInfo.setEndTime(rs.getTimestamp("end_time").getTime()); } catch (SQLException se) { jobExecutionInfo.setEndTime(0); } jobExecutionInfo.setDuration(rs.getLong("duration")); String state = rs.getString("state"); if (!Strings.isNullOrEmpty(state)) { jobExecutionInfo.setState(JobStateEnum.valueOf(state)); } jobExecutionInfo.setLaunchedTasks(rs.getInt("launched_tasks")); jobExecutionInfo.setCompletedTasks(rs.getInt("completed_tasks")); String launcherType = rs.getString("launcher_type"); if (!Strings.isNullOrEmpty(launcherType)) { jobExecutionInfo.setLauncherType(LauncherTypeEnum.valueOf(launcherType)); } String trackingUrl = rs.getString("tracking_url"); if (!Strings.isNullOrEmpty(trackingUrl)) { jobExecutionInfo.setTrackingUrl(trackingUrl); } return jobExecutionInfo; } private static TaskExecutionInfo resultSetToTaskExecutionInfo(ResultSet rs) throws SQLException { TaskExecutionInfo taskExecutionInfo = new TaskExecutionInfo(); taskExecutionInfo.setTaskId(rs.getString("task_id")); taskExecutionInfo.setJobId(rs.getString("job_id")); try { taskExecutionInfo.setStartTime(rs.getTimestamp("start_time").getTime()); } catch (SQLException se) { taskExecutionInfo.setStartTime(0); } try { taskExecutionInfo.setEndTime(rs.getTimestamp("end_time").getTime()); } catch (SQLException se) { taskExecutionInfo.setEndTime(0); } taskExecutionInfo.setDuration(rs.getLong("duration")); String state = rs.getString("state"); if (!Strings.isNullOrEmpty(state)) { taskExecutionInfo.setState(TaskStateEnum.valueOf(state)); } String failureException = rs.getString("failure_exception"); if (!Strings.isNullOrEmpty(failureException)) { taskExecutionInfo.setFailureException(failureException); } taskExecutionInfo.setLowWatermark(rs.getLong("low_watermark")); taskExecutionInfo.setHighWatermark(rs.getLong("high_watermark")); Table table = new Table(); String namespace = rs.getString("table_namespace"); if (!Strings.isNullOrEmpty(namespace)) { table.setNamespace(namespace); } String name = rs.getString("table_name"); if (!Strings.isNullOrEmpty(name)) { table.setName(name); } String type = rs.getString("table_type"); if (!Strings.isNullOrEmpty(type)) { table.setType(TableTypeEnum.valueOf(type)); } taskExecutionInfo.setTable(table); return taskExecutionInfo; } private static Metric resultSetToMetric(ResultSet rs) throws SQLException { Metric metric = new Metric(); metric.setGroup(rs.getString("metric_group")); metric.setName(rs.getString("metric_name")); metric.setType(MetricTypeEnum.valueOf(rs.getString("metric_type"))); metric.setValue(rs.getString("metric_value")); return metric; } private static AbstractMap.SimpleEntry<String, String> resultSetToProperty(ResultSet rs) throws SQLException { return new AbstractMap.SimpleEntry<>(rs.getString(1), rs.getString(2)); } private Filter constructTimeRangeFilter(TimeRange timeRange) throws ParseException { List<String> values = Lists.newArrayList(); StringBuilder sb = new StringBuilder(); if (!timeRange.hasTimeFormat()) { LOGGER.warn("Skipping the time range filter as there is no time format in: " + timeRange); return Filter.MISSING; } DateFormat dateFormat = new SimpleDateFormat(timeRange.getTimeFormat()); boolean hasStartTime = timeRange.hasStartTime(); if (hasStartTime) { sb.append("start_time>?"); values.add(new Timestamp(dateFormat.parse(timeRange.getStartTime()).getTime()).toString()); } if (timeRange.hasEndTime()) { if (hasStartTime) { sb.append(" AND "); } sb.append("end_time<?"); values.add(new Timestamp(dateFormat.parse(timeRange.getEndTime()).getTime()).toString()); } if (sb.length() > 0) { return new Filter(sb.toString(), values); } return Filter.MISSING; } private Filter constructTableFilter(Table table) { List<String> values = Lists.newArrayList(); StringBuilder sb = new StringBuilder(); boolean hasNamespace = table.hasNamespace(); if (hasNamespace) { sb.append("table_namespace=?"); values.add(table.getNamespace()); } boolean hasName = table.hasName(); if (hasName) { if (hasNamespace) { sb.append(" AND "); } sb.append("table_name=?"); values.add(table.getName()); } if (table.hasType()) { if (hasName) { sb.append(" AND "); } sb.append("table_type=?"); values.add(table.getType().name()); } if (sb.length() > 0) { return new Filter(sb.toString(), values); } return Filter.MISSING; } private static Calendar getCalendarUTCInstance() { return Calendar.getInstance(TimeZone.getTimeZone("UTC")); } }