blob: 81825b376a502dbe4e2d031cb2e7291ce5814f99 [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 <unordered_map>
#include "exec/tuple-text-file-reader.h"
#include "exec/tuple-text-file-util.h"
#include "common/names.h"
using std::unordered_map;
namespace impala {
const char* DEBUG_TUPLE_CACHE_BAD_POSTFIX = ".bad";
struct ReferenceRowCount {
uint64_t reference_rows_count;
uint64_t comparison_rows_count;
ReferenceRowCount() : reference_rows_count(0), comparison_rows_count(0) {}
void setCounts(uint64_t ref_rows_cnt, uint64_t cmp_rows_cnt) {
reference_rows_count = ref_rows_cnt;
comparison_rows_count = cmp_rows_cnt;
}
};
typedef unordered_map<string, ReferenceRowCount> ReferenceRowsMap;
typedef std::function<Status(const string&)> TupleCacheRowFunction;
// Applies fn to each row read from reader. Returns the number of rows read.
static Status ForEachRow(
TupleTextFileReader* reader, TupleCacheRowFunction& fn, int64_t* row_count) {
DCHECK(reader != nullptr);
DCHECK(row_count != nullptr);
bool eos = false;
int64_t num_rows = 0;
Status read_status;
string row_str;
do {
RETURN_IF_ERROR(reader->GetNext(&row_str, &eos));
RETURN_IF_ERROR(fn(row_str));
num_rows++;
} while (!eos);
*row_count = num_rows;
return Status::OK();
}
static Status VerifyRowsFromComparisonFile(const string& ref_file_path,
ReferenceRowsMap& cache, TupleTextFileReader* reader, int64_t* row_count) {
TupleCacheRowFunction verify_fn = [&ref_file_path, &cache, reader](const string& str) {
auto iter = cache.find(str);
if (iter == cache.end()) {
return Status(TErrorCode::TUPLE_CACHE_INCONSISTENCY,
Substitute("Result '$0' of file '$1' doesn't exist in the reference file: '$2'",
str, reader->GetPath() + DEBUG_TUPLE_CACHE_BAD_POSTFIX,
ref_file_path + DEBUG_TUPLE_CACHE_BAD_POSTFIX));
}
iter->second.comparison_rows_count++;
return Status::OK();
};
return ForEachRow(reader, verify_fn, row_count);
}
static Status InsertRowsFromReferenceFile(
ReferenceRowsMap& cache, TupleTextFileReader* reader, int64_t* row_count) {
TupleCacheRowFunction insert_fn = [&cache](const string& str) {
cache[str].reference_rows_count++;
return Status::OK();
};
return ForEachRow(reader, insert_fn, row_count);
}
static Status VerifyRowCount(ReferenceRowsMap& cache) {
for (const auto& it : cache) {
DCHECK_GE(it.second.reference_rows_count, 0);
if (it.second.reference_rows_count == 0) {
return Status(TErrorCode::TUPLE_CACHE_INCONSISTENCY,
Substitute("Row count abnormal for key '$0', '$1' vs '$2'", it.first,
it.second.reference_rows_count, it.second.comparison_rows_count));
}
if (it.second.reference_rows_count != it.second.comparison_rows_count) {
return Status(TErrorCode::TUPLE_CACHE_INCONSISTENCY,
Substitute("Row count doesn't match for key '$0', '$1' vs '$2'", it.first,
it.second.reference_rows_count, it.second.comparison_rows_count));
}
}
return Status::OK();
}
Status TupleTextFileUtil::VerifyRows(
const string& cmp_file_path, const string& ref_file_path) {
ReferenceRowsMap cache;
int64_t ref_row_count = 0;
int64_t cmp_row_count = 0;
{
TupleTextFileReader ref_reader(ref_file_path);
RETURN_IF_ERROR(ref_reader.Open());
RETURN_IF_ERROR(InsertRowsFromReferenceFile(cache, &ref_reader, &ref_row_count));
}
// Verify all the rows.
{
TupleTextFileReader cmp_reader(cmp_file_path);
RETURN_IF_ERROR(cmp_reader.Open());
RETURN_IF_ERROR(
VerifyRowsFromComparisonFile(ref_file_path, cache, &cmp_reader, &cmp_row_count));
}
// Verify all the row counts.
if (ref_row_count != cmp_row_count) {
return Status(TErrorCode::TUPLE_CACHE_INCONSISTENCY,
Substitute(
"Row count different. Reference file '$0': '$1', comparison file '$2': '$3'",
ref_file_path + DEBUG_TUPLE_CACHE_BAD_POSTFIX, ref_row_count,
cmp_file_path + DEBUG_TUPLE_CACHE_BAD_POSTFIX, cmp_row_count));
}
return VerifyRowCount(cache);
}
static Status ValidateAndOpenFile(const string& path, TupleTextFileReader& reader) {
if (!reader.Open().ok()) {
return Status(TErrorCode::TUPLE_CACHE_INCONSISTENCY,
Substitute("Failed to open file '$0'", path + DEBUG_TUPLE_CACHE_BAD_POSTFIX));
}
if (reader.GetFileSize() == TUPLE_TEXT_FILE_SIZE_ERROR) {
return Status(TErrorCode::TUPLE_CACHE_INCONSISTENCY,
Substitute("Size of file '$0' is invalid", path + DEBUG_TUPLE_CACHE_BAD_POSTFIX));
}
return Status::OK();
}
static Status CacheFileCmp(
const std::string& path_a, const std::string& path_b, bool* passed) {
DCHECK(passed != nullptr);
*passed = false;
// Create readers for the two files.
TupleTextFileReader reader_a(path_a);
TupleTextFileReader reader_b(path_b);
// Validate and open files
RETURN_IF_ERROR(ValidateAndOpenFile(path_a, reader_a));
RETURN_IF_ERROR(ValidateAndOpenFile(path_b, reader_b));
// Compare file sizes.
// Files are supposed to be written in tuple-text-file-writer.cc, and are plain
// text with '\n' as row delimiters and no padding, so a size mismatch likely
// indicates content differences.
// If the sizes are different, we leave "passed" as false to let the caller to proceed
// with a slower check. This may provide a clearer error message and prevent false
// mismatches when identical rows appear in a different order, which may result in
// different sizes.
if (reader_a.GetFileSize() != reader_b.GetFileSize()) return Status::OK();
// Reset readers to the beginning of the files.
reader_a.Rewind();
reader_b.Rewind();
// Compare the content line by line.
string line_a, line_b;
bool eos_a = false, eos_b = false;
*passed = true;
while (true) {
// Read the next line from each file.
RETURN_IF_ERROR(reader_a.GetNext(&line_a, &eos_a));
RETURN_IF_ERROR(reader_b.GetNext(&line_b, &eos_b));
// If both files reached the end, the comparison is complete.
if (eos_a && eos_b) break;
DCHECK_EQ(eos_a, eos_b);
// If the lines differ, the files are not identical.
if (line_a != line_b) {
*passed = false;
break;
}
}
return Status::OK();
}
Status TupleTextFileUtil::VerifyDebugDumpCache(
const string& file_name, const string& ref_file_name, bool* passed) {
DCHECK(passed != nullptr);
*passed = true;
DCHECK(!ref_file_name.empty());
DCHECK(!file_name.empty());
// This is the fast path to compare the content of the files, passed will be set
// to true if all contents are the same.
return CacheFileCmp(ref_file_name, file_name, passed);
}
} // namespace impala