diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml new file mode 100644 index 000000000..5676a965c --- /dev/null +++ b/.github/workflows/cppcheck.yml @@ -0,0 +1,167 @@ +# 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. + +name: Cppcheck + +on: + push: + branches: + - develop + - iotdb + - rc/* + paths: + - '.github/workflows/cppcheck.yml' + - '.mvn/**' + - 'cpp/**' + - 'mvnw' + - 'pom.xml' + pull_request: + branches: + - develop + - dev/* + - iotdb + - rc/* + paths: + - '.github/workflows/cppcheck.yml' + - '.mvn/**' + - 'cpp/**' + - 'mvnw' + - 'pom.xml' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + CPPCHECK_VERSION: 2.17.1 + CPPCHECK_SOURCE_SHA256: bfd681868248ec03855ca7c2aea7bcb1f39b8b18860d76aec805a92a967b966c + MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3 + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + +jobs: + cppcheck: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Set up JDK 17 + uses: actions/setup-java@v5.6.0 + with: + distribution: corretto + java-version: 17 + + - name: Cache Maven packages + uses: actions/cache@v6 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-cppcheck-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2- + + - name: Cache Cppcheck + uses: actions/cache@v6 + with: + path: ~/.cache/cppcheck/${{ env.CPPCHECK_VERSION }} + key: cppcheck-${{ runner.os }}-${{ runner.arch }}-${{ env.CPPCHECK_VERSION }}-${{ env.CPPCHECK_SOURCE_SHA256 }} + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y uuid-dev + + - name: Install Cppcheck + shell: bash + run: | + set -euo pipefail + + cppcheck_home="${HOME}/.cache/cppcheck/${CPPCHECK_VERSION}" + if [[ ! -x "${cppcheck_home}/bin/cppcheck" ]]; then + build_root="$(mktemp -d)" + archive="${build_root}/cppcheck.tar.gz" + source_dir="${build_root}/source" + build_dir="${build_root}/build" + + mkdir -p "${source_dir}" "${build_dir}" "${cppcheck_home}" + curl --fail --location --retry 3 \ + "https://github.com/cppcheck-opensource/cppcheck/archive/refs/tags/${CPPCHECK_VERSION}.tar.gz" \ + --output "${archive}" + echo "${CPPCHECK_SOURCE_SHA256} ${archive}" | sha256sum --check - + tar -xzf "${archive}" --strip-components=1 -C "${source_dir}" + + cmake -S "${source_dir}" -B "${build_dir}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DUSE_MATCHCOMPILER=ON \ + -DBUILD_GUI=OFF \ + -DBUILD_TESTS=OFF \ + -DCMAKE_INSTALL_PREFIX="${cppcheck_home}" + cmake --build "${build_dir}" --parallel "$(nproc)" + cmake --install "${build_dir}" + fi + + echo "${cppcheck_home}/bin" >> "${GITHUB_PATH}" + "${cppcheck_home}/bin/cppcheck" --version + + - name: Generate compilation database with Maven + run: | + ./mvnw -P with-cpp,with-cppcheck -pl cpp \ + -Dbuild.test=OFF \ + cmake:generate@cmake-generate-test-compile + test -s cpp/target/build/compile_commands.json + + - name: Run Cppcheck + shell: bash + run: | + mkdir -p cpp/target/cppcheck + + set +e + cppcheck \ + --project=cpp/target/build/compile_commands.json \ + --file-filter='*cpp/src/*' \ + --file-filter='*cpp/tools/*' \ + --file-filter='*cpp/examples/*' \ + --enable=warning,performance,portability \ + --check-level=exhaustive \ + --inline-suppr \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + --suppress='*:*/third_party/*' \ + --suppress='*:*/target/*' \ + --suppress='*:*/parser/generated/*' \ + --error-exitcode=2 \ + --template=gcc \ + --output-file=cpp/target/cppcheck/cppcheck.txt \ + --quiet \ + -j "$(nproc)" + cppcheck_status=$? + set -e + + cat cpp/target/cppcheck/cppcheck.txt + exit "${cppcheck_status}" + + - name: Upload Cppcheck report + if: always() + uses: actions/upload-artifact@v7 + with: + name: cppcheck-report + path: cpp/target/cppcheck/cppcheck.txt + if-no-files-found: ignore diff --git a/cpp/examples/c_examples/demo_write.c b/cpp/examples/c_examples/demo_write.c index cf5ffea28..7c795452e 100644 --- a/cpp/examples/c_examples/demo_write.c +++ b/cpp/examples/c_examples/demo_write.c @@ -33,25 +33,69 @@ static char* duplicate_string(const char* src) { return dst; } +static ERRNO cleanup_write_tsfile_resources(WriteFile* file, + TsFileWriter writer, Tablet* tablet, + TableSchema* table_schema, + ERRNO code) { + if (*tablet != NULL) { + free_tablet(tablet); + } + + if (writer != NULL) { + ERRNO close_code = tsfile_writer_close(writer); + if (code == RET_OK) { + code = close_code; + } + } + + if (table_schema->table_name != NULL || + table_schema->column_schemas != NULL) { + free_table_schema(*table_schema); + } + + if (*file != NULL) { + free_write_file(file); + } + + if (code != RET_OK) { + printf("get err no: %d", code); + } + return code; +} + // This example shows you how to write tsfile. ERRNO write_tsfile() { ERRNO code = 0; + WriteFile file = NULL; + TsFileWriter writer = NULL; + Tablet tablet = NULL; + TableSchema table_schema = {0}; + code = set_global_compression(TS_COMPRESSION_LZ4); if (code != RET_OK) { - return code; + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, code); } code = set_datatype_encoding(TS_DATATYPE_INT32, TS_ENCODING_TS_2DIFF); if (code != RET_OK) { - return code; + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, code); } char* table_name = "table1"; // Create table schema to describe a table in a tsfile. - TableSchema table_schema; table_schema.table_name = duplicate_string(table_name); - table_schema.column_num = 3; + if (table_schema.table_name == NULL) { + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, RET_OOM); + } table_schema.column_schemas = - (ColumnSchema*)malloc(sizeof(ColumnSchema) * 3); + (ColumnSchema*)calloc(3, sizeof(ColumnSchema)); + if (table_schema.column_schemas == NULL) { + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, RET_OOM); + } + table_schema.column_num = 3; table_schema.column_schemas[0] = (ColumnSchema){.column_name = duplicate_string("id1"), .data_type = TS_DATATYPE_STRING, @@ -64,22 +108,37 @@ ERRNO write_tsfile() { (ColumnSchema){.column_name = duplicate_string("s1"), .data_type = TS_DATATYPE_INT32, .column_category = FIELD}; + if (table_schema.column_schemas[0].column_name == NULL || + table_schema.column_schemas[1].column_name == NULL || + table_schema.column_schemas[2].column_name == NULL) { + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, RET_OOM); + } remove("test_c.tsfile"); // Create a file with specify path to write tsfile. - WriteFile file = write_file_new("test_c.tsfile", &code); - HANDLE_ERROR(code); + file = write_file_new("test_c.tsfile", &code); + if (code != RET_OK) { + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, code); + } // Create tsfile writer with specify table schema. - TsFileWriter writer = tsfile_writer_new(file, &table_schema, &code); - HANDLE_ERROR(code); + writer = tsfile_writer_new(file, &table_schema, &code); + if (code != RET_OK) { + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, code); + } // Create tablet to insert data. - Tablet tablet = - tablet_new((char*[]){"id1", "id2", "s1"}, - (TSDataType[]){TS_DATATYPE_STRING, TS_DATATYPE_STRING, - TS_DATATYPE_INT32}, - 3, 5); + tablet = tablet_new((char*[]){"id1", "id2", "s1"}, + (TSDataType[]){TS_DATATYPE_STRING, TS_DATATYPE_STRING, + TS_DATATYPE_INT32}, + 3, 5); + if (tablet == NULL) { + return cleanup_write_tsfile_resources(&file, writer, &tablet, + &table_schema, RET_OOM); + } for (int row = 0; row < 5; row++) { Timestamp timestamp = row; @@ -92,19 +151,7 @@ ERRNO write_tsfile() { } // Write tablet data. - HANDLE_ERROR(tsfile_writer_write(writer, tablet)); - - // Free tablet. - free_tablet(&tablet); - - // Free table schema we used before. - free_table_schema(table_schema); - - // Close writer. - HANDLE_ERROR(tsfile_writer_close(writer)); - - // Close write file after closing writer. - free_write_file(&file); - - return 0; + code = tsfile_writer_write(writer, tablet); + return cleanup_write_tsfile_resources(&file, writer, &tablet, &table_schema, + code); } diff --git a/cpp/pom.xml b/cpp/pom.xml index 9b47ce0a7..e94debff2 100644 --- a/cpp/pom.xml +++ b/cpp/pom.xml @@ -292,6 +292,32 @@ + + + with-cppcheck + + + + com.googlecode.cmake-maven-project + cmake-maven-plugin + + + cmake-generate-test-compile + + + + + + + + + + + .java-9-and-above diff --git a/cpp/src/common/path.h b/cpp/src/common/path.h index f09b8c316..05f988696 100644 --- a/cpp/src/common/path.h +++ b/cpp/src/common/path.h @@ -40,9 +40,8 @@ struct Path { Path(std::string& device, std::string& measurement) : measurement_(measurement), - device_id_(std::make_shared(device)) { - full_path_ = device + "." + measurement; - } + device_id_(std::make_shared(device)), + full_path_(device + "." + measurement) {} Path(const std::string& path_sc, bool if_split = true) { if (!path_sc.empty()) { diff --git a/cpp/src/common/row_record.h b/cpp/src/common/row_record.h index d6da63efd..a640ecc29 100644 --- a/cpp/src/common/row_record.h +++ b/cpp/src/common/row_record.h @@ -188,8 +188,8 @@ FORCE_INLINE Field* make_null_literal() { class RowRecord { public: - explicit RowRecord(uint32_t col_num) : col_num_(col_num) { - fields_ = new std::vector(); + explicit RowRecord(uint32_t col_num) + : time_(0), col_num_(col_num), fields_(new std::vector()) { fields_->reserve(col_num); for (uint32_t i = 0; i < col_num; ++i) { Field* val = make_null_literal(); @@ -197,8 +197,8 @@ class RowRecord { } } - RowRecord(int64_t time, uint32_t col_num) : time_(time), col_num_(col_num) { - fields_ = new std::vector(); + RowRecord(int64_t time, uint32_t col_num) + : time_(time), col_num_(col_num), fields_(new std::vector()) { fields_->reserve(col_num_); for (uint32_t i = 0; i < col_num_; ++i) { Field* val = make_null_literal(); @@ -206,6 +206,9 @@ class RowRecord { } } + RowRecord(const RowRecord&) = delete; + RowRecord& operator=(const RowRecord&) = delete; + ~RowRecord() { if (fields_) { int size = fields_->size(); diff --git a/cpp/src/common/tsblock/tsblock.cc b/cpp/src/common/tsblock/tsblock.cc index 9d69566be..fd29f9f07 100644 --- a/cpp/src/common/tsblock/tsblock.cc +++ b/cpp/src/common/tsblock/tsblock.cc @@ -18,6 +18,7 @@ */ #include "tsblock.h" +#include #include namespace common { @@ -73,29 +74,32 @@ void TsBlock::write_data(ByteStream* __restrict byte_stream, std::string strval; switch (type) { case common::INT64: { - int64_t ival = *reinterpret_cast(val); + int64_t ival; + std::memcpy(&ival, val, sizeof(ival)); strval = std::to_string(ival); break; } case common::INT32: { - int32_t ival = *reinterpret_cast(val); + int32_t ival; + std::memcpy(&ival, val, sizeof(ival)); strval = std::to_string(ival); break; } case common::FLOAT: { - float ival = *reinterpret_cast( - val); // cppcheck-suppress invalidPointerCast + float ival; + std::memcpy(&ival, val, sizeof(ival)); strval = std::to_string(ival); break; } case common::DOUBLE: { - double ival = *reinterpret_cast( - val); // cppcheck-suppress invalidPointerCast + double ival; + std::memcpy(&ival, val, sizeof(ival)); strval = std::to_string(ival); break; } case common::BOOLEAN: { - bool ival = *reinterpret_cast(val); + bool ival; + std::memcpy(&ival, val, sizeof(ival)); if (ival) { strval = "true"; } else { diff --git a/cpp/src/common/tsblock/tsblock.h b/cpp/src/common/tsblock/tsblock.h index b68af1611..7959f7c36 100644 --- a/cpp/src/common/tsblock/tsblock.h +++ b/cpp/src/common/tsblock/tsblock.h @@ -21,6 +21,8 @@ #include +#include + #include "common/allocator/byte_stream.h" #include "common/container/byte_buffer.h" #include "common/global.h" @@ -158,11 +160,13 @@ class RowAppender { // TODO(Colin): Refine this. TSDataType datatype = vec->get_vector_type(); if (len == 4 && datatype == INT64) { - int32_t int32_val = *reinterpret_cast(value); + int32_t int32_val; + std::memcpy(&int32_val, value, sizeof(int32_val)); int64_t int64_val = static_cast(int32_val); vec->append(reinterpret_cast(&int64_val), 8); } else if (len == 4 && datatype == DOUBLE) { - float float_val = *reinterpret_cast(value); + float float_val; + std::memcpy(&float_val, value, sizeof(float_val)); double double_val = static_cast(float_val); vec->append(reinterpret_cast(&double_val), 8); } else { diff --git a/cpp/src/common/tsblock/tuple_desc.h b/cpp/src/common/tsblock/tuple_desc.h index 981d42385..39dce6273 100644 --- a/cpp/src/common/tsblock/tuple_desc.h +++ b/cpp/src/common/tsblock/tuple_desc.h @@ -62,7 +62,7 @@ class TupleDesc { time_column_index_ = -1; } - FORCE_INLINE void push_back(ColumnSchema schema) { + FORCE_INLINE void push_back(const ColumnSchema& schema) { if (schema.column_category_ == ColumnCategory::TIME) { ASSERT(time_column_index_ == -1); time_column_index_ = static_cast(column_list_.size()); @@ -97,7 +97,7 @@ class TupleDesc { return time_column_index_; } - FORCE_INLINE std::string get_column_name(uint32_t index) { + FORCE_INLINE const std::string& get_column_name(uint32_t index) const { return column_list_[index].column_name_; } diff --git a/cpp/src/compress/gzip_compressor.cc b/cpp/src/compress/gzip_compressor.cc index b49830bf2..49e7ac566 100644 --- a/cpp/src/compress/gzip_compressor.cc +++ b/cpp/src/compress/gzip_compressor.cc @@ -23,7 +23,8 @@ using namespace common; namespace storage { -GzipCompressor::GzipCompressor() : compressed_buf() { zstream_valid_ = false; } +GzipCompressor::GzipCompressor() + : compress_stream_(), compressed_buf(), zstream_valid_(false) {} GzipCompressor::~GzipCompressor() { end_zstream(); } @@ -140,9 +141,8 @@ int GzipCompressor::compress(char* uncompressed_buf, return ret; } -GzipDeCompressor::GzipDeCompressor() : decompressed_buf() { - zstream_valid_ = false; -} +GzipDeCompressor::GzipDeCompressor() + : decompress_stream_(), decompressed_buf(), zstream_valid_(false) {} GzipDeCompressor::~GzipDeCompressor() { end_zstream(); } diff --git a/cpp/src/file/write_file.h b/cpp/src/file/write_file.h index 014fa1ae6..84358dcdf 100644 --- a/cpp/src/file/write_file.h +++ b/cpp/src/file/write_file.h @@ -46,7 +46,7 @@ class WriteFile { int truncate(int64_t size); /** Seek to end of file. Used after open to position for append. */ int seek_to_end(); - FORCE_INLINE std::string get_file_path() { return path_; } + FORCE_INLINE const std::string& get_file_path() const { return path_; } /** Current file offset. After seek_to_end(), equals file size (for * recovery). */ int64_t get_position(); diff --git a/cpp/src/parser/path_visitor.cpp b/cpp/src/parser/path_visitor.cpp index 6c7968410..96b6c9478 100644 --- a/cpp/src/parser/path_visitor.cpp +++ b/cpp/src/parser/path_visitor.cpp @@ -103,7 +103,8 @@ bool PathVisitor::ends_with(const std::string& str, const std::string& suffix) { bool PathVisitor::is_creatable(const std::string& str) { try { - std::stod(str); + const double parsed_value = std::stod(str); + (void)parsed_value; return true; } catch (const std::invalid_argument& e) { return false; diff --git a/cpp/src/reader/expression.cc b/cpp/src/reader/expression.cc index 06a615e15..08ffac98e 100644 --- a/cpp/src/reader/expression.cc +++ b/cpp/src/reader/expression.cc @@ -108,8 +108,9 @@ Expression* QueryExpression::merge_second_tree_to_first_tree( Expression* QueryExpression::push_global_time_filter_to_all_series( Expression* time_filter, std::vector& selected_series) { - if (selected_series.size() == 0) { + if (selected_series.empty()) { std::cout << "size of selectSeries could not be 0" << std::endl; + return nullptr; } Expression* expression = new Expression(SERIES_EXPR, selected_series.at(0), @@ -129,6 +130,9 @@ Expression* QueryExpression::handle_one_global_time_filter( Expression* left, Expression* expression, std::vector& selected_series, ExpressionType type) { Expression* expr = optimize(expression, selected_series); + if (expr == nullptr) { + return nullptr; + } if (expr->type_ == GLOBALTIME_EXPR) { return combine_two_global_time_filter(left, expr, type); @@ -140,6 +144,9 @@ Expression* QueryExpression::handle_one_global_time_filter( } else if (type == OR_EXPR) { Expression* after_transform = push_global_time_filter_to_all_series(left, selected_series); + if (after_transform == nullptr) { + return nullptr; + } return merge_second_tree_to_first_tree(after_transform, expr); } std::cout << "unknown relation in Expression:" << type << std::endl; @@ -168,6 +175,9 @@ Expression* QueryExpression::optimize(Expression* expression, right->type_ != GLOBALTIME_EXPR) { Expression* regular_left = optimize(left, series_paths); Expression* regular_right = optimize(right, series_paths); + if (regular_left == nullptr || regular_right == nullptr) { + return nullptr; + } Expression* mid_ret = nullptr; if (type == AND_EXPR) { mid_ret = new Expression(AND_EXPR, regular_left, regular_right); diff --git a/cpp/src/reader/filter/binary_filter.h b/cpp/src/reader/filter/binary_filter.h index 4cacd52a1..586e94f6a 100644 --- a/cpp/src/reader/filter/binary_filter.h +++ b/cpp/src/reader/filter/binary_filter.h @@ -25,7 +25,7 @@ namespace storage { class BinaryFilter : public Filter { public: - BinaryFilter() : Filter() {} + BinaryFilter() : Filter(), left_(nullptr), right_(nullptr) {} BinaryFilter(Filter* left, Filter* right) : Filter(), left_(left), right_(right) {} virtual ~BinaryFilter() {} diff --git a/cpp/src/reader/table_result_set.cc b/cpp/src/reader/table_result_set.cc index 6de093d24..1a8d2a687 100644 --- a/cpp/src/reader/table_result_set.cc +++ b/cpp/src/reader/table_result_set.cc @@ -20,6 +20,8 @@ #include +#include + namespace storage { void TableResultSet::init() { row_record_ = new RowRecord(column_names_.size() + 1); @@ -127,14 +129,16 @@ bool TableResultSet::is_null(uint32_t column_index) { // the column's physical storage width (DATE is int32, not int64). On a // mismatch it fires in debug instead of silently splicing the adjacent cell's // bytes into the result. -#define TSFILE_FAST_PRIMITIVE_READ(TYPE, DFLT) \ - if (!row_ready_) return DFLT; \ - common::Vector* vec = row_iterator_->get_vector(column_index - 1); \ - ASSERT(common::TypeMatch(vec->get_vector_type())); \ - if (vec->has_null() && vec->is_null(row_iterator_->get_row_id())) \ - return DFLT; \ - return *reinterpret_cast(vec->get_value_data().get_data() + \ - vec->get_offset()) +#define TSFILE_FAST_PRIMITIVE_READ(TYPE, DFLT) \ + if (!row_ready_) return DFLT; \ + common::Vector* vec = row_iterator_->get_vector(column_index - 1); \ + ASSERT(common::TypeMatch(vec->get_vector_type())); \ + if (vec->has_null() && vec->is_null(row_iterator_->get_row_id())) \ + return DFLT; \ + TYPE value; \ + std::memcpy(&value, vec->get_value_data().get_data() + vec->get_offset(), \ + sizeof(value)); \ + return value bool TableResultSet::get_bool_at(uint32_t column_index) { TSFILE_FAST_PRIMITIVE_READ(bool, false); diff --git a/cpp/test/common/row_record_test.cc b/cpp/test/common/row_record_test.cc index 6b8b54a15..a88d7e2b9 100644 --- a/cpp/test/common/row_record_test.cc +++ b/cpp/test/common/row_record_test.cc @@ -104,6 +104,7 @@ TEST(FieldTest, MakeNullLiteral) { TEST(RowRecordTest, ConstructorWithColNum) { RowRecord row_record(5); + EXPECT_EQ(row_record.get_timestamp(), 0); EXPECT_EQ(row_record.get_fields()->size(), 5); for (Field* field : *row_record.get_fields()) { EXPECT_EQ(field->type_, common::NULL_TYPE); diff --git a/cpp/test/reader/expression_test.cc b/cpp/test/reader/expression_test.cc new file mode 100644 index 000000000..f4f9fbacc --- /dev/null +++ b/cpp/test/reader/expression_test.cc @@ -0,0 +1,40 @@ +/* + * 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 "reader/expression.h" + +#include + +#include + +namespace storage { + +TEST(QueryExpressionTest, EmptySelectedSeriesReturnsNull) { + QueryExpression query_expression; + std::vector selected_series; + Expression* expression = new Expression( + OR_EXPR, new Expression(GLOBALTIME_EXPR, static_cast(nullptr)), + new Expression(SERIES_EXPR, Path(), static_cast(nullptr))); + + EXPECT_EQ(query_expression.optimize(expression, selected_series), nullptr); + + delete expression; +} + +} // namespace storage