adamdebreceni commented on a change in pull request #1073:
URL: https://github.com/apache/nifi-minifi-cpp/pull/1073#discussion_r644548803



##########
File path: win_build_vs.bat
##########
@@ -65,7 +65,7 @@ for %%x in (%*) do (
 mkdir %builddir%
 pushd %builddir%\
 
-cmake -G %generator% -A %build_platform% 
-DINSTALLER_MERGE_MODULES=%installer_merge_modules% 
-DTEST_CUSTOM_WEL_PROVIDER=%test_custom_wel_provider% -DENABLE_SQL=%build_SQL% 
-DCMAKE_BUILD_TYPE_INIT=%cmake_build_type% 
-DCMAKE_BUILD_TYPE=%cmake_build_type% -DWIN32=WIN32 
-DENABLE_LIBRDKAFKA=%build_kafka% -DENABLE_JNI=%build_jni% -DOPENSSL_OFF=OFF 
-DENABLE_COAP=%build_coap% -DENABLE_AWS=%build_AWS% -DENABLE_PDH=%build_PDH% 
-DENABLE_AZURE=%build_azure% -DENABLE_SFTP=%build_SFTP%  -DUSE_SHARED_LIBS=OFF 
-DDISABLE_CONTROLLER=ON  -DBUILD_ROCKSDB=ON -DFORCE_WINDOWS=ON 
-DUSE_SYSTEM_UUID=OFF -DDISABLE_LIBARCHIVE=OFF -DDISABLE_SCRIPTING=ON 
-DEXCLUDE_BOOST=ON -DENABLE_WEL=TRUE -DFAIL_ON_WARNINGS=OFF 
-DSKIP_TESTS=%skiptests% %strict_gsl_checks% %redist% 
-DENABLE_LINTER=%build_linter% .. && msbuild /m nifi-minifi-cpp.sln 
/property:Configuration=%cmake_build_type% /property:Platform=%build_platform% 
&& copy main\%cmake_build_type%\minifi.exe main\
+cmake -G %generator% -A %build_platform% 
-DINSTALLER_MERGE_MODULES=%installer_merge_modules% 
-DTEST_CUSTOM_WEL_PROVIDER=%test_custom_wel_provider% -DENABLE_SQL=%build_SQL% 
-DUSE_REAL_ODBC_TEST_DRIVER=ON -DCMAKE_BUILD_TYPE_INIT=%cmake_build_type% 
-DCMAKE_BUILD_TYPE=%cmake_build_type% -DWIN32=WIN32 
-DENABLE_LIBRDKAFKA=%build_kafka% -DENABLE_JNI=%build_jni% -DOPENSSL_OFF=OFF 
-DENABLE_COAP=%build_coap% -DENABLE_AWS=%build_AWS% -DENABLE_PDH=%build_PDH% 
-DENABLE_AZURE=%build_azure% -DENABLE_SFTP=%build_SFTP%  -DUSE_SHARED_LIBS=OFF 
-DDISABLE_CONTROLLER=ON  -DBUILD_ROCKSDB=ON -DFORCE_WINDOWS=ON 
-DUSE_SYSTEM_UUID=OFF -DDISABLE_LIBARCHIVE=OFF -DDISABLE_SCRIPTING=ON 
-DEXCLUDE_BOOST=ON -DENABLE_WEL=TRUE -DFAIL_ON_WARNINGS=OFF 
-DSKIP_TESTS=%skiptests% %strict_gsl_checks% %redist% 
-DENABLE_LINTER=%build_linter% .. && msbuild /m nifi-minifi-cpp.sln 
/property:Configuration=%cmake_build_type% /property:Platform=%build_platform% 
&& copy main\%cmake_build_type%\minifi.exe main\

Review comment:
       as I understand this will always make the tests try to use the real odbc 
driver on windows, shouldn't we introduce some flag which is only 
default-enabled in the ci?

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.cpp
##########
@@ -0,0 +1,386 @@
+/**
+ *
+ * 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 "MockConnectors.h"
+
+#include <fstream>
+#include <algorithm>
+#include <utility>
+#include <string>
+#include <memory>
+
+#include "utils/GeneralUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+std::size_t MockRow::size() const {
+  return column_names_.size();
+}
+
+std::string MockRow::getColumnName(std::size_t index) const {
+  return column_names_.at(index);
+}
+
+bool MockRow::isNull(std::size_t index) const {
+  return column_values_.at(index) == "NULL";
+}
+
+DataType MockRow::getDataType(std::size_t index) const {
+  return column_types_.at(index);
+}
+
+std::string MockRow::getString(std::size_t index) const {
+  return column_values_.at(index);
+}
+
+double MockRow::getDouble(std::size_t index) const {
+  return std::atof(column_values_.at(index).c_str());
+}
+
+int MockRow::getInteger(std::size_t index) const {
+  return std::atoi(column_values_.at(index).c_str());
+}
+
+long long MockRow::getLongLong(std::size_t index) const {  // NOLINT
+  return std::atoll(column_values_.at(index).c_str());
+}
+
+unsigned long long MockRow::getUnsignedLongLong(std::size_t index) const {  // 
NOLINT
+  return static_cast<unsigned long 
long>(std::atoll(column_values_.at(index).c_str()));  // NOLINT
+}
+
+std::tm MockRow::getDate(std::size_t /*index*/) const {
+  throw std::runtime_error("date not implemented");
+}
+
+std::vector<std::string> MockRow::getValues() const {
+  return column_values_;
+}
+
+std::string MockRow::getValue(const std::string& col_name) const {
+  auto it = std::find(column_names_.begin(),  column_names_.end(), col_name);
+  if (it != column_names_.end()) {
+    return column_values_.at(it-column_names_.begin());
+  }
+  throw std::runtime_error("Unknown column name for getting value");
+}
+
+void MockRowset::addRow(const std::vector<std::string>& column_values) {
+  rows_.emplace_back(column_names_, column_types_, column_values);
+}
+
+void MockRowset::reset() {
+  current_row_ = rows_.begin();
+}
+
+bool MockRowset::is_done() {
+  return current_row_ == rows_.end();
+}
+
+Row& MockRowset::getCurrent() {
+  return *current_row_;
+}
+
+void MockRowset::next() {
+  ++current_row_;
+}
+
+std::vector<std::string> MockRowset::getColumnNames() const {
+  return column_names_;
+}
+
+std::vector<DataType> MockRowset::getColumnTypes() const {
+  return column_types_;
+}
+
+std::vector<MockRow> MockRowset::getRows() const {
+  return rows_;
+}
+
+std::size_t MockRowset::getColumnIndex(const std::string& col_name) const {
+  auto it = std::find(column_names_.begin(),  column_names_.end(), col_name);
+  if (it != column_names_.end()) {
+    return it-column_names_.begin();
+  }
+  throw std::runtime_error("Unknown column name for getting index");
+}
+
+void MockRowset::sort(const std::string& order_by_col, bool order_ascending) {
+  std::sort(rows_.begin(), rows_.end(), [&](const MockRow& first, const 
MockRow& second) {
+    if (order_ascending) {
+      return first.getValue(order_by_col) < second.getValue(order_by_col);
+    } else {
+      return first.getValue(order_by_col) > second.getValue(order_by_col);
+    }
+  });
+}
+
+std::unique_ptr<MockRowset> MockRowset::select(const std::vector<std::string>& 
cols, const std::function<bool(const MockRow&)>& condition, const std::string& 
order_by_col, bool order_ascending) {
+  if (!order_by_col.empty()) {
+    sort(order_by_col, order_ascending);
+  }
+
+  std::unique_ptr<MockRowset> rowset;
+  if (cols.empty()) {
+    rowset = utils::make_unique<MockRowset>(column_names_, column_types_);
+  } else {
+    std::vector<DataType> col_types;
+    for (const auto& col : cols) {
+      col_types.push_back(column_types_.at(getColumnIndex(col)));
+    }
+    rowset = utils::make_unique<MockRowset>(cols, col_types);
+  }
+
+  std::vector<std::string> used_cols = cols.empty() ? column_names_ : cols;
+  for (const auto& row : rows_) {
+    if (condition(row)) {
+      std::vector<std::string> values;
+      for (const auto& col : used_cols) {
+        values.push_back(row.getValue(col));
+      }
+      rowset->addRow(values);
+    }
+  }
+
+  return rowset;
+}
+
+std::unique_ptr<Rowset> MockDB::execute(const std::string& query, const 
std::vector<std::string>& args) {
+  if (minifi::utils::StringUtils::startsWith(query, "create table")) {
+    createTable(query);
+  } else if (minifi::utils::StringUtils::startsWith(query, "insert into")) {
+    insertInto(query, args);
+  } else if (minifi::utils::StringUtils::startsWith(query, "select")) {
+    return select(query, args);
+  } else {
+    throw std::runtime_error("Unknown query type");
+  }
+
+  return nullptr;
+}
+
+void MockDB::createTable(const std::string& query) {
+  std::smatch match;
+  std::regex expr("create table (\\w+)\\s*\\((.*)\\);");
+  std::regex_search(query, match, expr);
+  std::string table_name = match[1];
+  auto columns_with_type = minifi::utils::StringUtils::splitAndTrim(match[2], 
",");
+  std::vector<std::string> col_names;
+  std::vector<DataType> col_types;
+  for (const auto& col_with_type : columns_with_type) {
+    auto splitted = minifi::utils::StringUtils::splitAndTrim(col_with_type, " 
");
+    col_names.push_back(splitted[0]);
+    col_types.push_back(stringToDataType(splitted[1]));
+  }
+  tables_.emplace(table_name, MockRowset{col_names, col_types});
+  storeDb();
+}
+
+void MockDB::insertInto(const std::string& query, const 
std::vector<std::string>& args) {
+  std::string replaced_query = query;
+  for (const auto& arg : args) {
+    replaced_query = minifi::utils::StringUtils::replaceOne(replaced_query, 
"?", arg);
+  }
+
+  std::smatch match;
+  std::regex expr("insert into 
(\\w+)\\s*(\\((.*)\\))*\\s*values\\s*\\((.+)\\)");
+  std::regex_search(replaced_query, match, expr);
+  std::string table_name = match[1];
+  std::vector<std::string> values = 
minifi::utils::StringUtils::splitAndTrim(match[4], ",");
+  for (auto& value : values) {
+    value = minifi::utils::StringUtils::removeFramingCharacters(value, '\'');
+  }
+  auto insert_col_names = minifi::utils::StringUtils::splitAndTrim(match[3], 
",");
+  if (!insert_col_names.empty()) {
+    auto col_names = tables_.at(table_name).getColumnNames();
+    std::vector<std::string> row;
+    for (const auto& col_name : col_names) {
+      auto it = std::find(insert_col_names.begin(),  insert_col_names.end(), 
col_name);
+      if (it != insert_col_names.end()) {
+        row.push_back(values.at(it-insert_col_names.begin()));
+      } else {
+        row.push_back("NULL");
+      }
+    }
+    tables_.at(table_name).addRow(row);
+  } else {
+    tables_.at(table_name).addRow(values);
+  }
+
+  storeDb();
+}
+
+std::unique_ptr<Rowset> MockDB::select(const std::string& query, const 
std::vector<std::string>& args) {
+  std::string replaced_query = query;
+  for (const auto& arg : args) {
+    replaced_query = minifi::utils::StringUtils::replaceOne(replaced_query, 
"?", arg);
+  }
+
+  std::smatch match;
+  std::regex expr("select\\s+(.+)\\s+from\\s+(\\w+)\\s*(where ((.+(?= order 
by))|.+$))*\\s*(order by (.+))*");
+  std::regex_search(replaced_query, match, expr);
+  auto cols = minifi::utils::StringUtils::splitAndTrim(match[1], ",");
+  if (cols[0] == "*") {
+    cols = {};
+  }
+  std::string table_name = match[2];
+  std::string condition_str = match[4];
+  std::function<bool(const MockRow&)> condition;
+  if (!condition_str.empty()) {
+    if (condition_str == "int_col > 103") {
+      condition = [&](const MockRow& row){ return 
std::atoi(row.getValue("int_col").c_str()) > 103; };
+    } else if (condition_str == "int_col > 102") {
+      condition = [&](const MockRow& row){ return 
std::atoi(row.getValue("int_col").c_str()) > 102; };
+    } else if (condition_str == "int_col = 11") {
+      condition = [&](const MockRow& row){ return 
std::atoi(row.getValue("int_col").c_str()) == 11; };
+    } else if (condition_str == "int_col = 11 and text_col = banana") {
+      condition = [&](const MockRow& row){ return 
std::atoi(row.getValue("int_col").c_str()) == 11 && row.getValue("text_col") == 
"banana"; };
+    } else {
+      throw std::runtime_error("Unimplemented WHERE condition");
+    }
+  } else {
+    condition = [](const MockRow&){ return true; };
+  }
+  std::string order = match[7];
+  std::string order_col;
+  bool descending = false;
+  if (!order.empty()) {
+    auto order_col_and_sort = minifi::utils::StringUtils::splitAndTrim(order, 
" ");
+    order_col = order_col_and_sort[0];
+    descending = order_col_and_sort[1] == "desc";
+  }
+  return tables_.at(table_name).select(cols, condition, order_col, 
!descending);
+}
+
+void MockDB::readDb() {
+  tables_.clear();
+  std::ifstream file(file_path_);
+  std::string line;
+  ParsePhase phase = ParsePhase::NEW_TABLE;
+  std::string table_name;
+  std::vector<std::string> column_names;
+  std::vector<DataType> column_types;
+  while (std::getline(file, line)) {
+    switch (phase) {
+      case ParsePhase::NEW_TABLE: {
+        table_name = minifi::utils::StringUtils::trim(line);
+        phase = ParsePhase::COLUMN_NAMES;
+        break;
+      }
+      case ParsePhase::COLUMN_NAMES: {
+        column_names = minifi::utils::StringUtils::splitAndTrim(line, "|");
+        phase = ParsePhase::COLUMN_TYPES;
+        break;
+      }
+      case ParsePhase::COLUMN_TYPES: {
+        column_types.clear();
+        auto type_strs = minifi::utils::StringUtils::splitAndTrim(line, "|");
+        for (const auto& type : type_strs) {
+          column_types.push_back(stringToDataType(type));
+        }
+        tables_.emplace(table_name, MockRowset(column_names, column_types));
+        phase = ParsePhase::ROW_VALUES;
+        break;
+      }
+      case ParsePhase::ROW_VALUES: {
+        line = minifi::utils::StringUtils::trim(line);
+        if (line.empty()) {
+          phase = ParsePhase::NEW_TABLE;
+          break;
+        }
+        auto cells = minifi::utils::StringUtils::splitAndTrim(line, "|");
+        tables_.at(table_name).addRow(cells);
+        break;
+      }
+    }
+  }
+}
+
+void MockDB::storeDb() {
+  std::ofstream file(file_path_);
+  for (const auto& table : tables_) {
+    file << table.first << std::endl;
+    auto& rowset = table.second;
+    for (const auto& col_name : rowset.getColumnNames()) {
+      file << col_name << "|";
+    }
+    file << std::endl;
+    for (const auto& col_type : rowset.getColumnTypes()) {
+      file << dataTypeToString(col_type) << "|";
+    }
+    file << std::endl;
+    for (const auto& row : rowset.getRows()) {
+      for (const auto& cell : row.getValues()) {
+        file << cell << "|";
+      }
+      file << std::endl;
+    }
+    file << std::endl;
+  }
+}
+
+DataType MockDB::stringToDataType(const std::string& type_str) {
+  if (type_str == "integer") return DataType::INTEGER;
+  if (type_str == "text") return DataType::STRING;
+  if (type_str == "real") return DataType::DOUBLE;
+  throw std::runtime_error("Unimplemented data type");
+}
+
+std::string MockDB::dataTypeToString(DataType data_type) {
+  switch (data_type) {
+    case DataType::INTEGER: return "integer";
+    case DataType::STRING: return "text";
+    case DataType::DOUBLE: return "real";
+    default: throw std::runtime_error("Unimplemented data type");
+  }
+}
+
+std::unique_ptr<Rowset> MockStatement::execute(const std::vector<std::string>& 
args) {
+  MockDB db(file_path_);
+  return db.execute(query_, args);
+}
+
+MockODBCConnection::MockODBCConnection(std::string connectionString)
+    : connection_string_(std::move(connectionString)) {
+  std::smatch match;
+  std::regex expr("Database=(.*)(;|$)");

Review comment:
       not clear whether we need it or not, but there is a `Regex` class in 
`RegexUtils.h`, aiming to address GCC<4.9 compilers, should we use that here as 
well?

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.cpp
##########
@@ -0,0 +1,386 @@
+/**
+ *
+ * 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 "MockConnectors.h"
+
+#include <fstream>
+#include <algorithm>
+#include <utility>
+#include <string>
+#include <memory>
+
+#include "utils/GeneralUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+std::size_t MockRow::size() const {
+  return column_names_.size();
+}
+
+std::string MockRow::getColumnName(std::size_t index) const {
+  return column_names_.at(index);
+}
+
+bool MockRow::isNull(std::size_t index) const {
+  return column_values_.at(index) == "NULL";
+}
+
+DataType MockRow::getDataType(std::size_t index) const {
+  return column_types_.at(index);
+}
+
+std::string MockRow::getString(std::size_t index) const {
+  return column_values_.at(index);
+}
+
+double MockRow::getDouble(std::size_t index) const {
+  return std::atof(column_values_.at(index).c_str());
+}
+
+int MockRow::getInteger(std::size_t index) const {
+  return std::atoi(column_values_.at(index).c_str());
+}
+
+long long MockRow::getLongLong(std::size_t index) const {  // NOLINT
+  return std::atoll(column_values_.at(index).c_str());
+}
+
+unsigned long long MockRow::getUnsignedLongLong(std::size_t index) const {  // 
NOLINT
+  return static_cast<unsigned long 
long>(std::atoll(column_values_.at(index).c_str()));  // NOLINT
+}
+
+std::tm MockRow::getDate(std::size_t /*index*/) const {
+  throw std::runtime_error("date not implemented");
+}
+
+std::vector<std::string> MockRow::getValues() const {
+  return column_values_;
+}
+
+std::string MockRow::getValue(const std::string& col_name) const {
+  auto it = std::find(column_names_.begin(),  column_names_.end(), col_name);
+  if (it != column_names_.end()) {
+    return column_values_.at(it-column_names_.begin());
+  }
+  throw std::runtime_error("Unknown column name for getting value");
+}
+
+void MockRowset::addRow(const std::vector<std::string>& column_values) {
+  rows_.emplace_back(column_names_, column_types_, column_values);
+}
+
+void MockRowset::reset() {
+  current_row_ = rows_.begin();
+}
+
+bool MockRowset::is_done() {
+  return current_row_ == rows_.end();
+}
+
+Row& MockRowset::getCurrent() {
+  return *current_row_;
+}
+
+void MockRowset::next() {
+  ++current_row_;
+}
+
+std::vector<std::string> MockRowset::getColumnNames() const {
+  return column_names_;
+}
+
+std::vector<DataType> MockRowset::getColumnTypes() const {
+  return column_types_;
+}
+
+std::vector<MockRow> MockRowset::getRows() const {
+  return rows_;
+}
+
+std::size_t MockRowset::getColumnIndex(const std::string& col_name) const {
+  auto it = std::find(column_names_.begin(),  column_names_.end(), col_name);
+  if (it != column_names_.end()) {
+    return it-column_names_.begin();
+  }
+  throw std::runtime_error("Unknown column name for getting index");
+}
+
+void MockRowset::sort(const std::string& order_by_col, bool order_ascending) {
+  std::sort(rows_.begin(), rows_.end(), [&](const MockRow& first, const 
MockRow& second) {
+    if (order_ascending) {
+      return first.getValue(order_by_col) < second.getValue(order_by_col);
+    } else {
+      return first.getValue(order_by_col) > second.getValue(order_by_col);
+    }
+  });
+}
+
+std::unique_ptr<MockRowset> MockRowset::select(const std::vector<std::string>& 
cols, const std::function<bool(const MockRow&)>& condition, const std::string& 
order_by_col, bool order_ascending) {
+  if (!order_by_col.empty()) {
+    sort(order_by_col, order_ascending);
+  }
+
+  std::unique_ptr<MockRowset> rowset;
+  if (cols.empty()) {
+    rowset = utils::make_unique<MockRowset>(column_names_, column_types_);
+  } else {
+    std::vector<DataType> col_types;
+    for (const auto& col : cols) {
+      col_types.push_back(column_types_.at(getColumnIndex(col)));
+    }
+    rowset = utils::make_unique<MockRowset>(cols, col_types);
+  }
+
+  std::vector<std::string> used_cols = cols.empty() ? column_names_ : cols;
+  for (const auto& row : rows_) {
+    if (condition(row)) {
+      std::vector<std::string> values;
+      for (const auto& col : used_cols) {
+        values.push_back(row.getValue(col));
+      }
+      rowset->addRow(values);
+    }
+  }
+
+  return rowset;
+}
+
+std::unique_ptr<Rowset> MockDB::execute(const std::string& query, const 
std::vector<std::string>& args) {
+  if (minifi::utils::StringUtils::startsWith(query, "create table")) {
+    createTable(query);
+  } else if (minifi::utils::StringUtils::startsWith(query, "insert into")) {
+    insertInto(query, args);
+  } else if (minifi::utils::StringUtils::startsWith(query, "select")) {
+    return select(query, args);
+  } else {
+    throw std::runtime_error("Unknown query type");
+  }
+
+  return nullptr;
+}
+
+void MockDB::createTable(const std::string& query) {
+  std::smatch match;
+  std::regex expr("create table (\\w+)\\s*\\((.*)\\);");
+  std::regex_search(query, match, expr);
+  std::string table_name = match[1];
+  auto columns_with_type = minifi::utils::StringUtils::splitAndTrim(match[2], 
",");
+  std::vector<std::string> col_names;
+  std::vector<DataType> col_types;
+  for (const auto& col_with_type : columns_with_type) {
+    auto splitted = minifi::utils::StringUtils::splitAndTrim(col_with_type, " 
");
+    col_names.push_back(splitted[0]);
+    col_types.push_back(stringToDataType(splitted[1]));
+  }
+  tables_.emplace(table_name, MockRowset{col_names, col_types});
+  storeDb();
+}
+
+void MockDB::insertInto(const std::string& query, const 
std::vector<std::string>& args) {
+  std::string replaced_query = query;
+  for (const auto& arg : args) {
+    replaced_query = minifi::utils::StringUtils::replaceOne(replaced_query, 
"?", arg);
+  }
+
+  std::smatch match;
+  std::regex expr("insert into 
(\\w+)\\s*(\\((.*)\\))*\\s*values\\s*\\((.+)\\)");
+  std::regex_search(replaced_query, match, expr);
+  std::string table_name = match[1];
+  std::vector<std::string> values = 
minifi::utils::StringUtils::splitAndTrim(match[4], ",");
+  for (auto& value : values) {
+    value = minifi::utils::StringUtils::removeFramingCharacters(value, '\'');
+  }
+  auto insert_col_names = minifi::utils::StringUtils::splitAndTrim(match[3], 
",");
+  if (!insert_col_names.empty()) {
+    auto col_names = tables_.at(table_name).getColumnNames();
+    std::vector<std::string> row;
+    for (const auto& col_name : col_names) {
+      auto it = std::find(insert_col_names.begin(),  insert_col_names.end(), 
col_name);
+      if (it != insert_col_names.end()) {
+        row.push_back(values.at(it-insert_col_names.begin()));
+      } else {
+        row.push_back("NULL");
+      }
+    }
+    tables_.at(table_name).addRow(row);
+  } else {
+    tables_.at(table_name).addRow(values);
+  }
+
+  storeDb();
+}
+
+std::unique_ptr<Rowset> MockDB::select(const std::string& query, const 
std::vector<std::string>& args) {
+  std::string replaced_query = query;
+  for (const auto& arg : args) {
+    replaced_query = minifi::utils::StringUtils::replaceOne(replaced_query, 
"?", arg);
+  }
+
+  std::smatch match;
+  std::regex expr("select\\s+(.+)\\s+from\\s+(\\w+)\\s*(where ((.+(?= order 
by))|.+$))*\\s*(order by (.+))*");
+  std::regex_search(replaced_query, match, expr);
+  auto cols = minifi::utils::StringUtils::splitAndTrim(match[1], ",");
+  if (cols[0] == "*") {
+    cols = {};
+  }
+  std::string table_name = match[2];
+  std::string condition_str = match[4];
+  std::function<bool(const MockRow&)> condition;
+  if (!condition_str.empty()) {
+    if (condition_str == "int_col > 103") {

Review comment:
       I feel like we should go for a different solution than hard-coded 
conditions, I agree that writing an expression parser would complicate things a 
lot, could we reuse some expression parsing utility from our codebase? (e.g. 
from Expression.cpp)

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.cpp
##########
@@ -0,0 +1,386 @@
+/**
+ *
+ * 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 "MockConnectors.h"
+
+#include <fstream>
+#include <algorithm>
+#include <utility>
+#include <string>
+#include <memory>
+
+#include "utils/GeneralUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+std::size_t MockRow::size() const {
+  return column_names_.size();
+}
+
+std::string MockRow::getColumnName(std::size_t index) const {
+  return column_names_.at(index);
+}
+
+bool MockRow::isNull(std::size_t index) const {
+  return column_values_.at(index) == "NULL";
+}
+
+DataType MockRow::getDataType(std::size_t index) const {
+  return column_types_.at(index);
+}
+
+std::string MockRow::getString(std::size_t index) const {
+  return column_values_.at(index);
+}
+
+double MockRow::getDouble(std::size_t index) const {
+  return std::atof(column_values_.at(index).c_str());
+}
+
+int MockRow::getInteger(std::size_t index) const {
+  return std::atoi(column_values_.at(index).c_str());

Review comment:
       should we use the `std::string` accepting version? (`std::stoi` and 
others)

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.cpp
##########
@@ -0,0 +1,386 @@
+/**
+ *
+ * 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 "MockConnectors.h"
+
+#include <fstream>
+#include <algorithm>
+#include <utility>
+#include <string>
+#include <memory>
+
+#include "utils/GeneralUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+std::size_t MockRow::size() const {
+  return column_names_.size();
+}
+
+std::string MockRow::getColumnName(std::size_t index) const {
+  return column_names_.at(index);
+}
+
+bool MockRow::isNull(std::size_t index) const {
+  return column_values_.at(index) == "NULL";
+}
+
+DataType MockRow::getDataType(std::size_t index) const {
+  return column_types_.at(index);
+}
+
+std::string MockRow::getString(std::size_t index) const {
+  return column_values_.at(index);
+}
+
+double MockRow::getDouble(std::size_t index) const {
+  return std::atof(column_values_.at(index).c_str());
+}
+
+int MockRow::getInteger(std::size_t index) const {
+  return std::atoi(column_values_.at(index).c_str());
+}
+
+long long MockRow::getLongLong(std::size_t index) const {  // NOLINT
+  return std::atoll(column_values_.at(index).c_str());
+}
+
+unsigned long long MockRow::getUnsignedLongLong(std::size_t index) const {  // 
NOLINT
+  return static_cast<unsigned long 
long>(std::atoll(column_values_.at(index).c_str()));  // NOLINT
+}
+
+std::tm MockRow::getDate(std::size_t /*index*/) const {
+  throw std::runtime_error("date not implemented");
+}
+
+std::vector<std::string> MockRow::getValues() const {
+  return column_values_;
+}
+
+std::string MockRow::getValue(const std::string& col_name) const {
+  auto it = std::find(column_names_.begin(),  column_names_.end(), col_name);
+  if (it != column_names_.end()) {
+    return column_values_.at(it-column_names_.begin());
+  }
+  throw std::runtime_error("Unknown column name for getting value");
+}
+
+void MockRowset::addRow(const std::vector<std::string>& column_values) {
+  rows_.emplace_back(column_names_, column_types_, column_values);
+}
+
+void MockRowset::reset() {
+  current_row_ = rows_.begin();
+}
+
+bool MockRowset::is_done() {
+  return current_row_ == rows_.end();
+}
+
+Row& MockRowset::getCurrent() {
+  return *current_row_;
+}
+
+void MockRowset::next() {
+  ++current_row_;
+}
+
+std::vector<std::string> MockRowset::getColumnNames() const {
+  return column_names_;
+}
+
+std::vector<DataType> MockRowset::getColumnTypes() const {
+  return column_types_;
+}
+
+std::vector<MockRow> MockRowset::getRows() const {
+  return rows_;
+}
+
+std::size_t MockRowset::getColumnIndex(const std::string& col_name) const {
+  auto it = std::find(column_names_.begin(),  column_names_.end(), col_name);
+  if (it != column_names_.end()) {
+    return it-column_names_.begin();
+  }
+  throw std::runtime_error("Unknown column name for getting index");
+}
+
+void MockRowset::sort(const std::string& order_by_col, bool order_ascending) {
+  std::sort(rows_.begin(), rows_.end(), [&](const MockRow& first, const 
MockRow& second) {
+    if (order_ascending) {
+      return first.getValue(order_by_col) < second.getValue(order_by_col);
+    } else {
+      return first.getValue(order_by_col) > second.getValue(order_by_col);
+    }
+  });
+}
+
+std::unique_ptr<MockRowset> MockRowset::select(const std::vector<std::string>& 
cols, const std::function<bool(const MockRow&)>& condition, const std::string& 
order_by_col, bool order_ascending) {
+  if (!order_by_col.empty()) {
+    sort(order_by_col, order_ascending);
+  }
+
+  std::unique_ptr<MockRowset> rowset;
+  if (cols.empty()) {
+    rowset = utils::make_unique<MockRowset>(column_names_, column_types_);
+  } else {
+    std::vector<DataType> col_types;
+    for (const auto& col : cols) {
+      col_types.push_back(column_types_.at(getColumnIndex(col)));
+    }
+    rowset = utils::make_unique<MockRowset>(cols, col_types);
+  }
+
+  std::vector<std::string> used_cols = cols.empty() ? column_names_ : cols;
+  for (const auto& row : rows_) {
+    if (condition(row)) {
+      std::vector<std::string> values;
+      for (const auto& col : used_cols) {
+        values.push_back(row.getValue(col));
+      }
+      rowset->addRow(values);
+    }
+  }
+
+  return rowset;
+}
+
+std::unique_ptr<Rowset> MockDB::execute(const std::string& query, const 
std::vector<std::string>& args) {
+  if (minifi::utils::StringUtils::startsWith(query, "create table")) {
+    createTable(query);
+  } else if (minifi::utils::StringUtils::startsWith(query, "insert into")) {
+    insertInto(query, args);
+  } else if (minifi::utils::StringUtils::startsWith(query, "select")) {
+    return select(query, args);
+  } else {
+    throw std::runtime_error("Unknown query type");
+  }
+
+  return nullptr;
+}
+
+void MockDB::createTable(const std::string& query) {
+  std::smatch match;
+  std::regex expr("create table (\\w+)\\s*\\((.*)\\);");
+  std::regex_search(query, match, expr);
+  std::string table_name = match[1];
+  auto columns_with_type = minifi::utils::StringUtils::splitAndTrim(match[2], 
",");
+  std::vector<std::string> col_names;
+  std::vector<DataType> col_types;
+  for (const auto& col_with_type : columns_with_type) {
+    auto splitted = minifi::utils::StringUtils::splitAndTrim(col_with_type, " 
");
+    col_names.push_back(splitted[0]);
+    col_types.push_back(stringToDataType(splitted[1]));
+  }
+  tables_.emplace(table_name, MockRowset{col_names, col_types});
+  storeDb();
+}
+
+void MockDB::insertInto(const std::string& query, const 
std::vector<std::string>& args) {
+  std::string replaced_query = query;
+  for (const auto& arg : args) {
+    replaced_query = minifi::utils::StringUtils::replaceOne(replaced_query, 
"?", arg);
+  }
+
+  std::smatch match;
+  std::regex expr("insert into 
(\\w+)\\s*(\\((.*)\\))*\\s*values\\s*\\((.+)\\)");
+  std::regex_search(replaced_query, match, expr);
+  std::string table_name = match[1];
+  std::vector<std::string> values = 
minifi::utils::StringUtils::splitAndTrim(match[4], ",");
+  for (auto& value : values) {
+    value = minifi::utils::StringUtils::removeFramingCharacters(value, '\'');
+  }
+  auto insert_col_names = minifi::utils::StringUtils::splitAndTrim(match[3], 
",");
+  if (!insert_col_names.empty()) {
+    auto col_names = tables_.at(table_name).getColumnNames();
+    std::vector<std::string> row;
+    for (const auto& col_name : col_names) {
+      auto it = std::find(insert_col_names.begin(),  insert_col_names.end(), 
col_name);
+      if (it != insert_col_names.end()) {
+        row.push_back(values.at(it-insert_col_names.begin()));
+      } else {
+        row.push_back("NULL");
+      }
+    }
+    tables_.at(table_name).addRow(row);
+  } else {
+    tables_.at(table_name).addRow(values);
+  }
+
+  storeDb();
+}
+
+std::unique_ptr<Rowset> MockDB::select(const std::string& query, const 
std::vector<std::string>& args) {
+  std::string replaced_query = query;
+  for (const auto& arg : args) {
+    replaced_query = minifi::utils::StringUtils::replaceOne(replaced_query, 
"?", arg);
+  }
+
+  std::smatch match;
+  std::regex expr("select\\s+(.+)\\s+from\\s+(\\w+)\\s*(where ((.+(?= order 
by))|.+$))*\\s*(order by (.+))*");
+  std::regex_search(replaced_query, match, expr);
+  auto cols = minifi::utils::StringUtils::splitAndTrim(match[1], ",");
+  if (cols[0] == "*") {
+    cols = {};
+  }
+  std::string table_name = match[2];
+  std::string condition_str = match[4];
+  std::function<bool(const MockRow&)> condition;
+  if (!condition_str.empty()) {
+    if (condition_str == "int_col > 103") {

Review comment:
       maybe we could cover our test use-cases with some well-crafted regex-es 
(and maybe calling further regex-es on submatches if need be)

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.h
##########
@@ -0,0 +1,175 @@
+/**
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <regex>
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "data/DatabaseConnectors.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+class MockRow : public Row {
+ public:
+  MockRow(std::vector<std::string>& column_names, std::vector<DataType>& 
column_types, const std::vector<std::string>& column_values)
+    : column_names_(column_names), column_types_(column_types), 
column_values_(column_values) {
+  }
+
+  MockRow(MockRow&& other) = default;
+  MockRow(const MockRow& other) = default;
+  MockRow& operator=(MockRow&& other) {
+    column_names_ = other.column_names_;
+    column_types_ = other.column_types_;

Review comment:
       I don't know the exact intention here, but I wager it won't be doing that

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.h
##########
@@ -0,0 +1,175 @@
+/**
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <regex>
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "data/DatabaseConnectors.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+class MockRow : public Row {
+ public:
+  MockRow(std::vector<std::string>& column_names, std::vector<DataType>& 
column_types, const std::vector<std::string>& column_values)
+    : column_names_(column_names), column_types_(column_types), 
column_values_(column_values) {
+  }
+
+  MockRow(MockRow&& other) = default;
+  MockRow(const MockRow& other) = default;
+  MockRow& operator=(MockRow&& other) {
+    column_names_ = other.column_names_;
+    column_types_ = other.column_types_;

Review comment:
       using `std::reference_wrapper` would most likely offer the semantics we 
are looking for here

##########
File path: libminifi/test/sql-tests/mocks/MockConnectors.h
##########
@@ -0,0 +1,175 @@
+/**
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <regex>
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "data/DatabaseConnectors.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace sql {
+
+class MockRow : public Row {
+ public:
+  MockRow(std::vector<std::string>& column_names, std::vector<DataType>& 
column_types, const std::vector<std::string>& column_values)
+    : column_names_(column_names), column_types_(column_types), 
column_values_(column_values) {
+  }
+
+  MockRow(MockRow&& other) = default;
+  MockRow(const MockRow& other) = default;
+  MockRow& operator=(MockRow&& other) {
+    column_names_ = other.column_names_;
+    column_types_ = other.column_types_;

Review comment:
       also won't the lack of `noexcept` make it unavailable for `std::vector`?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to