blob: b4b95101ee9253c574b5e5eb91d795a2f5ed36c0 [file]
/*
* 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.
*/
#include "commands/statistics.h"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <limits>
#include <map>
#include <set>
#include <sstream>
#include <utility>
#include "cli/exit_codes.h"
#include "commands/commands.h"
#include "common/datatype/date_converter.h"
#include "common/device_id.h"
#include "common/statistic.h"
#include "format/output_format.h"
#include "reader/result_set.h"
#include "reader/tsfile_reader.h"
#include "utils/storage_utils.h"
namespace tsfile_cli {
namespace {
template <typename T>
std::string value_to_string(const T& value) {
std::ostringstream ss;
ss << value;
return ss.str();
}
std::string value_to_string(float value) {
std::ostringstream ss;
ss << std::setprecision(std::numeric_limits<float>::max_digits10) << value;
return ss.str();
}
std::string value_to_string(double value) {
std::ostringstream ss;
ss << std::setprecision(std::numeric_limits<double>::max_digits10) << value;
return ss.str();
}
std::string bool_to_string(bool value) { return value ? "true" : "false"; }
std::string string_to_std(const common::String& value) {
return value.to_std_string();
}
std::string date_to_string(int32_t value) {
std::tm date = std::tm();
if (common::DateConverter::int_to_date(value, date) != common::E_OK) {
return "";
}
char result[16];
std::snprintf(result, sizeof(result), "%04d-%02d-%02d", date.tm_year + 1900,
date.tm_mon + 1, date.tm_mday);
return result;
}
long long file_size(const std::string& path) {
std::ifstream in(path.c_str(), std::ios::binary | std::ios::ate);
if (!in.good()) {
return 0;
}
return static_cast<long long>(in.tellg());
}
} // namespace
StatisticCells statistic_value_cells(storage::Statistic* st) {
StatisticCells cells;
cells.values.assign(5, "");
cells.is_null.assign(5, true);
if (st == nullptr || st->get_count() == 0) {
return cells;
}
switch (st->get_type()) {
case common::BOOLEAN: {
auto* s = static_cast<storage::BooleanStatistic*>(st);
cells.values = {"", "", bool_to_string(s->first_value_),
bool_to_string(s->last_value_),
value_to_string(s->sum_value_)};
cells.is_null = {true, true, false, false, false};
break;
}
case common::INT32: {
auto* s = static_cast<storage::Int32Statistic*>(st);
cells.values = {value_to_string(s->min_value_),
value_to_string(s->max_value_),
value_to_string(s->first_value_),
value_to_string(s->last_value_),
value_to_string(s->sum_value_)};
cells.is_null = {false, false, false, false, false};
break;
}
case common::DATE: {
auto* s = static_cast<storage::Int32Statistic*>(st);
cells.values = {
date_to_string(s->min_value_), date_to_string(s->max_value_),
date_to_string(s->first_value_), date_to_string(s->last_value_),
""};
cells.is_null = {false, false, false, false, true};
break;
}
case common::INT64:
case common::TIMESTAMP: {
// Int64Statistic stores sum in double, which can lose precision
// for large INT64 values; a sum is not meaningful for timestamps.
// Keep the externally visible sum cell null for both types.
auto* s = static_cast<storage::Int64Statistic*>(st);
cells.values = {value_to_string(s->min_value_),
value_to_string(s->max_value_),
value_to_string(s->first_value_),
value_to_string(s->last_value_), ""};
cells.is_null = {false, false, false, false, true};
break;
}
case common::FLOAT: {
auto* s = static_cast<storage::FloatStatistic*>(st);
cells.values = {value_to_string(s->min_value_),
value_to_string(s->max_value_),
value_to_string(s->first_value_),
value_to_string(s->last_value_),
value_to_string(s->sum_value_)};
cells.is_null = {false, false, false, false, false};
break;
}
case common::DOUBLE: {
auto* s = static_cast<storage::DoubleStatistic*>(st);
cells.values = {value_to_string(s->min_value_),
value_to_string(s->max_value_),
value_to_string(s->first_value_),
value_to_string(s->last_value_),
value_to_string(s->sum_value_)};
cells.is_null = {false, false, false, false, false};
break;
}
case common::STRING: {
auto* s = static_cast<storage::StringStatistic*>(st);
cells.values = {string_to_std(s->min_value_),
string_to_std(s->max_value_),
string_to_std(s->first_value_),
string_to_std(s->last_value_), ""};
cells.is_null = {false, false, false, false, true};
break;
}
case common::TEXT: {
auto* s = static_cast<storage::TextStatistic*>(st);
cells.values = {"", "", string_to_std(s->first_value_),
string_to_std(s->last_value_), ""};
cells.is_null = {true, true, false, false, true};
break;
}
default:
break;
}
return cells;
}
int collect_series_stats(const ParsedArgs& args, storage::TsFileReader& reader,
std::vector<SeriesStatRow>& rows, std::ostream& err) {
rows.clear();
const auto all_devices = reader.get_all_device_ids();
std::vector<std::shared_ptr<storage::IDeviceID>> devices;
for (const auto& device : all_devices) {
if (device &&
(args.device.empty() || device->get_device_name() == args.device)) {
devices.push_back(device);
}
}
if (!args.device.empty() && devices.empty()) {
err << "Error: device '" << args.device << "' does not exist\n";
return kExitUsage;
}
storage::DeviceTimeseriesMetadataMap metadata =
reader.get_timeseries_metadata(devices);
std::map<std::string,
std::map<std::string, std::shared_ptr<storage::ITimeseriesIndex>>>
metadata_by_name;
for (auto& entry : metadata) {
if (!entry.first) {
continue;
}
const std::string device_name = entry.first->get_device_name();
for (const auto& series : entry.second) {
if (series) {
metadata_by_name[device_name][series->get_measurement_name()
.to_std_string()] = series;
}
}
}
std::set<std::string> matched_measurements;
for (const auto& device : devices) {
const std::string device_name = device->get_device_name();
std::vector<storage::MeasurementSchema> schemas;
int schema_ret = reader.get_timeseries_schema(device, schemas);
if (schema_ret != common::E_OK) {
err << "Error: failed to read schema for device '" << device_name
<< "': " << error_code_message(schema_ret) << "\n";
return kExitFile;
}
std::set<std::string> known;
std::vector<std::string> paths;
for (const auto& schema : schemas) {
known.insert(schema.measurement_name_);
paths.push_back(device_name + "." + schema.measurement_name_);
}
if (!args.device.empty()) {
for (const std::string& requested : args.measurements) {
if (known.find(requested) != known.end()) {
continue;
}
err << "Error: FIELD '" << requested
<< "' does not exist in device '" << device_name << "'\n";
return kExitUsage;
}
}
long long row_count = 0;
if (!paths.empty()) {
storage::ResultSet* result = nullptr;
int query_ret =
reader.query(paths, std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::max(), result);
if (query_ret != common::E_OK || result == nullptr) {
if (result != nullptr) {
reader.destroy_query_data_set(result);
}
err << "Error: failed to count rows for device '" << device_name
<< "': " << error_code_message(query_ret) << "\n";
return kExitFile;
}
bool has_next = false;
int next_ret = common::E_OK;
while ((next_ret = result->next(has_next)) == common::E_OK &&
has_next) {
++row_count;
}
reader.destroy_query_data_set(result);
if (next_ret != common::E_OK) {
err << "Error: failed to count rows for device '" << device_name
<< "': " << error_code_message(next_ret) << "\n";
return kExitFile;
}
}
for (const auto& schema : schemas) {
if (!args.measurements.empty() &&
std::find(args.measurements.begin(), args.measurements.end(),
schema.measurement_name_) ==
args.measurements.end()) {
continue;
}
matched_measurements.insert(schema.measurement_name_);
SeriesStatRow row;
row.target = device_name;
row.measurement = schema.measurement_name_;
row.data_type = schema.data_type_;
row.row_count = row_count;
auto device_meta = metadata_by_name.find(device_name);
if (device_meta != metadata_by_name.end()) {
auto series =
device_meta->second.find(schema.measurement_name_);
if (series != device_meta->second.end() && series->second) {
storage::Statistic* statistic =
series->second->get_statistic();
if (statistic != nullptr) {
row.has_statistic = true;
row.count = statistic->get_count();
row.start_time = statistic->start_time_;
row.end_time = statistic->end_time_;
row.value_cells = statistic_value_cells(statistic);
}
}
}
if (row.value_cells.values.empty()) {
row.value_cells.values.assign(5, "");
row.value_cells.is_null.assign(5, true);
}
rows.push_back(row);
}
}
if (args.device.empty()) {
for (const std::string& requested : args.measurements) {
if (matched_measurements.find(requested) ==
matched_measurements.end()) {
err << "Error: FIELD '" << requested
<< "' does not exist in the file\n";
return kExitUsage;
}
}
}
return kExitOk;
}
int collect_file_summary(const ParsedArgs& args, storage::TsFileReader& reader,
FileSummary& summary, std::ostream& err) {
bool table_model = false;
const int model_ret = resolve_table_model(args, reader, table_model, err);
if (model_ret != kExitOk) {
return model_ret;
}
FileSummary s;
s.file = args.file;
s.model = table_model ? "table" : "tree";
std::vector<std::shared_ptr<storage::IDeviceID>> devices;
const int devices_ret = reader.get_all_devices(devices);
if (devices_ret != common::E_OK) {
err << "Error: failed to read devices: "
<< error_code_message(devices_ret) << "\n";
return kExitFile;
}
std::vector<std::shared_ptr<storage::TableSchema>> schemas;
const int schemas_ret = reader.get_all_table_schemas(schemas);
if (schemas_ret != common::E_OK) {
err << "Error: failed to read table schemas: "
<< error_code_message(schemas_ret) << "\n";
return kExitFile;
}
s.device_count = static_cast<long long>(devices.size());
s.table_count = static_cast<long long>(schemas.size());
s.file_size_bytes = file_size(args.file);
storage::DeviceTimeseriesMetadataMap metadata;
const int metadata_ret = reader.get_timeseries_metadata(metadata);
if (metadata_ret != common::E_OK) {
err << "Error: failed to read timeseries metadata: "
<< error_code_message(metadata_ret) << "\n";
return kExitFile;
}
int64_t min_start = std::numeric_limits<int64_t>::max();
int64_t max_end = std::numeric_limits<int64_t>::min();
for (const auto& device : metadata) {
for (const auto& series : device.second) {
if (!series) {
continue;
}
++s.series_count;
storage::Statistic* statistic = series->get_statistic();
if (statistic == nullptr || statistic->get_count() <= 0) {
continue;
}
min_start = std::min(min_start, statistic->start_time_);
max_end = std::max(max_end, statistic->get_end_time());
s.has_time_range = true;
}
}
if (s.has_time_range) {
s.start_time = min_start;
s.end_time = max_end;
}
summary = std::move(s);
return kExitOk;
}
} // namespace tsfile_cli