Repository: heavyai/heavydb (language: python)

Issue:
Improve hoisted literals code generation
Currently, we generate memory loads (from the literal buffer) when the literal node is visited and rely on loop invariant code motion pass to hoist these loads outside of the query loop. This is a heavy-handed use of LICM and might sometimes not be optimized. We should collect the constant expression with a visitor and generate the loads in the entry block.


Discussion / hints:
Hi, can I tackle this one?

Yes, good one for you.
@psockali You can use `ScalarExprVisitor` to build a constant-collecting visitor. There's a pitfall though: we don't know all constants upfront when sub-queries are involved. Have a look at `RelAlgTranslator::translateInOper`. We can use a hybrid approach: collect and generate all the other constants upfront and leave the current handling in place (and keep LICM). Alternatively, we can reorganize the compiler to do a full pass first without emitting any IR, which we know we'll eventually have to do for other reasons as well. Going for the first approach won't hurt our ability to achieve the second and the code should be fully reusable once we have the full solution in place, so I recommend going for it as an incremental, nearly-complete solution.
Hi @asuhan , I will do so.
Am I  correct in assuming that only runtime constants (within the scope of a block) shall be hoisted, but literals can remain as they are?

Hey @asuhan 

I think I need to be much more clearer when addressing things in mapd. (e.g. literal in LLVM IR, literal in REX, literal in Analyzer::Exp, etc...).

Finally managed to look at this in more detail and it turns out that `Analyzer::Constant` literals are hoisted in `Executor::codegenHoistedConstants` into a memory buffer. And each _unoptimized_ access to the literal requires a memory read from that buffer. As you have pointed out `RelAlgTranslator::translateInOper` is creating `Analyzer::Constant` dynamically, so scanning early on, right after translating from REX, is not very promising.

Currently I would propose to scan for `Analyzer::Constant` via an `ScalarExprVisitor` before the actual LLVM IR generation (somewhere in `Executor::compileWorkUnit`) and also extend the Constant class to hold a few more attributes required for this extra step.

I would then generate the memory load for each literal in `@query_group_by_template` and pass them as arguments to the always inlined `@row_func `, which also needs to be extended to take these new parameters.

I would also make this, in effect, a configurable behaviour and would probably introduce a new compiler option flag.

I hope to start tomorrow on it, if there are no objections to the above approach.
Hi @psockali, your approach sounds reasonable. Add a constant scanning visitor, generate corresponding literal loads in the first block of the query func and feed these values to the always_inlined `@row_func`. Not sure if any Constant extensions are necessary but let's see if your motivation can be justified. No need to make this behavior configurable - we're just putting some literal loads where they should have been generated from the get go, instead of relying on LICM to move them there.

You mentioned `@query_group_by_template`, note that it's just one of the templates that the query func can be built from, the other one is `@query_template`.

Hey @shtilman, as usual:  theory does not reflect reality.

It turns out that during LLVM IR generation `Analyzer::Constant` nodes are created on the fly and sometimes the original `Constant` node is translated.
See  `Executor::codegenRowId`, `Executor::codegenCmpDecimalConst` and `Executor::codegenOuterJoinNullPlaceholder`. 

This invalidates the proposed approach, because scanning the source `Analyzer::Exp` tree will not be reliable nor sufficient.

I have to rethink .. please bear with me.
Hey @shtilman,

@asuhan mentioned two approaches:

- Hybrid: 
  Currently this is the approach I am going for.
- Full Pass without IR generation: 
  I am not confident enough at this stage to make such a central change to the compiler as I am not sure what state(s) the compiler currently maintains and how I would reset it correctly.

I think there is an alternative way, but it would require to clone the row_function (in order to correct the function type with the hoisted variable).

Either way, let me first try the hybrid approach, it probably will be complicated enough.
Hi @shtilman / @asuhan 
I committed an initial version of the hybrid solution. the PR is #145 .
Please let me know if this approach is OK for now as I fear that it does not capture all the cases.

Comments and discussion are in #145 PR.

Fix patch:
diff --git a/Analyzer/Analyzer.cpp b/Analyzer/Analyzer.cpp
index 18dd505157..a6d3bed182 100644
--- a/Analyzer/Analyzer.cpp
+++ b/Analyzer/Analyzer.cpp
@@ -797,7 +797,7 @@ std::shared_ptr<Analyzer::Expr> Expr::add_cast(const SQLTypeInfo& new_type_info)
       new_type_info.getStringDictKey().dict_id <= TRANSIENT_DICT_ID) {
     if (type_info.is_string() && type_info.get_compression() != kENCODING_DICT) {
       throw std::runtime_error(
-          "Implict casts of TEXT ENCODING NONE to TEXT ENCODED DICT are not allowed "
+          "Implicit casts of TEXT ENCODING NONE to TEXT ENCODED DICT are not allowed "
           "for non-literal arguments. Consider adding an explicit conversion to a "
           "dictionary-encoded text type with ENCODE_TEXT(<none-encoded text arg>).");
     }
@@ -3788,6 +3788,16 @@ std::shared_ptr<Analyzer::Expr> PositionStringOper::deep_copy() const {
       std::dynamic_pointer_cast<Analyzer::StringOper>(StringOper::deep_copy()));
 }
 
+std::shared_ptr<Analyzer::Expr> JarowinklerSimilarityStringOper::deep_copy() const {
+  return makeExpr<Analyzer::JarowinklerSimilarityStringOper>(
+      std::dynamic_pointer_cast<Analyzer::StringOper>(StringOper::deep_copy()));
+}
+
+std::shared_ptr<Analyzer::Expr> LevenshteinDistanceStringOper::deep_copy() const {
+  return makeExpr<Analyzer::LevenshteinDistanceStringOper>(
+      std::dynamic_pointer_cast<Analyzer::StringOper>(StringOper::deep_copy()));
+}
+
 std::shared_ptr<Analyzer::Expr> FunctionOper::deep_copy() const {
   std::vector<std::shared_ptr<Analyzer::Expr>> args_copy;
   for (size_t i = 0; i < getArity(); ++i) {
@@ -4515,6 +4525,45 @@ std::vector<std::shared_ptr<Analyzer::Expr>> PositionStringOper::normalize_opera
   }
   return normalized_operands;
 }
+
+std::vector<std::shared_ptr<Analyzer::Expr>>
+JarowinklerSimilarityStringOper::normalize_operands(
+    const std::vector<std::shared_ptr<Analyzer::Expr>>& operands) {
+  if (operands.size() != 2UL) {
+    std::ostringstream oss;
+    oss << "JAROWINKLER_SIMILARITY operator expects two arguments, but was provided "
+        << operands.size() << ".";
+    throw std::runtime_error(oss.str());
+  }
+  const auto operand0_is_literal =
+      dynamic_cast<const Analyzer::Constant*>(operands[0].get());
+  const auto operand1_is_literal =
+      dynamic_cast<const Analyzer::Constant*>(operands[1].get());
+  if (operand0_is_literal && !operand1_is_literal) {
+    return {operands[1], operands[0]};
+  }
+  return operands;
+}
+
+std::vector<std::shared_ptr<Analyzer::Expr>>
+LevenshteinDistanceStringOper::normalize_operands(
+    const std::vector<std::shared_ptr<Analyzer::Expr>>& operands) {
+  if (operands.size() != 2UL) {
+    std::ostringstream oss;
+    oss << "LEVENSHTEIN_DISTANCE operator expects two arguments, but was provided "
+        << operands.size() << ".";
+    throw std::runtime_error(oss.str());
+  }
+  const auto operand0_is_literal =
+      dynamic_cast<const Analyzer::Constant*>(operands[0].get());
+  const auto operand1_is_literal =
+      dynamic_cast<const Analyzer::Constant*>(operands[1].get());
+  if (operand0_is_literal && !operand1_is_literal) {
+    return {operands[1], operands[0]};
+  }
+  return operands;
+}
+
 }  // namespace Analyzer
 
 bool expr_list_match(const std::vector<std::shared_ptr<Analyzer::Expr>>& lhs,
diff --git a/Analyzer/Analyzer.h b/Analyzer/Analyzer.h
index ba3c5a0516..27237fe7f1 100644
--- a/Analyzer/Analyzer.h
+++ b/Analyzer/Analyzer.h
@@ -1619,12 +1619,14 @@ class StringOper : public Expr {
              const std::vector<OperandTypeFamily>& expected_type_families,
              const std::vector<std::string>& arg_names)
       : Expr(StringOper::get_return_type(kind, args)), kind_(kind), args_(args) {
-    check_operand_types(min_args,
-                        expected_type_families,
-                        arg_names,
-                        false /* dict_encoded_cols_only */,
-                        !(kind == SqlStringOpKind::CONCAT ||
-                          kind == SqlStringOpKind::RCONCAT) /* cols_first_arg_only */);
+    check_operand_types(
+        min_args,
+        expected_type_families,
+        arg_names,
+        false /* dict_encoded_cols_only */,
+        !(kind == SqlStringOpKind::CONCAT || kind == SqlStringOpKind::RCONCAT ||
+          kind == SqlStringOpKind::JAROWINKLER_SIMILARITY ||
+          kind == SqlStringOpKind::LEVENSHTEIN_DISTANCE) /* cols_first_arg_only */);
   }
 
   StringOper(const SqlStringOpKind kind,
@@ -1634,12 +1636,14 @@ class StringOper : public Expr {
              const std::vector<OperandTypeFamily>& expected_type_families,
              const std::vector<std::string>& arg_names)
       : Expr(return_ti), kind_(kind), args_(args) {
-    check_operand_types(min_args,
-                        expected_type_families,
-                        arg_names,
-                        false /* dict_encoded_cols_only */,
-                        !(kind == SqlStringOpKind::CONCAT ||
-                          kind == SqlStringOpKind::RCONCAT) /* cols_first_arg_only */);
+    check_operand_types(
+        min_args,
+        expected_type_families,
+        arg_names,
+        false /* dict_encoded_cols_only */,
+        !(kind == SqlStringOpKind::CONCAT || kind == SqlStringOpKind::RCONCAT ||
+          kind == SqlStringOpKind::JAROWINKLER_SIMILARITY ||
+          kind == SqlStringOpKind::LEVENSHTEIN_DISTANCE) /* cols_first_arg_only */);
   }
 
   StringOper(const SqlStringOpKind kind,
@@ -2518,6 +2522,87 @@ class PositionStringOper : public StringOper {
       const std::vector<std::shared_ptr<Analyzer::Expr>>& operands);
 };
 
+class JarowinklerSimilarityStringOper : public StringOper {
+ public:
+  JarowinklerSimilarityStringOper(const std::shared_ptr<Analyzer::Expr>& left_operand,
+                                  const std::shared_ptr<Analyzer::Expr>& right_operand)
+      : StringOper(SqlStringOpKind::JAROWINKLER_SIMILARITY,
+                   SQLTypeInfo(kBIGINT),
+                   JarowinklerSimilarityStringOper::normalize_operands(
+                       {left_operand, right_operand}),
+                   getMinArgs(),
+                   getExpectedTypeFamilies(),
+                   getArgNames()) {}
+
+  JarowinklerSimilarityStringOper(
+      const std::vector<std::shared_ptr<Analyzer::Expr>>& operands)
+      : StringOper(SqlStringOpKind::JAROWINKLER_SIMILARITY,
+                   SQLTypeInfo(kBIGINT),
+                   JarowinklerSimilarityStringOper::normalize_operands(operands),
+                   getMinArgs(),
+                   getExpectedTypeFamilies(),
+                   getArgNames()) {}
+
+  JarowinklerSimilarityStringOper(
+      const std::shared_ptr<Analyzer::StringOper>& string_oper)
+      : StringOper(string_oper) {}
+
+  std::shared_ptr<Analyzer::Expr> deep_copy() const override;
+
+  size_t getMinArgs() const override { return 2UL; }
+
+  std::vector<OperandTypeFamily> getExpectedTypeFamilies() const override {
+    return {OperandTypeFamily::STRING_FAMILY, OperandTypeFamily::STRING_FAMILY};
+  }
+  std::vector<std::string> getArgNames() const override {
+    return {"left operand", "right operand"};
+  }
+
+ private:
+  static std::vector<std::shared_ptr<Analyzer::Expr>> normalize_operands(
+      const std::vector<std::shared_ptr<Analyzer::Expr>>& operands);
+};
+
+class LevenshteinDistanceStringOper : public StringOper {
+ public:
+  LevenshteinDistanceStringOper(const std::shared_ptr<Analyzer::Expr>& left_operand,
+                                const std::shared_ptr<Analyzer::Expr>& right_operand)
+      : StringOper(SqlStringOpKind::LEVENSHTEIN_DISTANCE,
+                   SQLTypeInfo(kBIGINT),
+                   LevenshteinDistanceStringOper::normalize_operands(
+                       {left_operand, right_operand}),
+                   getMinArgs(),
+                   getExpectedTypeFamilies(),
+                   getArgNames()) {}
+
+  LevenshteinDistanceStringOper(
+      const std::vector<std::shared_ptr<Analyzer::Expr>>& operands)
+      : StringOper(SqlStringOpKind::LEVENSHTEIN_DISTANCE,
+                   SQLTypeInfo(kBIGINT),
+                   LevenshteinDistanceStringOper::normalize_operands(operands),
+                   getMinArgs(),
+                   getExpectedTypeFamilies(),
+                   getArgNames()) {}
+
+  LevenshteinDistanceStringOper(const std::shared_ptr<Analyzer::StringOper>& string_oper)
+      : StringOper(string_oper) {}
+
+  std::shared_ptr<Analyzer::Expr> deep_copy() const override;
+
+  size_t getMinArgs() const override { return 2UL; }
+
+  std::vector<OperandTypeFamily> getExpectedTypeFamilies() const override {
+    return {OperandTypeFamily::STRING_FAMILY, OperandTypeFamily::STRING_FAMILY};
+  }
+  std::vector<std::string> getArgNames() const override {
+    return {"left operand", "right operand"};
+  }
+
+ private:
+  static std::vector<std::shared_ptr<Analyzer::Expr>> normalize_operands(
+      const std::vector<std::shared_ptr<Analyzer::Expr>>& operands);
+};
+
 class FunctionOper : public Expr {
  public:
   FunctionOper(const SQLTypeInfo& ti,
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4d30b85440..30e5cb5211 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -90,7 +90,7 @@ string(TOLOWER "${MAPD_EDITION}" MAPD_EDITION_LOWER)
 
 # HeavyDB version number
 set(MAPD_VERSION_MAJOR "7")
-set(MAPD_VERSION_MINOR "0")
+set(MAPD_VERSION_MINOR "1")
 set(MAPD_VERSION_PATCH "0")
 set(MAPD_VERSION_EXTRA "dev")
 set(MAPD_VERSION_RAW "${MAPD_VERSION_MAJOR}.${MAPD_VERSION_MINOR}.${MAPD_VERSION_PATCH}${MAPD_VERSION_EXTRA}")
@@ -464,9 +464,9 @@ if(ENABLE_ML_ONEDAL_TFS)
     set(ENABLE_ML_ONEDAL_TFS OFF CACHE BOOL "Enable Intel oneDal ML system table functions" FORCE)
     message(STATUS "System table functions must be enabled to use oneDal ML functions, disabling oneDal support.")
   else()
-    set(USE_DPCPP no)
-    set(USE_NEW_IFACES no)
-    set(TARGET_LINK static)
+    set(ONEDAL_USE_DPCPP no)
+    set(ONEDAL_INTERFACE yes)
+    set(ONEDAL_LINK static)
     find_package(oneDAL)
     if (oneDAL_FOUND)
       message(STATUS "Found the following oneDAL libraries:")
@@ -657,15 +657,28 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
   list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/llvm")
 endif()
 find_package(LLVM CONFIG REQUIRED)
-
+if (TARGET LLVM)
+  get_target_property(LLVM_LIB LLVM IMPORTED_LOCATION_RELEASE)
+endif()
+if (NOT LLVM_LIB)
+  find_library(LLVM_LIB LLVM)
+endif()
 message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
 message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
+message(STATUS "Using LLVM_LIB: ${LLVM_LIB}")
 
-include_directories(${LLVM_INCLUDE_DIRS})
+include_directories(SYSTEM ${LLVM_INCLUDE_DIRS})
 add_definitions(${LLVM_DEFINITIONS})
 
-find_library(CLANG_LIB clang-cpp)
-find_library(LLVM_LIB LLVM)
+find_package(Clang CONFIG)
+if (TARGET clang-cpp)
+  get_target_property(CLANG_LIB clang-cpp IMPORTED_LOCATION_RELEASE)
+endif()
+if (NOT CLANG_LIB)
+  find_library(CLANG_LIB clang-cpp)
+endif()
+message(STATUS "Using CLANG_LIB: ${CLANG_LIB}")
+
 # Deps builds use separate libs for each clang component, while some distros now bundle into a single lib
 if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR NOT LLVM_LIB)
   set(LLVM_COMPONENTS support mcjit core irreader option linker)
diff --git a/Calcite/Calcite.cpp b/Calcite/Calcite.cpp
index 07532d1071..e9bcc41881 100644
--- a/Calcite/Calcite.cpp
+++ b/Calcite/Calcite.cpp
@@ -719,11 +719,15 @@ void Calcite::setRuntimeExtensionFunctions(
 
 TQueryParsingOption Calcite::getCalciteQueryParsingOption(bool legacy_syntax,
                                                           bool is_explain,
-                                                          bool check_privileges) {
+                                                          bool check_privileges,
+                                                          bool is_explain_detail) {
   TQueryParsingOption query_parsing_info;
   query_parsing_info.legacy_syntax = legacy_syntax;
   query_parsing_info.is_explain = is_explain;
   query_parsing_info.check_privileges = check_privileges;
+  query_parsing_info.is_explain_detail = is_explain_detail;
+  // `EXPLAIN CALCITE DETAIL` requires `is_explain` set to TRUE
+  CHECK_LE(is_explain_detail, is_explain);
   return query_parsing_info;
 }
 
diff --git a/Calcite/Calcite.h b/Calcite/Calcite.h
index ffedc297ce..c7f9b7b169 100644
--- a/Calcite/Calcite.h
+++ b/Calcite/Calcite.h
@@ -59,7 +59,7 @@ class TFilterPushDownInfo;
 class TPlanResult;
 class TCompletionHint;
 
-class Calcite final {
+class Calcite {
  public:
   Calcite(const int db_port,
           const int port,
@@ -71,6 +71,7 @@ class Calcite final {
   Calcite(const SystemParameters& db_parameters,
           const std::string& data_dir,
           const std::string& udf_filename = "");
+  Calcite() {}
   // sql_string may differ from what is in query_state due to legacy_syntax option.
   TPlanResult process(query_state::QueryStateProxy,
                       std::string sql_string,
@@ -86,9 +87,9 @@ class Calcite final {
       const int cursor);
   std::string getExtensionFunctionWhitelist();
   std::string getUserDefinedFunctionWhitelist();
-  void updateMetadata(std::string catalog, std::string table);
+  virtual void updateMetadata(std::string catalog, std::string table);
   void close_calcite_server(bool log = true);
-  ~Calcite();
+  virtual ~Calcite();
   std::string getRuntimeExtensionFunctionWhitelist();
   void setRuntimeExtensionFunctions(const std::vector<TUserDefinedFunction>& udfs,
                                     const std::vector<TUserDefinedTableFunction>& udtfs,
@@ -99,7 +100,8 @@ class Calcite final {
   }
   TQueryParsingOption getCalciteQueryParsingOption(bool legacy_syntax,
                                                    bool is_explain,
-                                                   bool check_privileges);
+                                                   bool check_privileges,
+                                                   bool is_explain_detail);
   TOptimizationOption getCalciteOptimizationOption(
       bool is_view_optimize,
       bool enable_watchdog,
diff --git a/Catalog/Catalog.cpp b/Catalog/Catalog.cpp
index 60fca95cc7..473dc4f082 100644
--- a/Catalog/Catalog.cpp
+++ b/Catalog/Catalog.cpp
@@ -566,8 +566,8 @@ void Catalog::updateDefaultColumnValues() {
       sqliteConnector_.query("ALTER TABLE mapd_columns ADD default_value TEXT");
     }
   } catch (std::exception& e) {
-    LOG(ERROR) << "Failed to make metadata update for default values` support";
     sqliteConnector_.query("ROLLBACK TRANSACTION");
+    LOG(ERROR) << "Failed to make metadata update for default values` support";
     throw;
   }
   sqliteConnector_.query("END TRANSACTION");
@@ -1101,35 +1101,22 @@ void Catalog::reloadTableMetadataUnlocked(int table_id) {
   // Reload dictionaries first.
   // TODO(sy): Does dictionary reloading really belong here?
   //           We don't have dictionary locks in the system but maybe we need them.
-  list<DictDescriptor> dicts;
-  std::string dictQuery(
-      "SELECT dictid, name, nbits, is_shared, refcount from mapd_dictionaries");
-  sqliteConnector_.query(dictQuery);
-  auto numRows = sqliteConnector_.getNumRows();
-  for (size_t r = 0; r < numRows; ++r) {
-    auto dictId = sqliteConnector_.getData<int>(r, 0);
-    auto dictName = sqliteConnector_.getData<string>(r, 1);
-    auto dictNBits = sqliteConnector_.getData<int>(r, 2);
-    auto is_shared = sqliteConnector_.getData<bool>(r, 3);
-    auto refcount = sqliteConnector_.getData<int>(r, 4);
-    auto fname = g_base_path + "/" + shared::kDataDirectoryName + "/DB_" +
-                 std::to_string(currentDB_.dbId) + "_DICT_" + std::to_string(dictId);
-    DictRef dict_ref(currentDB_.dbId, dictId);
-    DictDescriptor dd(dict_ref, dictName, dictNBits, is_shared, refcount, fname, false);
-    if (auto it = dictDescriptorMapByRef_.find(dict_ref);
-        it == dictDescriptorMapByRef_.end()) {
-      dictDescriptorMapByRef_[dict_ref] = std::make_unique<DictDescriptor>(dd);
-    } else {
-      *it->second = dd;
-    }
-  }
+  reloadDictionariesFromDiskUnlocked();
 
   // Delete this table's metadata from the in-memory cache before reloading.
-  TableDescriptor* original_td;
+  TableDescriptor* original_td = nullptr;
   std::list<ColumnDescriptor*> original_cds;
   if (auto it1 = tableDescriptorMapById_.find(table_id);
       it1 != tableDescriptorMapById_.end()) {
     original_td = it1->second;
+    if (dynamic_cast<foreign_storage::ForeignTable*>(original_td)) {
+      // If have a foreign table then we need to destroy the local data wrapper or it will
+      // contain (potentially invalid) cached data.  This needs to be done before we
+      // remove the table descriptors.
+      dataMgr_->removeTableRelatedDS(currentDB_.dbId, table_id);
+    } else {
+      dataMgr_->removeMutableTableDiskCacheData(currentDB_.dbId, table_id);
+    }
     tableDescriptorMapById_.erase(it1);
     if (auto it2 = tableDescriptorMap_.find(to_upper(original_td->tableName));
         it2 != tableDescriptorMap_.end()) {
@@ -1150,66 +1137,12 @@ void Catalog::reloadTableMetadataUnlocked(int table_id) {
     }
   }
 
-  // Reload the table descriptor.
-  std::string tableQuery(
-      "SELECT tableid, name, ncolumns, isview, fragments, frag_type, max_frag_rows, "
-      "max_chunk_size, frag_page_size, max_rows, partitions, shard_column_id, shard, "
-      "num_shards, key_metainfo, userid, sort_column_id, storage_type, "
-      "max_rollback_epochs, is_system_table from mapd_tables WHERE tableid = " +
-      std::to_string(table_id));
-  sqliteConnector_.query(tableQuery);
-  numRows = sqliteConnector_.getNumRows();
-  if (!numRows) {
-    return;  // Table was deleted by another node.
-  }
-
   TableDescriptor* td;
-  const auto& storage_type = sqliteConnector_.getData<string>(0, 17);
-  if (!storage_type.empty() && storage_type != StorageType::FOREIGN_TABLE) {
-    const auto& table_name = sqliteConnector_.getData<string>(0, 1);
-    LOG(FATAL) << "Unable to read Catalog metadata: storage type is currently not a "
-                  "supported table option (table "
-               << table_name << " [" << table_id << "] in database " << currentDB_.dbName
-               << ").";
-  }
-
-  if (storage_type == StorageType::FOREIGN_TABLE) {
-    td = new foreign_storage::ForeignTable();
-  } else {
-    td = new TableDescriptor();
-  }
-
-  td->storageType = storage_type;
-  td->tableId = sqliteConnector_.getData<int>(0, 0);
-  td->tableName = sqliteConnector_.getData<string>(0, 1);
-  td->nColumns = sqliteConnector_.getData<int>(0, 2);
-  td->isView = sqliteConnector_.getData<bool>(0, 3);
-  td->fragments = sqliteConnector_.getData<string>(0, 4);
-  td->fragType =
-      (Fragmenter_Namespace::FragmenterType)sqliteConnector_.getData<int>(0, 5);
-  td->maxFragRows = sqliteConnector_.getData<int>(0, 6);
-  td->maxChunkSize = sqliteConnector_.getData<int64_t>(0, 7);
-  td->fragPageSize = sqliteConnector_.getData<int>(0, 8);
-  td->maxRows = sqliteConnector_.getData<int64_t>(0, 9);
-  td->partitions = sqliteConnector_.getData<string>(0, 10);
-  td->shardedColumnId = sqliteConnector_.getData<int>(0, 11);
-  td->shard = sqliteConnector_.getData<int>(0, 12);
-  td->nShards = sqliteConnector_.getData<int>(0, 13);
-  td->keyMetainfo = sqliteConnector_.getData<string>(0, 14);
-  td->userId = sqliteConnector_.getData<int>(0, 15);
-  td->sortedColumnId =
-      sqliteConnector_.isNull(0, 16) ? 0 : sqliteConnector_.getData<int>(0, 16);
-  if (!td->isView) {
-    td->fragmenter = nullptr;
-  }
-  td->maxRollbackEpochs = sqliteConnector_.getData<int>(0, 18);
-  td->is_system_table = sqliteConnector_.getData<bool>(0, 19);
-  td->hasDeletedCol = false;
-
-  if (td->isView) {
-    // If we have a view, then we need to refresh the viewSQL from the mapd_views table,
-    // since this value is not contained in the mapd_tables table.
-    updateViewUnlocked(*td);
+  try {
+    td = createTableFromDiskUnlocked(table_id);
+  } catch (const NoTableFoundException& e) {
+    // No entry found for table on disk.  Another node may have deleted it.
+    return;
   }
 
   if (auto tableDescIt = tableDescriptorMapById_.find(table_id);
@@ -1219,43 +1152,7 @@ void Catalog::reloadTableMetadataUnlocked(int table_id) {
   }
 
   // Reload the column descriptors.
-  std::list<ColumnDescriptor*> cds;
-  std::string columnQuery(
-      "SELECT tableid, columnid, name, coltype, colsubtype, coldim, colscale, "
-      "is_notnull, compression, comp_param, size, chunks, is_systemcol, is_virtualcol, "
-      "virtual_expr, is_deletedcol, default_value from mapd_columns WHERE tableid = " +
-      std::to_string(table_id) + " ORDER BY tableid, columnid");
-  sqliteConnector_.query(columnQuery);
-  numRows = sqliteConnector_.getNumRows();
-  int32_t skip_physical_cols = 0;
-  for (size_t r = 0; r < numRows; ++r) {
-    ColumnDescriptor* cd = new ColumnDescriptor();
-    cd->tableId = sqliteConnector_.getData<int>(r, 0);
-    cd->columnId = sqliteConnector_.getData<int>(r, 1);
-    cd->columnName = sqliteConnector_.getData<string>(r, 2);
-    cd->columnType.set_type((SQLTypes)sqliteConnector_.getData<int>(r, 3));
-    cd->columnType.set_subtype((SQLTypes)sqliteConnector_.getData<int>(r, 4));
-    cd->columnType.set_dimension(sqliteConnector_.getData<int>(r, 5));
-    cd->columnType.set_scale(sqliteConnector_.getData<int>(r, 6));
-    cd->columnType.set_notnull(sqliteConnector_.getData<bool>(r, 7));
-    cd->columnType.set_compression((EncodingType)sqliteConnector_.getData<int>(r, 8));
-    cd->columnType.set_comp_param(sqliteConnector_.getData<int>(r, 9));
-    cd->columnType.set_size(sqliteConnector_.getData<int>(r, 10));
-    cd->chunks = sqliteConnector_.getData<string>(r, 11);
-    cd->isSystemCol = sqliteConnector_.getData<bool>(r, 12);
-    cd->isVirtualCol = sqliteConnector_.getData<bool>(r, 13);
-    cd->virtualExpr = sqliteConnector_.getData<string>(r, 14);
-    cd->isDeletedCol = sqliteConnector_.getData<bool>(r, 15);
-    if (sqliteConnector_.isNull(r, 16)) {
-      cd->default_value = std::nullopt;
-    } else {
-      cd->default_value = std::make_optional(sqliteConnector_.getData<string>(r, 16));
-    }
-    cd->isGeoPhyCol = skip_physical_cols-- > 0;
-    cd->db_id = getDatabaseId();
-    set_dict_key(*cd);
-    cds.push_back(cd);
-  }
+  auto cds = sqliteGetColumnsForTableUnlocked(table_id);
 
   // Store the descriptors into the cache.
   if (original_td) {
@@ -1269,7 +1166,7 @@ void Catalog::reloadTableMetadataUnlocked(int table_id) {
   original_cds.clear();
   tableDescriptorMap_[to_upper(td->tableName)] = td;
   tableDescriptorMapById_[td->tableId] = td;
-  skip_physical_cols = 0;
+  int32_t skip_physical_cols = 0;
   for (ColumnDescriptor* cd : cds) {
     addToColumnMap(cd);
 
@@ -4351,7 +4248,12 @@ void Catalog::invalidateCachesForTable(const int table_id) {
   }
   getDataMgr().getGlobalFileMgr()->closeFileMgr(table_key[CHUNK_KEY_DB_IDX],
                                                 table_key[CHUNK_KEY_TABLE_IDX]);
-  // getMetadataForTable(table_key[CHUNK_KEY_TABLE_IDX], /*populateFragmenter=*/true);
+  if (dynamic_cast<foreign_storage::ForeignTable*>(td)) {
+    dataMgr_->getPersistentStorageMgr()->getForeignStorageMgr()->refreshTable(table_key,
+                                                                              true);
+  } else {
+    dataMgr_->removeMutableTableDiskCacheData(currentDB_.dbId, table_id);
+  }
   instantiateFragmenter(td);
 }
 
@@ -4539,30 +4441,24 @@ void Catalog::renameTable(const TableDescriptor* td, const string& newTableName)
   }
 }
 
-void Catalog::renamePhysicalTable(std::vector<std::pair<std::string, std::string>>& names,
-                                  std::vector<int>& tableIds) {
+void Catalog::renamePhysicalTables(
+    std::vector<std::pair<std::string, std::string>>& names,
+    std::vector<int>& tableIds) {
   cat_write_lock write_lock(this);
   cat_sqlite_lock sqlite_lock(getObjForLock());
 
   // execute the SQL query
-  try {
-    for (size_t i = 0; i < names.size(); i++) {
-      int tableId = tableIds[i];
-      std::string& newTableName = names[i].second;
-
-      sqliteConnector_.query_with_text_params(
-          "UPDATE mapd_tables SET name = ? WHERE tableid = ?",
-          std::vector<std::string>{newTableName, std::to_string(tableId)});
-    }
-  } catch (std::exception& e) {
-    sqliteConnector_.query("ROLLBACK TRANSACTION");
-    throw;
+  for (size_t i = 0; i < names.size(); i++) {
+    int tableId = tableIds[i];
+    const std::string& newTableName = names[i].second;
+    sqliteConnector_.query_with_text_params(
+        "UPDATE mapd_tables SET name = ? WHERE tableid = ?",
+        std::vector<std::string>{newTableName, std::to_string(tableId)});
   }
 
   // reset the table descriptors, give Calcite a kick
   for (size_t i = 0; i < names.size(); i++) {
-    std::string& curTableName = names[i].first;
-    std::string& newTableName = names[i].second;
+    const auto& [curTableName, newTableName] = names[i];
 
     TableDescriptorMap::iterator tableDescIt =
         tableDescriptorMap_.find(to_upper(curTableName));
@@ -4581,38 +4477,31 @@ void Catalog::renamePhysicalTable(std::vector<std::pair<std::string, std::string
 // Collect an 'overlay' mapping of the tableNames->tableId
 //    to account for possible chained renames
 //    (for swap:  a->b, b->c, c->d, d->a)
-
-const TableDescriptor* lookupTableDescriptor(Catalog* cat,
-                                             std::map<std::string, int>& cachedTableMap,
-                                             std::string& curTableName) {
-  auto iter = cachedTableMap.find(curTableName);
-  if ((iter != cachedTableMap.end())) {
-    // get the cached tableId
-    //   and use that to lookup the TableDescriptor
-    int tableId = (*iter).second;
-    if (tableId == -1) {
-      return NULL;
-    } else {
-      return cat->getMetadataForTable(tableId);
-    }
+const TableDescriptor* Catalog::getCachedTableDescriptor(
+    const std::map<std::string, int>& cached_table_map,
+    const std::string& cur_table_name) {
+  if (auto it = cached_table_map.find(cur_table_name); it != cached_table_map.end()) {
+    auto table_id = it->second;
+    return (table_id == -1) ? NULL : getMetadataForTable(table_id);
   }
-
-  // else ... lookup in standard location
-  return cat->getMetadataForTable(curTableName);
+  return getMetadataForTable(cur_table_name);
 }
 
-void replaceTableName(std::map<std::string, int>& cachedTableMap,
-                      std::string& curTableName,
-                      std::string& newTableName,
-                      int tableId) {
+namespace {
+void replace_cached_table_name(std::map<std::string, int>& cachedTableMap,
+                               const std::string& curTableName,
+                               const std::string& newTableName,
+                               int tableId) {
   // mark old/cur name as deleted
   cachedTableMap[curTableName] = -1;
 
   // insert the 'new' name
   cachedTableMap[newTableName] = tableId;
 }
+}  // namespace
 
-void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& names) {
+void Catalog::renameTables(
+    const std::vector<std::pair<std::string, std::string>>& names) {
   // tableId of all tables being renamed
   //    ... in matching order to 'names'
   std::vector<int> tableIds;
@@ -4631,12 +4520,11 @@ void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& name
 
   // gather tableIds pre-execute; build maps
   for (size_t i = 0; i < names.size(); i++) {
-    std::string& curTableName = names[i].first;
-    std::string& newTableName = names[i].second;
+    const auto& [curTableName, newTableName] = names[i];
 
     // make sure the table being renamed exists,
     //    or will exist when executed in 'name' order
-    auto td = lookupTableDescriptor(this, cachedTableMap, curTableName);
+    auto td = getCachedTableDescriptor(cachedTableMap, curTableName);
     CHECK(td);
 
     tableIds.push_back(td->tableId);
@@ -4644,7 +4532,7 @@ void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& name
       // don't overwrite as it should map to the first names index 'i'
       uniqueOrderedTableIds[td->tableId] = i;
     }
-    replaceTableName(cachedTableMap, curTableName, newTableName, td->tableId);
+    replace_cached_table_name(cachedTableMap, curTableName, newTableName, td->tableId);
   }
 
   CHECK_EQ(tableIds.size(), names.size());
@@ -4662,7 +4550,7 @@ void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& name
   // acquire the locks for all tables being renamed
   lockmgr::LockedTableDescriptors tableLocks;
   for (auto& idPair : uniqueOrderedTableIds) {
-    std::string& tableName = names[idPair.second].first;
+    const std::string& tableName = names[idPair.second].first;
     tableLocks.emplace_back(
         std::make_unique<lockmgr::TableSchemaLockContainer<lockmgr::WriteLock>>(
             lockmgr::TableSchemaLockContainer<lockmgr::WriteLock>::acquireTableDescriptor(
@@ -4673,18 +4561,13 @@ void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& name
 
   {
     cat_write_lock write_lock(this);
-    cat_sqlite_lock sqlite_lock(getObjForLock());
-
-    sqliteConnector_.query("BEGIN TRANSACTION");
-
     // collect all (tables + physical tables) into a single list
     std::vector<std::pair<std::string, std::string>> allNames;
     std::vector<int> allTableIds;
 
     for (size_t i = 0; i < names.size(); i++) {
       int tableId = tableIds[i];
-      std::string& curTableName = names[i].first;
-      std::string& newTableName = names[i].second;
+      const auto& [curTableName, newTableName] = names[i];
 
       // rename all corresponding physical tables if this is a logical table
       const auto physicalTableIt = logicalToPhysicalTableMapById_.find(tableId);
@@ -4695,8 +4578,7 @@ void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& name
           int32_t physical_tb_id = physicalTables[k];
           const TableDescriptor* phys_td = getMetadataForTable(physical_tb_id);
           CHECK(phys_td);
-          std::string newPhysTableName =
-              generatePhysicalTableName(newTableName, static_cast<int32_t>(k + 1));
+          std::string newPhysTableName = generatePhysicalTableName(newTableName, (k + 1));
           allNames.emplace_back(phys_td->tableName, newPhysTableName);
           allTableIds.push_back(phys_td->tableId);
         }
@@ -4704,43 +4586,36 @@ void Catalog::renameTable(std::vector<std::pair<std::string, std::string>>& name
       allNames.emplace_back(curTableName, newTableName);
       allTableIds.push_back(tableId);
     }
-    // rename all tables in one shot
-    renamePhysicalTable(allNames, allTableIds);
 
-    sqliteConnector_.query("END TRANSACTION");
-    //  cat write/sqlite locks are released when they go out scope
+    // rename all tables in one transaction
+    execInTransaction(&Catalog::renamePhysicalTables, allNames, allTableIds);
   }
-  {
-    // now update the SysCatalog
-    for (size_t i = 0; i < names.size(); i++) {
-      int tableId = tableIds[i];
-      std::string& newTableName = names[i].second;
-      {
-        // update table name in direct and effective priv map
-        DBObjectKey key;
-        key.dbId = currentDB_.dbId;
-        key.objectId = tableId;
-        key.permissionType = static_cast<int>(DBObjectType::TableDBObjectType);
-
-        DBObject object(newTableName, TableDBObjectType);
-        object.setObjectKey(key);
-
-        auto objdescs = SysCatalog::instance().getMetadataForObject(
-            currentDB_.dbId, static_cast<int>(DBObjectType::TableDBObjectType), tableId);
-        for (auto obj : objdescs) {
-          Grantee* grnt = SysCatalog::instance().getGrantee(obj->roleName);
-          if (grnt) {
-            grnt->renameDbObject(object);
-          }
+
+  // now update the SysCatalog
+  for (size_t i = 0; i < names.size(); i++) {
+    int tableId = tableIds[i];
+    const std::string& newTableName = names[i].second;
+    {
+      // update table name in direct and effective priv map
+      DBObjectKey key;
+      key.dbId = currentDB_.dbId;
+      key.objectId = tableId;
+      key.permissionType = static_cast<int>(DBObjectType::TableDBObjectType);
+
+      DBObject object(newTableName, TableDBObjectType);
+      object.setObjectKey(key);
+
+      auto objdescs = SysCatalog::instance().getMetadataForObject(
+          currentDB_.dbId, static_cast<int>(DBObjectType::TableDBObjectType), tableId);
+      for (auto obj : objdescs) {
+        Grantee* grnt = SysCatalog::instance().getGrantee(obj->roleName);
+        if (grnt) {
+          grnt->renameDbObject(object);
         }
-        SysCatalog::instance().renameObjectsInDescriptorMap(object, *this);
       }
+      SysCatalog::instance().renameObjectsInDescriptorMap(object, *this);
     }
   }
-
-  // -------- Cleanup --------
-
-  // table locks are released when 'tableLocks' goes out of scope
 }
 
 void Catalog::renameColumn(const TableDescriptor* td,
@@ -5208,7 +5083,7 @@ void Catalog::eraseTablePhysicalData(const TableDescriptor* td) {
 }
 
 std::string Catalog::generatePhysicalTableName(const std::string& logicalTableName,
-                                               const int32_t& shardNumber) {
+                                               const size_t shardNumber) {
   std::string physicalTableName =
       logicalTableName + physicalTableNameTag_ + std::to_string(shardNumber);
   return (physicalTableName);
@@ -5264,6 +5139,174 @@ void Catalog::updateForeignTablesInMapUnlocked() {
   }
 }
 
+void Catalog::reloadForeignTableUnlocked(foreign_storage::ForeignTable& foreign_table) {
+  CHECK(g_enable_fsi);
+  CHECK_NE(foreign_table.tableId, 0)
+      << "reloadForeignTable expects a table with valid id";
+  sqliteConnector_.query(
+      "SELECT server_id, options, last_refresh_time, next_refresh_time from "
+      "omnisci_foreign_tables WHERE table_id == " +
+      std::to_string(foreign_table.tableId));
+  auto num_rows = sqliteConnector_.getNumRows();
+  CHECK_EQ(num_rows, 1U) << "Expected single entry in omnisci_foreign_tables for table'"
+                         << foreign_table.tableName << "', instead got " << num_rows;
+  const auto server_id = sqliteConnector_.getData<int32_t>(0, 0);
+  const auto& options = sqliteConnector_.getData<std::string>(0, 1);
+  const auto last_refresh_time = sqliteConnector_.getData<int64_t>(0, 2);
+  const auto next_refresh_time = sqliteConnector_.getData<int64_t>(0, 3);
+
+  foreign_table.foreign_server = foreignServerMapById_[server_id].get();
+  CHECK(foreign_table.foreign_server);
+  foreign_table.populateOptionsMap(options);
+  foreign_table.last_refresh_time = last_refresh_time;
+  foreign_table.next_refresh_time = next_refresh_time;
+  if (foreign_table.is_system_table) {
+    foreign_table.is_in_memory_system_table =
+        shared::contains(foreign_storage::DataWrapperType::IN_MEMORY_DATA_WRAPPERS,
+                         foreign_table.foreign_server->data_wrapper_type);
+  }
+}
+
+void Catalog::reloadDictionariesFromDiskUnlocked() {
+  std::string dictQuery(
+      "SELECT dictid, name, nbits, is_shared, refcount from mapd_dictionaries");
+  sqliteConnector_.query(dictQuery);
+  auto numRows = sqliteConnector_.getNumRows();
+  for (size_t r = 0; r < numRows; ++r) {
+    auto dictId = sqliteConnector_.getData<int>(r, 0);
+    auto dictName = sqliteConnector_.getData<string>(r, 1);
+    auto dictNBits = sqliteConnector_.getData<int>(r, 2);
+    auto is_shared = sqliteConnector_.getData<bool>(r, 3);
+    auto refcount = sqliteConnector_.getData<int>(r, 4);
+    auto fname = g_base_path + "/" + shared::kDataDirectoryName + "/DB_" +
+                 std::to_string(currentDB_.dbId) + "_DICT_" + std::to_string(dictId);
+    DictRef dict_ref(currentDB_.dbId, dictId);
+    DictDescriptor dd(dict_ref, dictName, dictNBits, is_shared, refcount, fname, false);
+    if (auto it = dictDescriptorMapByRef_.find(dict_ref);
+        it == dictDescriptorMapByRef_.end()) {
+      dictDescriptorMapByRef_[dict_ref] = std::make_unique<DictDescriptor>(dd);
+    } else {
+      *it->second = dd;
+    }
+  }
+}
+
+std::list<ColumnDescriptor*> Catalog::sqliteGetColumnsForTableUnlocked(int32_t table_id) {
+  std::list<ColumnDescriptor*> cds;
+  // TODO(Misiu): Change ColumnDescriptorMap_ to use smartpointers.  Right now we use
+  // temporary smartpointers that release their memory once initialized.
+  std::list<std::unique_ptr<ColumnDescriptor>> smart_cds;
+  std::string columnQuery(
+      "SELECT tableid, columnid, name, coltype, colsubtype, coldim, colscale, "
+      "is_notnull, compression, comp_param, size, chunks, is_systemcol, is_virtualcol, "
+      "virtual_expr, is_deletedcol, default_value from mapd_columns WHERE tableid = " +
+      std::to_string(table_id) + " ORDER BY tableid, columnid");
+  sqliteConnector_.query(columnQuery);
+  auto numRows = sqliteConnector_.getNumRows();
+  int32_t skip_physical_cols = 0;
+  for (size_t r = 0; r < numRows; ++r) {
+    std::unique_ptr<ColumnDescriptor> cd = std::make_unique<ColumnDescriptor>();
+    cd->tableId = sqliteConnector_.getData<int>(r, 0);
+    cd->columnId = sqliteConnector_.getData<int>(r, 1);
+    cd->columnName = sqliteConnector_.getData<string>(r, 2);
+    cd->columnType.set_type((SQLTypes)sqliteConnector_.getData<int>(r, 3));
+    cd->columnType.set_subtype((SQLTypes)sqliteConnector_.getData<int>(r, 4));
+    cd->columnType.set_dimension(sqliteConnector_.getData<int>(r, 5));
+    cd->columnType.set_scale(sqliteConnector_.getData<int>(r, 6));
+    cd->columnType.set_notnull(sqliteConnector_.getData<bool>(r, 7));
+    cd->columnType.set_compression((EncodingType)sqliteConnector_.getData<int>(r, 8));
+    cd->columnType.set_comp_param(sqliteConnector_.getData<int>(r, 9));
+    cd->columnType.set_size(sqliteConnector_.getData<int>(r, 10));
+    cd->chunks = sqliteConnector_.getData<string>(r, 11);
+    cd->isSystemCol = sqliteConnector_.getData<bool>(r, 12);
+    cd->isVirtualCol = sqliteConnector_.getData<bool>(r, 13);
+    cd->virtualExpr = sqliteConnector_.getData<string>(r, 14);
+    cd->isDeletedCol = sqliteConnector_.getData<bool>(r, 15);
+    if (sqliteConnector_.isNull(r, 16)) {
+      cd->default_value = std::nullopt;
+    } else {
+      cd->default_value = std::make_optional(sqliteConnector_.getData<string>(r, 16));
+    }
+    cd->isGeoPhyCol = skip_physical_cols-- > 0;
+    cd->db_id = getDatabaseId();
+    set_dict_key(*cd);
+    smart_cds.emplace_back(std::move(cd));
+  }
+  // Once we have correctly initialized all columns, release their ownership as we
+  // currently handle them as free pointers.
+  for (auto& cd : smart_cds) {
+    cds.emplace_back(cd.release());
+  }
+  return cds;
+}
+
+TableDescriptor* Catalog::createTableFromDiskUnlocked(int32_t table_id) {
+  std::string query(
+      "SELECT tableid, name, ncolumns, isview, fragments, frag_type, max_frag_rows, "
+      "max_chunk_size, frag_page_size, max_rows, partitions, shard_column_id, shard, "
+      "num_shards, key_metainfo, userid, sort_column_id, storage_type, "
+      "max_rollback_epochs, is_system_table from mapd_tables WHERE tableid = " +
+      std::to_string(table_id));
+  sqliteConnector_.query(query);
+  auto numRows = sqliteConnector_.getNumRows();
+  if (!numRows) {
+    throw NoTableFoundException(table_id);
+  }
+
+  const auto& storage_type = sqliteConnector_.getData<string>(0, 17);
+  if (!storage_type.empty() && storage_type != StorageType::FOREIGN_TABLE) {
+    const auto& table_name = sqliteConnector_.getData<string>(0, 1);
+    LOG(FATAL) << "Unable to read Catalog metadata: storage type is currently not a "
+                  "supported table option (table "
+               << table_name << " [" << table_id << "] in database " << currentDB_.dbName
+               << ").";
+  }
+
+  // TODO(Misiu): Get rid of manual memory allocation and use smart pointers for
+  // TableDecriptorMap_.  Currently we use a smartpointer to cleanup if we catch
+  // exceptions during initialization and then release ownership into the existing system.
+  std::unique_ptr<TableDescriptor> td;
+  td = (storage_type == StorageType::FOREIGN_TABLE)
+           ? std::make_unique<foreign_storage::ForeignTable>()
+           : std::make_unique<TableDescriptor>();
+
+  td->tableId = sqliteConnector_.getData<int>(0, 0);
+  td->tableName = sqliteConnector_.getData<string>(0, 1);
+  td->nColumns = sqliteConnector_.getData<int>(0, 2);
+  td->isView = sqliteConnector_.getData<bool>(0, 3);
+  td->fragments = sqliteConnector_.getData<string>(0, 4);
+  td->fragType = static_cast<Fragmenter_Namespace::FragmenterType>(
+      sqliteConnector_.getData<int>(0, 5));
+  td->maxFragRows = sqliteConnector_.getData<int>(0, 6);
+  td->maxChunkSize = sqliteConnector_.getData<int64_t>(0, 7);
+  td->fragPageSize = sqliteConnector_.getData<int>(0, 8);
+  td->maxRows = sqliteConnector_.getData<int64_t>(0, 9);
+  td->partitions = sqliteConnector_.getData<string>(0, 10);
+  td->shardedColumnId = sqliteConnector_.getData<int>(0, 11);
+  td->shard = sqliteConnector_.getData<int>(0, 12);
+  td->nShards = sqliteConnector_.getData<int>(0, 13);
+  td->keyMetainfo = sqliteConnector_.getData<string>(0, 14);
+  td->userId = sqliteConnector_.getData<int>(0, 15);
+  td->sortedColumnId =
+      sqliteConnector_.isNull(0, 16) ? 0 : sqliteConnector_.getData<int>(0, 16);
+  td->storageType = storage_type;
+  td->maxRollbackEpochs = sqliteConnector_.getData<int>(0, 18);
+  td->is_system_table = sqliteConnector_.getData<bool>(0, 19);
+  td->hasDeletedCol = false;
+
+  if (td->isView) {
+    updateViewUnlocked(*td);
+  } else {
+    td->fragmenter = nullptr;
+  }
+
+  if (auto ftd = dynamic_cast<foreign_storage::ForeignTable*>(td.get())) {
+    reloadForeignTableUnlocked(*ftd);
+  }
+
+  return td.release();
+}
+
 void Catalog::setForeignServerProperty(const std::string& server_name,
                                        const std::string& property,
                                        const std::string& value) {
@@ -5408,7 +5451,7 @@ std::string Catalog::dumpSchema(const TableDescriptor* td) const {
   for (const auto cd : cds) {
     if (!(cd->isSystemCol || cd->isVirtualCol)) {
       const auto& ti = cd->columnType;
-      os << comma << cd->columnName;
+      os << comma << quoteIfRequired(cd->columnName);
       // CHAR is perculiar... better dump it as TEXT(32) like \d does
       if (ti.get_type() == SQLTypes::kCHAR) {
         os << " "
@@ -6953,4 +6996,19 @@ void Catalog::removeFromColumnMap(ColumnDescriptor* cd) {
   columnDescriptorMap_.erase(ColumnKey{cd->tableId, to_upper(cd->columnName)});
   columnDescriptorMapById_.erase(ColumnIdKey{cd->tableId, cd->columnId});
 }
+
+// TODO(Misiu): Replace most sqlite transactions with this idiom.
+template <typename F, typename... Args>
+void Catalog::execInTransaction(F&& f, Args&&... args) {
+  cat_write_lock write_lock(this);
+  cat_sqlite_lock sqlite_lock(this);
+  sqliteConnector_.query("BEGIN TRANSACTION");
+  try {
+    (this->*f)(std::forward<Args>(args)...);
+  } catch (std::exception&) {
+    sqliteConnector_.query("ROLLBACK TRANSACTION");
+    throw;
+  }
+  sqliteConnector_.query("END TRANSACTION");
+}
 }  // namespace Catalog_Namespace
diff --git a/Catalog/Catalog.h b/Catalog/Catalog.h
index 931d41f3cc..e7b6ed0b9c 100644
--- a/Catalog/Catalog.h
+++ b/Catalog/Catalog.h
@@ -187,7 +187,7 @@ class Catalog final {
   void dropTable(const TableDescriptor* td);
   void truncateTable(const TableDescriptor* td);
   void renameTable(const TableDescriptor* td, const std::string& newTableName);
-  void renameTable(std::vector<std::pair<std::string, std::string>>& names);
+  void renameTables(const std::vector<std::pair<std::string, std::string>>& names);
   void renameColumn(const TableDescriptor* td,
                     const ColumnDescriptor* cd,
                     const std::string& newColumnName);
@@ -265,6 +265,11 @@ class Catalog final {
   const DBMetadata& getCurrentDB() const { return currentDB_; }
   Data_Namespace::DataMgr& getDataMgr() const { return *dataMgr_; }
   std::shared_ptr<Calcite> getCalciteMgr() const { return calciteMgr_; }
+  void setCalciteMgr(const std::shared_ptr<Calcite>& new_calcite_mgr) {
+    // Used for testing.
+    calciteMgr_ = new_calcite_mgr;
+  }
+
   const std::string& getCatalogBasePath() const { return basePath_; }
 
   const DictDescriptor* getMetadataForDict(int dict_ref, bool loadDict = true) const;
@@ -675,8 +680,8 @@ class Catalog final {
   void executeDropTableSqliteQueries(const TableDescriptor* td);
   void doTruncateTable(const TableDescriptor* td);
   void renamePhysicalTable(const TableDescriptor* td, const std::string& newTableName);
-  void renamePhysicalTable(std::vector<std::pair<std::string, std::string>>& names,
-                           std::vector<int>& tableIds);
+  void renamePhysicalTables(std::vector<std::pair<std::string, std::string>>& names,
+                            std::vector<int>& tableIds);
   void instantiateFragmenter(TableDescriptor* td) const;
   void getAllColumnMetadataForTableImpl(const TableDescriptor* td,
                                         std::list<const ColumnDescriptor*>& colDescs,
@@ -685,7 +690,7 @@ class Catalog final {
                                         const bool fetchPhysicalColumns) const;
   std::string calculateSHA1(const std::string& data);
   std::string generatePhysicalTableName(const std::string& logicalTableName,
-                                        const int32_t& shardNumber);
+                                        const size_t shardNumber);
   std::vector<DBObject> parseDashboardObjects(const std::string& view_meta,
                                               const int& user_id);
   void createOrUpdateDashboardSystemRole(const std::string& view_meta,
@@ -744,6 +749,10 @@ class Catalog final {
   template <class T>
   friend class lockmgr::TableLockMgrImpl;  // for reloadTableMetadataUnlocked()
   void reloadTableMetadataUnlocked(int table_id);
+  TableDescriptor* createTableFromDiskUnlocked(int32_t table_id);
+  void reloadDictionariesFromDiskUnlocked();
+  std::list<ColumnDescriptor*> sqliteGetColumnsForTableUnlocked(int32_t table_id);
+  void reloadForeignTableUnlocked(foreign_storage::ForeignTable& foreign_table);
   void reloadCatalogMetadata(const std::map<int32_t, std::string>& user_name_by_user_id);
   void reloadCatalogMetadataUnlocked(
       const std::map<int32_t, std::string>& user_name_by_user_id);
@@ -866,6 +875,20 @@ class Catalog final {
                                       bool multiline_formatting,
                                       bool dump_defaults) const;
 
+  void removeDiskCachedDataForMutableTable(int32_t table_id) const;
+
+  struct NoTableFoundException : std::runtime_error {
+    NoTableFoundException(int32_t table_id)
+        : std::runtime_error{"No entry found for table: " + std::to_string(table_id)} {}
+  };
+
+  template <typename F, typename... Args>
+  void execInTransaction(F&& f, Args&&... args);
+
+  const TableDescriptor* getCachedTableDescriptor(
+      const std::map<std::string, int>& cached_table_map,
+      const std::string& cur_table_name);
+
   static constexpr const char* CATALOG_SERVER_NAME{"system_catalog_server"};
   static constexpr const char* MEMORY_STATS_SERVER_NAME{"system_memory_stats_server"};
   static constexpr const char* STORAGE_STATS_SERVER_NAME{"system_storage_stats_server"};
diff --git a/Catalog/DdlCommandExecutor.cpp b/Catalog/DdlCommandExecutor.cpp
index 1935b1c004..7823f5537e 100644
--- a/Catalog/DdlCommandExecutor.cpp
+++ b/Catalog/DdlCommandExecutor.cpp
@@ -1769,7 +1769,7 @@ ExecutionResult ShowCreateTableCommand::execute(bool read_only_mode) {
     auto query_state_proxy = query_state->createQueryStateProxy();
     auto calcite_mgr = catalog.getCalciteMgr();
     const auto calciteQueryParsingOption =
-        calcite_mgr->getCalciteQueryParsingOption(true, false, false);
+        calcite_mgr->getCalciteQueryParsingOption(true, false, false, false);
     const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
         false,
         g_enable_watchdog,
diff --git a/Catalog/SysCatalog.cpp b/Catalog/SysCatalog.cpp
index ae00c4d9a0..affdf11680 100644
--- a/Catalog/SysCatalog.cpp
+++ b/Catalog/SysCatalog.cpp
@@ -398,8 +398,8 @@ void SysCatalog::importDataFromOldMapdDB() {
   std::string mapd_db_path = basePath_ + "/" + shared::kCatalogDirectoryName + "/mapd";
   sqliteConnector_->query("ATTACH DATABASE `" + mapd_db_path + "` as old_cat");
   sqliteConnector_->query("BEGIN TRANSACTION");
-  LOG(INFO) << "Moving global metadata into a separate catalog";
   try {
+    LOG(INFO) << "Moving global metadata into a separate catalog";
     auto moveTableIfExists = [conn = sqliteConnector_.get()](const std::string& tableName,
                                                              bool deleteOld = true) {
       conn->query("SELECT sql FROM old_cat.sqlite_master WHERE type='table' AND name='" +
@@ -419,8 +419,8 @@ void SysCatalog::importDataFromOldMapdDB() {
     moveTableIfExists("mapd_privileges");
     moveTableIfExists("mapd_version_history", false);
   } catch (const std::exception& e) {
-    LOG(ERROR) << "Failed to move global metadata into a separate catalog: " << e.what();
     sqliteConnector_->query("ROLLBACK TRANSACTION");
+    LOG(ERROR) << "Failed to move global metadata into a separate catalog: " << e.what();
     try {
       sqliteConnector_->query("DETACH DATABASE old_cat");
     } catch (const std::exception&) {
@@ -719,8 +719,8 @@ void SysCatalog::updatePasswordsToHashes() {
     sqliteConnector_->query("DROP TABLE mapd_users");
     sqliteConnector_->query("ALTER TABLE mapd_users_tmp RENAME TO mapd_users");
   } catch (const std::exception& e) {
-    LOG(ERROR) << "Failed to hash passwords: " << e.what();
     sqliteConnector_->query("ROLLBACK TRANSACTION");
+    LOG(ERROR) << "Failed to hash passwords: " << e.what();
     throw;
   }
   sqliteConnector_->query("END TRANSACTION");
@@ -768,8 +768,8 @@ void SysCatalog::updateBlankPasswordsToRandom() {
         std::vector<std::string>{std::to_string(MAPD_VERSION),
                                  UPDATE_BLANK_PASSWORDS_TO_RANDOM});
   } catch (const std::exception& e) {
-    LOG(ERROR) << "Failed to fix blank passwords: " << e.what();
     sqliteConnector_->query("ROLLBACK TRANSACTION");
+    LOG(ERROR) << "Failed to fix blank passwords: " << e.what();
     throw;
   }
   sqliteConnector_->query("END TRANSACTION");
@@ -795,8 +795,8 @@ void SysCatalog::updateSupportUserDeactivation() {
         std::vector<std::string>{std::to_string(MAPD_VERSION),
                                  UPDATE_SUPPORT_USER_DEACTIVATION});
   } catch (const std::exception& e) {
-    LOG(ERROR) << "Failed to add support for user deactivation: " << e.what();
     sqliteConnector_->query("ROLLBACK TRANSACTION");
+    LOG(ERROR) << "Failed to add support for user deactivation: " << e.what();
     throw;
   }
   sqliteConnector_->query("END TRANSACTION");
@@ -872,8 +872,8 @@ void SysCatalog::migrateDBAccessPrivileges() {
       }
     }
   } catch (const std::exception& e) {
-    LOG(ERROR) << "Failed to migrate db access privileges: " << e.what();
     sqliteConnector_->query("ROLLBACK TRANSACTION");
+    LOG(ERROR) << "Failed to migrate db access privileges: " << e.what();
     throw;
   }
   sqliteConnector_->query("END TRANSACTION");
diff --git a/CudaMgr/CMakeLists.txt b/CudaMgr/CMakeLists.txt
index 9f077665cc..880574ec80 100644
--- a/CudaMgr/CMakeLists.txt
+++ b/CudaMgr/CMakeLists.txt
@@ -1,5 +1,5 @@
 if(ENABLE_CUDA)
-    add_library(CudaMgr CudaMgr.cpp)
+    add_library(CudaMgr CudaMgr.cpp DeviceMemoryAllocationMap.cpp)
 else()
     add_library(CudaMgr CudaMgrNoCuda.cpp)
 endif()
diff --git a/CudaMgr/CudaMgr.cpp b/CudaMgr/CudaMgr.cpp
index 622c34442d..cdfaf0ad74 100644
--- a/CudaMgr/CudaMgr.cpp
+++ b/CudaMgr/CudaMgr.cpp
@@ -48,7 +48,8 @@ std::string errorMessage(CUresult const status) {
 CudaMgr::CudaMgr(const int num_gpus, const int start_gpu)
     : start_gpu_(start_gpu)
     , min_shared_memory_per_block_for_all_devices(0)
-    , min_num_mps_for_all_devices(0) {
+    , min_num_mps_for_all_devices(0)
+    , device_memory_allocation_map_{std::make_unique<DeviceMemoryAllocationMap>()} {
   checkError(cuInit(0));
   checkError(cuDeviceGetCount(&device_count_));
 
@@ -83,7 +84,9 @@ CudaMgr::~CudaMgr() {
     // This should be enforced by the lifetime policies, but take this lock to be safe.
     std::lock_guard<std::mutex> device_lock(device_mutex_);
     synchronizeDevices();
-    CHECK(device_memory_allocation_map_.empty());
+
+    CHECK(getDeviceMemoryAllocationMap().mapEmpty());
+    device_memory_allocation_map_ = nullptr;
 
     for (int d = 0; d < device_count_; ++d) {
       checkError(cuCtxDestroy(device_contexts_[d]));
@@ -145,14 +148,10 @@ void CudaMgr::copyDeviceToHost(int8_t* host_ptr,
   auto const cu_device_ptr = reinterpret_cast<CUdeviceptr>(device_ptr);
   {
     std::lock_guard<std::mutex> device_lock(device_mutex_);
-    auto itr = device_memory_allocation_map_.upper_bound(cu_device_ptr);
-    CHECK(itr != device_memory_allocation_map_.begin());
-    --itr;
-    auto const& allocation_base = itr->first;
-    auto const& allocation_size = itr->second.size;
-    CHECK_LE(cu_device_ptr + num_bytes, allocation_base + allocation_size);
-    auto const& allocation_device_num = itr->second.device_num;
-    setContext(allocation_device_num);
+    auto const [allocation_base, allocation] =
+        getDeviceMemoryAllocationMap().getAllocation(cu_device_ptr);
+    CHECK_LE(cu_device_ptr + num_bytes, allocation_base + allocation.size);
+    setContext(allocation.device_num);
   }
   if (!cuda_stream) {
     checkError(cuMemcpyDtoH(host_ptr, cu_device_ptr, num_bytes));
@@ -227,10 +226,32 @@ void CudaMgr::unloadGpuModuleData(CUmodule* module, const int device_id) const {
   }
 }
 
-CudaMgr::CudaMemoryUsage CudaMgr::getCudaMemoryUsage() {
-  CudaMemoryUsage usage;
-  cuMemGetInfo(&usage.free, &usage.total);
-  return usage;
+std::vector<CudaMgr::CudaMemoryUsage> CudaMgr::getCudaMemoryUsage() {
+  std::vector<CudaMgr::CudaMemoryUsage> m;
+  std::lock_guard<std::mutex> map_lock(device_mutex_);
+  CUcontext cnow;
+  checkError(cuCtxGetCurrent(&cnow));
+  for (int device_num = 0; device_num < device_count_; ++device_num) {
+    setContext(device_num);
+    CudaMemoryUsage usage;
+    cuMemGetInfo(&usage.free, &usage.total);
+    m.push_back(usage);
+  }
+  cuCtxSetCurrent(cnow);
+  return m;
+}
+
+std::string CudaMgr::getCudaMemoryUsageInString() {
+  auto const device_mem_status = getCudaMemoryUsage();
+  std::ostringstream oss;
+  int device_id = 0;
+  oss << "{ \"name\": \"GPU Memory Info\", ";
+  for (auto& info : device_mem_status) {
+    oss << "{\"device_id\": " << device_id++ << ", \"freeMB:\": " << info.free / 1048576.0
+        << ", \"totalMB\": " << info.total / 1048576.0 << "} ";
+  }
+  oss << "}";
+  return oss.str();
 }
 
 void CudaMgr::fillDeviceProperties() {
@@ -309,7 +330,9 @@ int8_t* CudaMgr::allocatePinnedHostMem(const size_t num_bytes) {
   return reinterpret_cast<int8_t*>(host_ptr);
 }
 
-int8_t* CudaMgr::allocateDeviceMem(const size_t num_bytes, const int device_num) {
+int8_t* CudaMgr::allocateDeviceMem(const size_t num_bytes,
+                                   const int device_num,
+                                   const bool is_slab) {
   std::lock_guard<std::mutex> map_lock(device_mutex_);
   setContext(device_num);
 
@@ -357,33 +380,32 @@ int8_t* CudaMgr::allocateDeviceMem(const size_t num_bytes, const int device_num)
     }
     throw CudaErrorException(status);
   }
-  DeviceMemoryMetadata device_ptr_metadata{
-      padded_num_bytes, handle, getDeviceProperties(device_num)->uuid, device_num};
-  CHECK(
-      device_memory_allocation_map_.try_emplace(device_ptr, device_ptr_metadata).second);
+  // emplace in the map
+  auto const& device_uuid = getDeviceProperties(device_num)->uuid;
+  getDeviceMemoryAllocationMap().addAllocation(
+      device_ptr, padded_num_bytes, handle, device_uuid, device_num, is_slab);
+  // notify
+  getDeviceMemoryAllocationMap().notifyMapChanged(device_uuid, is_slab);
   return reinterpret_cast<int8_t*>(device_ptr);
 }
 
 void CudaMgr::freeDeviceMem(int8_t* device_ptr) {
   // take lock
   std::lock_guard<std::mutex> map_lock(device_mutex_);
-  // find in map
+  // fetch and remove from map
   auto const cu_device_ptr = reinterpret_cast<CUdeviceptr>(device_ptr);
-  auto const itr = device_memory_allocation_map_.find(cu_device_ptr);
-  CHECK(itr != device_memory_allocation_map_.end());
-  // get attributes
-  auto const size = itr->second.size;
-  auto const handle = itr->second.handle;
+  auto allocation = getDeviceMemoryAllocationMap().removeAllocation(cu_device_ptr);
   // attempt to unmap, release, free
-  auto status_unmap = cuMemUnmap(cu_device_ptr, size);
-  auto status_release = cuMemRelease(handle);
-  auto status_free = cuMemAddressFree(cu_device_ptr, size);
-  // remove from map
-  device_memory_allocation_map_.erase(itr);
+  auto status_unmap = cuMemUnmap(cu_device_ptr, allocation.size);
+  auto status_release = cuMemRelease(allocation.handle);
+  auto status_free = cuMemAddressFree(cu_device_ptr, allocation.size);
   // check for errors
   checkError(status_unmap);
   checkError(status_release);
   checkError(status_free);
+  // notify
+  getDeviceMemoryAllocationMap().notifyMapChanged(allocation.device_uuid,
+                                                  allocation.is_slab);
 }
 
 void CudaMgr::zeroDeviceMem(int8_t* device_ptr,
@@ -544,6 +566,18 @@ void CudaMgr::checkError(CUresult status) const {
   }
 }
 
+DeviceMemoryAllocationMap& CudaMgr::getDeviceMemoryAllocationMap() {
+  CHECK(device_memory_allocation_map_);
+  return *device_memory_allocation_map_;
+}
+
+int CudaMgr::exportHandle(const uint64_t handle) const {
+  int fd{-1};
+  checkError(cuMemExportToShareableHandle(
+      &fd, handle, CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR, 0));
+  return fd;
+}
+
 }  // namespace CudaMgr_Namespace
 
 std::string get_cuda_home(void) {
diff --git a/CudaMgr/CudaMgr.h b/CudaMgr/CudaMgr.h
index f37eb95890..8c0cc3598c 100644
--- a/CudaMgr/CudaMgr.h
+++ b/CudaMgr/CudaMgr.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <vector>
 
+#include "CudaMgr/DeviceMemoryAllocationMap.h"
 #include "Logger/Logger.h"
 #include "Shared/DeviceGroup.h"
 
@@ -33,16 +34,6 @@
 
 namespace CudaMgr_Namespace {
 
-using DeviceMemoryPtrConstant = uint64_t;
-
-struct DeviceMemoryMetadata {
-  uint64_t size;
-  uint64_t handle;
-  heavyai::UUID device_uuid;
-  int device_num;
-};
-using DeviceMemoryAllocationMap = std::map<DeviceMemoryPtrConstant, DeviceMemoryMetadata>;
-
 enum class NvidiaDeviceArch {
   Kepler,   // compute major = 3
   Maxwell,  // compute major = 5
@@ -119,7 +110,9 @@ class CudaMgr {
                           CUstream cuda_stream = 0);
 
   int8_t* allocatePinnedHostMem(const size_t num_bytes);
-  virtual int8_t* allocateDeviceMem(const size_t num_bytes, const int device_num);
+  virtual int8_t* allocateDeviceMem(const size_t num_bytes,
+                                    const int device_num,
+                                    const bool is_slab = false);
   void freePinnedHostMem(int8_t* host_ptr);
   void freeDeviceMem(int8_t* device_ptr);
   void zeroDeviceMem(int8_t* device_ptr,
@@ -244,7 +237,13 @@ class CudaMgr {
     size_t total;  // total GPU RAM memory on active card in bytes
   };
 
-  static CudaMemoryUsage getCudaMemoryUsage();
+  std::vector<CudaMgr::CudaMemoryUsage> getCudaMemoryUsage();
+
+  std::string getCudaMemoryUsageInString();
+
+  DeviceMemoryAllocationMap& getDeviceMemoryAllocationMap();
+  int exportHandle(const uint64_t handle) const;
+
 #endif
 
  private:
@@ -266,8 +265,11 @@ class CudaMgr {
   std::vector<DeviceProperties> device_properties_;
   heavyai::DeviceGroup device_group_;
   std::vector<CUcontext> device_contexts_;
-  DeviceMemoryAllocationMap device_memory_allocation_map_;
   mutable std::mutex device_mutex_;
+
+#ifdef HAVE_CUDA
+  DeviceMemoryAllocationMapUqPtr device_memory_allocation_map_;
+#endif
 };
 
 }  // Namespace CudaMgr_Namespace
diff --git a/CudaMgr/CudaMgrNoCuda.cpp b/CudaMgr/CudaMgrNoCuda.cpp
index 7232439467..a4171831f6 100644
--- a/CudaMgr/CudaMgrNoCuda.cpp
+++ b/CudaMgr/CudaMgrNoCuda.cpp
@@ -65,7 +65,9 @@ int8_t* CudaMgr::allocatePinnedHostMem(const size_t num_bytes) {
   CHECK(false);
   return nullptr;
 }
-int8_t* CudaMgr::allocateDeviceMem(const size_t num_bytes, const int device_num) {
+int8_t* CudaMgr::allocateDeviceMem(const size_t num_bytes,
+                                   const int device_num,
+                                   const bool is_slab) {
   CHECK(false);
   return nullptr;
 }
diff --git a/CudaMgr/DeviceMemoryAllocationMap.cpp b/CudaMgr/DeviceMemoryAllocationMap.cpp
new file mode 100644
index 0000000000..c3da9e409b
--- /dev/null
+++ b/CudaMgr/DeviceMemoryAllocationMap.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 HEAVY.AI, Inc.
+ *
+ * Licensed 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 <utility>
+
+#include "CudaMgr/DeviceMemoryAllocationMap.h"
+#include "Logger/Logger.h"
+
+namespace CudaMgr_Namespace {
+
+DeviceMemoryAllocationMap::DeviceMemoryAllocationMap() : last_map_changed_cbid_{0u} {}
+
+const DeviceMemoryAllocationMap::Map& DeviceMemoryAllocationMap::getMap() const {
+  return map_;
+}
+
+const bool DeviceMemoryAllocationMap::mapEmpty() const {
+  return map_.empty();
+}
+
+void DeviceMemoryAllocationMap::addAllocation(const DevicePtr device_ptr,
+                                              const uint64_t size,
+                                              const uint64_t handle,
+                                              const heavyai::UUID uuid,
+                                              const int device_num,
+                                              const bool is_slab) {
+  Allocation allocation{size, handle, uuid, device_num, is_slab};
+  CHECK(map_.try_emplace(device_ptr, std::move(allocation)).second);
+}
+
+DeviceMemoryAllocationMap::Allocation DeviceMemoryAllocationMap::removeAllocation(
+    const DevicePtr device_ptr) {
+  // find the exact match
+  auto const itr = map_.find(device_ptr);
+  CHECK(itr != map_.end());
+  // copy out the allocation
+  Allocation allocation = itr->second;
+  // remove from map
+  map_.erase(itr);
+  // return the allocation that was
+  return allocation;
+}
+
+std::pair<DeviceMemoryAllocationMap::DevicePtr, DeviceMemoryAllocationMap::Allocation>
+DeviceMemoryAllocationMap::getAllocation(const DevicePtr device_ptr) {
+  // find the map entry above this address
+  auto itr = map_.upper_bound(device_ptr);
+  CHECK(itr != map_.begin());
+  // return the previous entry
+  --itr;
+  return std::make_pair(itr->first, itr->second);
+}
+
+const DeviceMemoryAllocationMap::MapChangedCBID
+DeviceMemoryAllocationMap::registerMapChangedCB(MapChangedCB cb) {
+  auto const cbid = ++last_map_changed_cbid_;
+  CHECK(map_changed_cbs_.emplace(cbid, cb).second) << "Repeat registration";
+  return cbid;
+}
+
+void DeviceMemoryAllocationMap::unregisterMapChangedCB(const MapChangedCBID cbid) {
+  auto itr = map_changed_cbs_.find(cbid);
+  CHECK(itr != map_changed_cbs_.end()) << "Failed to unregister";
+  map_changed_cbs_.erase(itr);
+}
+
+void DeviceMemoryAllocationMap::notifyMapChanged(const heavyai::UUID device_uuid,
+                                                 const bool is_slab) const {
+  for (auto const& cb : map_changed_cbs_) {
+    cb.second(device_uuid, is_slab);
+  }
+}
+
+}  // namespace CudaMgr_Namespace
diff --git a/CudaMgr/DeviceMemoryAllocationMap.h b/CudaMgr/DeviceMemoryAllocationMap.h
new file mode 100644
index 0000000000..b5a88d79c1
--- /dev/null
+++ b/CudaMgr/DeviceMemoryAllocationMap.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 HEAVY.AI, Inc.
+ *
+ * Licensed 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 <functional>
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include "Shared/uuid.h"
+
+namespace CudaMgr_Namespace {
+
+class DeviceMemoryAllocationMap {
+ public:
+  using DevicePtr = uint64_t;
+  struct Allocation {
+    uint64_t size = 0u;
+    uint64_t handle = 0u;
+    heavyai::UUID device_uuid;
+    int device_num = -1;
+    bool is_slab = false;
+  };
+  using Map = std::map<DevicePtr, Allocation>;
+  using MapChangedCBID = uint32_t;
+  using MapChangedCB = std::function<void(const heavyai::UUID, const bool)>;
+
+  DeviceMemoryAllocationMap();
+  ~DeviceMemoryAllocationMap() = default;
+
+  const Map& getMap() const;
+  const bool mapEmpty() const;
+
+  void addAllocation(const DevicePtr device_ptr,
+                     const uint64_t size,
+                     const uint64_t handle,
+                     const heavyai::UUID uuid,
+                     const int device_num,
+                     const bool is_slab);
+  Allocation removeAllocation(const DevicePtr device_ptr);
+  std::pair<DevicePtr, Allocation> getAllocation(const DevicePtr device_ptr);
+
+  const MapChangedCBID registerMapChangedCB(MapChangedCB cb);
+  void unregisterMapChangedCB(const MapChangedCBID cbid);
+  void notifyMapChanged(const heavyai::UUID device_uuid, const bool is_slab) const;
+
+ private:
+  Map map_;
+  MapChangedCBID last_map_changed_cbid_;
+  std::unordered_map<MapChangedCBID, MapChangedCB> map_changed_cbs_;
+};
+
+using DeviceMemoryAllocationMapUqPtr = std::unique_ptr<DeviceMemoryAllocationMap>;
+
+}  // namespace CudaMgr_Namespace
diff --git a/DataMgr/Allocators/ArenaAllocator.h b/DataMgr/Allocators/ArenaAllocator.h
index bbc067d324..dfea972a60 100644
--- a/DataMgr/Allocators/ArenaAllocator.h
+++ b/DataMgr/Allocators/ArenaAllocator.h
@@ -49,6 +49,7 @@ class Arena {
   virtual void* allocate(size_t size) = 0;
   virtual void* allocateAndZero(const size_t size) = 0;
   virtual size_t bytesUsed() const = 0;
+  virtual size_t totalBytes() const = 0;
   virtual MemoryType getMemoryType() const = 0;
 };
 
@@ -92,6 +93,10 @@ class DramArena : public Arena, public folly::Arena<::SysAllocator<AllocatorType
     return folly::Arena<::SysAllocator<AllocatorType>>::bytesUsed();
   }
 
+  size_t totalBytes() const override {
+    return folly::Arena<::SysAllocator<AllocatorType>>::totalSize();
+  }
+
   MemoryType getMemoryType() const override { return MemoryType::DRAM; }
 };
 
@@ -141,6 +146,8 @@ class DramArena : public Arena {
 
   size_t bytesUsed() const override { return size_; }
 
+  size_t totalBytes() const override { return size_; }
+
   MemoryType getMemoryType() const override { return MemoryType::DRAM; }
 
  private:
diff --git a/DataMgr/Allocators/PMemAllocator.cpp b/DataMgr/Allocators/PMemAllocator.cpp
index ba8065e65c..2e508ef32d 100644
--- a/DataMgr/Allocators/PMemAllocator.cpp
+++ b/DataMgr/Allocators/PMemAllocator.cpp
@@ -64,3 +64,7 @@ void* PMemArena::allocateAndZero(const size_t num_bytes) {
 size_t PMemArena::bytesUsed() const {
   return size_;
 }
+
+size_t PMemArena::totalBytes() const {
+  return 0;
+}
diff --git a/DataMgr/Allocators/PMemAllocator.h b/DataMgr/Allocators/PMemAllocator.h
index b4f5ade3cd..4178a307d9 100644
--- a/DataMgr/Allocators/PMemAllocator.h
+++ b/DataMgr/Allocators/PMemAllocator.h
@@ -33,6 +33,8 @@ class PMemArena : public Arena {
 
   size_t bytesUsed() const override;
 
+  size_t totalBytes() const override;
+
   MemoryType getMemoryType() const override { return MemoryType::PMEM; }
 
  private:
diff --git a/DataMgr/BufferMgr/GpuCudaBufferMgr/GpuCudaBufferMgr.cpp b/DataMgr/BufferMgr/GpuCudaBufferMgr/GpuCudaBufferMgr.cpp
index 076368e701..e4f474493a 100644
--- a/DataMgr/BufferMgr/GpuCudaBufferMgr/GpuCudaBufferMgr.cpp
+++ b/DataMgr/BufferMgr/GpuCudaBufferMgr/GpuCudaBufferMgr.cpp
@@ -56,7 +56,8 @@ GpuCudaBufferMgr::~GpuCudaBufferMgr() {
 void GpuCudaBufferMgr::addSlab(const size_t slab_size) {
   slabs_.resize(slabs_.size() + 1);
   try {
-    slabs_.back() = cuda_mgr_->allocateDeviceMem(slab_size, device_id_);
+    slabs_.back() =
+        cuda_mgr_->allocateDeviceMem(slab_size, device_id_, /* is_slab */ true);
   } catch (std::runtime_error& error) {
     slabs_.resize(slabs_.size() - 1);
     throw FailedToCreateSlab(slab_size);
diff --git a/DataMgr/DataMgr.cpp b/DataMgr/DataMgr.cpp
index 3a3ac081bf..741dc81069 100644
--- a/DataMgr/DataMgr.cpp
+++ b/DataMgr/DataMgr.cpp
@@ -1,3 +1,4 @@
+
 /*
  * Copyright 2022 HEAVY.AI, Inc.
  *
@@ -121,7 +122,6 @@ DataMgr::~DataMgr() {
 
 DataMgr::SystemMemoryUsage DataMgr::getSystemMemoryUsage() const {
   SystemMemoryUsage usage;
-
 #ifdef __linux__
 
   // Determine Linux available memory and total memory.
@@ -621,6 +621,10 @@ void DataMgr::removeTableRelatedDS(const int db_id, const int tb_id) {
   bufferMgrs_[0][0]->removeTableRelatedDS(db_id, tb_id);
 }
 
+void DataMgr::removeMutableTableDiskCacheData(const int db_id, const int tb_id) const {
+  getPersistentStorageMgr()->removeMutableTableCacheData(db_id, tb_id);
+}
+
 void DataMgr::setTableEpoch(const int db_id, const int tb_id, const int start_epoch) {
   File_Namespace::GlobalFileMgr* gfm{nullptr};
   gfm = dynamic_cast<PersistentStorageMgr*>(bufferMgrs_[0][0])->getGlobalFileMgr();
diff --git a/DataMgr/DataMgr.h b/DataMgr/DataMgr.h
index d22bbbb091..c24995dc75 100644
--- a/DataMgr/DataMgr.h
+++ b/DataMgr/DataMgr.h
@@ -227,6 +227,7 @@ class DataMgr {
                                        const ChunkKey& keyPrefix);
   inline bool gpusPresent() const { return hasGpus_; }
   void removeTableRelatedDS(const int db_id, const int tb_id);
+  void removeMutableTableDiskCacheData(const int db_id, const int tb_id) const;
   void setTableEpoch(const int db_id, const int tb_id, const int start_epoch);
   size_t getTableEpoch(const int db_id, const int tb_id);
   void resetTableEpochFloor(const int32_t db_id, const int32_t tb_id);
diff --git a/DataMgr/FileMgr/CachingGlobalFileMgr.cpp b/DataMgr/FileMgr/CachingGlobalFileMgr.cpp
index f018bcfaae..15f951168d 100644
--- a/DataMgr/FileMgr/CachingGlobalFileMgr.cpp
+++ b/DataMgr/FileMgr/CachingGlobalFileMgr.cpp
@@ -167,7 +167,7 @@ void CachingGlobalFileMgr::checkpoint(const int db_id, const int tb_id) {
   GlobalFileMgr::checkpoint(db_id, tb_id);
 }
 
-void CachingGlobalFileMgr::removeTableRelatedDS(const int db_id, const int table_id) {
+void CachingGlobalFileMgr::removeCachedData(const int db_id, const int table_id) {
   if (isChunkPrefixCacheable({db_id, table_id})) {
     const ChunkKey table_key{db_id, table_id};
     disk_cache_->clearForTablePrefix(table_key);
@@ -180,6 +180,10 @@ void CachingGlobalFileMgr::removeTableRelatedDS(const int db_id, const int table
       chunk_key_it = cached_chunk_keys_.erase(chunk_key_it);
     }
   }
+}
+
+void CachingGlobalFileMgr::removeTableRelatedDS(const int db_id, const int table_id) {
+  removeCachedData(db_id, table_id);
   GlobalFileMgr::removeTableRelatedDS(db_id, table_id);
 }
 
diff --git a/DataMgr/FileMgr/CachingGlobalFileMgr.h b/DataMgr/FileMgr/CachingGlobalFileMgr.h
index d82c7a2908..7586c4d9db 100644
--- a/DataMgr/FileMgr/CachingGlobalFileMgr.h
+++ b/DataMgr/FileMgr/CachingGlobalFileMgr.h
@@ -58,6 +58,8 @@ class CachingGlobalFileMgr : public GlobalFileMgr {
 
   void removeTableRelatedDS(const int db_id, const int table_id) override;
 
+  void removeCachedData(const int db_id, const int table_id);
+
  private:
   bool isChunkPrefixCacheable(const ChunkKey& chunk_prefix) const;
 
diff --git a/DataMgr/FileMgr/FileInfo.cpp b/DataMgr/FileMgr/FileInfo.cpp
index 419393c12e..d10dcda9a2 100644
--- a/DataMgr/FileMgr/FileInfo.cpp
+++ b/DataMgr/FileMgr/FileInfo.cpp
@@ -56,14 +56,9 @@ FileInfo::~FileInfo() {
 void FileInfo::initNewFile() {
   // initialize pages and free page list
   // Also zeroes out first four bytes of every header
-
-  int32_t headerSize = 0;
-  int8_t* headerSizePtr = (int8_t*)(&headerSize);
   for (size_t pageId = 0; pageId < numPages; ++pageId) {
-    fileMgr->writeFile(f, pageId * pageSize, sizeof(int32_t), headerSizePtr);
-    freePages.insert(pageId);
+    freePageImmediate(pageId);
   }
-  isDirty = true;
 }
 
 size_t FileInfo::write(const size_t offset, const size_t size, const int8_t* buf) {
@@ -149,7 +144,6 @@ void FileInfo::openExistingFile(std::vector<HeaderInfo>& headerVec) {
       if (!g_read_only && !g_multi_instance) {
         // Read-only mode can find pages like this if the server was previously run in
         // write-mode but is not allowed to free them.
-        // TODO(sy): Confirm that proper locking is held before writing here.
         freePageImmediate(pageNum);
         LOG(WARNING) << "Was not checkpointed: Chunk key: " << show_chunk(chunkKey)
                      << " Page id: " << pageId << " Epoch: " << versionEpoch
@@ -191,17 +185,14 @@ static void sighandler(int sig) {
 #endif
 
 void FileInfo::freePage(int pageId, const bool isRolloff, int32_t epoch) {
-  std::lock_guard<std::mutex> lock(readWriteMutex_);
   int32_t epoch_freed_page[2] = {DELETE_CONTINGENT, epoch};
   if (isRolloff) {
     epoch_freed_page[0] = ROLLOFF_CONTINGENT;
   }
-  fileMgr->writeFile(f,
-                     pageId * pageSize + sizeof(int32_t),
-                     sizeof(epoch_freed_page),
-                     reinterpret_cast<const int8_t*>(epoch_freed_page));
+  write(pageId * pageSize + sizeof(int32_t),
+        sizeof(epoch_freed_page),
+        reinterpret_cast<const int8_t*>(epoch_freed_page));
   fileMgr->free_page(std::make_pair(this, pageId));
-  isDirty = true;
 
 #ifdef ENABLE_CRASH_CORRUPTION_TEST
   signal(SIGUSR2, sighandler);
@@ -252,20 +243,17 @@ int32_t FileInfo::syncToDisk() {
 }
 
 void FileInfo::freePageImmediate(int32_t page_num) {
-  CHECK(!g_multi_instance) << "Attempted unsynchronized write in multi-instance mode";
   int32_t zero{0};
-  fileMgr->writeFile(
-      f, page_num * pageSize, sizeof(int32_t), reinterpret_cast<const int8_t*>(&zero));
+  write(page_num * pageSize, sizeof(int32_t), reinterpret_cast<const int8_t*>(&zero));
   freePageDeferred(page_num);
 }
 
 // Overwrites delete/rollback contingents by re-writing chunk key to page.
 void FileInfo::recoverPage(const ChunkKey& chunk_key, int32_t page_num) {
   CHECK(!g_multi_instance) << "Attempted unsynchronized write in multi-instance mode";
-  fileMgr->writeFile(f,
-                     page_num * pageSize + sizeof(int32_t),
-                     2 * sizeof(int32_t),
-                     reinterpret_cast<const int8_t*>(chunk_key.data()));
+  write(page_num * pageSize + sizeof(int32_t),
+        2 * sizeof(int32_t),
+        reinterpret_cast<const int8_t*>(chunk_key.data()));
 }
 
 bool is_page_deleted_with_checkpoint(int32_t table_epoch,
diff --git a/DataMgr/FileMgr/FileMgr.cpp b/DataMgr/FileMgr/FileMgr.cpp
index 33940db6aa..db0934df60 100644
--- a/DataMgr/FileMgr/FileMgr.cpp
+++ b/DataMgr/FileMgr/FileMgr.cpp
@@ -1651,7 +1651,6 @@ bool FileMgr::updatePageIfDeleted(FileInfo* file_info,
     if (!g_read_only && !g_multi_instance) {
       // Read-only mode can find pages like this if the server was previously run in
       // write-mode but is not allowed to free them.
-      // TODO(sy): Confirm that proper locking is held before writing here.
       file_info->freePageImmediate(page_num);
     }
     return true;
@@ -1662,7 +1661,6 @@ bool FileMgr::updatePageIfDeleted(FileInfo* file_info,
     if (!g_read_only && !g_multi_instance) {
       // Read-only mode can find pages like this if the server was previously run in
       // write-mode but is not allowed to free them.
-      // TODO(sy): Confirm that proper locking is held before writing here.
       file_info->recoverPage(chunk_key, page_num);
     }
   }
diff --git a/DataMgr/ForeignStorage/CacheEvictionAlgorithms/LRUEvictionAlgorithm.h b/DataMgr/ForeignStorage/CacheEvictionAlgorithms/LRUEvictionAlgorithm.h
index aa60099c3f..e05f8a79de 100644
--- a/DataMgr/ForeignStorage/CacheEvictionAlgorithms/LRUEvictionAlgorithm.h
+++ b/DataMgr/ForeignStorage/CacheEvictionAlgorithms/LRUEvictionAlgorithm.h
@@ -21,7 +21,7 @@
  * This file includes the class specification for the Least Recently Used cache eviction
  * algorithm used by the Foreign Storage Interface (FSI).
  *
- * // TODO(Misiu): A lot of the code here is replicated from StringDictionary/LruCache.hpp
+ * // TODO(Misiu): A lot of the code here is replicated from Shared/LruCache.h
  * with some minor extensions for deletion and changed to use a set.  It should be merged.
  *
  * This algorithm tracks which chunks were the least recently used by relying on the
diff --git a/DataMgr/ForeignStorage/CachingForeignStorageMgr.cpp b/DataMgr/ForeignStorage/CachingForeignStorageMgr.cpp
index a1fc88d6b8..2e4fa6a9c6 100644
--- a/DataMgr/ForeignStorage/CachingForeignStorageMgr.cpp
+++ b/DataMgr/ForeignStorage/CachingForeignStorageMgr.cpp
@@ -253,14 +253,13 @@ void CachingForeignStorageMgr::refreshTableInCache(const ChunkKey& table_key) {
 void CachingForeignStorageMgr::eraseDataWrapper(const ChunkKey& key) {
   CHECK(is_table_key(key));
   std::lock_guard data_wrapper_lock(data_wrapper_mutex_);
-  // May not be created yet
-  if (data_wrapper_map_.find(key) != data_wrapper_map_.end()) {
-    auto [db, tb] = get_table_prefix(key);
-    // Need to erase serialized version on disk if it exists so we don't accidentally
-    // recover it after deleting.
-    boost::filesystem::remove_all(disk_cache_->getSerializedWrapperPath(db, tb));
-    data_wrapper_map_.erase(key);
-  }
+  auto [db, tb] = get_table_prefix(key);
+  // Need to erase serialized version on disk if it exists so we don't accidentally
+  // recover it after deleting.  It is possible for a cached wrapper file to exist without
+  // a wrapper (in multi-instance-mode for instance, so we make sure to remove the file
+  // regardless).
+  boost::filesystem::remove_all(disk_cache_->getSerializedWrapperPath(db, tb));
+  data_wrapper_map_.erase(key);
 }
 
 void CachingForeignStorageMgr::clearTable(const ChunkKey& table_key) {
@@ -403,6 +402,7 @@ bool CachingForeignStorageMgr::createDataWrapperIfNotExists(const ChunkKey& chun
 
 void CachingForeignStorageMgr::removeTableRelatedDS(const int db_id, const int table_id) {
   disk_cache_->clearForTablePrefix({db_id, table_id});
+  eraseDataWrapper({db_id, table_id});
   ForeignStorageMgr::removeTableRelatedDS(db_id, table_id);
 }
 
diff --git a/DataMgr/ForeignStorage/LazyParquetChunkLoader.cpp b/DataMgr/ForeignStorage/LazyParquetChunkLoader.cpp
index dbe5c98442..6d519901c1 100644
--- a/DataMgr/ForeignStorage/LazyParquetChunkLoader.cpp
+++ b/DataMgr/ForeignStorage/LazyParquetChunkLoader.cpp
@@ -1118,7 +1118,7 @@ void set_definition_levels_for_zero_max_definition_level_case(
     if (!parquet_column_descriptor->schema_node()->is_required()) {
       throw std::runtime_error(
           "Unsupported parquet column detected. Column '" +
-          parquet_column_descriptor->name() +
+          parquet_column_descriptor->path()->ToDotString() +
           "' detected to have max definition level of 0 but is optional.");
     }
     def_levels.assign(def_levels.size(), 1);
@@ -1131,7 +1131,8 @@ void validate_max_repetition_and_definition_level(
   bool is_valid_parquet_list = is_valid_parquet_list_column(parquet_column_descriptor);
   if (is_valid_parquet_list && !omnisci_column_descriptor->columnType.is_array()) {
     throw std::runtime_error(
-        "Unsupported mapping detected. Column '" + parquet_column_descriptor->name() +
+        "Unsupported mapping detected. Column '" +
+        parquet_column_descriptor->path()->ToDotString() +
         "' detected to be a parquet list but HeavyDB mapped column '" +
         omnisci_column_descriptor->columnName + "' is not an array.");
   }
@@ -1140,7 +1141,7 @@ void validate_max_repetition_and_definition_level(
         parquet_column_descriptor->max_definition_level() != 3) {
       throw std::runtime_error(
           "Incorrect schema max repetition level detected in column '" +
-          parquet_column_descriptor->name() +
+          parquet_column_descriptor->path()->ToDotString() +
           "'. Expected a max repetition level of 1 and max definition level of 3 for "
           "list column but column has a max "
           "repetition level of " +
@@ -1154,7 +1155,7 @@ void validate_max_repetition_and_definition_level(
           parquet_column_descriptor->max_definition_level() == 0)) {
       throw std::runtime_error(
           "Incorrect schema max repetition level detected in column '" +
-          parquet_column_descriptor->name() +
+          parquet_column_descriptor->path()->ToDotString() +
           "'. Expected a max repetition level of 0 and max definition level of 1 or 0 "
           "for "
           "flat column but column has a max "
@@ -1283,7 +1284,7 @@ SQLTypeInfo suggest_integral_mapping(const parquet::ColumnDescriptor* parquet_co
     if (!int_logical_column->is_signed()) {
       if (within_range(33, 64, bit_width)) {
         throw ForeignStorageException(
-            "Unsigned integer column \"" + parquet_column->name() +
+            "Unsigned integer column \"" + parquet_column->path()->ToDotString() +
             "\" in Parquet file with 64 bit-width has no supported type for ingestion "
             "that will not result in data loss");
       } else if (within_range(17, 32, bit_width)) {
@@ -1486,17 +1487,6 @@ SQLTypeInfo suggest_string_mapping(const parquet::ColumnDescriptor* parquet_colu
   return type;
 }
 
-bool validate_array_mapping(const ColumnDescriptor* omnisci_column,
-                            const parquet::ColumnDescriptor* parquet_column) {
-  if (is_valid_parquet_list_column(parquet_column) &&
-      omnisci_column->columnType.is_array()) {
-    auto omnisci_column_sub_type_column = get_sub_type_column_descriptor(omnisci_column);
-    return LazyParquetChunkLoader::isColumnMappingSupported(
-        omnisci_column_sub_type_column.get(), parquet_column);
-  }
-  return false;
-}
-
 bool validate_geospatial_mapping(const ColumnDescriptor* omnisci_column,
                                  const parquet::ColumnDescriptor* parquet_column) {
   return is_valid_parquet_string(parquet_column) &&
@@ -1531,11 +1521,21 @@ void validate_equal_schema(const parquet::arrow::FileReader* reference_file_read
 
 void validate_allowed_mapping(const parquet::ColumnDescriptor* parquet_column,
                               const ColumnDescriptor* omnisci_column) {
-  parquet::Type::type physical_type = parquet_column->physical_type();
-  auto logical_type = parquet_column->logical_type();
-  bool allowed_type =
-      LazyParquetChunkLoader::isColumnMappingSupported(omnisci_column, parquet_column);
+  validate_max_repetition_and_definition_level(omnisci_column, parquet_column);
+  bool allowed_type = false;
+  if (omnisci_column->columnType.is_array()) {
+    if (is_valid_parquet_list_column(parquet_column)) {
+      auto omnisci_column_sub_type_column =
+          get_sub_type_column_descriptor(omnisci_column);
+      allowed_type = LazyParquetChunkLoader::isColumnMappingSupported(
+          omnisci_column_sub_type_column.get(), parquet_column);
+    }
+  } else {
+    allowed_type =
+        LazyParquetChunkLoader::isColumnMappingSupported(omnisci_column, parquet_column);
+  }
   if (!allowed_type) {
+    auto logical_type = parquet_column->logical_type();
     if (logical_type->is_timestamp()) {
       auto timestamp_type =
           dynamic_cast<const parquet::TimestampLogicalType*>(logical_type.get());
@@ -1548,6 +1548,7 @@ void validate_allowed_mapping(const parquet::ColumnDescriptor* parquet_column,
       }
     }
     std::string parquet_type;
+    parquet::Type::type physical_type = parquet_column->physical_type();
     if (parquet_column->logical_type()->is_none()) {
       parquet_type = parquet::TypeToString(physical_type);
     } else {
@@ -1659,7 +1660,7 @@ MaxRowGroupSizeStats validate_column_mapping_and_row_group_metadata(
       validate_allowed_mapping(descr, *column_it);
     } catch (std::runtime_error& e) {
       std::stringstream error_message;
-      error_message << e.what() << " Parquet column: " << descr->name()
+      error_message << e.what() << " Parquet column: " << descr->path()->ToDotString()
                     << ", HeavyDB column: " << (*column_it)->columnName
                     << ", Parquet file: " << file_path << ".";
       throw std::runtime_error(error_message.str());
@@ -1988,12 +1989,11 @@ SQLTypeInfo LazyParquetChunkLoader::suggestColumnMapping(
 bool LazyParquetChunkLoader::isColumnMappingSupported(
     const ColumnDescriptor* omnisci_column,
     const parquet::ColumnDescriptor* parquet_column) {
+  CHECK(!omnisci_column->columnType.is_array())
+      << "isColumnMappingSupported should not be called on arrays";
   if (validate_geospatial_mapping(omnisci_column, parquet_column)) {
     return true;
   }
-  if (validate_array_mapping(omnisci_column, parquet_column)) {
-    return true;
-  }
   if (validate_decimal_mapping(omnisci_column, parquet_column)) {
     return true;
   }
@@ -2167,7 +2167,8 @@ std::pair<size_t, size_t> LazyParquetChunkLoader::loadRowGroups(
       validate_allowed_mapping(parquet_column, column_descriptor);
     } catch (std::runtime_error& e) {
       std::stringstream error_message;
-      error_message << e.what() << " Parquet column: " << parquet_column->name()
+      error_message << e.what()
+                    << " Parquet column: " << parquet_column->path()->ToDotString()
                     << ", HeavyDB column: " << column_descriptor->columnName
                     << ", Parquet file: " << file_path << ".";
       throw std::runtime_error(error_message.str());
diff --git a/DataMgr/PersistentStorageMgr/PersistentStorageMgr.cpp b/DataMgr/PersistentStorageMgr/PersistentStorageMgr.cpp
index 6a29e57d60..e9a4fa1a2d 100644
--- a/DataMgr/PersistentStorageMgr/PersistentStorageMgr.cpp
+++ b/DataMgr/PersistentStorageMgr/PersistentStorageMgr.cpp
@@ -155,6 +155,17 @@ void PersistentStorageMgr::removeTableRelatedDS(const int db_id, const int table
   getStorageMgrForTableKey({db_id, table_id})->removeTableRelatedDS(db_id, table_id);
 }
 
+void PersistentStorageMgr::removeMutableTableCacheData(const int db_id,
+                                                       const int tb_id) const {
+  if (disk_cache_config_.isEnabledForMutableTables()) {
+    // If the disk cache supports mutable tables then clear the cached data.
+    if (auto cfm =
+            dynamic_cast<File_Namespace::CachingGlobalFileMgr*>(global_file_mgr_.get())) {
+      cfm->removeCachedData(db_id, tb_id);
+    }
+  }
+}
+
 bool PersistentStorageMgr::isForeignStorage(const ChunkKey& chunk_key) const {
   CHECK(has_table_prefix(chunk_key));
   auto db_id = chunk_key[CHUNK_KEY_DB_IDX];
diff --git a/DataMgr/PersistentStorageMgr/PersistentStorageMgr.h b/DataMgr/PersistentStorageMgr/PersistentStorageMgr.h
index 3629024300..3339bb4580 100644
--- a/DataMgr/PersistentStorageMgr/PersistentStorageMgr.h
+++ b/DataMgr/PersistentStorageMgr/PersistentStorageMgr.h
@@ -58,6 +58,7 @@ class PersistentStorageMgr : public AbstractBufferMgr {
   std::string getStringMgrType() override;
   size_t getNumChunks() override;
   void removeTableRelatedDS(const int db_id, const int table_id) override;
+  void removeMutableTableCacheData(const int db_id, const int table_id) const;
 
   File_Namespace::GlobalFileMgr* getGlobalFileMgr() const;
   foreign_storage::ForeignStorageMgr* getForeignStorageMgr() const;
diff --git a/HeavyDB.cpp b/HeavyDB.cpp
index 1d25bbc341..89c00d8a92 100644
--- a/HeavyDB.cpp
+++ b/HeavyDB.cpp
@@ -477,6 +477,7 @@ int startHeavyDBServer(CommandLineOptions& prog_config_opts,
                                       prog_config_opts.max_concurrent_render_sessions,
                                       prog_config_opts.reserved_gpu_mem,
                                       prog_config_opts.render_compositor_use_last_gpu,
+                                      prog_config_opts.renderer_enable_slab_allocation,
                                       prog_config_opts.num_reader_threads,
                                       prog_config_opts.authMetadata,
                                       prog_config_opts.system_parameters,
diff --git a/ImportExport/Importer.cpp b/ImportExport/Importer.cpp
index fb12f37678..d3fdf8f973 100644
--- a/ImportExport/Importer.cpp
+++ b/ImportExport/Importer.cpp
@@ -3989,10 +3989,10 @@ void Importer::import_local_parquet(const std::string& file_path,
       if (total_file_offset) {
         import_status_.rows_estimated =
             (float)total_file_size / total_file_offset * import_status_.rows_completed;
-        VLOG(3) << "rows_completed " << import_status_.rows_completed
-                << ", rows_estimated " << import_status_.rows_estimated
-                << ", total_file_size " << total_file_size << ", total_file_offset "
-                << total_file_offset;
+        LOG(INFO) << "rows_completed " << import_status_.rows_completed
+                  << ", rows_estimated " << import_status_.rows_estimated
+                  << ", total_file_size " << total_file_size << ", total_file_offset "
+                  << total_file_offset;
       }
     }
   });
@@ -4500,10 +4500,11 @@ ImportStatus Importer::importDelimited(
                                 : (float)file_size / current_pos) *
                   import_status_.rows_completed;
             }
-            VLOG(3) << "rows_completed " << import_status_.rows_completed
-                    << ", rows_estimated " << import_status_.rows_estimated
-                    << ", total_file_size " << total_file_size << ", total_file_offset "
-                    << total_file_offset;
+
+            LOG(INFO) << "rows_completed " << import_status_.rows_completed
+                      << ", rows_estimated " << import_status_.rows_estimated
+                      << ", total_file_size " << total_file_size << ", total_file_offset "
+                      << total_file_offset;
             set_import_status(import_id, import_status_);
             // recall thread_id for reuse
             stack_thread_ids.push(ret_import_status.thread_id);
diff --git a/Parser/ParserNode.cpp b/Parser/ParserNode.cpp
index 67b26b1dce..0865ee492a 100644
--- a/Parser/ParserNode.cpp
+++ b/Parser/ParserNode.cpp
@@ -3874,7 +3874,7 @@ std::shared_ptr<ResultSet> getResultSet(QueryStateProxy query_state_proxy,
   // TODO MAT this should actually get the global or the session parameter for
   // view optimization
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
       false,
       g_enable_watchdog,
@@ -3908,6 +3908,7 @@ std::shared_ptr<ResultSet> getResultSet(QueryStateProxy query_state_proxy,
                          g_running_query_interrupt_freq,
                          g_pending_query_interrupt_freq,
                          g_optimize_cuda_block_and_grid_sizes,
+                         false,
                          std::numeric_limits<size_t>::max(),
                          ExecutorType::Native,
                          outer_fragment_indices};
@@ -3941,7 +3942,7 @@ size_t LocalQueryConnector::getOuterFragmentCount(QueryStateProxy query_state_pr
   // TODO MAT this should actually get the global or the session parameter for
   // view optimization
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
       false,
       g_enable_watchdog,
@@ -4601,7 +4602,7 @@ lockmgr::LockedTableDescriptors acquire_query_table_locks(
   auto& sys_catalog = SysCatalog::instance();
   auto& calcite_mgr = sys_catalog.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr.getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr.getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption = calcite_mgr.getCalciteOptimizationOption(
       false, g_enable_watchdog, {}, sys_catalog.isAggregator());
   const auto result = calcite_mgr.process(query_state_proxy,
@@ -5302,7 +5303,7 @@ void RenameTableStmt::execute(const Catalog_Namespace::SessionInfo& session,
   }
   checkNameSubstition(tableSubtituteMap);
 
-  catalog.renameTable(names);
+  catalog.renameTables(names);
 
   // just to be explicit, clean out the list, the unique_ptr will delete
   while (!tablesToRename_.empty()) {
@@ -6744,7 +6745,7 @@ void CreateViewStmt::execute(const Catalog_Namespace::SessionInfo& session,
 
   // this now also ensures that access permissions are checked
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
       false, g_enable_watchdog, {}, SysCatalog::instance().isAggregator());
   calcite_mgr->process(query_state->createQueryStateProxy(),
@@ -7254,7 +7255,7 @@ std::unique_ptr<Parser::Stmt> create_stmt_for_query(
   const auto& cat = session_info.getCatalog();
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
       false,
       g_enable_watchdog,
diff --git a/Parser/ParserWrapper.cpp b/Parser/ParserWrapper.cpp
index 6d3ef47dcb..92ffc129ce 100644
--- a/Parser/ParserWrapper.cpp
+++ b/Parser/ParserWrapper.cpp
@@ -31,6 +31,7 @@ namespace {  // anonymous namespace
 
 const std::string explain_str = {"explain"};
 const std::string calcite_explain_str = {"explain calcite"};
+const std::string calcite_explain_detailed_str = {"explain calcite detailed"};
 const std::string optimized_explain_str = {"explain optimized"};
 const std::string plan_explain_str = {"explain plan"};
 const std::string plan_explain_detailed_str = {"explain plan detailed"};
@@ -44,8 +45,14 @@ ExplainInfo::ExplainInfo(std::string query_string) {
 
   if (boost::istarts_with(query_string, explain_str)) {
     if (boost::istarts_with(query_string, calcite_explain_str)) {
-      actual_query_ = boost::trim_copy(query_string.substr(calcite_explain_str.size()));
-      explain_type_ = ExplainType::Calcite;
+      if (boost::istarts_with(query_string, calcite_explain_detailed_str)) {
+        explain_type_ = ExplainType::CalciteDetail;
+        actual_query_ =
+            boost::trim_copy(query_string.substr(calcite_explain_detailed_str.size()));
+      } else {
+        actual_query_ = boost::trim_copy(query_string.substr(calcite_explain_str.size()));
+        explain_type_ = ExplainType::Calcite;
+      }
     } else if (boost::istarts_with(query_string, optimized_explain_str)) {
       actual_query_ = boost::trim_copy(query_string.substr(optimized_explain_str.size()));
       explain_type_ = ExplainType::OptimizedIR;
diff --git a/Parser/ParserWrapper.h b/Parser/ParserWrapper.h
index 73d61406cc..4e241f23d3 100644
--- a/Parser/ParserWrapper.h
+++ b/Parser/ParserWrapper.h
@@ -33,7 +33,15 @@
 
 class ExplainInfo {
  public:
-  enum class ExplainType { None, IR, OptimizedIR, Calcite, ExecutionPlan, Other };
+  enum class ExplainType {
+    None,
+    IR,
+    OptimizedIR,
+    Calcite,
+    CalciteDetail,
+    ExecutionPlan,
+    Other
+  };
 
   ExplainInfo() : explain_type_(ExplainType::None), actual_query_("") {}
   ExplainInfo(ExplainType type) : explain_type_(type), actual_query_("") {}
@@ -51,6 +59,7 @@ class ExplainInfo {
     return explain_type_ == ExplainType::IR ||
            explain_type_ == ExplainType::OptimizedIR ||
            explain_type_ == ExplainType::Calcite ||
+           explain_type_ == ExplainType::CalciteDetail ||
            explain_type_ == ExplainType::ExecutionPlan;
   }
 
@@ -59,7 +68,13 @@ class ExplainInfo {
   }
 
   bool isOptimizedExplain() const { return explain_type_ == ExplainType::OptimizedIR; }
-  bool isCalciteExplain() const { return explain_type_ == ExplainType::Calcite; }
+  bool isCalciteExplain() const {
+    return explain_type_ == ExplainType::Calcite ||
+           explain_type_ == ExplainType::CalciteDetail;
+  }
+  bool isCalciteExplainDetail() const {
+    return explain_type_ == ExplainType::CalciteDetail;
+  }
   bool isPlanExplain() const { return explain_type_ == ExplainType::ExecutionPlan; }
   bool isOtherExplain() const { return explain_type_ == ExplainType::Other; }
 
diff --git a/Parser/ReservedKeywords.h b/Parser/ReservedKeywords.h
index 2b40bdb22e..c2dd0eaf37 100644
--- a/Parser/ReservedKeywords.h
+++ b/Parser/ReservedKeywords.h
@@ -228,6 +228,7 @@ static std::set<std::string> reserved_keywords{
     "INTERVAL",
     "INTO",
     "IS",
+    "JAROWINKLER_SIMILARITY"
     "JOIN",
     "LAG",
     "LANGUAGE",
@@ -237,6 +238,7 @@ static std::set<std::string> reserved_keywords{
     "LEAD",
     "LEADING",
     "LEFT",
+    "LEVENSHTEIN_DISTANCE"
     "LIKE",
     "LIKE_REGEX",
     "LIMIT",
diff --git a/QueryEngine/CalciteAdapter.cpp b/QueryEngine/CalciteAdapter.cpp
index c645309973..9e30d75ffb 100644
--- a/QueryEngine/CalciteAdapter.cpp
+++ b/QueryEngine/CalciteAdapter.cpp
@@ -57,14 +57,14 @@ std::string pg_shim_impl(const std::string& query) {
   }
   {
     static const auto& ilike_expr = *new boost::regex(
-        R"((\s+|\()((?!\()[^\s]+)\s+ilike\s+('(?:[^']+|'')+')(\s+escape(\s+('[^']+')))?)",
+        R"((\s+|\()((?!\()[^\s]+)\s+(not\s)?\s*ilike\s+('(?:[^']+|'')+')(\s+escape(\s+('[^']+')))?)",
         boost::regex::perl | boost::regex::icase);
     static_assert(std::is_trivially_destructible_v<decltype(ilike_expr)>);
     apply_shim(result, ilike_expr, [](std::string& result, const boost::smatch& what) {
       std::string esc = what[6];
       result.replace(what.position(),
                      what.length(),
-                     what[1] + "PG_ILIKE(" + what[2] + ", " + what[3] +
+                     what[1] + what[3] + "PG_ILIKE(" + what[2] + ", " + what[4] +
                          (esc.empty() ? "" : ", " + esc) + ")");
     });
   }
diff --git a/QueryEngine/CalciteDeserializerUtils.cpp b/QueryEngine/CalciteDeserializerUtils.cpp
index 551e0f298c..11e9d94ac5 100644
--- a/QueryEngine/CalciteDeserializerUtils.cpp
+++ b/QueryEngine/CalciteDeserializerUtils.cpp
@@ -26,6 +26,7 @@ extern bool g_bigint_count;
 SQLTypeInfo get_agg_type(const SQLAgg agg_kind, const Analyzer::Expr* arg_expr) {
   switch (agg_kind) {
     case kCOUNT:
+      return SQLTypeInfo(g_bigint_count ? kBIGINT : kINT, true);
     case kCOUNT_IF:
       return SQLTypeInfo(g_bigint_count ? kBIGINT : kINT, false);
     case kMIN:
@@ -38,7 +39,7 @@ SQLTypeInfo get_agg_type(const SQLAgg agg_kind, const Analyzer::Expr* arg_expr)
     case kAVG:
       return SQLTypeInfo(kDOUBLE, false);
     case kAPPROX_COUNT_DISTINCT:
-      return SQLTypeInfo(kBIGINT, false);
+      return SQLTypeInfo(kBIGINT, true);
     case kAPPROX_QUANTILE:
       return SQLTypeInfo(kDOUBLE, false);
     case kSINGLE_VALUE:
diff --git a/QueryEngine/CardinalityEstimator.cpp b/QueryEngine/CardinalityEstimator.cpp
index d5e2b9e08b..75eb3b4bf8 100644
--- a/QueryEngine/CardinalityEstimator.cpp
+++ b/QueryEngine/CardinalityEstimator.cpp
@@ -134,7 +134,8 @@ RelAlgExecutionUnit RelAlgExecutionUnit::createCountAllExecutionUnit(
           false,
           union_all,
           query_state,
-          {replacement_target}};
+          {replacement_target},
+          /*per_device_cardinality=*/{}};
 }
 
 ResultSetPtr reduce_estimator_results(
diff --git a/QueryEngine/CastIR.cpp b/QueryEngine/CastIR.cpp
index ba91d7d8de..e5a77b2335 100644
--- a/QueryEngine/CastIR.cpp
+++ b/QueryEngine/CastIR.cpp
@@ -570,13 +570,13 @@ llvm::Value* CodeGenerator::codegenCastToFp(llvm::Value* operand_lv,
                              : llvm::Type::getDoubleTy(cgen_state_->context_);
     result_lv = cgen_state_->ir_builder_.CreateSIToFP(operand_lv, fp_type);
     if (auto const scale = static_cast<unsigned>(operand_ti.get_scale())) {
-      double const multiplier = shared::power10inv(scale);
-      result_lv = cgen_state_->ir_builder_.CreateFMul(
-          result_lv, llvm::ConstantFP::get(result_lv->getType(), multiplier));
+      double const divider = shared::power10(scale);
+      result_lv = cgen_state_->ir_builder_.CreateFDiv(
+          result_lv, llvm::ConstantFP::get(result_lv->getType(), divider));
     }
   } else {
     if (auto const scale = static_cast<unsigned>(operand_ti.get_scale())) {
-      double const multiplier = shared::power10inv(scale);
+      double const divider = shared::power10(scale);
       auto const fp_type = ti.get_type() == kFLOAT
                                ? llvm::Type::getFloatTy(cgen_state_->context_)
                                : llvm::Type::getDoubleTy(cgen_state_->context_);
@@ -585,7 +585,7 @@ llvm::Value* CodeGenerator::codegenCastToFp(llvm::Value* operand_lv,
                                         {operand_lv,
                                          cgen_state_->inlineIntNull(operand_ti),
                                          cgen_state_->inlineFpNull(ti),
-                                         llvm::ConstantFP::get(fp_type, multiplier)});
+                                         llvm::ConstantFP::get(fp_type, divider)});
     } else {
       result_lv = cgen_state_->emitCall("cast_" + numeric_type_name(operand_ti) + "_to_" +
                                             numeric_type_name(ti) + "_nullable",
diff --git a/QueryEngine/CodeCache.h b/QueryEngine/CodeCache.h
index 273241fabe..97c91e8ada 100644
--- a/QueryEngine/CodeCache.h
+++ b/QueryEngine/CodeCache.h
@@ -19,7 +19,7 @@
 #include <boost/functional/hash.hpp>
 #include <memory>
 
-#include "StringDictionary/LruCache.hpp"
+#include "Shared/LruCache.h"
 
 using CodeCacheKey = std::vector<std::string>;
 template <typename CC>
diff --git a/QueryEngine/CodeCacheAccessor.cpp b/QueryEngine/CodeCacheAccessor.cpp
index 591f46be71..a6637897b9 100644
--- a/QueryEngine/CodeCacheAccessor.cpp
+++ b/QueryEngine/CodeCacheAccessor.cpp
@@ -25,14 +25,14 @@ CodeCacheVal<CompilationContext> CodeCacheAccessor<CompilationContext>::get_valu
   auto it = code_cache_.find(key);
   if (it != code_cache_.cend()) {
     found_count_++;
-    VLOG(1) << "Reuse cached compiled kernel";
+    VLOG(1) << name_ << ": Reuse cached compiled kernel";
     return it->second;
   }
   return {};
 }
 
 template <typename CompilationContext>
-void CodeCacheAccessor<CompilationContext>::put(const CodeCacheKey& key,
+bool CodeCacheAccessor<CompilationContext>::put(const CodeCacheKey& key,
                                                 CodeCacheVal<CompilationContext>& value) {
   bool warn = false;
   {
@@ -41,8 +41,8 @@ void CodeCacheAccessor<CompilationContext>::put(const CodeCacheKey& key,
     auto it = code_cache_.find(key);
     put_count_++;
     if (it == code_cache_.cend()) {
-      VLOG(1) << "Add compiled kernel to code cache";
-      code_cache_.put(key, value);
+      VLOG(1) << name_ << ": Add compiled kernel to code cache";
+      evict_count_ += code_cache_.put(key, value);
     } else {
       ignore_count_++;
       warn = true;
@@ -50,7 +50,9 @@ void CodeCacheAccessor<CompilationContext>::put(const CodeCacheKey& key,
   }
   if (warn) {
     LOG(WARNING) << *this << ": code already in cache, ignoring.\n";
+    return false;
   }
+  return true;
 }
 
 template <typename CompilationContext>
@@ -74,7 +76,7 @@ CodeCacheVal<CompilationContext>* CodeCacheAccessor<CompilationContext>::get_or_
       CHECK(cached_code->get());
     }
     found_count_++;
-    VLOG(1) << "Reuse a cached compiled code";
+    VLOG(1) << name_ << ": Reuse a cached compiled code";
     return cached_code;
   }
   // This is the first time the key is used to acquire code from
@@ -82,7 +84,7 @@ CodeCacheVal<CompilationContext>* CodeCacheAccessor<CompilationContext>::get_or_
   // the same key will wait (see above) until the code is put to the
   // cache:
   CodeCacheVal<CompilationContext> not_a_code(nullptr);
-  code_cache_.put(key, std::move(not_a_code));
+  evict_count_ += code_cache_.put(key, std::move(not_a_code));
   // returning nullptr will notify caller to trigger code compilation
   // for the given key:
   return nullptr;
diff --git a/QueryEngine/CodeCacheAccessor.h b/QueryEngine/CodeCacheAccessor.h
index 5e62216124..aacc8394b6 100644
--- a/QueryEngine/CodeCacheAccessor.h
+++ b/QueryEngine/CodeCacheAccessor.h
@@ -18,25 +18,50 @@
 #define QUERYENGINE_CODECACHEACCESSOR_HPP
 
 #include <mutex>
+#include <sstream>
 #include <string>
+
 #include "QueryEngine/CodeCache.h"
 
+extern bool g_is_test_env;
+
+struct CodeCacheMetric {
+  int64_t get_count{0};
+  int64_t found_count{0};
+  int64_t put_count{0};
+  int64_t ignore_count{0};
+  int64_t overwrite_count{0};
+  int64_t evict_count{0};
+};
+
 template <typename CompilationContext>
 class CodeCacheAccessor {
  public:
-  CodeCacheAccessor(size_t cache_size, std::string name = "")
-      : code_cache_(cache_size)
+  CodeCacheAccessor(EvictionMetricType eviction_metric_type,
+                    size_t max_cache_size,
+                    std::string name)
+      : code_cache_(eviction_metric_type, max_cache_size)
+      , eviction_metric_type_(eviction_metric_type)
       , get_count_(0)
       , found_count_(0)
       , put_count_(0)
       , ignore_count_(0)
       , overwrite_count_(0)
       , evict_count_(0)
-      , name_(std::move(name)) {}
+      , name_(std::move(name)) {
+    std::ostringstream oss;
+    std::string eviction_type = eviction_metric_type_ == EvictionMetricType::EntryCount
+                                    ? "EntryCount"
+                                    : "ByteSize";
+    oss << "Initialize a code cache (name: " << name_
+        << ", eviction_metric_type: " << eviction_type
+        << ", max_cache_size: " << max_cache_size << ")";
+    LOG(INFO) << oss.str();
+  }
 
   // TODO: replace get_value/put with get_or_wait/reset workflow.
   CodeCacheVal<CompilationContext> get_value(const CodeCacheKey& key);
-  void put(const CodeCacheKey& key, CodeCacheVal<CompilationContext>& value);
+  bool put(const CodeCacheKey& key, CodeCacheVal<CompilationContext>& value);
 
   // get_or_wait and reset/erase should be used in pair.
   CodeCacheVal<CompilationContext>* get_or_wait(const CodeCacheKey& key);
@@ -56,19 +81,6 @@ class CodeCacheAccessor {
     code_cache_.evictNEntries(n);
   }
 
-  size_t getSumSizeEvicted(const size_t n) {
-    std::lock_guard<std::mutex> lock(code_cache_mutex_);
-    auto last = code_cache_.cend();
-    size_t visited = 0;
-    size_t ret = 0;
-    while (visited < n && last != code_cache_.cbegin()) {
-      last--;
-      ret += last->second->getMemSize();
-      visited++;
-    }
-    return ret;
-  }
-
   friend std::ostream& operator<<(std::ostream& os, CodeCacheAccessor& c) {
     std::lock_guard<std::mutex> lock(c.code_cache_mutex_);
     os << "CodeCacheAccessor<" << c.name_ << ">[current size=" << c.code_cache_.size()
@@ -78,8 +90,30 @@ class CodeCacheAccessor {
     return os;
   }
 
+  size_t getCacheSize() {
+    std::lock_guard<std::mutex> lock(code_cache_mutex_);
+    return code_cache_.size();
+  }
+
+  void resetCache(size_t new_max_size) {
+    CHECK(g_is_test_env) << "Call the resetCache function from non-test env.";
+    std::lock_guard<std::mutex> lock(code_cache_mutex_);
+    code_cache_ = CodeCache<CompilationContext>(eviction_metric_type_, new_max_size);
+  }
+
+  CodeCacheMetric getCodeCacheMetric() {
+    std::lock_guard<std::mutex> lock(code_cache_mutex_);
+    return {get_count_,
+            found_count_,
+            put_count_,
+            ignore_count_,
+            overwrite_count_,
+            evict_count_};
+  }
+
  private:
   CodeCache<CompilationContext> code_cache_;
+  EvictionMetricType const eviction_metric_type_;
   // cumulative statistics of code cache usage
   int64_t get_count_, found_count_, put_count_, ignore_count_, overwrite_count_,
       evict_count_;
diff --git a/QueryEngine/ColumnIR.cpp b/QueryEngine/ColumnIR.cpp
index 8ed761279d..2da9e1f9fd 100644
--- a/QueryEngine/ColumnIR.cpp
+++ b/QueryEngine/ColumnIR.cpp
@@ -127,6 +127,7 @@ std::vector<llvm::Value*> CodeGenerator::codegenColVar(const Analyzer::ColumnVar
       std::vector<llvm::Value*> cols;
       const auto col_id = column_key.column_id;
       auto temp_column_key = column_key;
+      bool fetch_physical_columns = fetch_column;
       for (auto i = 0; i < col_ti.get_physical_coord_cols(); i++) {
         temp_column_key.column_id = col_id + i + 1;
         const auto cd0 = get_column_descriptor(temp_column_key);
@@ -135,13 +136,17 @@ std::vector<llvm::Value*> CodeGenerator::codegenColVar(const Analyzer::ColumnVar
         CHECK(!cd0->isVirtualCol);
         const auto col0_var =
             makeExpr<Analyzer::ColumnVar>(col0_ti, temp_column_key, rte_idx);
-        const auto col = codegenColVar(col0_var.get(), fetch_column, false, co);
+        if (plan_state_->isColumnToFetch(temp_column_key)) {
+          // sync the fetch status for all physical columns
+          fetch_physical_columns = true;
+        }
+        const auto col = codegenColVar(col0_var.get(), fetch_physical_columns, false, co);
         cols.insert(cols.end(), col.begin(), col.end());
-        if (!fetch_column && plan_state_->isLazyFetchColumn(col_var)) {
+        if (!fetch_physical_columns && plan_state_->isLazyFetchColumn(col_var)) {
           plan_state_->addColumnToNotFetch(temp_column_key);
         }
       }
-      if (!fetch_column && plan_state_->isLazyFetchColumn(col_var)) {
+      if (!fetch_physical_columns && plan_state_->isLazyFetchColumn(col_var)) {
         plan_state_->addColumnToNotFetch(column_key);
       } else {
         plan_state_->addColumnToFetch(column_key);
diff --git a/QueryEngine/CompilationContext.h b/QueryEngine/CompilationContext.h
index acd5464540..c6f5bcb113 100644
--- a/QueryEngine/CompilationContext.h
+++ b/QueryEngine/CompilationContext.h
@@ -26,6 +26,7 @@ class CompilationContext {
  public:
   virtual ~CompilationContext() {}
   virtual size_t getMemSize() const = 0;
+  size_t size() const { return getMemSize(); }
 };
 
 struct CompilationOptions;
diff --git a/QueryEngine/CompilationOptions.h b/QueryEngine/CompilationOptions.h
index ef31df6f3a..8b30d2bdfa 100644
--- a/QueryEngine/CompilationOptions.h
+++ b/QueryEngine/CompilationOptions.h
@@ -81,6 +81,7 @@ struct ExecutionOptions {
   double running_query_interrupt_freq;
   unsigned pending_query_interrupt_freq;
   bool optimize_cuda_block_and_grid_sizes;
+  bool estimate_output_cardinality{false};
   size_t max_join_hash_table_size = std::numeric_limits<size_t>::max();
   ExecutorType executor_type = ExecutorType::Native;
   std::vector<size_t> outer_fragment_indices{};
diff --git a/QueryEngine/Descriptors/CountDistinctDescriptor.h b/QueryEngine/Descriptors/CountDistinctDescriptor.h
index 1b2080a711..8da5b8b064 100644
--- a/QueryEngine/Descriptors/CountDistinctDescriptor.h
+++ b/QueryEngine/Descriptors/CountDistinctDescriptor.h
@@ -41,6 +41,7 @@ enum class CountDistinctImplType { Invalid, Bitmap, UnorderedSet };
 struct CountDistinctDescriptor {
   CountDistinctImplType impl_type_;
   int64_t min_val;
+  int64_t bucket_size;
   // When used in the approximate count distinct algorithm, bitmap_sz_bits has a
   // different meaning than the bitmap size: https://en.wikipedia.org/wiki/HyperLogLog
   int64_t bitmap_sz_bits;
diff --git a/QueryEngine/Descriptors/QueryMemoryDescriptor.cpp b/QueryEngine/Descriptors/QueryMemoryDescriptor.cpp
index 596bbaa6b5..b58dede072 100644
--- a/QueryEngine/Descriptors/QueryMemoryDescriptor.cpp
+++ b/QueryEngine/Descriptors/QueryMemoryDescriptor.cpp
@@ -489,7 +489,8 @@ QueryMemoryDescriptor::QueryMemoryDescriptor(
     , use_streaming_top_n_(use_streaming_top_n)
     , threads_can_reuse_group_by_buffers_(threads_can_reuse_group_by_buffers)
     , force_4byte_float_(false)
-    , col_slot_context_(col_slot_context) {
+    , col_slot_context_(col_slot_context)
+    , num_available_threads_(cpu_threads()) {
   CHECK(!(query_desc_type_ == QueryDescriptionType::TableFunction));
   col_slot_context_.setAllUnsetSlotsPaddedSize(8);
   col_slot_context_.validate();
@@ -587,7 +588,8 @@ QueryMemoryDescriptor::QueryMemoryDescriptor(const Executor* executor,
     , must_use_baseline_sort_(false)
     , use_streaming_top_n_(false)
     , threads_can_reuse_group_by_buffers_(false)
-    , force_4byte_float_(false) {
+    , force_4byte_float_(false)
+    , num_available_threads_(cpu_threads()) {
   if (query_desc_type == QueryDescriptionType::TableFunction) {
     // Table functions output columns are always columnar
     output_columnar_ = true;
@@ -618,7 +620,8 @@ QueryMemoryDescriptor::QueryMemoryDescriptor(const QueryDescriptionType query_de
     , must_use_baseline_sort_(false)
     , use_streaming_top_n_(false)
     , threads_can_reuse_group_by_buffers_(false)
-    , force_4byte_float_(false) {}
+    , force_4byte_float_(false)
+    , num_available_threads_(cpu_threads()) {}
 
 bool QueryMemoryDescriptor::operator==(const QueryMemoryDescriptor& other) const {
   // Note that this method does not check ptr reference members (e.g. executor_) or
@@ -1065,11 +1068,9 @@ size_t QueryMemoryDescriptor::getBufferSizeBytes(const ExecutorDeviceType device
   if (keyless_hash_ && !output_columnar_) {
     CHECK_GE(group_col_widths_.size(), size_t(1));
     auto row_bytes = align_to_int64(getColsSize());
-
     return (interleavedBins(device_type) ? executor_->warpSize() : 1) * entry_count *
            row_bytes;
   }
-
   constexpr size_t row_index_width = sizeof(int64_t);
   size_t total_bytes{0};
   if (output_columnar_) {
@@ -1362,3 +1363,26 @@ size_t QueryMemoryDescriptor::varlenOutputRowSizeToSlot(const size_t slot_idx) c
   }
   return buffer_element_size;
 }
+
+std::optional<size_t> QueryMemoryDescriptor::getMaxPerDeviceCardinality(
+    const RelAlgExecutionUnit& ra_exe_unit) const {
+  auto& pdc = ra_exe_unit.per_device_cardinality;
+  auto by_cardinality = [](auto& a, auto& b) { return a.second < b.second; };
+  auto itr = std::max_element(pdc.begin(), pdc.end(), by_cardinality);
+  if (itr != pdc.end() && itr->second > 0) {
+    return itr->second;
+  }
+  return std::nullopt;
+}
+
+bool QueryMemoryDescriptor::canUsePerDeviceCardinality(
+    const RelAlgExecutionUnit& ra_exe_unit) const {
+  // union-query needs to consider the "SUM" of each subquery's result
+  if (query_desc_type_ != QueryDescriptionType::Projection ||
+      !ra_exe_unit.target_exprs_union.empty()) {
+    return false;
+  }
+  auto is_left_join = [](auto& join_qual) { return join_qual.type == JoinType::LEFT; };
+  auto& join_quals = ra_exe_unit.join_quals;
+  return !std::any_of(join_quals.begin(), join_quals.end(), is_left_join);
+}
diff --git a/QueryEngine/Descriptors/QueryMemoryDescriptor.h b/QueryEngine/Descriptors/QueryMemoryDescriptor.h
index 90104eedd0..b5db1099de 100644
--- a/QueryEngine/Descriptors/QueryMemoryDescriptor.h
+++ b/QueryEngine/Descriptors/QueryMemoryDescriptor.h
@@ -361,6 +361,17 @@ class QueryMemoryDescriptor {
     return col_slot_context_.slotIsVarlen(slot_idx);
   }
 
+  size_t getAvailableCpuThreads() const { return num_available_threads_; }
+
+  void setAvailableCpuThreads(size_t num_available_threads) const {
+    num_available_threads_ = num_available_threads;
+  }
+
+  std::optional<size_t> getMaxPerDeviceCardinality(
+      const RelAlgExecutionUnit& ra_exe_unit) const;
+
+  bool canUsePerDeviceCardinality(const RelAlgExecutionUnit& ra_exe_unit) const;
+
  protected:
   void resetGroupColWidths(const std::vector<int8_t>& new_group_col_widths) {
     group_col_widths_ = new_group_col_widths;
@@ -398,6 +409,12 @@ class QueryMemoryDescriptor {
 
   ColSlotContext col_slot_context_;
 
+  // # available CPU threads can be used for this query kernel, i.e., to parallelize rest
+  // of query initialization step its default value is one which means we do not
+  // parallelize for the query kernel, and it will be updated to a proper value before
+  // performing the query initialization
+  mutable size_t num_available_threads_{1};
+
   size_t getTotalBytesOfColumnarBuffers() const;
   size_t getTotalBytesOfColumnarBuffers(const size_t num_entries_per_column) const;
   size_t getTotalBytesOfColumnarProjections(const size_t projection_count) const;
diff --git a/QueryEngine/Descriptors/RowSetMemoryOwner.h b/QueryEngine/Descriptors/RowSetMemoryOwner.h
index c767587bf1..b8f8fd445f 100644
--- a/QueryEngine/Descriptors/RowSetMemoryOwner.h
+++ b/QueryEngine/Descriptors/RowSetMemoryOwner.h
@@ -49,9 +49,14 @@ class ResultSet;
  */
 class RowSetMemoryOwner final : public SimpleAllocator, boost::noncopyable {
  public:
-  RowSetMemoryOwner(const size_t arena_block_size, const size_t num_kernel_threads = 0)
+  RowSetMemoryOwner(const size_t arena_block_size,
+                    const size_t executor_id,
+                    const size_t num_kernel_threads = 0)
       : non_owned_group_by_buffers_(num_kernel_threads + 1, nullptr)
-      , arena_block_size_(arena_block_size) {
+      , arena_block_size_(arena_block_size)
+      , executor_id_(executor_id) {
+    VLOG(2) << "Prepare " << num_kernel_threads + 1
+            << " allocators from RowSetMemoryOwner attached to Executor-" << executor_id_;
     allocators_.reserve(num_kernel_threads + 1);
     for (size_t i = 0; i < num_kernel_threads + 1; i++) {
       allocators_.emplace_back(std::make_unique<DramArena>(arena_block_size));
@@ -286,6 +291,19 @@ class RowSetMemoryOwner final : public SimpleAllocator, boost::noncopyable {
   }
 
   ~RowSetMemoryOwner() {
+    std::ostringstream oss;
+    oss << "Destruct RowSetMemoryOwner attached to Executor-" << executor_id_ << "{\t";
+    int allocator_id = 0;
+    for (auto const& allocator : allocators_) {
+      auto const usedBytes = allocator->bytesUsed();
+      if (usedBytes > 0) {
+        oss << "allocator-" << allocator_id << ", byteUsed: " << usedBytes << "/"
+            << allocator->totalBytes() << "\t";
+      }
+      ++allocator_id;
+    }
+    oss << "}";
+    VLOG(2) << oss.str();
     for (auto count_distinct_set : count_distinct_sets_) {
       delete count_distinct_set;
     }
@@ -302,7 +320,8 @@ class RowSetMemoryOwner final : public SimpleAllocator, boost::noncopyable {
   }
 
   std::shared_ptr<RowSetMemoryOwner> cloneStrDictDataOnly() {
-    auto rtn = std::make_shared<RowSetMemoryOwner>(arena_block_size_, /*num_kernels=*/1);
+    auto rtn = std::make_shared<RowSetMemoryOwner>(
+        arena_block_size_, executor_id_, /*num_kernels=*/1);
     rtn->str_dict_proxy_owned_ = str_dict_proxy_owned_;
     rtn->lit_str_dict_proxy_ = lit_str_dict_proxy_;
     return rtn;
@@ -384,6 +403,7 @@ class RowSetMemoryOwner final : public SimpleAllocator, boost::noncopyable {
 
   size_t arena_block_size_;  // for cloning
   std::vector<std::unique_ptr<Arena>> allocators_;
+  size_t executor_id_;
 
   mutable std::mutex state_mutex_;
 
diff --git a/QueryEngine/Execute.cpp b/QueryEngine/Execute.cpp
index 4ee46665f4..d1c3712537 100644
--- a/QueryEngine/Execute.cpp
+++ b/QueryEngine/Execute.cpp
@@ -80,6 +80,8 @@
 bool g_enable_watchdog{false};
 bool g_enable_dynamic_watchdog{false};
 size_t g_watchdog_none_encoded_string_translation_limit{1000000UL};
+size_t g_watchdog_max_projected_rows_per_device{128000000};
+size_t g_preflight_count_query_threshold{1000000};
 bool g_enable_cpu_sub_tasks{false};
 size_t g_cpu_sub_task_size{500'000};
 bool g_enable_filter_function{true};
@@ -118,6 +120,7 @@ bool g_restrict_ml_model_metadata_to_superusers{false};
 bool g_enable_dev_table_functions{false};
 bool g_enable_geo_ops_on_uncompressed_coords{true};
 bool g_enable_rf_prop_table_functions{true};
+bool g_allow_memory_status_log{true};
 size_t g_max_memory_allocation_size{2000000000};  // set to max slab size
 size_t g_min_memory_allocation_size{
     256};  // minimum memory allocation required for projection query output buffer
@@ -188,6 +191,7 @@ bool g_executor_resource_mgr_allow_cpu_result_mem_oversubscription_concurrency{f
 double g_executor_resource_mgr_max_available_resource_use_ratio{0.8};
 
 extern bool g_cache_string_hash;
+extern bool g_allow_memory_status_log;
 
 int const Executor::max_gpu_count;
 
@@ -720,6 +724,45 @@ ExpressionRange Executor::getColRange(const PhysicalInput& phys_input) const {
 
 namespace {
 
+void log_system_memory_info_impl(std::string const& mem_log,
+                                 size_t executor_id,
+                                 size_t log_time_ms,
+                                 std::string const& log_tag,
+                                 size_t const thread_idx) {
+  std::ostringstream oss;
+  oss << mem_log;
+  oss << " (" << log_tag << ", EXECUTOR-" << executor_id << ", THREAD-" << thread_idx
+      << ", TOOK: " << log_time_ms << " ms)";
+  VLOG(1) << oss.str();
+}
+}  // namespace
+
+void Executor::logSystemCPUMemoryStatus(std::string const& log_tag,
+                                        size_t const thread_idx) const {
+  if (g_allow_memory_status_log && getDataMgr()) {
+    auto timer = timer_start();
+    std::ostringstream oss;
+    oss << getDataMgr()->getSystemMemoryUsage();
+    log_system_memory_info_impl(
+        oss.str(), executor_id_, timer_stop(timer), log_tag, thread_idx);
+  }
+}
+
+void Executor::logSystemGPUMemoryStatus(std::string const& log_tag,
+                                        size_t const thread_idx) const {
+#ifdef HAVE_CUDA
+  if (g_allow_memory_status_log && getDataMgr() && getDataMgr()->gpusPresent() &&
+      getDataMgr()->getCudaMgr()) {
+    auto timer = timer_start();
+    auto mem_log = getDataMgr()->getCudaMgr()->getCudaMemoryUsageInString();
+    log_system_memory_info_impl(
+        mem_log, executor_id_, timer_stop(timer), log_tag, thread_idx);
+  }
+#endif
+}
+
+namespace {
+
 size_t get_col_byte_width(const shared::ColumnKey& column_key) {
   if (column_key.table_id < 0) {
     // We have an intermediate results table
@@ -1707,34 +1750,50 @@ namespace {
 // Compute a very conservative entry count for the output buffer entry count using no
 // other information than the number of tuples in each table and multiplying them
 // together.
-size_t compute_buffer_entry_guess(const std::vector<InputTableInfo>& query_infos) {
+size_t compute_buffer_entry_guess(const std::vector<InputTableInfo>& query_infos,
+                                  const RelAlgExecutionUnit& ra_exe_unit) {
+  // we can use filtered_count_all's result if available
+  if (ra_exe_unit.scan_limit) {
+    VLOG(1)
+        << "Exploiting a result of filtered count query as output buffer entry count: "
+        << ra_exe_unit.scan_limit;
+    return ra_exe_unit.scan_limit;
+  }
   using Fragmenter_Namespace::FragmentInfo;
-  // Check for overflows since we're multiplying potentially big table sizes.
   using checked_size_t = boost::multiprecision::number<
       boost::multiprecision::cpp_int_backend<64,
                                              64,
                                              boost::multiprecision::unsigned_magnitude,
                                              boost::multiprecision::checked,
                                              void>>;
-  checked_size_t max_groups_buffer_entry_guess = 1;
-  for (const auto& query_info : query_infos) {
-    CHECK(!query_info.info.fragments.empty());
-    auto it = std::max_element(query_info.info.fragments.begin(),
-                               query_info.info.fragments.end(),
-                               [](const FragmentInfo& f1, const FragmentInfo& f2) {
-                                 return f1.getNumTuples() < f2.getNumTuples();
-                               });
-    max_groups_buffer_entry_guess *= it->getNumTuples();
-  }
+  checked_size_t checked_max_groups_buffer_entry_guess = 1;
   // Cap the rough approximation to 100M entries, it's unlikely we can do a great job for
   // baseline group layout with that many entries anyway.
   constexpr size_t max_groups_buffer_entry_guess_cap = 100000000;
+  // Check for overflows since we're multiplying potentially big table sizes.
   try {
-    return std::min(static_cast<size_t>(max_groups_buffer_entry_guess),
-                    max_groups_buffer_entry_guess_cap);
+    for (const auto& table_info : query_infos) {
+      CHECK(!table_info.info.fragments.empty());
+      checked_size_t table_cardinality = 0;
+      std::for_each(table_info.info.fragments.begin(),
+                    table_info.info.fragments.end(),
+                    [&table_cardinality](const FragmentInfo& frag_info) {
+                      table_cardinality += frag_info.getNumTuples();
+                    });
+      checked_max_groups_buffer_entry_guess *= table_cardinality;
+    }
   } catch (...) {
-    return max_groups_buffer_entry_guess_cap;
+    checked_max_groups_buffer_entry_guess = max_groups_buffer_entry_guess_cap;
+    VLOG(1) << "Detect overflow when approximating output buffer entry count, "
+               "resetting it as "
+            << max_groups_buffer_entry_guess_cap;
   }
+  size_t max_groups_buffer_entry_guess =
+      std::min(static_cast<size_t>(checked_max_groups_buffer_entry_guess),
+               max_groups_buffer_entry_guess_cap);
+  VLOG(1) << "Set an approximated output entry count as: "
+          << max_groups_buffer_entry_guess;
+  return max_groups_buffer_entry_guess;
 }
 
 std::string get_table_name(const InputDescriptor& input_desc) {
@@ -1750,12 +1809,14 @@ std::string get_table_name(const InputDescriptor& input_desc) {
   }
 }
 
-inline size_t getDeviceBasedScanLimit(const ExecutorDeviceType device_type,
-                                      const int device_count) {
+inline size_t getDeviceBasedWatchdogScanLimit(
+    size_t watchdog_max_projected_rows_per_device,
+    const ExecutorDeviceType device_type,
+    const int device_count) {
   if (device_type == ExecutorDeviceType::GPU) {
-    return device_count * Executor::high_scan_limit;
+    return device_count * watchdog_max_projected_rows_per_device;
   }
-  return Executor::high_scan_limit;
+  return watchdog_max_projected_rows_per_device;
 }
 
 void checkWorkUnitWatchdog(const RelAlgExecutionUnit& ra_exe_unit,
@@ -1767,8 +1828,18 @@ void checkWorkUnitWatchdog(const RelAlgExecutionUnit& ra_exe_unit,
       return;
     }
   }
+  size_t watchdog_max_projected_rows_per_device =
+      g_watchdog_max_projected_rows_per_device;
+  if (ra_exe_unit.query_hint.isHintRegistered(
+          QueryHint::kWatchdogMaxProjectedRowsPerDevice)) {
+    watchdog_max_projected_rows_per_device =
+        ra_exe_unit.query_hint.watchdog_max_projected_rows_per_device;
+    VLOG(1) << "Set the watchdog per device maximum projection limit: "
+            << watchdog_max_projected_rows_per_device << " by a query hint";
+  }
   if (!ra_exe_unit.scan_limit && table_infos.size() == 1 &&
-      table_infos.front().info.getPhysicalNumTuples() < Executor::high_scan_limit) {
+      table_infos.front().info.getPhysicalNumTuples() <
+          watchdog_max_projected_rows_per_device) {
     // Allow a query with no scan limit to run on small tables
     return;
   }
@@ -1780,7 +1851,9 @@ void checkWorkUnitWatchdog(const RelAlgExecutionUnit& ra_exe_unit,
   if (ra_exe_unit.sort_info.algorithm != SortAlgorithm::StreamingTopN &&
       ra_exe_unit.groupby_exprs.size() == 1 && !ra_exe_unit.groupby_exprs.front() &&
       (!ra_exe_unit.scan_limit ||
-       ra_exe_unit.scan_limit > getDeviceBasedScanLimit(device_type, device_count))) {
+       ra_exe_unit.scan_limit >
+           getDeviceBasedWatchdogScanLimit(
+               watchdog_max_projected_rows_per_device, device_type, device_count))) {
     std::vector<std::string> table_names;
     const auto& input_descs = ra_exe_unit.input_descs;
     for (const auto& input_desc : input_descs) {
@@ -1796,7 +1869,8 @@ void checkWorkUnitWatchdog(const RelAlgExecutionUnit& ra_exe_unit,
           boost::algorithm::join(table_names, ", ") + "  would contain " +
           std::to_string(ra_exe_unit.scan_limit) +
           " rows, which is more than the current system limit of " +
-          std::to_string(getDeviceBasedScanLimit(device_type, device_count)));
+          std::to_string(getDeviceBasedWatchdogScanLimit(
+              watchdog_max_projected_rows_per_device, device_type, device_count)));
     }
   }
 }
@@ -2084,7 +2158,8 @@ ResultSetPtr Executor::executeWorkUnitImpl(
     // of group by slots. Make the conservative choice: allocate fragment size
     // slots and run on the CPU.
     CHECK(device_type == ExecutorDeviceType::CPU);
-    max_groups_buffer_entry_guess = compute_buffer_entry_guess(query_infos);
+    max_groups_buffer_entry_guess =
+        compute_buffer_entry_guess(query_infos, ra_exe_unit_in);
   }
 
   int8_t crt_min_byte_width{MAX_BYTE_WIDTH_SUPPORTED};
@@ -2132,6 +2207,18 @@ ResultSetPtr Executor::executeWorkUnitImpl(
       return executeExplain(*query_comp_desc_owned);
     }
 
+    if (query_mem_desc_owned->canUsePerDeviceCardinality(ra_exe_unit)) {
+      auto const max_rows_per_device =
+          query_mem_desc_owned->getMaxPerDeviceCardinality(ra_exe_unit);
+      if (max_rows_per_device && *max_rows_per_device >= 0 &&
+          *max_rows_per_device < query_mem_desc_owned->getEntryCount()) {
+        VLOG(1) << "Setting the max per device cardinality of {max_rows_per_device} as "
+                   "the new scan limit: "
+                << *max_rows_per_device;
+        throw CompilationRetryNewScanLimit(*max_rows_per_device);
+      }
+    }
+
     if (!eo.just_validate) {
       int available_cpus = cpu_threads();
       auto available_gpus = get_available_gpus(data_mgr_);
@@ -2201,6 +2288,23 @@ ResultSetPtr Executor::executeWorkUnitImpl(
         }
       }
       try {
+        if (eo.estimate_output_cardinality) {
+          for (const auto& result : shared_context.getFragmentResults()) {
+            auto row = result.first->getNextRow(false, false);
+            CHECK_EQ(1u, row.size());
+            auto scalar_r = boost::get<ScalarTargetValue>(&row[0]);
+            CHECK(scalar_r);
+            auto p = boost::get<int64_t>(scalar_r);
+            CHECK(p);
+            // todo(yoonmin): sort the frag_ids to make it consistent for later usage
+            auto frag_ids = result.second;
+            VLOG(1) << "Filtered cardinality for fragments-{" << ::toString(result.second)
+                    << "} : " << static_cast<size_t>(*p);
+            ra_exe_unit_in.per_device_cardinality.emplace_back(result.second,
+                                                               static_cast<size_t>(*p));
+            result.first->moveToBegin();
+          }
+        }
         return collectAllDeviceResults(shared_context,
                                        ra_exe_unit,
                                        *query_mem_desc_owned,
@@ -2442,6 +2546,9 @@ void Executor::addTransientStringLiterals(
 ExecutorDeviceType Executor::getDeviceTypeForTargets(
     const RelAlgExecutionUnit& ra_exe_unit,
     const ExecutorDeviceType requested_device_type) {
+  if (!getDataMgr()->gpusPresent()) {
+    return ExecutorDeviceType::CPU;
+  }
   for (const auto target_expr : ra_exe_unit.target_exprs) {
     const auto agg_info = get_target_info(target_expr, g_bigint_count);
     if (!ra_exe_unit.groupby_exprs.empty() &&
@@ -2911,6 +3018,7 @@ void Executor::launchKernelsImpl(SharedKernelContext& shared_context,
 #else
   const size_t num_threads = cpu_threads();
 #endif
+  shared_context.setNumAllocatedThreads(num_threads);
   LOG(EXECUTOR) << "Launching query step with " << num_threads << " threads.";
   threading::task_group tg;
   // A hack to have unused unit for results collection.
@@ -4817,8 +4925,8 @@ TableGenerations Executor::computeTableGenerations(
 
 void Executor::setupCaching(const std::unordered_set<PhysicalInput>& phys_inputs,
                             const std::unordered_set<shared::TableKey>& phys_table_ids) {
-  row_set_mem_owner_ =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(), cpu_threads());
+  row_set_mem_owner_ = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), executor_id_, cpu_threads());
   row_set_mem_owner_->setDictionaryGenerations(
       computeStringDictionaryGenerations(phys_inputs));
   agg_col_range_cache_ = computeColRangesCache(phys_inputs);
diff --git a/QueryEngine/Execute.h b/QueryEngine/Execute.h
index f914135cfb..2496687d24 100644
--- a/QueryEngine/Execute.h
+++ b/QueryEngine/Execute.h
@@ -69,13 +69,13 @@
 #include "DataMgr/Chunk/Chunk.h"
 #include "Logger/Logger.h"
 #include "Shared/DbObjectKeys.h"
+#include "Shared/LruCache.h"
 #include "Shared/SystemParameters.h"
 #include "Shared/funcannotations.h"
 #include "Shared/heavyai_shared_mutex.h"
 #include "Shared/measure.h"
 #include "Shared/thread_count.h"
 #include "Shared/toString.h"
-#include "StringDictionary/LruCache.hpp"
 #include "StringDictionary/StringDictionary.h"
 #include "StringDictionary/StringDictionaryProxy.h"
 #include "ThriftHandler/CommandLineOptions.h"
@@ -420,8 +420,8 @@ class Executor {
 
  public:
   using ExecutorId = size_t;
-  static const ExecutorId UNITARY_EXECUTOR_ID = 0;
-  static const ExecutorId INVALID_EXECUTOR_ID = SIZE_MAX;
+  static constexpr ExecutorId UNITARY_EXECUTOR_ID = 0;
+  static constexpr ExecutorId INVALID_EXECUTOR_ID = SIZE_MAX;
 
   Executor(const ExecutorId id,
            Data_Namespace::DataMgr* data_mgr,
@@ -690,8 +690,6 @@ class Executor {
   void enableRuntimeQueryInterrupt(const double runtime_query_check_freq,
                                    const unsigned pending_query_check_freq) const;
 
-  static const size_t high_scan_limit{128000000};
-
   int8_t warpSize() const;
   unsigned gridSize() const;
   void setGridSize(unsigned grid_size);
@@ -728,6 +726,10 @@ class Executor {
 
   int deviceCount(const ExecutorDeviceType) const;
 
+  void logSystemCPUMemoryStatus(std::string const& tag, size_t const thread_idx) const;
+
+  void logSystemGPUMemoryStatus(std::string const& tag, size_t const thread_idx) const;
+
  private:
   void clearMetaInfoCache();
 
@@ -1538,7 +1540,7 @@ class Executor {
   static uint32_t gpu_active_modules_device_mask_;
   static void* gpu_active_modules_[max_gpu_count];
   // indicates whether this executor has been interrupted
-  std::atomic<bool> interrupted_;
+  std::atomic<bool> interrupted_{false};
 
   mutable std::mutex str_dict_mutex_;
 
diff --git a/QueryEngine/ExecutionKernel.cpp b/QueryEngine/ExecutionKernel.cpp
index d09d73e117..578f0b8ef1 100644
--- a/QueryEngine/ExecutionKernel.cpp
+++ b/QueryEngine/ExecutionKernel.cpp
@@ -157,6 +157,29 @@ void ExecutionKernel::run(Executor* executor,
   }
 }
 
+namespace {
+size_t get_available_cpu_threads_per_task(Executor* executor,
+                                          SharedKernelContext& shared_context) {
+  // total # allocated slots (i.e., threads) for compiled kernels of the input query
+  auto const num_kernels = shared_context.getNumAllocatedThreads();
+  CHECK_GE(num_kernels, 1u);
+  size_t available_slots_per_task;
+  if (executor->executor_resource_mgr_) {
+    auto const resources_status = executor->executor_resource_mgr_->get_resource_info();
+    // # available slots (i.e., threads) in the resource pool; idle threads
+    auto const idle_cpu_slots =
+        resources_status.total_cpu_slots - resources_status.allocated_cpu_slots;
+    // we want to evenly use idle slots for each kernel task to avoid oversubscription
+    available_slots_per_task = 1u + (idle_cpu_slots + num_kernels - 1u) / num_kernels;
+  } else {
+    available_slots_per_task = std::max(static_cast<size_t>(cpu_threads()) / num_kernels,
+                                        static_cast<size_t>(1));
+  }
+  CHECK_GE(available_slots_per_task, 1u);
+  return available_slots_per_task;
+}
+}  // namespace
+
 void ExecutionKernel::runImpl(Executor* executor,
                               const size_t thread_idx,
                               SharedKernelContext& shared_context) {
@@ -176,6 +199,10 @@ void ExecutionKernel::runImpl(Executor* executor,
   CHECK_LT(chosen_device_id, Executor::max_gpu_count);
 
   auto data_mgr = executor->getDataMgr();
+  executor->logSystemCPUMemoryStatus("Before Query Execution", thread_idx);
+  if (chosen_device_type == ExecutorDeviceType::GPU) {
+    executor->logSystemGPUMemoryStatus("Before Query Execution", thread_idx);
+  }
 
   // need to own them while query executes
   auto chunk_iterators_ptr = std::make_shared<std::list<ChunkIter>>();
@@ -299,6 +326,11 @@ void ExecutionKernel::runImpl(Executor* executor,
     }
   }
 
+  // determine the # available CPU threads for each kernel to parallelize rest of
+  // initialization steps when necessary
+  query_mem_desc.setAvailableCpuThreads(
+      get_available_cpu_threads_per_task(executor, shared_context));
+
 #ifdef HAVE_TBB
   bool can_run_subkernels = shared_context.getThreadPool() != nullptr;
 
@@ -378,6 +410,8 @@ void ExecutionKernel::runImpl(Executor* executor,
       chosen_device_type == ExecutorDeviceType::GPU &&
       eo.optimize_cuda_block_and_grid_sizes;
 
+  executor->logSystemCPUMemoryStatus("After Query Memory Initialization", thread_idx);
+
   if (ra_exe_unit_.groupby_exprs.empty()) {
     err = executor->executePlanWithoutGroupBy(ra_exe_unit_,
                                               compilation_result,
@@ -440,6 +474,10 @@ void ExecutionKernel::runImpl(Executor* executor,
     throw QueryExecutionError(err);
   }
   shared_context.addDeviceResults(std::move(device_results_), outer_tab_frag_ids);
+  executor->logSystemCPUMemoryStatus("After Query Execution", thread_idx);
+  if (chosen_device_type == ExecutorDeviceType::GPU) {
+    executor->logSystemGPUMemoryStatus("After Query Execution", thread_idx);
+  }
 }
 
 #ifdef HAVE_TBB
diff --git a/QueryEngine/ExecutionKernel.h b/QueryEngine/ExecutionKernel.h
index 7e6f74026a..5c8342a276 100644
--- a/QueryEngine/ExecutionKernel.h
+++ b/QueryEngine/ExecutionKernel.h
@@ -47,6 +47,14 @@ class SharedKernelContext {
     return query_infos_;
   }
 
+  void setNumAllocatedThreads(size_t num_threads) {
+    num_allocated_threads_ = num_threads;
+  }
+
+  size_t getNumAllocatedThreads() {
+    return num_allocated_threads_;
+  }
+
   std::atomic_flag dynamic_watchdog_set = ATOMIC_FLAG_INIT;
 
 #ifdef HAVE_TBB
@@ -69,6 +77,10 @@ class SharedKernelContext {
   std::mutex all_frag_row_offsets_mutex_;
   const std::vector<InputTableInfo>& query_infos_;
   const RegisteredQueryHint query_hint_;
+  // the # threads to execute the query (kernel) w/ a value one by default (means serial
+  // query execution). After finishing the compilation of the kernel, we will set it to a
+  // proper value based on the query's status
+  size_t num_allocated_threads_{1};
 
 #ifdef HAVE_TBB
   threading::task_group* task_group_;
diff --git a/QueryEngine/ExtensionFunctionsWhitelist.cpp b/QueryEngine/ExtensionFunctionsWhitelist.cpp
index 247b93b92c..eab2f8d82c 100644
--- a/QueryEngine/ExtensionFunctionsWhitelist.cpp
+++ b/QueryEngine/ExtensionFunctionsWhitelist.cpp
@@ -361,6 +361,7 @@ SQLTypeInfo ext_arg_type_to_type_info(const ExtArgumentType ext_arg_type) {
     type = kARRAY;                                                    \
     c = kENCODING_##ENCODING;                                         \
     subtype = ELEMTYPE;                                               \
+    n = true;                                                         \
     break;                                                            \
   case ExtArgumentType::Column##EXTARGTYPE:                           \
     type = kCOLUMN;                                                   \
diff --git a/QueryEngine/GeoIR.cpp b/QueryEngine/GeoIR.cpp
index 77bf1f225d..854f645dd0 100644
--- a/QueryEngine/GeoIR.cpp
+++ b/QueryEngine/GeoIR.cpp
@@ -127,6 +127,23 @@ std::vector<llvm::Value*> CodeGenerator::codegenGeoConstant(
   return ret;
 }
 
+namespace {
+void mark_logical_column_to_fetch(const Analyzer::GeoOperator* geo_operator,
+                                  PlanState* plan_state) {
+  auto comparator = Analyzer::ColumnVar::colvar_comp;
+  std::set<const Analyzer::ColumnVar*,
+           bool (*)(const Analyzer::ColumnVar*, const Analyzer::ColumnVar*)>
+      colvar_set(comparator);
+  geo_operator->collect_column_var(colvar_set, false);
+  std::for_each(colvar_set.begin(), colvar_set.end(), [&](const Analyzer::ColumnVar* cv) {
+    // we set `unmark_lazy_fetch` to be FALSE to restart the compilation step
+    // if we already generated a code for the expression used in this geo_oper as
+    // lazy-fetch
+    plan_state->addColumnToFetch(cv->getColumnKey(), false);
+  });
+}
+}  // namespace
+
 std::vector<llvm::Value*> CodeGenerator::codegenGeoOperator(
     const Analyzer::GeoOperator* geo_operator,
     const CompilationOptions& co) {
@@ -142,6 +159,8 @@ std::vector<llvm::Value*> CodeGenerator::codegenGeoOperator(
 
   auto op_codegen = spatial_type::Codegen::init(geo_operator);
   CHECK(op_codegen);
+  // we fetch all physical columns, so we sync the logical geo column w/ it
+  mark_logical_column_to_fetch(geo_operator, plan_state_);
 
   std::vector<llvm::Value*> load_lvs;
   std::vector<llvm::Value*> pos_lvs;
diff --git a/QueryEngine/GroupByAndAggregate.cpp b/QueryEngine/GroupByAndAggregate.cpp
index 1ba4af5923..1483a25ae0 100644
--- a/QueryEngine/GroupByAndAggregate.cpp
+++ b/QueryEngine/GroupByAndAggregate.cpp
@@ -725,6 +725,7 @@ CountDistinctDescriptors init_count_distinct_descriptors(
         count_distinct_descriptors.emplace_back(
             CountDistinctDescriptor{CountDistinctImplType::Bitmap,
                                     0,
+                                    arg_range_info.bucket,
                                     64,
                                     agg_info.agg_kind == kAPPROX_COUNT_DISTINCT,
                                     device_type,
@@ -831,13 +832,14 @@ CountDistinctDescriptors init_count_distinct_descriptors(
       count_distinct_descriptors.emplace_back(
           CountDistinctDescriptor{count_distinct_impl_type,
                                   arg_range_info.min,
+                                  arg_range_info.bucket,
                                   bitmap_sz_bits,
                                   agg_info.agg_kind == kAPPROX_COUNT_DISTINCT,
                                   device_type,
                                   sub_bitmap_count});
     } else {
       count_distinct_descriptors.emplace_back(CountDistinctDescriptor{
-          CountDistinctImplType::Invalid, 0, 0, false, device_type, 0});
+          CountDistinctImplType::Invalid, 0, 0, 0, false, device_type, 0});
     }
   }
   return count_distinct_descriptors;
@@ -1885,7 +1887,8 @@ void GroupByAndAggregate::codegenCountDistinct(
   std::string agg_fname{"agg_count_distinct"};
   if (count_distinct_descriptor.impl_type_ == CountDistinctImplType::Bitmap) {
     agg_fname += "_bitmap";
-    agg_args.push_back(LL_INT(static_cast<int64_t>(count_distinct_descriptor.min_val)));
+    agg_args.push_back(LL_INT(count_distinct_descriptor.min_val));
+    agg_args.push_back(LL_INT(count_distinct_descriptor.bucket_size));
   }
   if (agg_info.skip_null_val) {
     auto null_lv = executor_->cgen_state_->castToTypeIn(
diff --git a/QueryEngine/IRCodegen.cpp b/QueryEngine/IRCodegen.cpp
index 9d572cee31..638787ed52 100644
--- a/QueryEngine/IRCodegen.cpp
+++ b/QueryEngine/IRCodegen.cpp
@@ -598,6 +598,11 @@ void check_valid_join_qual(std::shared_ptr<Analyzer::BinOper>& bin_oper) {
           "indexed array columns (i.e., R.arr[1] = S.arr[2]) is not supported yet.");
     }
   }
+  auto const lhs_et =
+      dynamic_cast<const Analyzer::ExpressionTuple*>(bin_oper->get_left_operand());
+  if (lhs_et) {
+    CHECK_LE(lhs_et->getTuple().size(), g_maximum_conditions_to_coalesce);
+  }
 }
 
 }  // namespace
diff --git a/QueryEngine/InValuesBitmap.cpp b/QueryEngine/InValuesBitmap.cpp
index 3d98c904e0..fac32e1246 100644
--- a/QueryEngine/InValuesBitmap.cpp
+++ b/QueryEngine/InValuesBitmap.cpp
@@ -81,7 +81,8 @@ InValuesBitmap::InValuesBitmap(const std::vector<int64_t>& values,
     if (value == null_val) {
       continue;
     }
-    agg_count_distinct_bitmap(reinterpret_cast<int64_t*>(&cpu_bitset), value, min_val_);
+    agg_count_distinct_bitmap(
+        reinterpret_cast<int64_t*>(&cpu_bitset), value, min_val_, 0);
   }
 #ifdef HAVE_CUDA
   if (memory_level_ == Data_Namespace::GPU_LEVEL) {
diff --git a/QueryEngine/InputMetadata.cpp b/QueryEngine/InputMetadata.cpp
index dc52f08fae..7269b7ecc2 100644
--- a/QueryEngine/InputMetadata.cpp
+++ b/QueryEngine/InputMetadata.cpp
@@ -362,6 +362,7 @@ ChunkMetadataMap synthesize_metadata(const ResultSet* rows) {
   auto timer = DEBUG_TIMER(__func__);
   ChunkMetadataMap metadata_map;
 
+  // If the ResultSet has no rows, fill with dummy metadata and return early.
   if (rows->definitelyHasNoRows()) {
     // resultset has no valid storage, so we fill dummy metadata and return early
     std::vector<std::unique_ptr<Encoder>> decoders;
@@ -374,6 +375,7 @@ ChunkMetadataMap synthesize_metadata(const ResultSet* rows) {
     return metadata_map;
   }
 
+  // Create a vector of Encoder vectors for each worker.
   std::vector<std::vector<std::unique_ptr<Encoder>>> dummy_encoders;
   const size_t worker_count =
       result_set::use_parallel_algorithms(*rows) ? cpu_threads() : 1;
@@ -385,11 +387,14 @@ ChunkMetadataMap synthesize_metadata(const ResultSet* rows) {
     }
   }
 
+  // For TableFunctions, call the optimized function we have for this format.
   if (rows->getQueryMemDesc().getQueryDescriptionType() ==
       QueryDescriptionType::TableFunction) {
     return synthesize_metadata_table_function(rows);
   }
   rows->moveToBegin();
+
+  // Code in the do_work lambda runs for and processes each row.
   const auto do_work = [rows](const std::vector<TargetValue>& crt_row,
                               std::vector<std::unique_ptr<Encoder>>& dummy_encoders) {
     for (size_t i = 0; i < rows->colCount(); ++i) {
@@ -426,38 +431,24 @@ ChunkMetadataMap synthesize_metadata(const ResultSet* rows) {
       }
     }
   };
+
+  // Parallelize the processing using TBB if parallel algorithms are enabled.
   if (result_set::use_parallel_algorithms(*rows)) {
-    const size_t worker_count = cpu_threads();
-    std::vector<std::future<void>> compute_stats_threads;
-    const auto entry_count = rows->entryCount();
-    for (size_t i = 0,
-                start_entry = 0,
-                stride = (entry_count + worker_count - 1) / worker_count;
-         i < worker_count && start_entry < entry_count;
-         ++i, start_entry += stride) {
-      const auto end_entry = std::min(start_entry + stride, entry_count);
-      compute_stats_threads.push_back(std::async(
-          std::launch::async,
-          [rows, &do_work, &dummy_encoders](
-              const size_t start, const size_t end, const size_t worker_idx) {
-            for (size_t i = start; i < end; ++i) {
-              const auto crt_row = rows->getRowAtNoTranslations(i);
-              if (!crt_row.empty()) {
-                do_work(crt_row, dummy_encoders[worker_idx]);
-              }
+    const size_t entry_count = rows->entryCount();
+    tbb::parallel_for(
+        tbb::blocked_range<size_t>(0, entry_count),
+        [&do_work, &rows, &dummy_encoders](const tbb::blocked_range<size_t>& range) {
+          const size_t worker_idx = tbb::this_task_arena::current_thread_index();
+          for (size_t i = range.begin(); i < range.end(); ++i) {
+            const auto crt_row = rows->getRowAtNoTranslations(i);
+            if (!crt_row.empty()) {
+              do_work(crt_row, dummy_encoders[worker_idx]);
             }
-          },
-          start_entry,
-          end_entry,
-          i));
-    }
-    for (auto& child : compute_stats_threads) {
-      child.wait();
-    }
-    for (auto& child : compute_stats_threads) {
-      child.get();
-    }
+          }
+        });
+
   } else {
+    // If parallel algorithms are not enabled, process the rows sequentially.
     while (true) {
       auto crt_row = rows->getNextRow(false, false);
       if (crt_row.empty()) {
@@ -467,6 +458,8 @@ ChunkMetadataMap synthesize_metadata(const ResultSet* rows) {
     }
   }
   rows->moveToBegin();
+
+  // Reduce the results from each worker.
   for (size_t worker_idx = 1; worker_idx < worker_count; ++worker_idx) {
     CHECK_LT(worker_idx, dummy_encoders.size());
     const auto& worker_encoders = dummy_encoders[worker_idx];
@@ -474,6 +467,7 @@ ChunkMetadataMap synthesize_metadata(const ResultSet* rows) {
       dummy_encoders[0][i]->reduceStats(*worker_encoders[i]);
     }
   }
+  // Add each column's results to the metadata map.
   for (size_t i = 0; i < rows->colCount(); ++i) {
     const auto it_ok =
         metadata_map.emplace(i, dummy_encoders[0][i]->getMetadata(rows->getColType(i)));
diff --git a/QueryEngine/JoinHashTable/BaselineJoinHashTable.cpp b/QueryEngine/JoinHashTable/BaselineJoinHashTable.cpp
index a0ab6e7eca..dabe57bdde 100644
--- a/QueryEngine/JoinHashTable/BaselineJoinHashTable.cpp
+++ b/QueryEngine/JoinHashTable/BaselineJoinHashTable.cpp
@@ -527,6 +527,7 @@ std::pair<size_t, size_t> BaselineJoinHashTable::approximateTupleCount(
   CountDistinctDescriptor count_distinct_desc{
       CountDistinctImplType::Bitmap,
       0,
+      0,
       11,
       true,
       effective_memory_level == Data_Namespace::MemoryLevel::GPU_LEVEL
diff --git a/QueryEngine/JoinHashTable/BoundingBoxIntersectJoinHashTable.cpp b/QueryEngine/JoinHashTable/BoundingBoxIntersectJoinHashTable.cpp
index 867c0c599d..6261077e42 100644
--- a/QueryEngine/JoinHashTable/BoundingBoxIntersectJoinHashTable.cpp
+++ b/QueryEngine/JoinHashTable/BoundingBoxIntersectJoinHashTable.cpp
@@ -1010,6 +1010,7 @@ std::pair<size_t, size_t> BoundingBoxIntersectJoinHashTable::approximateTupleCou
   CountDistinctDescriptor count_distinct_desc{
       CountDistinctImplType::Bitmap,
       0,
+      0,
       11,
       true,
       effective_memory_level == Data_Namespace::MemoryLevel::GPU_LEVEL
diff --git a/QueryEngine/JoinHashTable/RangeJoinHashTable.cpp b/QueryEngine/JoinHashTable/RangeJoinHashTable.cpp
index b86bc8bac1..7e7730ef44 100644
--- a/QueryEngine/JoinHashTable/RangeJoinHashTable.cpp
+++ b/QueryEngine/JoinHashTable/RangeJoinHashTable.cpp
@@ -525,6 +525,7 @@ std::pair<size_t, size_t> RangeJoinHashTable::approximateTupleCount(
   CountDistinctDescriptor count_distinct_desc{
       CountDistinctImplType::Bitmap,
       0,
+      0,
       11,
       true,
       effective_memory_level_ == Data_Namespace::MemoryLevel::GPU_LEVEL
@@ -536,6 +537,7 @@ std::pair<size_t, size_t> RangeJoinHashTable::approximateTupleCount(
   CountDistinctDescriptor count_distinct_desc{
       .impl_type_ = CountDistinctImplType::Bitmap,
       .min_val = 0,
+      .bucket_size = 0,
       .bitmap_sz_bits = 11,
       .approximate = true,
       .device_type = effective_memory_level_ == Data_Namespace::MemoryLevel::GPU_LEVEL
diff --git a/QueryEngine/JoinHashTable/Runtime/HashJoinRuntime.cpp b/QueryEngine/JoinHashTable/Runtime/HashJoinRuntime.cpp
index 334fe58bc7..8f90bb82bf 100644
--- a/QueryEngine/JoinHashTable/Runtime/HashJoinRuntime.cpp
+++ b/QueryEngine/JoinHashTable/Runtime/HashJoinRuntime.cpp
@@ -42,6 +42,7 @@
 #endif
 
 #if HAVE_CUDA
+#include <cuda_runtime.h>
 #include <thrust/scan.h>
 #endif
 #include "Shared/funcannotations.h"
diff --git a/QueryEngine/JoinHashTable/Runtime/JoinColumnIterator.h b/QueryEngine/JoinHashTable/Runtime/JoinColumnIterator.h
index 377a4efa3e..b6f7b42dfe 100644
--- a/QueryEngine/JoinHashTable/Runtime/JoinColumnIterator.h
+++ b/QueryEngine/JoinHashTable/Runtime/JoinColumnIterator.h
@@ -180,11 +180,6 @@ struct JoinColumnTupleIterator {
                                  size_t start,
                                  size_t step)
       : num_cols(num_cols) {
-#ifndef __CUDACC__
-    CHECK_LE(num_cols, g_maximum_conditions_to_coalesce);
-#else
-    assert(num_cols <= g_maximum_conditions_to_coalesce);
-#endif
     for (size_t i = 0; i < num_cols; ++i) {
       join_column_iterators[i] =
           JoinColumnIterator(&join_column_per_key[i],
@@ -211,9 +206,7 @@ struct JoinColumnTupleIterator {
     return *this;
   }
 
-  DEVICE FORCE_INLINE JoinColumnTupleIterator& operator*() {
-    return *this;
-  }
+  DEVICE FORCE_INLINE JoinColumnTupleIterator& operator*() { return *this; }
 };  // struct JoinColumnTupleIterator
 
 //! Helper class for viewing multiple JoinColumns and their matching JoinColumnTypeInfos
diff --git a/QueryEngine/NativeCodegen.cpp b/QueryEngine/NativeCodegen.cpp
index e1f368608d..2b37f573d8 100644
--- a/QueryEngine/NativeCodegen.cpp
+++ b/QueryEngine/NativeCodegen.cpp
@@ -508,6 +508,7 @@ std::shared_ptr<CompilationContext> Executor::optimizeAndCodegenCPU(
   if (cached_code) {
     return cached_code;
   }
+
   if (cgen_state_->needs_geos_) {
 #ifdef ENABLE_GEOS
     auto llvm_module = multifrag_query_func->getParent();
@@ -856,8 +857,8 @@ declare i8 @string_ne_nullable(i8*, i32, i8*, i32, i8);
 declare i1 @regexp_like(i8*, i32, i8*, i32, i8);
 declare i8 @regexp_like_nullable(i8*, i32, i8*, i32, i8, i8);
 declare void @linear_probabilistic_count(i8*, i32, i8*, i32);
-declare void @agg_count_distinct_bitmap_gpu(i64*, i64, i64, i64, i64, i64, i64);
-declare void @agg_count_distinct_bitmap_skip_val_gpu(i64*, i64, i64, i64, i64, i64, i64, i64);
+declare void @agg_count_distinct_bitmap_gpu(i64*, i64, i64, i64, i64, i64, i64, i64);
+declare void @agg_count_distinct_bitmap_skip_val_gpu(i64*, i64, i64, i64, i64, i64, i64, i64, i64);
 declare void @agg_approximate_count_distinct_gpu(i64*, i64, i32, i64, i64);
 declare void @record_error_code(i32, i32*);
 declare i32 @get_error_code(i32*);
@@ -1389,16 +1390,6 @@ std::shared_ptr<GpuCompilationContext> CodeGenerator::generateNativeGPUCode(
   }
 
   checkCudaErrors(cuLinkDestroy(link_state));
-  try {
-    QueryEngine::getInstance()->addGpuKernelSize(cubin_result.cubin_size);
-  } catch (std::runtime_error const& e) {
-    if (strcmp(e.what(), "QueryEngine instance hasn't been created")) {
-      LOG(WARNING) << "QueryEngine::getInstance() failed: " << e.what();
-    }
-  } catch (...) {
-    LOG(WARNING) << "Fail to get QueryEngine instance";
-    throw;
-  }
   return gpu_compilation_context;
 #else
   return {};
@@ -1429,6 +1420,7 @@ std::shared_ptr<CompilationContext> Executor::optimizeAndCodegenGPU(
   if (cached_code) {
     return cached_code;
   }
+
   bool row_func_not_inlined = false;
   if (no_inline) {
     for (auto it = llvm::inst_begin(cgen_state_->row_func_),
@@ -1468,20 +1460,6 @@ std::shared_ptr<CompilationContext> Executor::optimizeAndCodegenGPU(
       auto& code_cache_accessor = QueryEngine::getInstance()->gpu_code_accessor;
       auto const num_entries_to_evict =
           code_cache_accessor->computeNumEntriesToEvict(g_fraction_code_cache_to_evict);
-      auto evicted_kernels_size =
-          code_cache_accessor->getSumSizeEvicted(num_entries_to_evict);
-      LOG(WARNING) << "Failed to allocate GPU memory for generated code. Evicting "
-                   << num_entries_to_evict << " (" << evicted_kernels_size
-                   << " bytes) cached GPU code and re-trying.";
-      try {
-        QueryEngine::getInstance()->subGpuKernelSize(evicted_kernels_size);
-      } catch (std::runtime_error const& e) {
-        if (strcmp(e.what(), "QueryEngine instance hasn't been created")) {
-          LOG(WARNING) << "QueryEngine::getInstance() failed: " << e.what();
-        }
-      } catch (...) {
-        LOG(WARNING) << "Fail to get QueryEngine instance";
-      }
       code_cache_accessor->evictEntries(num_entries_to_evict);
       compilation_context = CodeGenerator::generateNativeGPUCode(this,
                                                                  query_func,
@@ -3486,8 +3464,9 @@ std::vector<llvm::Value*> generate_column_heads_load(const int num_columns,
         byte_stream_arg->getType()->getScalarType()->getPointerElementType(),
         byte_stream_arg,
         llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx), col_id));
-    col_heads.emplace_back(
-        ir_builder.CreateLoad(gep->getType()->getPointerElementType(), gep));
+    auto* load_gep = ir_builder.CreateLoad(gep->getType()->getPointerElementType(), gep);
+    load_gep->setName(byte_stream_arg->getName() + "_" + std::to_string(col_id) + "_ptr");
+    col_heads.emplace_back(load_gep);
   }
   return col_heads;
 }
diff --git a/QueryEngine/NvidiaKernel.h b/QueryEngine/NvidiaKernel.h
index b905eca00a..3eb68b9639 100644
--- a/QueryEngine/NvidiaKernel.h
+++ b/QueryEngine/NvidiaKernel.h
@@ -62,7 +62,7 @@ class GpuDeviceCompilationContext {
   CUfunction kernel() { return kernel_; }
   CUmodule module() { return module_; }
   std::string const& name() const { return kernel_name_; }
-  size_t getModulesize() const { return module_size_; }
+  size_t getModuleSize() const { return module_size_; }
 
  private:
   CUmodule module_;
@@ -105,7 +105,7 @@ class GpuCompilationContext : public CompilationContext {
   }
 
   size_t getMemSize() const {
-    return contexts_per_device_.begin()->get()->getModulesize();
+    return contexts_per_device_.begin()->get()->getModuleSize();
   }
 
  private:
diff --git a/QueryEngine/PlanState.cpp b/QueryEngine/PlanState.cpp
index 7fe2ed73cc..a781de24fd 100644
--- a/QueryEngine/PlanState.cpp
+++ b/QueryEngine/PlanState.cpp
@@ -119,6 +119,8 @@ bool PlanState::isColumnToNotFetch(const shared::ColumnKey& column_key) const {
   return shared::contains(columns_to_not_fetch_, column_key);
 }
 
+// todo (yoonmin): determine non-lazy fetch compilation in a high-level; to avoid
+// recompilation which may incur noticeable overhead
 void PlanState::addColumnToFetch(const shared::ColumnKey& column_key,
                                  bool unmark_lazy_fetch) {
   if (unmark_lazy_fetch) {
@@ -129,9 +131,12 @@ void PlanState::addColumnToFetch(const shared::ColumnKey& column_key,
       columns_to_not_fetch_.erase(it);
     }
   } else {
-    if (shared::contains(columns_to_not_fetch_, column_key)) {
-      // This column was previously marked for lazy fetch due to incomplete information.
-      // Throw an exception in this case to restart the compilation step.
+    if (columns_to_not_fetch_.count(column_key)) {
+      // this case represents a query w/ fetching expressions with the order of
+      // cv and an expression on it, i.e., SELECT col1, ST_NPoints(col1) FROM ...
+      // since we first touch the cv itself and we forcely mark it as lazy-fetch
+      // but to evaluate the expression, we must fetch the same cv;
+      // so let's fetch the cv to allow evaluating the expression using the same cv
       throw CompilationRetryNoLazyFetch();
     }
   }
diff --git a/QueryEngine/QueryEngine.h b/QueryEngine/QueryEngine.h
index cbe2849f10..050d3ddb44 100644
--- a/QueryEngine/QueryEngine.h
+++ b/QueryEngine/QueryEngine.h
@@ -5,28 +5,36 @@
 #include "CudaMgr/CudaMgr.h"
 #include "QueryEngine/CodeCacheAccessor.h"
 #include "QueryEngine/NvidiaKernel.h"
+#include "Shared/LruCache.h"
 
 inline bool g_query_engine_cuda_streams{false};
+inline size_t g_code_cache_max_num_items{1000};
+inline size_t g_gpu_code_cache_max_size_in_bytes{size_t(1) << 27};  // 128MB
 
 class QueryEngine {
  public:
   QueryEngine(CudaMgr_Namespace::CudaMgr* cuda_mgr, bool cpu_only)
       : cuda_mgr_(cuda_mgr)
-      , s_stubs_accessor(
-            std::make_unique<CodeCacheAccessor<CpuCompilationContext>>(code_cache_size,
-                                                                       "s_stubs_cache"))
-      , s_code_accessor(
-            std::make_unique<CodeCacheAccessor<CpuCompilationContext>>(code_cache_size,
-                                                                       "s_code_cache"))
-      , cpu_code_accessor(
-            std::make_unique<CodeCacheAccessor<CpuCompilationContext>>(code_cache_size,
-                                                                       "cpu_code_cache"))
-      , gpu_code_accessor(
-            std::make_unique<CodeCacheAccessor<GpuCompilationContext>>(code_cache_size,
-                                                                       "gpu_code_cache"))
-      , tf_code_accessor(
-            std::make_unique<CodeCacheAccessor<CompilationContext>>(code_cache_size,
-                                                                    "tf_code_cache")) {
+      , s_stubs_accessor(std::make_unique<CodeCacheAccessor<CpuCompilationContext>>(
+            EvictionMetricType::EntryCount,
+            g_code_cache_max_num_items,
+            "s_stubs_cache"))
+      , s_code_accessor(std::make_unique<CodeCacheAccessor<CpuCompilationContext>>(
+            EvictionMetricType::EntryCount,
+            g_code_cache_max_num_items,
+            "s_code_cache"))
+      , cpu_code_accessor(std::make_unique<CodeCacheAccessor<CpuCompilationContext>>(
+            EvictionMetricType::EntryCount,
+            g_code_cache_max_num_items,
+            "cpu_code_cache"))
+      , gpu_code_accessor(std::make_unique<CodeCacheAccessor<GpuCompilationContext>>(
+            EvictionMetricType::ByteSize,
+            g_gpu_code_cache_max_size_in_bytes,
+            "gpu_code_cache"))
+      , tf_code_accessor(std::make_unique<CodeCacheAccessor<CompilationContext>>(
+            EvictionMetricType::EntryCount,
+            g_code_cache_max_num_items,
+            "tf_code_cache")) {
     if (cpu_only) {
       g_query_engine_cuda_streams = false;
     }
@@ -78,11 +86,6 @@ class QueryEngine {
     }
   }
 
-  size_t getTotalCacheKeySize() const {
-    std::lock_guard<std::mutex> lock(mutex_);
-    return cached_gpu_kernel_total_size_;
-  }
-
   static std::shared_ptr<QueryEngine> getInstance() {
     if (auto s = instance_.lock()) {
       return s;
@@ -103,16 +106,6 @@ class QueryEngine {
     }
   }
 
-  void addGpuKernelSize(size_t sz) {
-    std::lock_guard<std::mutex> lock(mutex_);
-    cached_gpu_kernel_total_size_ += sz;
-  }
-
-  void subGpuKernelSize(size_t sz) {
-    std::lock_guard<std::mutex> lock(mutex_);
-    cached_gpu_kernel_total_size_ -= sz;
-  }
-
  private:
   CudaMgr_Namespace::CudaMgr* cuda_mgr_;
   std::vector<CUstream> cuda_streams_;
@@ -120,13 +113,6 @@ class QueryEngine {
   inline static std::mutex mutex_;  // TODO(sy): use atomics instead?
   inline static std::weak_ptr<QueryEngine> instance_;
 
-  // todo (yoonmin) : avoid hard-coded max cache entries
-  static constexpr size_t code_cache_size{1000};
-
-  // we currently only track GPU kernel cache size
-  // todo (yoonmin): add CPU kernel size calculator
-  size_t cached_gpu_kernel_total_size_{0};
-
  public:
   std::unique_ptr<CodeCacheAccessor<CpuCompilationContext>> s_stubs_accessor;
   std::unique_ptr<CodeCacheAccessor<CpuCompilationContext>> s_code_accessor;
diff --git a/QueryEngine/QueryExecutionContext.cpp b/QueryEngine/QueryExecutionContext.cpp
index 2ea5b7263b..990e66ff0f 100644
--- a/QueryEngine/QueryExecutionContext.cpp
+++ b/QueryEngine/QueryExecutionContext.cpp
@@ -527,11 +527,13 @@ std::vector<int64_t*> QueryExecutionContext::launchGpuCode(
       out_vec.push_back(host_out_vec);
     }
   }
-  const auto count_distinct_bitmap_mem = query_buffers_->getCountDistinctBitmapPtr();
-  if (count_distinct_bitmap_mem) {
-    gpu_allocator_->copyFromDevice(query_buffers_->getCountDistinctHostPtr(),
-                                   reinterpret_cast<void*>(count_distinct_bitmap_mem),
-                                   query_buffers_->getCountDistinctBitmapBytes());
+  const auto count_distinct_bitmap_device_mem =
+      query_buffers_->getCountDistinctBitmapDevicePtr();
+  if (count_distinct_bitmap_device_mem) {
+    gpu_allocator_->copyFromDevice(
+        query_buffers_->getCountDistinctBitmapHostPtr(),
+        reinterpret_cast<void*>(count_distinct_bitmap_device_mem),
+        query_buffers_->getCountDistinctBitmapBytes());
   }
 
   const auto varlen_output_gpu_buf = query_buffers_->getVarlenOutputPtr();
@@ -801,7 +803,7 @@ int8_t* QueryExecutionContext::copyJoinHashTablesToDevice(
 size_t QueryExecutionContext::sizeofLiterals(
     std::vector<int8_t> const& literal_buff) const {
   size_t const count_distinct_bytes =
-      query_buffers_->getCountDistinctBitmapPtr() ? 2 * sizeof(int64_t) : 0u;
+      query_buffers_->getCountDistinctBitmapDevicePtr() ? 2 * sizeof(int64_t) : 0u;
   return count_distinct_bytes + literal_buff.size();
 }
 // The count_distinct_addresses are considered "additional literals"
@@ -813,18 +815,20 @@ int8_t* QueryExecutionContext::copyLiteralsToDevice(
   // Calculate additional space
   //  * Count Distinct Bitmap
   int64_t count_distinct_addresses[2];
-  size_t const count_distinct_bytes =
-      query_buffers_->getCountDistinctBitmapPtr() ? sizeof count_distinct_addresses : 0u;
+  size_t const count_distinct_bytes = query_buffers_->getCountDistinctBitmapDevicePtr()
+                                          ? sizeof count_distinct_addresses
+                                          : 0u;
   CHECK_EQ(0u, uint64_t(device_ptr) % 8);
   // Copy to device, literals last.
   if (count_distinct_bytes) {
     // Store host and device addresses
-    auto const count_distinct_bitmap_host_mem = query_buffers_->getCountDistinctHostPtr();
+    auto const count_distinct_bitmap_host_mem =
+        query_buffers_->getCountDistinctBitmapHostPtr();
     CHECK(count_distinct_bitmap_host_mem);
     count_distinct_addresses[0] =  // getAdditionalLiteral(-2) in codegenCountDistinct()
         reinterpret_cast<int64_t>(count_distinct_bitmap_host_mem);
     count_distinct_addresses[1] =  // getAdditionalLiteral(-1) in codegenCountDistinct()
-        static_cast<int64_t>(query_buffers_->getCountDistinctBitmapPtr());
+        static_cast<int64_t>(query_buffers_->getCountDistinctBitmapDevicePtr());
     gpu_allocator_->copyToDevice(
         device_ptr, count_distinct_addresses, count_distinct_bytes);
     device_ptr += count_distinct_bytes;
diff --git a/QueryEngine/QueryHint.h b/QueryEngine/QueryHint.h
index bdc7c34b2f..9a305c27a6 100644
--- a/QueryEngine/QueryHint.h
+++ b/QueryEngine/QueryHint.h
@@ -52,6 +52,8 @@ enum QueryHint {
   kMaxJoinHashTableSize,
   kforceBaselineHashJoin,
   kforceOneToManyHashJoin,
+  kWatchdogMaxProjectedRowsPerDevice,
+  kPreflightCountQueryThreshold,
   kHintCount,   // should be at the last elem before INVALID enum value to count #
                 // supported hints correctly
   kInvalidHint  // this should be the last elem of this enum
@@ -82,7 +84,10 @@ static const std::unordered_map<std::string, QueryHint> SupportedQueryHints = {
     {"loop_join_inner_table_max_num_rows", QueryHint::kLoopJoinInnerTableMaxNumRows},
     {"max_join_hashtable_size", QueryHint::kMaxJoinHashTableSize},
     {"force_baseline_hash_join", QueryHint::kforceBaselineHashJoin},
-    {"force_one_to_many_hash_join", QueryHint::kforceOneToManyHashJoin}};
+    {"force_one_to_many_hash_join", QueryHint::kforceOneToManyHashJoin},
+    {"watchdog_max_projected_rows_per_device",
+     QueryHint::kWatchdogMaxProjectedRowsPerDevice},
+    {"preflight_count_query_threshold", QueryHint::kPreflightCountQueryThreshold}};
 
 struct HintIdentifier {
   bool global_hint;
@@ -195,6 +200,8 @@ struct RegisteredQueryHint {
       , watchdog(std::nullopt)
       , dynamic_watchdog(std::nullopt)
       , query_time_limit(0)
+      , watchdog_max_projected_rows_per_device(g_watchdog_max_projected_rows_per_device)
+      , preflight_count_query_threshold(g_preflight_count_query_threshold)
       , cuda_block_size(0)
       , cuda_grid_size_multiplier(0.0)
       , opt_cuda_grid_and_block_size(false)
@@ -301,6 +308,14 @@ struct RegisteredQueryHint {
             updated_query_hints.force_one_to_many_hash_join =
                 global_hints.force_one_to_many_hash_join;
             break;
+          case QueryHint::kWatchdogMaxProjectedRowsPerDevice:
+            updated_query_hints.watchdog_max_projected_rows_per_device =
+                global_hints.watchdog_max_projected_rows_per_device;
+            break;
+          case QueryHint::kPreflightCountQueryThreshold:
+            updated_query_hints.preflight_count_query_threshold =
+                global_hints.preflight_count_query_threshold;
+            break;
           default:
             UNREACHABLE();
         }
@@ -318,6 +333,8 @@ struct RegisteredQueryHint {
   std::optional<bool> watchdog;
   std::optional<bool> dynamic_watchdog;
   size_t query_time_limit;
+  size_t watchdog_max_projected_rows_per_device;
+  size_t preflight_count_query_threshold;
 
   // control CUDA behavior
   size_t cuda_block_size;
diff --git a/QueryEngine/QueryMemoryInitializer.cpp b/QueryEngine/QueryMemoryInitializer.cpp
index a012d0a894..62272cdf81 100644
--- a/QueryEngine/QueryMemoryInitializer.cpp
+++ b/QueryEngine/QueryMemoryInitializer.cpp
@@ -30,7 +30,7 @@ int64_t g_bitmap_memory_limit{8LL * 1000 * 1000 * 1000};
 namespace {
 
 inline void check_total_bitmap_memory(const QueryMemoryDescriptor& query_mem_desc) {
-  const int32_t groups_buffer_entry_count = query_mem_desc.getEntryCount();
+  const size_t groups_buffer_entry_count = query_mem_desc.getEntryCount();
   checked_int64_t total_bytes_per_group = 0;
   const size_t num_count_distinct_descs =
       query_mem_desc.getCountDistinctDescriptorsSize();
@@ -164,6 +164,52 @@ int get_input_idx(RelAlgExecutionUnit const& ra_exe_unit,
   return itr == input_descs.end() ? 0 : itr->getNestLevel();
 }
 
+void check_count_distinct_expr_metadata(const QueryMemoryDescriptor& query_mem_desc,
+                                        const RelAlgExecutionUnit& ra_exe_unit) {
+  const size_t agg_col_count{query_mem_desc.getSlotCount()};
+  CHECK_GE(agg_col_count, ra_exe_unit.target_exprs.size());
+  for (size_t target_idx = 0; target_idx < ra_exe_unit.target_exprs.size();
+       ++target_idx) {
+    const auto target_expr = ra_exe_unit.target_exprs[target_idx];
+    const auto agg_info = get_target_info(target_expr, g_bigint_count);
+    if (is_distinct_target(agg_info)) {
+      CHECK(agg_info.is_agg &&
+            (agg_info.agg_kind == kCOUNT || agg_info.agg_kind == kCOUNT_IF ||
+             agg_info.agg_kind == kAPPROX_COUNT_DISTINCT));
+      CHECK(!agg_info.sql_type.is_varlen());
+      const size_t agg_col_idx = query_mem_desc.getSlotIndexForSingleSlotCol(target_idx);
+      CHECK_LT(static_cast<size_t>(agg_col_idx), agg_col_count);
+      CHECK_EQ(static_cast<size_t>(query_mem_desc.getLogicalSlotWidthBytes(agg_col_idx)),
+               sizeof(int64_t));
+      const auto& count_distinct_desc =
+          query_mem_desc.getCountDistinctDescriptor(target_idx);
+      CHECK(count_distinct_desc.impl_type_ != CountDistinctImplType::Invalid);
+    }
+  }
+}
+
+QueryMemoryInitializer::TargetAggOpsMetadata collect_target_expr_metadata(
+    const QueryMemoryDescriptor& query_mem_desc,
+    const RelAlgExecutionUnit& ra_exe_unit) {
+  QueryMemoryInitializer::TargetAggOpsMetadata agg_op_metadata;
+  if (!query_mem_desc.countDistinctDescriptorsLogicallyEmpty()) {
+    agg_op_metadata.has_count_distinct = true;
+  }
+  std::for_each(
+      ra_exe_unit.target_exprs.begin(),
+      ra_exe_unit.target_exprs.end(),
+      [&agg_op_metadata](const Analyzer::Expr* expr) {
+        if (auto const* agg_expr = dynamic_cast<Analyzer::AggExpr const*>(expr)) {
+          if (agg_expr->get_aggtype() == kMODE) {
+            agg_op_metadata.has_mode = true;
+          } else if (agg_expr->get_aggtype() == kAPPROX_QUANTILE) {
+            agg_op_metadata.has_tdigest = true;
+          }
+        }
+      });
+  return agg_op_metadata;
+}
+
 }  // namespace
 
 // Row-based execution constructor
@@ -191,36 +237,63 @@ QueryMemoryInitializer::QueryMemoryInitializer(
     , num_buffers_(computeNumberOfBuffers(query_mem_desc, device_type, executor))
     , varlen_output_buffer_(0)
     , varlen_output_buffer_host_ptr_(nullptr)
-    , count_distinct_bitmap_mem_(0)
-    , count_distinct_bitmap_mem_bytes_(0)
-    , count_distinct_bitmap_crt_ptr_(nullptr)
-    , count_distinct_bitmap_host_mem_(nullptr)
+    , count_distinct_bitmap_device_mem_ptr_(0)
+    , count_distinct_bitmap_mem_size_(0)
+    , count_distinct_bitmap_host_crt_ptr_(nullptr)
+    , count_distinct_bitmap_host_mem_ptr_(nullptr)
     , device_allocator_(device_allocator)
     , thread_idx_(thread_idx) {
   CHECK(!sort_on_gpu || output_columnar);
+  executor->logSystemCPUMemoryStatus("Before Query Memory Initialization", thread_idx);
 
   const auto& consistent_frag_sizes = get_consistent_frags_sizes(frag_offsets);
   if (consistent_frag_sizes.empty()) {
     // No fragments in the input, no underlying buffers will be needed.
     return;
   }
-  if (!ra_exe_unit.use_bump_allocator) {
-    check_total_bitmap_memory(query_mem_desc);
-  }
-  if (device_type == ExecutorDeviceType::GPU) {
-    allocateCountDistinctGpuMem(query_mem_desc);
+
+  TargetAggOpsMetadata agg_op_metadata =
+      collect_target_expr_metadata(query_mem_desc, ra_exe_unit);
+  if (agg_op_metadata.has_count_distinct) {
+    check_count_distinct_expr_metadata(query_mem_desc, ra_exe_unit);
+    if (!ra_exe_unit.use_bump_allocator) {
+      check_total_bitmap_memory(query_mem_desc);
+    }
+    if (device_type == ExecutorDeviceType::GPU) {
+      allocateCountDistinctGpuMem(query_mem_desc);
+    }
   }
 
   if (render_allocator_map || !query_mem_desc.isGroupBy()) {
-    constexpr bool defer = false;  // Allocate aggregation data structures now
-    allocateCountDistinctBuffers(query_mem_desc, defer, executor, ra_exe_unit);
-    allocateModes(query_mem_desc, defer, executor, ra_exe_unit);
-    allocateTDigests(query_mem_desc, defer, executor, ra_exe_unit);
+    if (agg_op_metadata.has_count_distinct) {
+      allocateCountDistinctBuffers(query_mem_desc, ra_exe_unit);
+    }
+    if (agg_op_metadata.has_mode) {
+      allocateModeBuffer(query_mem_desc, ra_exe_unit);
+    }
+    if (agg_op_metadata.has_tdigest) {
+      allocateTDigestsBuffer(query_mem_desc, ra_exe_unit);
+    }
     if (render_info && render_info->useCudaBuffers()) {
       return;
     }
   }
 
+  if (query_mem_desc.isGroupBy()) {
+    if (agg_op_metadata.has_count_distinct) {
+      agg_op_metadata.count_distinct_buf_size =
+          calculateCountDistinctBufferSize(query_mem_desc, ra_exe_unit);
+    }
+    if (agg_op_metadata.has_mode) {
+      agg_op_metadata.mode_index_set =
+          initializeModeIndexSet(query_mem_desc, ra_exe_unit);
+    }
+    if (agg_op_metadata.has_tdigest) {
+      agg_op_metadata.qualtile_params =
+          initializeQuantileParams(query_mem_desc, ra_exe_unit);
+    }
+  }
+
   if (ra_exe_unit.estimator) {
     return;
   }
@@ -253,6 +326,7 @@ QueryMemoryInitializer::QueryMemoryInitializer(
     initGroupByBuffer(group_by_buffer_template,
                       ra_exe_unit,
                       query_mem_desc,
+                      agg_op_metadata,
                       device_type,
                       output_columnar,
                       executor);
@@ -278,8 +352,10 @@ QueryMemoryInitializer::QueryMemoryInitializer(
   if (query_mem_desc.hasVarlenOutput()) {
     const auto varlen_buffer_elem_size_opt = query_mem_desc.varlenOutputBufferElemSize();
     CHECK(varlen_buffer_elem_size_opt);  // TODO(adb): relax
-    auto varlen_output_buffer = reinterpret_cast<int64_t*>(row_set_mem_owner_->allocate(
-        query_mem_desc.getEntryCount() * varlen_buffer_elem_size_opt.value()));
+    auto const varlen_buffer_sz =
+        query_mem_desc.getEntryCount() * varlen_buffer_elem_size_opt.value();
+    auto varlen_output_buffer =
+        reinterpret_cast<int64_t*>(row_set_mem_owner_->allocate(varlen_buffer_sz));
     num_buffers_ += 1;
     group_by_buffers_.push_back(varlen_output_buffer);
   }
@@ -318,6 +394,7 @@ QueryMemoryInitializer::QueryMemoryInitializer(
           initGroupByBuffer(group_by_buffer + index_buffer_qw,
                             ra_exe_unit,
                             query_mem_desc,
+                            agg_op_metadata,
                             device_type,
                             output_columnar,
                             executor);
@@ -376,10 +453,10 @@ QueryMemoryInitializer::QueryMemoryInitializer(
     , num_buffers_(1)
     , varlen_output_buffer_(0)
     , varlen_output_buffer_host_ptr_(nullptr)
-    , count_distinct_bitmap_mem_(0)
-    , count_distinct_bitmap_mem_bytes_(0)
-    , count_distinct_bitmap_crt_ptr_(nullptr)
-    , count_distinct_bitmap_host_mem_(nullptr)
+    , count_distinct_bitmap_device_mem_ptr_(0)
+    , count_distinct_bitmap_mem_size_(0)
+    , count_distinct_bitmap_host_crt_ptr_(nullptr)
+    , count_distinct_bitmap_host_mem_ptr_(nullptr)
     , device_allocator_(device_allocator)
     , thread_idx_(0) {
   // Table functions output columnar, basically treat this as a projection
@@ -399,17 +476,38 @@ QueryMemoryInitializer::QueryMemoryInitializer(
       // memory managment.
       auto slot_idx = query_mem_desc.getSlotIndexForSingleSlotCol(i);
       CHECK(query_mem_desc.checkSlotUsesFlatBufferFormat(slot_idx));
-      int64_t flatbuffer_size = query_mem_desc.getFlatBufferSize(slot_idx);
-      total_group_by_buffer_size =
-          align_to_int64(total_group_by_buffer_size + flatbuffer_size);
+      checked_int64_t flatbuffer_size = query_mem_desc.getFlatBufferSize(slot_idx);
+      try {
+        total_group_by_buffer_size = align_to_int64(
+            static_cast<int64_t>(total_group_by_buffer_size + flatbuffer_size));
+      } catch (...) {
+        throw OutOfHostMemory(std::numeric_limits<int64_t>::max() / 8);
+      }
     } else {
-      const size_t col_width = ti.get_size();
-      const size_t group_buffer_size = num_rows_ * col_width;
-      total_group_by_buffer_size =
-          align_to_int64(total_group_by_buffer_size + group_buffer_size);
+      const checked_int64_t col_width = ti.get_size();
+      try {
+        const checked_int64_t group_buffer_size = col_width * num_rows_;
+        total_group_by_buffer_size = align_to_int64(
+            static_cast<int64_t>(group_buffer_size + total_group_by_buffer_size));
+      } catch (...) {
+        throw OutOfHostMemory(std::numeric_limits<int64_t>::max() / 8);
+      }
     }
   }
 
+#ifdef __SANITIZE_ADDRESS__
+  // AddressSanitizer will reject allocation sizes above 1 TiB
+#define MAX_BUFFER_SIZE 0x10000000000ll
+#else
+  // otherwise, we'll set the limit to 16 TiB, feel free to increase
+  // the limit if needed
+#define MAX_BUFFER_SIZE 0x100000000000ll
+#endif
+
+  if (total_group_by_buffer_size >= MAX_BUFFER_SIZE) {
+    throw OutOfHostMemory(total_group_by_buffer_size);
+  }
+
   CHECK_EQ(num_buffers_, size_t(1));
   auto group_by_buffer = alloc_group_by_buffer(total_group_by_buffer_size,
                                                nullptr,
@@ -444,6 +542,7 @@ void QueryMemoryInitializer::initGroupByBuffer(
     int64_t* buffer,
     const RelAlgExecutionUnit& ra_exe_unit,
     const QueryMemoryDescriptor& query_mem_desc,
+    TargetAggOpsMetadata& agg_op_metadata,
     const ExecutorDeviceType device_type,
     const bool output_columnar,
     const Executor* executor) {
@@ -471,6 +570,7 @@ void QueryMemoryInitializer::initGroupByBuffer(
     initRowGroups(query_mem_desc,
                   rows_ptr,
                   init_agg_vals_,
+                  agg_op_metadata,
                   actual_entry_count,
                   warp_size,
                   executor,
@@ -481,6 +581,7 @@ void QueryMemoryInitializer::initGroupByBuffer(
 void QueryMemoryInitializer::initRowGroups(const QueryMemoryDescriptor& query_mem_desc,
                                            int64_t* groups_buffer,
                                            const std::vector<int64_t>& init_vals,
+                                           TargetAggOpsMetadata& agg_op_metadata,
                                            const int32_t groups_buffer_entry_count,
                                            const size_t warp_size,
                                            const Executor* executor,
@@ -489,50 +590,52 @@ void QueryMemoryInitializer::initRowGroups(const QueryMemoryDescriptor& query_me
   const size_t row_size{query_mem_desc.getRowSize()};
   const size_t col_base_off{query_mem_desc.getColOffInBytes(0)};
 
-  constexpr bool defer = true;  // defer allocation of aggregation data structures
-  auto agg_bitmap_size =
-      allocateCountDistinctBuffers(query_mem_desc, defer, executor, ra_exe_unit);
-  auto mode_index_set = allocateModes(query_mem_desc, defer, executor, ra_exe_unit);
-  auto quantile_params = allocateTDigests(query_mem_desc, defer, executor, ra_exe_unit);
   auto buffer_ptr = reinterpret_cast<int8_t*>(groups_buffer);
-
   const auto query_mem_desc_fixedup =
       ResultSet::fixupQueryMemoryDescriptor(query_mem_desc);
-
-  auto const is_true = [](auto const& x) { return static_cast<bool>(x); };
   // not COUNT DISTINCT / APPROX_COUNT_DISTINCT / APPROX_QUANTILE
-  // we fallback to default implementation in that cases
-  if (!std::any_of(agg_bitmap_size.begin(), agg_bitmap_size.end(), is_true) &&
-      !std::any_of(quantile_params.begin(), quantile_params.end(), is_true) &&
-      mode_index_set.empty() && g_optimize_row_initialization) {
+  // we use the default implementation in those agg ops
+  auto const key_sz = query_mem_desc.getEffectiveKeyWidth();
+  if (!(agg_op_metadata.has_count_distinct || agg_op_metadata.has_mode ||
+        agg_op_metadata.has_tdigest) &&
+      g_optimize_row_initialization) {
     std::vector<int8_t> sample_row(row_size - col_base_off);
+    auto const num_available_cpu_threads =
+        std::min(query_mem_desc.getAvailableCpuThreads(),
+                 static_cast<size_t>(std::max(cpu_threads(), 1)));
+    tbb::task_arena initialization_arena(num_available_cpu_threads);
 
-    initColumnsPerRow(query_mem_desc_fixedup,
-                      sample_row.data(),
-                      init_vals,
-                      agg_bitmap_size,
-                      mode_index_set,
-                      quantile_params);
+    initColumnsPerRow(
+        query_mem_desc_fixedup, sample_row.data(), init_vals, agg_op_metadata);
 
     if (query_mem_desc.hasKeylessHash()) {
       CHECK(warp_size >= 1);
       CHECK(key_count == 1 || warp_size == 1);
-      for (size_t warp_idx = 0; warp_idx < warp_size; ++warp_idx) {
-        for (size_t bin = 0; bin < static_cast<size_t>(groups_buffer_entry_count);
-             ++bin, buffer_ptr += row_size) {
-          memcpy(buffer_ptr + col_base_off, sample_row.data(), sample_row.size());
-        }
-      }
+      initialization_arena.execute([&] {
+        tbb::parallel_for(
+            tbb::blocked_range<size_t>(0, groups_buffer_entry_count * warp_size),
+            [&](const tbb::blocked_range<size_t>& r) {
+              auto cur_row_buf = buffer_ptr + (row_size * r.begin());
+              for (size_t i = r.begin(); i != r.end(); ++i, cur_row_buf += row_size) {
+                memcpy(cur_row_buf + col_base_off, sample_row.data(), sample_row.size());
+              }
+            });
+      });
       return;
     }
-
-    for (size_t bin = 0; bin < static_cast<size_t>(groups_buffer_entry_count);
-         ++bin, buffer_ptr += row_size) {
-      memcpy(buffer_ptr + col_base_off, sample_row.data(), sample_row.size());
-      result_set::fill_empty_key(
-          buffer_ptr, key_count, query_mem_desc.getEffectiveKeyWidth());
-    }
+    initialization_arena.execute([&] {
+      tbb::parallel_for(
+          tbb::blocked_range<size_t>(0, groups_buffer_entry_count),
+          [&](const tbb::blocked_range<size_t>& r) {
+            auto cur_row_buf = buffer_ptr + (row_size * r.begin());
+            for (size_t i = r.begin(); i != r.end(); ++i, cur_row_buf += row_size) {
+              memcpy(cur_row_buf + col_base_off, sample_row.data(), sample_row.size());
+              result_set::fill_empty_key(cur_row_buf, key_count, key_sz);
+            }
+          });
+    });
   } else {
+    // todo(yoonmin): allow parallelization of `initColumnsPerRow`
     if (query_mem_desc.hasKeylessHash()) {
       CHECK(warp_size >= 1);
       CHECK(key_count == 1 || warp_size == 1);
@@ -542,9 +645,7 @@ void QueryMemoryInitializer::initRowGroups(const QueryMemoryDescriptor& query_me
           initColumnsPerRow(query_mem_desc_fixedup,
                             &buffer_ptr[col_base_off],
                             init_vals,
-                            agg_bitmap_size,
-                            mode_index_set,
-                            quantile_params);
+                            agg_op_metadata);
         }
       }
       return;
@@ -554,12 +655,8 @@ void QueryMemoryInitializer::initRowGroups(const QueryMemoryDescriptor& query_me
          ++bin, buffer_ptr += row_size) {
       result_set::fill_empty_key(
           buffer_ptr, key_count, query_mem_desc.getEffectiveKeyWidth());
-      initColumnsPerRow(query_mem_desc_fixedup,
-                        &buffer_ptr[col_base_off],
-                        init_vals,
-                        agg_bitmap_size,
-                        mode_index_set,
-                        quantile_params);
+      initColumnsPerRow(
+          query_mem_desc_fixedup, &buffer_ptr[col_base_off], init_vals, agg_op_metadata);
     }
   }
 }
@@ -647,37 +744,45 @@ void QueryMemoryInitializer::initColumnsPerRow(
     const QueryMemoryDescriptor& query_mem_desc,
     int8_t* row_ptr,
     const std::vector<int64_t>& init_vals,
-    const std::vector<int64_t>& bitmap_sizes,
-    const ModeIndexSet& mode_index_set,
-    const std::vector<QuantileParam>& quantile_params) {
+    const TargetAggOpsMetadata& agg_op_metadata) {
   int8_t* col_ptr = row_ptr;
   size_t init_vec_idx = 0;
   for (size_t col_idx = 0; col_idx < query_mem_desc.getSlotCount();
        col_ptr += query_mem_desc.getNextColOffInBytesRowOnly(col_ptr, col_idx++)) {
-    const int64_t bm_sz{bitmap_sizes[col_idx]};
     int64_t init_val{0};
-    if (bm_sz && query_mem_desc.isGroupBy()) {
-      // COUNT DISTINCT / APPROX_COUNT_DISTINCT
-      CHECK_EQ(static_cast<size_t>(query_mem_desc.getPaddedSlotWidthBytes(col_idx)),
-               sizeof(int64_t));
-      init_val =
-          bm_sz > 0 ? allocateCountDistinctBitmap(bm_sz) : allocateCountDistinctSet();
-      ++init_vec_idx;
-    } else if (query_mem_desc.isGroupBy() && quantile_params[col_idx]) {
-      auto const q = *quantile_params[col_idx];
-      // allocate for APPROX_QUANTILE only when slot is used
-      init_val = reinterpret_cast<int64_t>(row_set_mem_owner_->nullTDigest(q));
-      ++init_vec_idx;
-    } else if (query_mem_desc.isGroupBy() && mode_index_set.count(col_idx)) {
-      init_val = reinterpret_cast<int64_t>(row_set_mem_owner_->allocateMode());
-      ++init_vec_idx;
-    } else {
-      if (query_mem_desc.getPaddedSlotWidthBytes(col_idx) > 0) {
-        CHECK_LT(init_vec_idx, init_vals.size());
-        init_val = init_vals[init_vec_idx++];
+    if (query_mem_desc.isGroupBy()) {
+      if (agg_op_metadata.has_count_distinct) {
+        // COUNT DISTINCT / APPROX_COUNT_DISTINCT
+        // create a data structure for count_distinct operator per entries
+        const int64_t bm_sz{agg_op_metadata.count_distinct_buf_size[col_idx]};
+        if (bm_sz) {
+          CHECK_EQ(static_cast<size_t>(query_mem_desc.getPaddedSlotWidthBytes(col_idx)),
+                   sizeof(int64_t));
+          init_val =
+              bm_sz > 0 ? allocateCountDistinctBitmap(bm_sz) : allocateCountDistinctSet();
+          CHECK_NE(init_val, 0);
+          ++init_vec_idx;
+        }
+      } else if (agg_op_metadata.has_tdigest &&
+                 agg_op_metadata.qualtile_params[col_idx]) {
+        auto const q = *agg_op_metadata.qualtile_params[col_idx];
+        // allocate for APPROX_QUANTILE only when slot is used
+        init_val = reinterpret_cast<int64_t>(row_set_mem_owner_->nullTDigest(q));
+        CHECK_NE(init_val, 0);
+        ++init_vec_idx;
+      } else if (agg_op_metadata.has_mode &&
+                 agg_op_metadata.mode_index_set.count(col_idx)) {
+        init_val = reinterpret_cast<int64_t>(row_set_mem_owner_->allocateMode());
+        CHECK_NE(init_val, 0);
+        ++init_vec_idx;
       }
     }
-    switch (query_mem_desc.getPaddedSlotWidthBytes(col_idx)) {
+    auto const col_slot_width = query_mem_desc.getPaddedSlotWidthBytes(col_idx);
+    if (init_val == 0 && col_slot_width > 0) {
+      CHECK_LT(init_vec_idx, init_vals.size());
+      init_val = init_vals[init_vec_idx++];
+    }
+    switch (col_slot_width) {
       case 1:
         *col_ptr = static_cast<int8_t>(init_val);
         break;
@@ -717,72 +822,69 @@ void QueryMemoryInitializer::allocateCountDistinctGpuMem(
     total_bytes_per_entry += count_distinct_desc.bitmapPaddedSizeBytes();
   }
 
-  count_distinct_bitmap_mem_bytes_ =
+  count_distinct_bitmap_mem_size_ =
       total_bytes_per_entry * query_mem_desc.getEntryCount();
-  count_distinct_bitmap_mem_ = reinterpret_cast<CUdeviceptr>(
-      device_allocator_->alloc(count_distinct_bitmap_mem_bytes_));
-  device_allocator_->zeroDeviceMem(reinterpret_cast<int8_t*>(count_distinct_bitmap_mem_),
-                                   count_distinct_bitmap_mem_bytes_);
-
-  count_distinct_bitmap_crt_ptr_ = count_distinct_bitmap_host_mem_ =
-      row_set_mem_owner_->allocate(count_distinct_bitmap_mem_bytes_, thread_idx_);
+  count_distinct_bitmap_device_mem_ptr_ = reinterpret_cast<CUdeviceptr>(
+      device_allocator_->alloc(count_distinct_bitmap_mem_size_));
+  device_allocator_->zeroDeviceMem(
+      reinterpret_cast<int8_t*>(count_distinct_bitmap_device_mem_ptr_),
+      count_distinct_bitmap_mem_size_);
+  count_distinct_bitmap_host_crt_ptr_ = count_distinct_bitmap_host_mem_ptr_ =
+      row_set_mem_owner_->allocate(count_distinct_bitmap_mem_size_, thread_idx_);
 }
 
-// deferred is true for group by queries; initGroups will allocate a bitmap
-// for each group slot
-std::vector<int64_t> QueryMemoryInitializer::allocateCountDistinctBuffers(
+std::vector<int64_t> QueryMemoryInitializer::calculateCountDistinctBufferSize(
     const QueryMemoryDescriptor& query_mem_desc,
-    const bool deferred,
-    const Executor* executor,
-    const RelAlgExecutionUnit& ra_exe_unit) {
+    const RelAlgExecutionUnit& ra_exe_unit) const {
   const size_t agg_col_count{query_mem_desc.getSlotCount()};
-  std::vector<int64_t> agg_bitmap_size(deferred ? agg_col_count : 0);
-
-  CHECK_GE(agg_col_count, ra_exe_unit.target_exprs.size());
+  std::vector<int64_t> agg_bitmap_size(agg_col_count);
   for (size_t target_idx = 0; target_idx < ra_exe_unit.target_exprs.size();
        ++target_idx) {
     const auto target_expr = ra_exe_unit.target_exprs[target_idx];
     const auto agg_info = get_target_info(target_expr, g_bigint_count);
     if (is_distinct_target(agg_info)) {
-      CHECK(agg_info.is_agg &&
-            (agg_info.agg_kind == kCOUNT || agg_info.agg_kind == kCOUNT_IF ||
-             agg_info.agg_kind == kAPPROX_COUNT_DISTINCT));
-      CHECK(!agg_info.sql_type.is_varlen());
-
       const size_t agg_col_idx = query_mem_desc.getSlotIndexForSingleSlotCol(target_idx);
-      CHECK_LT(static_cast<size_t>(agg_col_idx), agg_col_count);
-
-      CHECK_EQ(static_cast<size_t>(query_mem_desc.getLogicalSlotWidthBytes(agg_col_idx)),
-               sizeof(int64_t));
       const auto& count_distinct_desc =
           query_mem_desc.getCountDistinctDescriptor(target_idx);
-      CHECK(count_distinct_desc.impl_type_ != CountDistinctImplType::Invalid);
       if (count_distinct_desc.impl_type_ == CountDistinctImplType::Bitmap) {
         const auto bitmap_byte_sz = count_distinct_desc.bitmapPaddedSizeBytes();
-        if (deferred) {
-          agg_bitmap_size[agg_col_idx] = bitmap_byte_sz;
-        } else {
-          init_agg_vals_[agg_col_idx] = allocateCountDistinctBitmap(bitmap_byte_sz);
-        }
+        agg_bitmap_size[agg_col_idx] = bitmap_byte_sz;
       } else {
         CHECK(count_distinct_desc.impl_type_ == CountDistinctImplType::UnorderedSet);
-        if (deferred) {
-          agg_bitmap_size[agg_col_idx] = -1;
-        } else {
-          init_agg_vals_[agg_col_idx] = allocateCountDistinctSet();
-        }
+        agg_bitmap_size[agg_col_idx] = -1;
       }
     }
   }
-
   return agg_bitmap_size;
 }
 
+void QueryMemoryInitializer::allocateCountDistinctBuffers(
+    const QueryMemoryDescriptor& query_mem_desc,
+    const RelAlgExecutionUnit& ra_exe_unit) {
+  for (size_t target_idx = 0; target_idx < ra_exe_unit.target_exprs.size();
+       ++target_idx) {
+    const auto target_expr = ra_exe_unit.target_exprs[target_idx];
+    const auto agg_info = get_target_info(target_expr, g_bigint_count);
+    if (is_distinct_target(agg_info)) {
+      const size_t agg_col_idx = query_mem_desc.getSlotIndexForSingleSlotCol(target_idx);
+      const auto& count_distinct_desc =
+          query_mem_desc.getCountDistinctDescriptor(target_idx);
+      if (count_distinct_desc.impl_type_ == CountDistinctImplType::Bitmap) {
+        const auto bitmap_byte_sz = count_distinct_desc.bitmapPaddedSizeBytes();
+        init_agg_vals_[agg_col_idx] = allocateCountDistinctBitmap(bitmap_byte_sz);
+      } else {
+        CHECK(count_distinct_desc.impl_type_ == CountDistinctImplType::UnorderedSet);
+        init_agg_vals_[agg_col_idx] = allocateCountDistinctSet();
+      }
+    }
+  }
+}
+
 int64_t QueryMemoryInitializer::allocateCountDistinctBitmap(const size_t bitmap_byte_sz) {
-  if (count_distinct_bitmap_host_mem_) {
-    CHECK(count_distinct_bitmap_crt_ptr_);
-    auto ptr = count_distinct_bitmap_crt_ptr_;
-    count_distinct_bitmap_crt_ptr_ += bitmap_byte_sz;
+  if (count_distinct_bitmap_host_mem_ptr_) {
+    CHECK(count_distinct_bitmap_host_crt_ptr_);
+    auto ptr = count_distinct_bitmap_host_crt_ptr_;
+    count_distinct_bitmap_host_crt_ptr_ += bitmap_byte_sz;
     row_set_mem_owner_->addCountDistinctBuffer(
         ptr, bitmap_byte_sz, /*physial_buffer=*/false);
     return reinterpret_cast<int64_t>(ptr);
@@ -815,15 +917,12 @@ void eachAggregateTargetIdxOfType(
 
 }  // namespace
 
-QueryMemoryInitializer::ModeIndexSet QueryMemoryInitializer::allocateModes(
+QueryMemoryInitializer::ModeIndexSet QueryMemoryInitializer::initializeModeIndexSet(
     const QueryMemoryDescriptor& query_mem_desc,
-    const bool deferred,
-    const Executor* executor,
     const RelAlgExecutionUnit& ra_exe_unit) {
   size_t const slot_count = query_mem_desc.getSlotCount();
   CHECK_LE(ra_exe_unit.target_exprs.size(), slot_count);
   ModeIndexSet mode_index_set;
-
   eachAggregateTargetIdxOfType(
       ra_exe_unit.target_exprs,
       kMODE,
@@ -831,25 +930,57 @@ QueryMemoryInitializer::ModeIndexSet QueryMemoryInitializer::allocateModes(
         size_t const agg_col_idx =
             query_mem_desc.getSlotIndexForSingleSlotCol(target_idx);
         CHECK_LT(agg_col_idx, slot_count);
-        if (deferred) {
-          mode_index_set.emplace(agg_col_idx);
-        } else {
-          AggMode* agg_mode = row_set_mem_owner_->allocateMode();
-          init_agg_vals_[agg_col_idx] = reinterpret_cast<int64_t>(agg_mode);
-        }
+        mode_index_set.emplace(agg_col_idx);
       });
   return mode_index_set;
 }
 
+void QueryMemoryInitializer::allocateModeBuffer(
+    const QueryMemoryDescriptor& query_mem_desc,
+    const RelAlgExecutionUnit& ra_exe_unit) {
+  size_t const slot_count = query_mem_desc.getSlotCount();
+  CHECK_LE(ra_exe_unit.target_exprs.size(), slot_count);
+  eachAggregateTargetIdxOfType(
+      ra_exe_unit.target_exprs,
+      kMODE,
+      [&](Analyzer::AggExpr const*, size_t const target_idx) {
+        size_t const agg_col_idx =
+            query_mem_desc.getSlotIndexForSingleSlotCol(target_idx);
+        CHECK_LT(agg_col_idx, slot_count);
+        AggMode* agg_mode = row_set_mem_owner_->allocateMode();
+        init_agg_vals_[agg_col_idx] = reinterpret_cast<int64_t>(agg_mode);
+      });
+}
+
 std::vector<QueryMemoryInitializer::QuantileParam>
-QueryMemoryInitializer::allocateTDigests(const QueryMemoryDescriptor& query_mem_desc,
-                                         const bool deferred,
-                                         const Executor* executor,
-                                         const RelAlgExecutionUnit& ra_exe_unit) {
+QueryMemoryInitializer::initializeQuantileParams(
+    const QueryMemoryDescriptor& query_mem_desc,
+    const RelAlgExecutionUnit& ra_exe_unit) {
   size_t const slot_count = query_mem_desc.getSlotCount();
   CHECK_LE(ra_exe_unit.target_exprs.size(), slot_count);
-  std::vector<QuantileParam> quantile_params(deferred ? slot_count : 0);
+  std::vector<QuantileParam> quantile_params(slot_count);
+  eachAggregateTargetIdxOfType(
+      ra_exe_unit.target_exprs,
+      kAPPROX_QUANTILE,
+      [&](Analyzer::AggExpr const* const agg_expr, size_t const target_idx) {
+        size_t const agg_col_idx =
+            query_mem_desc.getSlotIndexForSingleSlotCol(target_idx);
+        CHECK_LT(agg_col_idx, slot_count);
+        CHECK_EQ(static_cast<int8_t>(sizeof(int64_t)),
+                 query_mem_desc.getLogicalSlotWidthBytes(agg_col_idx));
+        auto const q_expr =
+            dynamic_cast<Analyzer::Constant const*>(agg_expr->get_arg1().get());
+        CHECK(q_expr);
+        quantile_params[agg_col_idx] = q_expr->get_constval().doubleval;
+      });
+  return quantile_params;
+}
 
+void QueryMemoryInitializer::allocateTDigestsBuffer(
+    const QueryMemoryDescriptor& query_mem_desc,
+    const RelAlgExecutionUnit& ra_exe_unit) {
+  size_t const slot_count = query_mem_desc.getSlotCount();
+  CHECK_LE(ra_exe_unit.target_exprs.size(), slot_count);
   eachAggregateTargetIdxOfType(
       ra_exe_unit.target_exprs,
       kAPPROX_QUANTILE,
@@ -863,15 +994,10 @@ QueryMemoryInitializer::allocateTDigests(const QueryMemoryDescriptor& query_mem_
             dynamic_cast<Analyzer::Constant const*>(agg_expr->get_arg1().get());
         CHECK(q_expr);
         auto const q = q_expr->get_constval().doubleval;
-        if (deferred) {
-          quantile_params[agg_col_idx] = q;
-        } else {
-          // allocate for APPROX_QUANTILE only when slot is used
-          init_agg_vals_[agg_col_idx] =
-              reinterpret_cast<int64_t>(row_set_mem_owner_->nullTDigest(q));
-        }
+        // allocate for APPROX_QUANTILE only when slot is used
+        init_agg_vals_[agg_col_idx] =
+            reinterpret_cast<int64_t>(row_set_mem_owner_->nullTDigest(q));
       });
-  return quantile_params;
 }
 
 GpuGroupByBuffers QueryMemoryInitializer::prepareTopNHeapsDevBuffer(
diff --git a/QueryEngine/QueryMemoryInitializer.h b/QueryEngine/QueryMemoryInitializer.h
index f2f6114aab..f193a58241 100644
--- a/QueryEngine/QueryMemoryInitializer.h
+++ b/QueryEngine/QueryMemoryInitializer.h
@@ -34,6 +34,17 @@
 
 class QueryMemoryInitializer {
  public:
+  using ModeIndexSet = robin_hood::unordered_set<size_t>;
+  using QuantileParam = std::optional<double>;
+  struct TargetAggOpsMetadata {
+    bool has_count_distinct{false};
+    bool has_mode{false};
+    bool has_tdigest{false};
+    std::vector<int64_t> count_distinct_buf_size;
+    ModeIndexSet mode_index_set;
+    std::vector<QuantileParam> qualtile_params;
+  };
+
   // Row-based execution constructor
   QueryMemoryInitializer(const RelAlgExecutionUnit& ra_exe_unit,
                          const QueryMemoryDescriptor& query_mem_desc,
@@ -65,12 +76,16 @@ class QueryMemoryInitializer {
                          DeviceAllocator* device_allocator,
                          const Executor* executor);
 
-  const auto getCountDistinctBitmapPtr() const { return count_distinct_bitmap_mem_; }
+  const auto getCountDistinctBitmapDevicePtr() const {
+    return count_distinct_bitmap_device_mem_ptr_;
+  }
 
-  const auto getCountDistinctHostPtr() const { return count_distinct_bitmap_host_mem_; }
+  const auto getCountDistinctBitmapHostPtr() const {
+    return count_distinct_bitmap_host_mem_ptr_;
+  }
 
   const auto getCountDistinctBitmapBytes() const {
-    return count_distinct_bitmap_mem_bytes_;
+    return count_distinct_bitmap_mem_size_;
   }
 
   // TODO: lazy init (maybe lazy init count distinct above, too?)
@@ -138,6 +153,7 @@ class QueryMemoryInitializer {
   void initGroupByBuffer(int64_t* buffer,
                          const RelAlgExecutionUnit& ra_exe_unit,
                          const QueryMemoryDescriptor& query_mem_desc,
+                         TargetAggOpsMetadata& agg_expr_metadata,
                          const ExecutorDeviceType device_type,
                          const bool output_columnar,
                          const Executor* executor);
@@ -145,6 +161,7 @@ class QueryMemoryInitializer {
   void initRowGroups(const QueryMemoryDescriptor& query_mem_desc,
                      int64_t* groups_buffer,
                      const std::vector<int64_t>& init_vals,
+                     TargetAggOpsMetadata& agg_expr_metadata,
                      const int32_t groups_buffer_entry_count,
                      const size_t warp_size,
                      const Executor* executor,
@@ -156,36 +173,36 @@ class QueryMemoryInitializer {
                           const Executor* executor,
                           const RelAlgExecutionUnit& ra_exe_unit);
 
-  using ModeIndexSet = robin_hood::unordered_set<size_t>;
-  using QuantileParam = std::optional<double>;
   void initColumnsPerRow(const QueryMemoryDescriptor& query_mem_desc,
                          int8_t* row_ptr,
                          const std::vector<int64_t>& init_vals,
-                         const std::vector<int64_t>& bitmap_sizes,
-                         const ModeIndexSet& mode_index_set,
-                         const std::vector<QuantileParam>& quantile_params);
+                         const TargetAggOpsMetadata& agg_op_metadata);
 
   void allocateCountDistinctGpuMem(const QueryMemoryDescriptor& query_mem_desc);
 
-  std::vector<int64_t> allocateCountDistinctBuffers(
+  std::vector<int64_t> calculateCountDistinctBufferSize(
       const QueryMemoryDescriptor& query_mem_desc,
-      const bool deferred,
-      const Executor* executor,
-      const RelAlgExecutionUnit& ra_exe_unit);
+      const RelAlgExecutionUnit& ra_exe_unit) const;
+
+  void allocateCountDistinctBuffers(const QueryMemoryDescriptor& query_mem_desc,
+                                    const RelAlgExecutionUnit& ra_exe_unit);
 
   int64_t allocateCountDistinctBitmap(const size_t bitmap_byte_sz);
 
   int64_t allocateCountDistinctSet();
 
-  ModeIndexSet allocateModes(const QueryMemoryDescriptor& query_mem_desc,
-                             const bool deferred,
-                             const Executor* executor,
-                             const RelAlgExecutionUnit& ra_exe_unit);
+  ModeIndexSet initializeModeIndexSet(const QueryMemoryDescriptor& query_mem_desc,
+                                      const RelAlgExecutionUnit& ra_exe_unit);
+
+  void allocateModeBuffer(const QueryMemoryDescriptor& query_mem_desc,
+                          const RelAlgExecutionUnit& ra_exe_unit);
+
+  std::vector<QuantileParam> initializeQuantileParams(
+      const QueryMemoryDescriptor& query_mem_desc,
+      const RelAlgExecutionUnit& ra_exe_unit);
 
-  std::vector<QuantileParam> allocateTDigests(const QueryMemoryDescriptor& query_mem_desc,
-                                              const bool deferred,
-                                              const Executor* executor,
-                                              const RelAlgExecutionUnit& ra_exe_unit);
+  void allocateTDigestsBuffer(const QueryMemoryDescriptor& query_mem_desc,
+                              const RelAlgExecutionUnit& ra_exe_unit);
 
   GpuGroupByBuffers prepareTopNHeapsDevBuffer(const QueryMemoryDescriptor& query_mem_desc,
                                               const int8_t* init_agg_vals_dev_ptr,
@@ -243,10 +260,10 @@ class QueryMemoryInitializer {
   CUdeviceptr varlen_output_buffer_;
   int8_t* varlen_output_buffer_host_ptr_;
 
-  CUdeviceptr count_distinct_bitmap_mem_;
-  size_t count_distinct_bitmap_mem_bytes_;
-  int8_t* count_distinct_bitmap_crt_ptr_;
-  int8_t* count_distinct_bitmap_host_mem_;
+  CUdeviceptr count_distinct_bitmap_device_mem_ptr_;
+  size_t count_distinct_bitmap_mem_size_;
+  int8_t* count_distinct_bitmap_host_crt_ptr_;
+  int8_t* count_distinct_bitmap_host_mem_ptr_;
 
   DeviceAllocator* device_allocator_{nullptr};
   std::vector<Data_Namespace::AbstractBuffer*> temporary_buffers_;
diff --git a/QueryEngine/RelAlgDag.cpp b/QueryEngine/RelAlgDag.cpp
index a29dc91ea6..b9c5f918c0 100644
--- a/QueryEngine/RelAlgDag.cpp
+++ b/QueryEngine/RelAlgDag.cpp
@@ -2168,6 +2168,30 @@ void handle_agg_over_join(
   }
 }
 
+void eliminate_redundant_projection(std::vector<std::shared_ptr<RelAlgNode>>& nodes) {
+  for (auto& node : nodes) {
+    if (auto* proj_node = dynamic_cast<RelProject*>(node.get())) {
+      if (proj_node->isSimple()) {
+        if (auto child_proj_node =
+                dynamic_cast<RelProject const*>(proj_node->getInput(0))) {
+          std::vector<std::unique_ptr<RexScalar const>> scalar_exprs;
+          RexDeepCopyVisitor copier;
+          for (size_t i = 0; i < proj_node->size(); i++) {
+            auto rex_abs_input =
+                dynamic_cast<RexAbstractInput const*>(proj_node->getProjectAt(i));
+            scalar_exprs.push_back(
+                copier.visit(child_proj_node->getProjectAt(rex_abs_input->getIndex())));
+          }
+          CHECK_EQ(scalar_exprs.size(), proj_node->getFields().size());
+          proj_node->setExpressions(scalar_exprs);
+          proj_node->replaceInput(proj_node->getAndOwnInput(0),
+                                  child_proj_node->getAndOwnInput(0));
+        }
+      }
+    }
+  }
+}
+
 class WindowFunctionCollector : public RexVisitor<void*> {
  public:
   WindowFunctionCollector(
@@ -3408,6 +3432,7 @@ void RelAlgDagBuilder::optimizeDag(RelAlgDag& rel_alg_dag) {
   CHECK(nodes.back().use_count() == 1);
   create_left_deep_join(nodes);
   handle_agg_over_join(nodes, query_hints);
+  eliminate_redundant_projection(nodes);
 
   setBuildState(rel_alg_dag, RelAlgDag::BuildState::kBuiltOptimized);
 }
diff --git a/QueryEngine/RelAlgDag.h b/QueryEngine/RelAlgDag.h
index d81ba53eed..4e7a254d97 100644
--- a/QueryEngine/RelAlgDag.h
+++ b/QueryEngine/RelAlgDag.h
@@ -72,7 +72,7 @@ using RelAlgInputs = std::vector<std::shared_ptr<const RelAlgNode>>;
 using ColumnNameList = std::vector<std::string>;
 
 struct RelRexToStringConfig {
-  bool skip_input_nodes{false};
+  bool skip_input_nodes{true};
   bool attributes_only{false};
 
   static RelRexToStringConfig defaults() { return RelRexToStringConfig(); }
@@ -3154,7 +3154,10 @@ class RelAlgDag : public boost::noncopyable {
           CHECK_EQ(1u, target.getListOptions().size());
           int loop_size_threshold = std::stoi(target.getListOptions()[0]);
           if (loop_size_threshold <= 0) {
-            VLOG(1) << "The loop size threshold should be larger than zero";
+            VLOG(1)
+                << "Skip the given query hint \"loop_join_inner_table_max_num_rows\" ("
+                << target.getListOptions()[0]
+                << ") : the hint value should be larger than zero";
           } else {
             query_hint.registerHint(QueryHint::kLoopJoinInnerTableMaxNumRows);
             query_hint.loop_join_inner_table_max_num_rows = loop_size_threshold;
@@ -3169,7 +3172,9 @@ class RelAlgDag : public boost::noncopyable {
           CHECK_EQ(1u, target.getListOptions().size());
           int max_join_hash_table_size = std::stoi(target.getListOptions()[0]);
           if (max_join_hash_table_size <= 0) {
-            VLOG(1) << "The maximum hash table size should be larger than zero";
+            VLOG(1) << "Skip the given query hint \"max_join_hashtable_size\" ("
+                    << target.getListOptions()[0]
+                    << ") : the hint value should be larger than zero";
           } else {
             query_hint.registerHint(QueryHint::kMaxJoinHashTableSize);
             query_hint.max_join_hash_table_size = max_join_hash_table_size;
@@ -3198,6 +3203,46 @@ class RelAlgDag : public boost::noncopyable {
           }
           break;
         }
+        case QueryHint::kWatchdogMaxProjectedRowsPerDevice: {
+          CHECK_EQ(1u, target.getListOptions().size());
+          int watchdog_max_projected_rows_per_device =
+              std::stoi(target.getListOptions()[0]);
+          if (watchdog_max_projected_rows_per_device <= 0) {
+            VLOG(1) << "Skip the given query hint "
+                       "\"watchdog_max_projected_rows_per_device\" ("
+                    << target.getListOptions()[0]
+                    << ") : the hint value should be larger than zero";
+          } else {
+            query_hint.registerHint(QueryHint::kWatchdogMaxProjectedRowsPerDevice);
+            query_hint.watchdog_max_projected_rows_per_device =
+                watchdog_max_projected_rows_per_device;
+            if (target.isGlobalHint()) {
+              global_query_hint.registerHint(
+                  QueryHint::kWatchdogMaxProjectedRowsPerDevice);
+              global_query_hint.watchdog_max_projected_rows_per_device =
+                  watchdog_max_projected_rows_per_device;
+            }
+          }
+          break;
+        }
+        case QueryHint::kPreflightCountQueryThreshold: {
+          CHECK_EQ(1u, target.getListOptions().size());
+          int preflight_count_query_threshold = std::stoi(target.getListOptions()[0]);
+          if (preflight_count_query_threshold <= 0) {
+            VLOG(1) << "Skip the given query hint \"preflight_count_query_threshold\" ("
+                    << target.getListOptions()[0]
+                    << ") : the hint value should be larger than zero";
+          } else {
+            query_hint.registerHint(QueryHint::kPreflightCountQueryThreshold);
+            query_hint.preflight_count_query_threshold = preflight_count_query_threshold;
+            if (target.isGlobalHint()) {
+              global_query_hint.registerHint(QueryHint::kPreflightCountQueryThreshold);
+              global_query_hint.preflight_count_query_threshold =
+                  preflight_count_query_threshold;
+            }
+          }
+          break;
+        }
         default:
           break;
       }
diff --git a/QueryEngine/RelAlgExecutionUnit.h b/QueryEngine/RelAlgExecutionUnit.h
index 961649e813..72d55bf82b 100644
--- a/QueryEngine/RelAlgExecutionUnit.h
+++ b/QueryEngine/RelAlgExecutionUnit.h
@@ -182,6 +182,7 @@ struct RelAlgExecutionUnit {
   const std::optional<bool> union_all;
   std::shared_ptr<const query_state::QueryState> query_state;
   std::vector<Analyzer::Expr*> target_exprs_union;  // targets in second subquery of UNION
+  mutable std::vector<std::pair<std::vector<size_t>, size_t>> per_device_cardinality;
 
   RelAlgExecutionUnit createNdvExecutionUnit(const int64_t range) const;
   RelAlgExecutionUnit createCountAllExecutionUnit(
diff --git a/QueryEngine/RelAlgExecutor.cpp b/QueryEngine/RelAlgExecutor.cpp
index 2875e53a6b..41b9f48f18 100644
--- a/QueryEngine/RelAlgExecutor.cpp
+++ b/QueryEngine/RelAlgExecutor.cpp
@@ -60,6 +60,7 @@ size_t g_columnar_large_projections_threshold{1000000};
 
 extern bool g_enable_watchdog;
 extern size_t g_watchdog_none_encoded_string_translation_limit;
+extern size_t g_preflight_count_query_threshold;
 extern bool g_enable_bump_allocator;
 extern size_t g_default_max_groups_buffer_entry_guess;
 extern bool g_enable_system_tables;
@@ -878,8 +879,8 @@ void RelAlgExecutor::prepareLeafExecution(
     executor_->resetInterrupt();
   }
   queue_time_ms_ = timer_stop(clock_begin);
-  executor_->row_set_mem_owner_ =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(), cpu_threads());
+  executor_->row_set_mem_owner_ = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), executor_->executor_id_, cpu_threads());
   executor_->row_set_mem_owner_->setDictionaryGenerations(string_dictionary_generations);
   executor_->table_generations_ = table_generations;
   executor_->agg_col_range_cache_ = agg_col_range;
@@ -1951,22 +1952,24 @@ bool is_agg(const Analyzer::Expr* expr) {
   const auto agg_expr = dynamic_cast<const Analyzer::AggExpr*>(expr);
   if (agg_expr && agg_expr->get_contains_agg()) {
     auto agg_type = agg_expr->get_aggtype();
-    if (agg_type == SQLAgg::kMIN || agg_type == SQLAgg::kMAX ||
-        agg_type == SQLAgg::kSUM || agg_type == SQLAgg::kSUM_IF ||
-        agg_type == SQLAgg::kAVG) {
+    if (shared::is_any<SQLAgg::kMIN,
+                       SQLAgg::kMAX,
+                       SQLAgg::kSUM,
+                       SQLAgg::kSUM_IF,
+                       SQLAgg::kAVG>(agg_type)) {
       return true;
     }
   }
   return false;
 }
 
-inline SQLTypeInfo get_logical_type_for_expr(const Analyzer::Expr& expr) {
-  if (is_count_distinct(&expr)) {
-    return SQLTypeInfo(kBIGINT, false);
-  } else if (is_agg(&expr)) {
-    return get_nullable_logical_type_info(expr.get_type_info());
+inline SQLTypeInfo get_logical_type_for_expr(const Analyzer::Expr* expr) {
+  if (is_count_distinct(expr)) {
+    return SQLTypeInfo(kBIGINT, true);
+  } else if (is_agg(expr)) {
+    return get_nullable_logical_type_info(expr->get_type_info());
   }
-  return get_logical_type_info(expr.get_type_info());
+  return get_logical_type_info(expr->get_type_info());
 }
 
 template <class RA>
@@ -1978,7 +1981,7 @@ std::vector<TargetMetaInfo> get_targets_meta(
   for (size_t i = 0; i < ra_node->size(); ++i) {
     CHECK(target_exprs[i]);
     // TODO(alex): remove the count distinct type fixup.
-    auto ti = get_logical_type_for_expr(*target_exprs[i]);
+    auto ti = get_logical_type_for_expr(target_exprs[i]);
     targets_meta.emplace_back(
         ra_node->getFieldName(i), ti, target_exprs[i]->get_type_info());
   }
@@ -3212,6 +3215,10 @@ bool first_oe_is_desc(const std::list<Analyzer::OrderEntry>& order_entries) {
   return !order_entries.empty() && order_entries.front().is_desc;
 }
 
+bool is_none_encoded_text(TargetMetaInfo const& target_meta_info) {
+  return target_meta_info.get_type_info().is_none_encoded_string();
+}
+
 }  // namespace
 
 ExecutionResult RelAlgExecutor::executeSort(const RelSort* sort,
@@ -3311,33 +3318,21 @@ ExecutionResult RelAlgExecutor::executeSort(const RelSort* sort,
     if (!source_result.getDataPtr()) {
       const auto source_work_unit = createSortInputWorkUnit(sort, order_entries, eo);
       is_desc = first_oe_is_desc(order_entries);
-      ExecutionOptions eo_copy = {
-          eo.output_columnar_hint,
-          eo.keep_result,
-          eo.allow_multifrag,
-          eo.just_explain,
-          eo.allow_loop_joins,
-          eo.with_watchdog,
-          eo.jit_debug,
-          eo.just_validate || sort->isEmptyResult(),
-          eo.with_dynamic_watchdog,
-          eo.dynamic_watchdog_time_limit,
-          eo.find_push_down_candidates,
-          eo.just_calcite_explain,
-          eo.gpu_input_mem_limit_percent,
-          eo.allow_runtime_query_interrupt,
-          eo.running_query_interrupt_freq,
-          eo.pending_query_interrupt_freq,
-          eo.optimize_cuda_block_and_grid_sizes,
-          eo.max_join_hash_table_size,
-          eo.executor_type,
-      };
+      ExecutionOptions eo_copy = eo;
+      CompilationOptions co_copy = co;
+      eo_copy.just_validate = eo.just_validate || sort->isEmptyResult();
+      if (hasStepForUnion() &&
+          boost::algorithm::any_of(source->getOutputMetainfo(), is_none_encoded_text)) {
+        co_copy.device_type = ExecutorDeviceType::CPU;
+        VLOG(1) << "Punt sort's input query to CPU: detect union(-all) of none-encoded "
+                   "text column";
+      }
 
       groupby_exprs = source_work_unit.exe_unit.groupby_exprs;
       source_result = executeWorkUnit(source_work_unit,
                                       source->getOutputMetainfo(),
                                       is_aggregate,
-                                      co,
+                                      co_copy,
                                       eo_copy,
                                       render_info,
                                       queue_time_ms);
@@ -3464,16 +3459,19 @@ namespace {
  * estimation. We don't account for cross-joins and / or group by unnested array, which
  * is the reason this estimation isn't entirely reliable.
  */
-size_t groups_approx_upper_bound(const std::vector<InputTableInfo>& table_infos) {
+std::pair<size_t, shared::TableKey> groups_approx_upper_bound(
+    const std::vector<InputTableInfo>& table_infos) {
   CHECK(!table_infos.empty());
   const auto& first_table = table_infos.front();
   size_t max_num_groups = first_table.info.getNumTuplesUpperBound();
+  auto table_key = first_table.table_key;
   for (const auto& table_info : table_infos) {
     if (table_info.info.getNumTuplesUpperBound() > max_num_groups) {
       max_num_groups = table_info.info.getNumTuplesUpperBound();
+      table_key = table_info.table_key;
     }
   }
-  return std::max(max_num_groups, size_t(1));
+  return std::make_pair(std::max(max_num_groups, size_t(1)), table_key);
 }
 
 bool is_projection(const RelAlgExecutionUnit& ra_exe_unit) {
@@ -3543,8 +3541,16 @@ bool compute_output_buffer_size(const RelAlgExecutionUnit& ra_exe_unit) {
       return false;
     }
   }
-  if (ra_exe_unit.groupby_exprs.size() == 1 && !ra_exe_unit.groupby_exprs.front() &&
-      (!ra_exe_unit.scan_limit || ra_exe_unit.scan_limit > Executor::high_scan_limit)) {
+  size_t preflight_count_query_threshold = g_preflight_count_query_threshold;
+  if (ra_exe_unit.query_hint.isHintRegistered(QueryHint::kPreflightCountQueryThreshold)) {
+    preflight_count_query_threshold =
+        ra_exe_unit.query_hint.preflight_count_query_threshold;
+    VLOG(1) << "Set the pre-flight count query's threshold as "
+            << preflight_count_query_threshold << " by a query hint";
+  }
+  if (is_projection(ra_exe_unit) &&
+      (!ra_exe_unit.scan_limit ||
+       ra_exe_unit.scan_limit > preflight_count_query_threshold)) {
     return true;
   }
   return false;
@@ -3606,12 +3612,14 @@ RelAlgExecutionUnit decide_approx_count_distinct_implementation(
     }
     CountDistinctDescriptor approx_count_distinct_desc{CountDistinctImplType::Bitmap,
                                                        arg_range.getIntMin(),
+                                                       0,
                                                        approx_bitmap_sz_bits,
                                                        true,
                                                        device_type,
                                                        sub_bitmap_count};
     CountDistinctDescriptor precise_count_distinct_desc{CountDistinctImplType::Bitmap,
                                                         arg_range.getIntMin(),
+                                                        0,
                                                         bitmap_sz_bits,
                                                         false,
                                                         device_type,
@@ -3794,9 +3802,21 @@ ExecutionResult RelAlgExecutor::executeWorkUnit(
       } else if (eo.executor_type == ::ExecutorType::Extern) {
         ra_exe_unit.scan_limit = 0;
       } else if (!eo.just_explain) {
-        const auto filter_count_all = getFilteredCountAll(work_unit, true, co, eo);
+        const auto filter_count_all = getFilteredCountAll(ra_exe_unit, true, co, eo);
         if (filter_count_all) {
           ra_exe_unit.scan_limit = std::max(*filter_count_all, size_t(1));
+          VLOG(1) << "Set a new scan limit from filtered_count_all: "
+                  << ra_exe_unit.scan_limit;
+          auto const has_limit_value = ra_exe_unit.sort_info.limit.has_value();
+          auto const top_k_sort_query =
+              has_limit_value && !ra_exe_unit.sort_info.order_entries.empty();
+          // top-k sort query needs to get a global result before sorting, so we cannot
+          // apply LIMIT value at this point
+          if (has_limit_value && !top_k_sort_query &&
+              ra_exe_unit.scan_limit > ra_exe_unit.sort_info.limit.value()) {
+            ra_exe_unit.scan_limit = ra_exe_unit.sort_info.limit.value();
+            VLOG(1) << "Override scan limit to LIMIT value: " << ra_exe_unit.scan_limit;
+          }
         }
       }
     }
@@ -3880,12 +3900,15 @@ ExecutionResult RelAlgExecutor::executeWorkUnit(
     auto cached_cardinality = executor_->getCachedCardinality(cache_key);
     auto card = cached_cardinality.second;
     if (cached_cardinality.first && card >= 0) {
+      VLOG(1) << "Use cached cardinality for max_groups_buffer_entry_guess: " << card;
       result = execute_and_handle_errors(
           card, /*has_cardinality_estimation=*/true, /*has_ndv_estimation=*/false);
     } else {
+      VLOG(1) << "Use default cardinality for max_groups_buffer_entry_guess: "
+              << max_groups_buffer_entry_guess;
       result = execute_and_handle_errors(
           max_groups_buffer_entry_guess,
-          groups_approx_upper_bound(table_infos) <= g_big_group_threshold,
+          groups_approx_upper_bound(table_infos).first <= g_big_group_threshold,
           /*has_ndv_estimation=*/false);
     }
   } catch (const CardinalityEstimationRequired& e) {
@@ -3893,15 +3916,23 @@ ExecutionResult RelAlgExecutor::executeWorkUnit(
     auto cached_cardinality = executor_->getCachedCardinality(cache_key);
     auto card = cached_cardinality.second;
     if (cached_cardinality.first && card >= 0) {
+      VLOG(1) << "CardinalityEstimationRequired, Use cached cardinality for "
+                 "max_groups_buffer_entry_guess: "
+              << card;
       result = execute_and_handle_errors(card, true, /*has_ndv_estimation=*/true);
     } else {
       const auto ndv_groups_estimation =
           getNDVEstimation(work_unit, e.range(), is_agg, co, eo);
       const auto estimated_groups_buffer_entry_guess =
-          ndv_groups_estimation > 0 ? 2 * ndv_groups_estimation
-                                    : std::min(groups_approx_upper_bound(table_infos),
-                                               g_estimator_failure_max_groupby_size);
+          ndv_groups_estimation > 0
+              ? 2 * ndv_groups_estimation
+              : std::min(groups_approx_upper_bound(table_infos).first,
+                         g_estimator_failure_max_groupby_size);
       CHECK_GT(estimated_groups_buffer_entry_guess, size_t(0));
+      VLOG(1) << "CardinalityEstimationRequired, Use ndv_estimation: "
+              << ndv_groups_estimation
+              << ", cardinality for estimated_groups_buffer_entry_guess: "
+              << estimated_groups_buffer_entry_guess;
       result = execute_and_handle_errors(
           estimated_groups_buffer_entry_guess, true, /*has_ndv_estimation=*/true);
       if (!(eo.just_validate || eo.just_explain)) {
@@ -3982,32 +4013,80 @@ ExecutionResult RelAlgExecutor::executeWorkUnit(
   return result;
 }
 
-std::optional<size_t> RelAlgExecutor::getFilteredCountAll(const WorkUnit& work_unit,
-                                                          const bool is_agg,
-                                                          const CompilationOptions& co,
-                                                          const ExecutionOptions& eo) {
+bool RelAlgExecutor::hasDeletedRowInQuery(
+    std::vector<InputTableInfo> const& input_tables_info) const {
+  return std::any_of(
+      input_tables_info.begin(), input_tables_info.end(), [](InputTableInfo const& info) {
+        auto const& table_key = info.table_key;
+        if (table_key.db_id > 0) {
+          auto catalog =
+              Catalog_Namespace::SysCatalog::instance().getCatalog(table_key.db_id);
+          CHECK(catalog);
+          auto td = catalog->getMetadataForTable(table_key.table_id);
+          CHECK(td);
+          if (catalog->getDeletedColumnIfRowsDeleted(td)) {
+            return true;
+          }
+        }
+        return false;
+      });
+}
+
+namespace {
+std::string get_table_name_from_table_key(shared::TableKey const& table_key) {
+  std::string table_name{""};
+  if (table_key.db_id > 0) {
+    auto catalog = Catalog_Namespace::SysCatalog::instance().getCatalog(table_key.db_id);
+    CHECK(catalog);
+    auto td = catalog->getMetadataForTable(table_key.table_id);
+    CHECK(td);
+    table_name = td->tableName;
+  }
+  return table_name;
+}
+}  // namespace
+
+std::optional<size_t> RelAlgExecutor::getFilteredCountAll(
+    const RelAlgExecutionUnit& ra_exe_unit,
+    const bool is_agg,
+    const CompilationOptions& co,
+    const ExecutionOptions& eo) {
+  auto const input_tables_info = get_table_infos(ra_exe_unit, executor_);
+  if (is_projection(ra_exe_unit) && ra_exe_unit.simple_quals.empty() &&
+      ra_exe_unit.quals.empty() && ra_exe_unit.join_quals.empty() &&
+      !hasDeletedRowInQuery(input_tables_info)) {
+    auto const max_row_info = groups_approx_upper_bound(input_tables_info);
+    auto const num_rows = max_row_info.first;
+    auto const table_name = get_table_name_from_table_key(max_row_info.second);
+    VLOG(1) << "Short-circuiting filtered count query for the projection query "
+               "containing input table "
+            << table_name << ": return its table cardinality " << num_rows << " instead";
+    return std::make_optional<size_t>(num_rows);
+  }
   const auto count =
       makeExpr<Analyzer::AggExpr>(SQLTypeInfo(g_bigint_count ? kBIGINT : kINT, false),
                                   kCOUNT,
                                   nullptr,
                                   false,
                                   nullptr);
-  const auto count_all_exe_unit =
-      work_unit.exe_unit.createCountAllExecutionUnit(count.get());
+  const auto count_all_exe_unit = ra_exe_unit.createCountAllExecutionUnit(count.get());
   size_t one{1};
   ResultSetPtr count_all_result;
   try {
+    VLOG(1) << "Try to execute pre-flight counts query";
     ColumnCacheMap column_cache;
-    count_all_result =
-        executor_->executeWorkUnit(one,
-                                   is_agg,
-                                   get_table_infos(work_unit.exe_unit, executor_),
-                                   count_all_exe_unit,
-                                   co,
-                                   eo,
-                                   nullptr,
-                                   false,
-                                   column_cache);
+    ExecutionOptions copied_eo = eo;
+    copied_eo.estimate_output_cardinality = true;
+    count_all_result = executor_->executeWorkUnit(one,
+                                                  is_agg,
+                                                  input_tables_info,
+                                                  count_all_exe_unit,
+                                                  co,
+                                                  copied_eo,
+                                                  nullptr,
+                                                  false,
+                                                  column_cache);
+    ra_exe_unit.per_device_cardinality = count_all_exe_unit.per_device_cardinality;
   } catch (const foreign_storage::ForeignStorageException& error) {
     throw;
   } catch (const QueryMustRunOnCpu&) {
diff --git a/QueryEngine/RelAlgExecutor.h b/QueryEngine/RelAlgExecutor.h
index b1cf84c583..8c3dbaf5b3 100644
--- a/QueryEngine/RelAlgExecutor.h
+++ b/QueryEngine/RelAlgExecutor.h
@@ -337,7 +337,9 @@ class RelAlgExecutor : private StorageIOFacility {
                           const CompilationOptions& co,
                           const ExecutionOptions& eo);
 
-  std::optional<size_t> getFilteredCountAll(const WorkUnit& work_unit,
+  bool hasDeletedRowInQuery(std::vector<InputTableInfo> const&) const;
+
+  std::optional<size_t> getFilteredCountAll(const RelAlgExecutionUnit& ra_exe_unit,
                                             const bool is_agg,
                                             const CompilationOptions& co,
                                             const ExecutionOptions& eo);
diff --git a/QueryEngine/RelAlgTranslator.cpp b/QueryEngine/RelAlgTranslator.cpp
index fc37394c93..e71944fc0f 100644
--- a/QueryEngine/RelAlgTranslator.cpp
+++ b/QueryEngine/RelAlgTranslator.cpp
@@ -269,6 +269,10 @@ bool is_agg_supported_for_type(const SQLAgg& agg_kind, const SQLTypeInfo& arg_ti
          !shared::is_any<kAVG, kMIN, kMAX, kSUM, kAPPROX_QUANTILE, kMODE>(agg_kind);
 }
 
+bool is_distinct_supported(SQLAgg const agg_kind) {
+  return shared::is_any<kMIN, kMAX, kCOUNT, kAPPROX_COUNT_DISTINCT>(agg_kind);
+}
+
 }  // namespace
 
 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateAggregateRex(
@@ -328,10 +332,7 @@ std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateAggregateRex(
         }
         break;
       case kCOUNT_IF:
-        if (rex->isDistinct()) {
-          throw std::runtime_error(
-              "Currently, COUNT_IF function does not support DISTINCT qualifier.");
-        } else if (arg_expr->get_type_info().is_geometry()) {
+        if (arg_expr->get_type_info().is_geometry()) {
           throw std::runtime_error(
               "COUNT_IF does not currently support geospatial types.");
         }
@@ -350,6 +351,10 @@ std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateAggregateRex(
       throw std::runtime_error("Aggregate on " + arg_ti.get_type_name() +
                                " is not supported yet.");
     }
+    if (is_distinct && !is_distinct_supported(agg_kind)) {
+      throw std::runtime_error(toString(agg_kind) +
+                               " does not currently support the DISTINCT qualifier.");
+    }
   }
   const auto agg_ti = get_agg_type(agg_kind, arg_expr.get());
   return makeExpr<Analyzer::AggExpr>(agg_ti, agg_kind, arg_expr, is_distinct, arg1);
@@ -1520,6 +1525,10 @@ std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateStringOper(
       return makeExpr<Analyzer::TryStringCastOper>(rex_function->getType(), args);
     case SqlStringOpKind::POSITION:
       return makeExpr<Analyzer::PositionStringOper>(args);
+    case SqlStringOpKind::JAROWINKLER_SIMILARITY:
+      return makeExpr<Analyzer::JarowinklerSimilarityStringOper>(args);
+    case SqlStringOpKind::LEVENSHTEIN_DISTANCE:
+      return makeExpr<Analyzer::LevenshteinDistanceStringOper>(args);
     default: {
       throw std::runtime_error("Unsupported string function.");
     }
@@ -1780,7 +1789,9 @@ std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateFunction(
                    "BASE64_ENCODE"sv,
                    "BASE64_DECODE"sv,
                    "TRY_CAST"sv,
-                   "POSITION"sv)) {
+                   "POSITION"sv,
+                   "JAROWINKLER_SIMILARITY"sv,
+                   "LEVENSHTEIN_DISTANCE"sv)) {
     return translateStringOper(rex_function);
   }
   if (func_resolve(rex_function->getName(), "CARDINALITY"sv, "ARRAY_LENGTH"sv)) {
@@ -2109,18 +2120,6 @@ std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateWindowFunction(
   if (window_function_is_value(window_func_kind)) {
     CHECK_GE(args.size(), 1u);
     ti = args.front()->get_type_info();
-  } else if (window_function_conditional_aggregate(window_func_kind)) {
-    switch (window_func_kind) {
-      case SqlWindowFunctionKind::COUNT_IF:
-        // count_if should have an input expression having boolean type
-        // but returned value should have the same as a normal count agg expr
-        // so we force to set its type to bigint
-        CHECK(ti.is_boolean());
-        ti = SQLTypeInfo(kBIGINT);
-        break;
-      default:
-        break;
-    }
   }
   auto determine_frame_bound_type =
       [](const RexWindowFunctionOperator::RexWindowBound& bound) {
diff --git a/QueryEngine/ResultSetBuilder.cpp b/QueryEngine/ResultSetBuilder.cpp
index 6097de730a..3e5ae47c70 100644
--- a/QueryEngine/ResultSetBuilder.cpp
+++ b/QueryEngine/ResultSetBuilder.cpp
@@ -196,7 +196,7 @@ ResultSet* ResultSetLogicalValuesBuilder::create(
       std::make_shared<RelLogicalValues>(label_infos, logical_values);
 
   const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(), 0);
 
   // Construct ResultSet
   ResultSet* rsp(ResultSetLogicalValuesBuilder(rel_logical_values.get(),
diff --git a/QueryEngine/RuntimeFunctions.cpp b/QueryEngine/RuntimeFunctions.cpp
index 0bfca3dd47..af83539216 100644
--- a/QueryEngine/RuntimeFunctions.cpp
+++ b/QueryEngine/RuntimeFunctions.cpp
@@ -271,8 +271,8 @@ DEF_UMINUS_NULLABLE(double, double)
       cast_##from_type##_to_##to_type##_scaled_nullable(const from_type operand,       \
                                                         const from_type from_null_val, \
                                                         const to_type to_null_val,     \
-                                                        const to_type multiplier) {    \
-    return operand == from_null_val ? to_null_val : multiplier * operand;              \
+                                                        const to_type divider) {       \
+    return operand == from_null_val ? to_null_val : operand / divider;                 \
   }
 
 #define DEF_CAST_NULLABLE_BIDIR(type1, type2) \
@@ -362,9 +362,15 @@ extern "C" RUNTIME_EXPORT ALWAYS_INLINE uint64_t agg_count(uint64_t* agg, const
   return (*agg)++;
 }
 
-extern "C" RUNTIME_EXPORT ALWAYS_INLINE void
-agg_count_distinct_bitmap(int64_t* agg, const int64_t val, const int64_t min_val) {
-  const uint64_t bitmap_idx = val - min_val;
+extern "C" RUNTIME_EXPORT ALWAYS_INLINE void agg_count_distinct_bitmap(
+    int64_t* agg,
+    const int64_t val,
+    const int64_t min_val,
+    const int64_t bucket_size) {
+  uint64_t bitmap_idx = val - min_val;
+  if (1 < bucket_size) {
+    bitmap_idx /= static_cast<uint64_t>(bucket_size);
+  }
   reinterpret_cast<int8_t*>(*agg)[bitmap_idx >> 3] |= (1 << (bitmap_idx & 7));
 }
 
@@ -379,6 +385,7 @@ extern "C" GPU_RT_STUB void agg_count_distinct_bitmap_gpu(int64_t*,
                                                           const int64_t,
                                                           const int64_t,
                                                           const int64_t,
+                                                          const int64_t,
                                                           const uint64_t,
                                                           const uint64_t) {}
 
@@ -1175,9 +1182,10 @@ extern "C" RUNTIME_EXPORT ALWAYS_INLINE void agg_count_distinct_bitmap_skip_val(
     int64_t* agg,
     const int64_t val,
     const int64_t min_val,
+    const int64_t bucket_size,
     const int64_t skip_val) {
   if (val != skip_val) {
-    agg_count_distinct_bitmap(agg, val, min_val);
+    agg_count_distinct_bitmap(agg, val, min_val, bucket_size);
   }
 }
 
@@ -1187,6 +1195,7 @@ extern "C" GPU_RT_STUB void agg_count_distinct_bitmap_skip_val_gpu(int64_t*,
                                                                    const int64_t,
                                                                    const int64_t,
                                                                    const int64_t,
+                                                                   const int64_t,
                                                                    const uint64_t,
                                                                    const uint64_t) {}
 
diff --git a/QueryEngine/RuntimeFunctions.h b/QueryEngine/RuntimeFunctions.h
index 0caedacf3c..44318eae16 100644
--- a/QueryEngine/RuntimeFunctions.h
+++ b/QueryEngine/RuntimeFunctions.h
@@ -151,7 +151,8 @@ extern "C" RUNTIME_EXPORT void agg_min_float_skip_val(int32_t* agg,
 
 extern "C" RUNTIME_EXPORT void agg_count_distinct_bitmap(int64_t* agg,
                                                          const int64_t val,
-                                                         const int64_t min_val);
+                                                         const int64_t min_val,
+                                                         const int64_t bucket_size);
 
 #define EMPTY_KEY_64 std::numeric_limits<int64_t>::max()
 #define EMPTY_KEY_32 std::numeric_limits<int32_t>::max()
diff --git a/QueryEngine/StringOpsIR.cpp b/QueryEngine/StringOpsIR.cpp
index 58d9456eba..a58b4a19b9 100644
--- a/QueryEngine/StringOpsIR.cpp
+++ b/QueryEngine/StringOpsIR.cpp
@@ -145,6 +145,26 @@ DEF_APPLY_NUMERIC_STRING_OPS(double, double)
 
 #undef DEF_APPLY_NUMERIC_STRING_OPS
 
+#define DEF_APPLY_MULTI_INPUT_NUMERIC_STRING_OPS(value_type, value_name)            \
+  extern "C" RUNTIME_EXPORT ALWAYS_INLINE value_type                                \
+      apply_multi_input_numeric_string_ops_##value_name(                            \
+          const char* str1_ptr,                                                     \
+          const int32_t str1_len,                                                   \
+          const char* str2_ptr,                                                     \
+          const int32_t str2_len,                                                   \
+          const int64_t string_ops_handle) {                                        \
+    const std::string_view raw_str1(str1_ptr, str1_len);                            \
+    const std::string_view raw_str2(str2_ptr, str2_len);                            \
+    auto string_ops =                                                               \
+        reinterpret_cast<const StringOps_Namespace::StringOps*>(string_ops_handle); \
+    const auto result_datum = string_ops->numericEval(raw_str1, raw_str2);          \
+    return result_datum.value_name##val;                                            \
+  }
+
+DEF_APPLY_MULTI_INPUT_NUMERIC_STRING_OPS(int64_t, bigint)
+
+#undef DEF_APPLY_MULTI_INPUT_NUMERIC_STRING_OPS
+
 inline int32_t write_string_to_proxy(const std::string& str,
                                      const int64_t string_dict_handle) {
   if (str.empty()) {
@@ -288,12 +308,11 @@ CodeGenerator::codegenStringFetchAndEncode(const Analyzer::StringOper* expr,
 
   auto primary_str_lv = codegen(expr->getArg(arg_idx), true, co);
   std::unique_ptr<CodeGenerator::NullCheckCodegen> nullcheck_codegen;
-  if (primary_str_lv.size() != 3) {
+  if (primary_str_lv.size() != 3 && arg_ti.is_dict_encoded_string()) {
     // If this is the case we should have a transient dictionary from a previous op
     // We can't use the dictionary values without decoding as this op occurs directly
     // inline on top of whatever operation created the transient dictionary
     CHECK_EQ(size_t(1), primary_str_lv.size());
-    CHECK(arg_ti.is_dict_encoded_string());
     const bool is_nullable = !arg_ti.get_notnull();
     if (codegen_nullcheck && is_nullable) {
       const auto decoded_input_ti = SQLTypeInfo(kTEXT, is_nullable, kENCODING_DICT);
@@ -314,6 +333,16 @@ CodeGenerator::codegenStringFetchAndEncode(const Analyzer::StringOper* expr,
     primary_str_lv.push_back(cgen_state_->ir_builder_.CreateExtractValue(string_view, 1));
     primary_str_lv.back() = cgen_state_->ir_builder_.CreateTrunc(
         primary_str_lv.back(), llvm::Type::getInt32Ty(cgen_state_->context_));
+  } else if (primary_str_lv.size() == 1 and arg_ti.is_none_encoded_string()) {
+    // real (not dictionary-encoded) strings
+    CHECK(primary_str_lv[0]->getType()->isPointerTy());
+    const auto none_enc_string = cgen_state_->ir_builder_.CreateLoad(
+        primary_str_lv[0]->getType()->getPointerElementType(), primary_str_lv[0]);
+    primary_str_lv.push_back(
+        cgen_state_->ir_builder_.CreateExtractValue(none_enc_string, 0));
+    primary_str_lv.push_back(cgen_state_->ir_builder_.CreateTrunc(
+        cgen_state_->ir_builder_.CreateExtractValue(none_enc_string, 1),
+        llvm::Type::getInt32Ty(cgen_state_->context_)));
   }
   CHECK_EQ(size_t(3), primary_str_lv.size());
   return std::make_pair(primary_str_lv, std::move(nullcheck_codegen));
@@ -348,11 +377,25 @@ llvm::Value* CodeGenerator::codegenPerRowStringOper(const Analyzer::StringOper*
   auto string_ops_handle_lv = cgen_state_->llInt(string_ops_handle);
 
   if (!return_ti.is_string()) {
-    CHECK_EQ(non_literals_arity, 1UL);
-    std::vector<llvm::Value*> string_oper_lvs{
-        primary_str_lv[1], primary_str_lv[2], string_ops_handle_lv};
+    CHECK_GE(non_literals_arity, 1UL);
+    CHECK_LE(non_literals_arity, 2UL);
+    std::vector<llvm::Value*> string_oper_lvs;
+    if (non_literals_arity == 1UL) {
+      string_oper_lvs = {primary_str_lv[1], primary_str_lv[2], string_ops_handle_lv};
+    } else {
+      const auto [secondary_str_lv, secondary_nullcheck_codegen] =
+          codegenStringFetchAndEncode(expr, co, 1UL, false);
+      CHECK_EQ(size_t(3), secondary_str_lv.size());
+      string_oper_lvs = {primary_str_lv[1],
+                         primary_str_lv[2],
+                         secondary_str_lv[1],
+                         secondary_str_lv[2],
+                         string_ops_handle_lv};
+    }
     const auto return_type = return_ti.get_type();
-    std::string fn_call = "apply_numeric_string_ops_";
+    std::string fn_call = non_literals_arity == 1UL
+                              ? "apply_numeric_string_ops_"
+                              : "apply_multi_input_numeric_string_ops_";
     switch (return_type) {
       case kBOOLEAN: {
         fn_call += "bool";
diff --git a/QueryEngine/TableFunctionHelper.h b/QueryEngine/TableFunctionHelper.h
index 7963c8be66..01aa45bde0 100644
--- a/QueryEngine/TableFunctionHelper.h
+++ b/QueryEngine/TableFunctionHelper.h
@@ -233,6 +233,7 @@ inline bool is_ext_arg_type_pointer(const ExtArgumentType ext_arg_type) {
   }
 }
 
+// TODO(pearu): rename is_ext_arg_type_scalar to is_ext_arg_type_literal
 inline bool is_ext_arg_type_scalar(const ExtArgumentType ext_arg_type) {
   switch (ext_arg_type) {
     case ExtArgumentType::Int8:
@@ -247,6 +248,15 @@ inline bool is_ext_arg_type_scalar(const ExtArgumentType ext_arg_type) {
     case ExtArgumentType::Timestamp:
     case ExtArgumentType::DayTimeInterval:
     case ExtArgumentType::YearMonthTimeInterval:
+    case ExtArgumentType::ArrayInt8:
+    case ExtArgumentType::ArrayInt16:
+    case ExtArgumentType::ArrayInt32:
+    case ExtArgumentType::ArrayInt64:
+    case ExtArgumentType::ArrayFloat:
+    case ExtArgumentType::ArrayDouble:
+    case ExtArgumentType::ArrayBool:
+    case ExtArgumentType::ArrayTextEncodingDict:
+    case ExtArgumentType::ArrayTextEncodingNone:
       return true;
 
     default:
diff --git a/QueryEngine/TableFunctions/SystemFunctions/os/ML/AbstractMLModel.h b/QueryEngine/TableFunctions/SystemFunctions/os/ML/AbstractMLModel.h
index ed43e8dace..961c4500cc 100644
--- a/QueryEngine/TableFunctions/SystemFunctions/os/ML/AbstractMLModel.h
+++ b/QueryEngine/TableFunctions/SystemFunctions/os/ML/AbstractMLModel.h
@@ -22,14 +22,23 @@
 #include <string>
 #include "Shared/base64.h"
 
+namespace {
+std::string default_metadata(const std::string& metadata) {
+  if (metadata == "" || metadata == "DEFAULT") {
+    return "{}";
+  }
+  return shared::decode_base64(metadata);
+}
+}  // namespace
+
 class AbstractMLModel {
  public:
   AbstractMLModel(const std::string& model_metadata)
-      : model_metadata_(shared::decode_base64(model_metadata)) {}
+      : model_metadata_(default_metadata(model_metadata)) {}
 
   AbstractMLModel(const std::string& model_metadata,
                   const std::vector<std::vector<std::string>>& cat_feature_keys)
-      : model_metadata_(shared::decode_base64(model_metadata))
+      : model_metadata_(default_metadata(model_metadata))
       , cat_feature_keys_(cat_feature_keys) {}
   virtual MLModelType getModelType() const = 0;
   virtual std::string getModelTypeString() const = 0;
diff --git a/QueryEngine/TableFunctions/SystemFunctions/os/MLTableFunctions.hpp b/QueryEngine/TableFunctions/SystemFunctions/os/MLTableFunctions.hpp
index 72d46560f8..6edc32bf50 100644
--- a/QueryEngine/TableFunctions/SystemFunctions/os/MLTableFunctions.hpp
+++ b/QueryEngine/TableFunctions/SystemFunctions/os/MLTableFunctions.hpp
@@ -332,13 +332,11 @@ linear_reg_fit_impl(TableFunctionManager& mgr,
    TextEncodingNone model_name,
    Cursor<Column<T> labels, ColumnList<T> features> data,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 linear_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -498,13 +496,11 @@ struct CategoricalFeaturesBuilder {
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 linear_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -541,13 +537,11 @@ linear_reg_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 linear_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -689,13 +683,11 @@ decision_tree_reg_impl(TableFunctionManager& mgr,
    int64_t max_tree_depth | require="max_tree_depth >= 0" | default=0,
    int64_t min_obs_per_leaf_node | require="min_obs_per_leaf_node >= 0" | default=5,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 decision_tree_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -730,13 +722,11 @@ decision_tree_reg_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t decision_tree_reg_fit__cpu_template(
     TableFunctionManager& mgr,
@@ -779,13 +769,11 @@ NEVER_INLINE HOST int32_t decision_tree_reg_fit__cpu_template(
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t decision_tree_reg_fit__cpu_template(
     TableFunctionManager& mgr,
@@ -971,13 +959,11 @@ gbt_reg_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 gbt_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -1043,13 +1029,11 @@ gbt_reg_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 gbt_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -1229,13 +1213,11 @@ random_forest_reg_fit_impl(TableFunctionManager& mgr,
    bool use_histogram | default=false,
    TextEncodingNone var_importance_metric | default="MDI",
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 random_forest_reg_fit__cpu_template(TableFunctionManager& mgr,
@@ -1303,13 +1285,11 @@ random_forest_reg_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t random_forest_reg_fit__cpu_template(
     TableFunctionManager& mgr,
@@ -1384,13 +1364,11 @@ NEVER_INLINE HOST int32_t random_forest_reg_fit__cpu_template(
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t random_forest_reg_fit__cpu_template(
     TableFunctionManager& mgr,
@@ -1512,13 +1490,11 @@ pca_fit_impl(TableFunctionManager& mgr,
    TextEncodingNone model_name,
    Cursor<ColumnList<T> features> data,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 pca_fit__cpu_template(TableFunctionManager& mgr,
@@ -1545,13 +1521,11 @@ pca_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>, T=[double]
  */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 template <typename T>
 NEVER_INLINE HOST int32_t
 pca_fit__cpu_template(TableFunctionManager& mgr,
@@ -1585,13 +1559,11 @@ pca_fit__cpu_template(TableFunctionManager& mgr,
    int32_t cat_top_k | require="cat_top_k >= 1" | default=10,
    float cat_min_fraction | require="cat_min_fraction > 0.0" | require="cat_min_fraction <= 1.0" | default=0.01,
    TextEncodingNone preferred_ml_framework | default="DEFAULT",
-   TextEncodingNone model_metadata | default="e30=") ->
+   TextEncodingNone model_metadata | default="DEFAULT") ->
    Column<TextEncodingDict> model_name | input_id=args<>
 */
 // clang-format on
 
-// default value for model_metadata of "e30=" is base64 encoded "{}"
-
 EXTENSION_NOINLINE_HOST int32_t
 pca_fit__cpu_1(TableFunctionManager& mgr,
                const TextEncodingNone& model_name,
diff --git a/QueryEngine/TableFunctions/SystemFunctions/os/UtilityTableFunctions.cpp b/QueryEngine/TableFunctions/SystemFunctions/os/UtilityTableFunctions.cpp
index abd2ff6cb5..f19f41d4bd 100644
--- a/QueryEngine/TableFunctions/SystemFunctions/os/UtilityTableFunctions.cpp
+++ b/QueryEngine/TableFunctions/SystemFunctions/os/UtilityTableFunctions.cpp
@@ -51,7 +51,7 @@ int32_t generate_series_parallel(const T start,
                         series_output[out_idx] = start + (step * out_idx);
                       }
                     });
-  return num_rows;
+  return SUCCESS;
 }
 
 template <typename T, typename K>
@@ -60,21 +60,17 @@ NEVER_INLINE HOST int32_t generate_series__cpu_template(TableFunctionManager& mg
                                                         const T stop,
                                                         const K step,
                                                         Column<T>& series_output) {
-  const int64_t MAX_ROWS{1L << 30};
   const int64_t PARALLEL_THRESHOLD{10000L};
   const int64_t num_rows = numStepsBetween(start, stop, step) + 1;
   if (num_rows <= 0) {
     mgr.set_output_row_size(0);
     return 0;
   }
+  // set_output_row_size ensures that the output buffer size will be
+  // in a reasonable range (up to 16 TiB), and if it is not, an
+  // OutOfHostMemory exception will be thrown.
   mgr.set_output_row_size(num_rows);
 
-  if (num_rows > MAX_ROWS) {
-    return mgr.ERROR_MESSAGE(
-        "Invocation of generate_series would result in " + std::to_string(num_rows) +
-        " rows, which exceeds the max limit of " + std::to_string(MAX_ROWS) + " rows.");
-  }
-
 #ifdef HAVE_TBB
   if (num_rows > PARALLEL_THRESHOLD) {
     return generate_series_parallel(start, stop, step, series_output);
@@ -84,7 +80,7 @@ NEVER_INLINE HOST int32_t generate_series__cpu_template(TableFunctionManager& mg
   for (int64_t out_idx = 0; out_idx != num_rows; ++out_idx) {
     series_output[out_idx] = start + (step * out_idx);
   }
-  return num_rows;
+  return SUCCESS;
 }
 
 template <typename T>
diff --git a/QueryEngine/TableFunctions/TableFunctionCompilationContext.cpp b/QueryEngine/TableFunctions/TableFunctionCompilationContext.cpp
index ceb3247410..395f61a8be 100644
--- a/QueryEngine/TableFunctions/TableFunctionCompilationContext.cpp
+++ b/QueryEngine/TableFunctions/TableFunctionCompilationContext.cpp
@@ -103,25 +103,25 @@ void initialize_ptr_member(llvm::Value* member_ptr,
   }
 }
 
-void initialize_int64_member(llvm::Value* member_ptr,
-                             llvm::Value* value,
-                             int64_t default_value,
-                             llvm::LLVMContext& ctx,
-                             llvm::IRBuilder<>& ir_builder) {
+template <typename T>
+void initialize_int_member(llvm::Value* member_ptr,
+                           llvm::Value* value,
+                           int64_t default_value,
+                           llvm::LLVMContext& ctx,
+                           llvm::IRBuilder<>& ir_builder) {
   llvm::Value* val = nullptr;
   if (value != nullptr) {
     auto value_type = value->getType();
     if (value_type->isPointerTy()) {
-      CHECK(value_type->getPointerElementType()->isIntegerTy(64));
+      CHECK(value_type->getPointerElementType()->isIntegerTy(sizeof(T) * 8));
       val = ir_builder.CreateLoad(value->getType()->getPointerElementType(), value);
     } else {
-      CHECK(value_type->isIntegerTy(64));
+      CHECK(value_type->isIntegerTy(sizeof(T) * 8));
       val = value;
     }
     ir_builder.CreateStore(val, member_ptr);
   } else {
-    auto const_default =
-        llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx), default_value, true);
+    auto const_default = ll_int<T>(default_value, ctx);
     ir_builder.CreateStore(const_default, member_ptr);
   }
 }
@@ -184,7 +184,7 @@ std::tuple<llvm::Value*, llvm::Value*> alloc_column(std::string col_name,
   }
 
   initialize_ptr_member(col_ptr_ptr, data_ptr_llvm_type, data_ptr, ir_builder);
-  initialize_int64_member(col_sz_ptr, data_size, -1, ctx, ir_builder);
+  initialize_int_member<int64_t>(col_sz_ptr, data_size, -1, ctx, ir_builder);
   if (is_text_encoding_dict_type) {
     initialize_ptr_member(col_str_dict_ptr,
                           llvm::Type::getInt8PtrTy(ctx),
@@ -257,7 +257,7 @@ llvm::Value* alloc_column_list(std::string col_list_name,
   auto const_length = llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx), length, true);
   ir_builder.CreateStore(const_length, col_list_length_ptr);
 
-  initialize_int64_member(col_list_size_ptr, data_size, -1, ctx, ir_builder);
+  initialize_int_member<int64_t>(col_list_size_ptr, data_size, -1, ctx, ir_builder);
 
   if (is_text_encoding_dict_type) {
     initialize_ptr_member(col_str_dict_ptr_ptr,
@@ -272,6 +272,51 @@ llvm::Value* alloc_column_list(std::string col_list_name,
   return col_list_ptr;
 }
 
+llvm::Value* alloc_array(std::string arr_name,
+                         const size_t index,
+                         const SQLTypeInfo& data_target_info,
+                         llvm::Value* data_ptr,
+                         llvm::Value* data_size,
+                         llvm::Value* data_is_null,
+                         llvm::LLVMContext& ctx,
+                         llvm::IRBuilder<>& ir_builder) {
+  /*
+    Creates a new Array instance of given element type and initialize
+    its data ptr and sz members when specified. If data ptr or sz are
+    unspecified (have nullptr values) then the corresponding members
+    are initialized with NULL and -1, respectively.
+
+    Return a pointer to Array instance.
+   */
+  llvm::StructType* arr_struct_type;
+  llvm::Type* data_ptr_llvm_type =
+      get_llvm_type_from_sql_column_type(data_target_info.get_elem_type(), ctx);
+  arr_struct_type =
+      llvm::StructType::get(ctx,
+                            {
+                                data_ptr_llvm_type,          /* T* ptr_ */
+                                llvm::Type::getInt64Ty(ctx), /* int64_t size_ */
+                                llvm::Type::getInt8Ty(ctx)   /* int8_t is_null_ */
+                            });
+
+  auto arr = ir_builder.CreateAlloca(arr_struct_type);
+  arr->setName(arr_name);
+  auto arr_ptr_ptr = ir_builder.CreateStructGEP(arr_struct_type, arr, 0);
+  auto arr_sz_ptr = ir_builder.CreateStructGEP(arr_struct_type, arr, 1);
+  auto arr_is_null_ptr = ir_builder.CreateStructGEP(arr_struct_type, arr, 2);
+  arr_ptr_ptr->setName(arr_name + ".ptr");
+  arr_sz_ptr->setName(arr_name + ".size");
+  arr_is_null_ptr->setName(arr_name + ".is_null");
+
+  initialize_ptr_member(arr_ptr_ptr, data_ptr_llvm_type, data_ptr, ir_builder);
+  initialize_int_member<int64_t>(arr_sz_ptr, data_size, -1, ctx, ir_builder);
+  initialize_int_member<int8_t>(arr_is_null_ptr, data_is_null, -1, ctx, ir_builder);
+  auto arr_ptr = ir_builder.CreatePointerCast(
+      arr_ptr_ptr, llvm::PointerType::get(llvm::Type::getInt8Ty(ctx), 0));
+  arr_ptr->setName(arr_name + "_pointer");
+  return arr_ptr;
+}
+
 std::string exprsKey(const std::vector<Analyzer::Expr*>& exprs) {
   std::string result;
   for (const auto& expr : exprs) {
@@ -384,6 +429,10 @@ void TableFunctionCompilationContext::generateTableFunctionCall(
   ir_builder->CreateRet(const_zero);
 
   ir_builder->SetInsertPoint(bb_exit);
+  // when table_func_return == TableFunctionErrorCode::NotAnError,
+  // then the table function is considered a success while
+  // output_row_count_ptr will be uninitialized and the output row
+  // count is defined by other means, see QE-877.
   ir_builder->CreateRet(table_func_return);
 }
 
@@ -522,12 +571,55 @@ void TableFunctionCompilationContext::generateEntryPoint(
       if (col_index + 1 == ti.get_dimension()) {
         col_index = -1;
       }
+    } else if (ti.is_array()) {
+      /*
+          Literal array expression is encoded in a contiguous buffer
+          with the following memory layout:
+
+          | <array size> | <array is_null> |  <array data>                             |
+          |<-- 8 bytes ->|<-- 8 bytes ---->|<-- <array size> * <array element size> -->|
+
+          Notice that while is_null in the `struct Array` has type
+          int8_t, in the buffer we use int64_t value to hold the
+          is_null state in order to have the array data 64-bit
+          aligned.
+       */
+      auto array_size =
+          cgen_state->ir_builder_.CreateBitCast(col_heads[i], get_int_ptr_type(64, ctx));
+      auto array_is_null_ptr = cgen_state->ir_builder_.CreateGEP(
+          col_heads[i]->getType()->getScalarType()->getPointerElementType(),
+          col_heads[i],
+          cgen_state->llInt(8));
+      auto array_is_null = cgen_state->ir_builder_.CreateLoad(
+          array_is_null_ptr->getType()->getPointerElementType(), array_is_null_ptr);
+
+      auto array_ptr = cgen_state->ir_builder_.CreateGEP(
+          col_heads[i]->getType()->getScalarType()->getPointerElementType(),
+          col_heads[i],
+          cgen_state->llInt(16));
+      array_size->setName(std::string("array_size.") + std::to_string(func_arg_index));
+      array_is_null->setName(std::string("array_is_null.") +
+                             std::to_string(func_arg_index));
+      array_ptr->setName(std::string("array_ptr.") + std::to_string(func_arg_index));
+
+      auto array_struct_ptr =
+          alloc_array(std::string("literal_array.") + std::to_string(func_arg_index),
+                      i,
+                      ti,
+                      array_ptr,
+                      array_size,
+                      array_is_null,
+                      ctx,
+                      cgen_state->ir_builder_);
+
+      // passing columns by value is a historical artifact, so no need
+      // to support it for array expressions:
+      CHECK_EQ(pass_column_by_value, false);
+      func_args.push_back(array_struct_ptr);
+      CHECK_EQ(col_index, -1);
     } else {
-      throw std::runtime_error(
-          "Only integer and floating point columns or scalars are supported as inputs to "
-          "table "
-          "functions, got " +
-          ti.get_type_name());
+      throw std::runtime_error("Table function input has unsupported type: " +
+                               ti.get_type_name());
     }
   }
   auto output_str_dict_proxy_heads =
diff --git a/QueryEngine/TableFunctions/TableFunctionExecutionContext.cpp b/QueryEngine/TableFunctions/TableFunctionExecutionContext.cpp
index 58416b2011..b74dcc171e 100644
--- a/QueryEngine/TableFunctions/TableFunctionExecutionContext.cpp
+++ b/QueryEngine/TableFunctions/TableFunctionExecutionContext.cpp
@@ -28,65 +28,49 @@
 
 namespace {
 
-template <typename T>
-const int8_t* create_literal_buffer(const T literal,
-                                    const ExecutorDeviceType device_type,
-                                    std::vector<std::unique_ptr<char[]>>& literals_owner,
-                                    CudaAllocator* gpu_allocator) {
-  CHECK_LE(sizeof(T), sizeof(int64_t));  // pad to 8 bytes
-  switch (device_type) {
-    case ExecutorDeviceType::CPU: {
-      literals_owner.emplace_back(std::make_unique<char[]>(sizeof(int64_t)));
-      std::memcpy(literals_owner.back().get(), &literal, sizeof(T));
-      return reinterpret_cast<const int8_t*>(literals_owner.back().get());
+void append_literal_buffer(const Datum& d,
+                           const SQLTypeInfo& ti,
+                           int8_t* literal_buffer,
+                           int64_t offset) {
+  if (ti.is_fp()) {
+    switch (get_bit_width(ti)) {
+      case 32:
+        std::memcpy(literal_buffer + offset, &d.floatval, sizeof(float));
+        break;
+      case 64:
+        std::memcpy(literal_buffer + offset, &d.doubleval, sizeof(double));
+        break;
+      default:
+        UNREACHABLE();
     }
-    case ExecutorDeviceType::GPU: {
-      CHECK(gpu_allocator);
-      const auto gpu_literal_buf_ptr = gpu_allocator->alloc(sizeof(int64_t));
-      gpu_allocator->copyToDevice(
-          gpu_literal_buf_ptr, reinterpret_cast<const int8_t*>(&literal), sizeof(T));
-      return gpu_literal_buf_ptr;
-    }
-  }
-  UNREACHABLE();
-  return nullptr;
-}
-
-// Specialization for std::string. Currently we simply hand the UDTF a char* to the
-// first char of a c-style null-terminated string we copy out of the std::string.
-// May want to evaluate moving to sending in the ptr and size
-template <>
-const int8_t* create_literal_buffer(std::string* const literal,
-                                    const ExecutorDeviceType device_type,
-                                    std::vector<std::unique_ptr<char[]>>& literals_owner,
-                                    CudaAllocator* gpu_allocator) {
-  const int64_t string_size = literal->size();
-  const int64_t padded_string_size =
-      (string_size + 7) / 8 * 8;  // round up to the next multiple of 8
-  switch (device_type) {
-    case ExecutorDeviceType::CPU: {
-      literals_owner.emplace_back(
-          std::make_unique<char[]>(sizeof(int64_t) + padded_string_size));
-      std::memcpy(literals_owner.back().get(), &string_size, sizeof(int64_t));
-      std::memcpy(
-          literals_owner.back().get() + sizeof(int64_t), literal->data(), string_size);
-      return reinterpret_cast<const int8_t*>(literals_owner.back().get());
-    }
-    case ExecutorDeviceType::GPU: {
-      CHECK(gpu_allocator);
-      const auto gpu_literal_buf_ptr =
-          gpu_allocator->alloc(sizeof(int64_t) + padded_string_size);
-      gpu_allocator->copyToDevice(gpu_literal_buf_ptr,
-                                  reinterpret_cast<const int8_t*>(&string_size),
-                                  sizeof(int64_t));
-      gpu_allocator->copyToDevice(gpu_literal_buf_ptr + sizeof(int64_t),
-                                  reinterpret_cast<const int8_t*>(literal->data()),
-                                  string_size);
-      return gpu_literal_buf_ptr;
+  } else if (ti.is_integer() || ti.is_timestamp() || ti.is_timeinterval()) {
+    switch (get_bit_width(ti)) {
+      case 8:
+        std::memcpy(literal_buffer + offset, &d.tinyintval, sizeof(int8_t));
+        break;
+      case 16:
+        std::memcpy(literal_buffer + offset, &d.smallintval, sizeof(int16_t));
+        break;
+      case 32:
+        std::memcpy(literal_buffer + offset, &d.intval, sizeof(int32_t));
+        break;
+      case 64:
+        std::memcpy(literal_buffer + offset, &d.bigintval, sizeof(int64_t));
+        break;
+      default:
+        UNREACHABLE();
     }
+  } else if (ti.is_boolean()) {
+    std::memcpy(literal_buffer + offset, &d.boolval, sizeof(int8_t));
+  } else if (ti.is_text_encoding_none()) {
+    auto string_size = d.stringval->size();
+    std::memcpy(literal_buffer + offset, &string_size, sizeof(int64_t));
+    std::memcpy(
+        literal_buffer + offset + sizeof(int64_t), d.stringval->data(), string_size);
+  } else {
+    throw TableFunctionError("Literal value " + DatumToString(d, ti) +
+                             " is not yet supported.");
   }
-  UNREACHABLE();
-  return nullptr;
 }
 
 size_t get_output_row_count(const TableFunctionExecutionUnit& exe_unit,
@@ -209,77 +193,85 @@ ResultSetPtr TableFunctionExecutionContext::execute(
         input_str_dict_proxy_ptrs.push_back(input_str_dict_proxy_ptr);
       }
       col_sizes.push_back(buf_elem_count);
-    } else if (const auto& constant_val = dynamic_cast<Analyzer::Constant*>(input_expr)) {
-      // TODO(adb): Unify literal handling with rest of system, either in Codegen or as a
-      // separate serialization component
+    } else {
+      // literals
       col_sizes.push_back(0);
       input_str_dict_proxy_ptrs.push_back(nullptr);
-      const auto const_val_datum = constant_val->get_constval();
-      const auto& ti = constant_val->get_type_info();
-      if (ti.is_fp()) {
-        switch (get_bit_width(ti)) {
-          case 32:
-            col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.floatval,
-                                                         device_type,
-                                                         literals_owner,
-                                                         device_allocator.get()));
-            break;
-          case 64:
-            col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.doubleval,
-                                                         device_type,
-                                                         literals_owner,
-                                                         device_allocator.get()));
-            break;
-          default:
-            UNREACHABLE();
+      size_t literal_buffer_size = 0;
+      int8_t* cpu_literal_buf_ptr = nullptr;
+
+      if (const auto& constant_val = dynamic_cast<Analyzer::Constant*>(input_expr)) {
+        // TODO(adb): Unify literal handling with rest of system, either in Codegen or as
+        // a separate serialization component
+        const auto const_val_datum = constant_val->get_constval();
+        const auto& ti = constant_val->get_type_info();
+        if (ti.is_text_encoding_none()) {
+          // clang-format off
+          /*
+            Literal string is encoded in a contiguous buffer with the
+            following memory layout:
+
+            | <string size> | <string data>       |
+            |<-- 8 bytes -->|<-- <string size> -->|
+          */
+          // clang-format on
+          literal_buffer_size =
+              sizeof(int64_t) + ((const_val_datum.stringval->size() + 7) / 8) * 8;
+        } else {
+          literal_buffer_size = ((get_bit_width(ti) / 8 + 7) / 8) * 8;
         }
-      } else if (ti.is_integer() || ti.is_timestamp() || ti.is_timeinterval()) {
-        switch (get_bit_width(ti)) {
-          case 8:
-            col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.tinyintval,
-                                                         device_type,
-                                                         literals_owner,
-                                                         device_allocator.get()));
-            break;
-          case 16:
-            col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.smallintval,
-                                                         device_type,
-                                                         literals_owner,
-                                                         device_allocator.get()));
-            break;
-          case 32:
-            col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.intval,
-                                                         device_type,
-                                                         literals_owner,
-                                                         device_allocator.get()));
-            break;
-          case 64:
-            col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.bigintval,
-                                                         device_type,
-                                                         literals_owner,
-                                                         device_allocator.get()));
-            break;
-          default:
+        // literal_buffer_size is round up to the next multiple of 8
+        literals_owner.emplace_back(std::make_unique<char[]>(literal_buffer_size));
+        cpu_literal_buf_ptr = reinterpret_cast<int8_t*>(literals_owner.back().get());
+        append_literal_buffer(const_val_datum, ti, cpu_literal_buf_ptr, 0);
+      } else if (const auto& array_expr =
+                     dynamic_cast<Analyzer::ArrayExpr*>(input_expr)) {
+        const auto& ti = input_expr->get_type_info().get_elem_type();
+        // clang-format off
+        /*
+          Literal array expression is encoded in a contiguous buffer
+          with the following memory layout:
+
+          | <array size> | <array is_null> |  <array data>                             |
+          |<-- 8 bytes ->|<-- 8 bytes ---->|<-- <array size> * <array element size> -->|
+        */
+        // clang-format on
+        int64_t size = array_expr->getElementCount();
+        int64_t is_null = (array_expr->isNull() ? 0xffffffffffffffff : 0);
+        const auto elem_size = get_bit_width(ti) / 8;
+        // literal_buffer_size is round up to the next multiple of 8
+        literal_buffer_size = 2 * sizeof(int64_t) + (((size + 7) / 8) * 8) * elem_size;
+        literals_owner.emplace_back(std::make_unique<char[]>(literal_buffer_size));
+        cpu_literal_buf_ptr = reinterpret_cast<int8_t*>(literals_owner.back().get());
+        std::memcpy(cpu_literal_buf_ptr, &size, sizeof(int64_t));
+        std::memcpy(cpu_literal_buf_ptr + sizeof(int64_t), &is_null, sizeof(int64_t));
+        for (int64_t i = 0; i < size; i++) {
+          if (const auto& constant_val =
+                  dynamic_cast<const Analyzer::Constant*>(array_expr->getElement(i))) {
+            const auto const_val_datum = constant_val->get_constval();
+            append_literal_buffer(const_val_datum,
+                                  ti,
+                                  cpu_literal_buf_ptr,
+                                  sizeof(int64_t) * 2 + i * elem_size);
+          } else {
             UNREACHABLE();
+          }
         }
-      } else if (ti.is_boolean()) {
-        col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.boolval,
-                                                     device_type,
-                                                     literals_owner,
-                                                     device_allocator.get()));
-      } else if (ti.is_text_encoding_none()) {
-        col_buf_ptrs.push_back(create_literal_buffer(const_val_datum.stringval,
-                                                     device_type,
-                                                     literals_owner,
-                                                     device_allocator.get()));
       } else {
-        throw TableFunctionError("Literal value " + constant_val->toString() +
-                                 " is not yet supported.");
+        throw TableFunctionError("Unsupported expression as input to table function: " +
+                                 input_expr->toString() +
+                                 "\n Only literal constants and columns are supported!");
+      }
+      if (device_type == ExecutorDeviceType::GPU) {
+        auto* gpu_allocator = device_allocator.get();
+        const auto gpu_literal_buf_ptr = gpu_allocator->alloc(literal_buffer_size);
+        gpu_allocator->copyToDevice(
+            gpu_literal_buf_ptr, cpu_literal_buf_ptr, literal_buffer_size);
+        col_buf_ptrs.push_back(gpu_literal_buf_ptr);
+      } else {
+        CHECK_EQ(device_type, ExecutorDeviceType::CPU);
+        col_buf_ptrs.push_back(cpu_literal_buf_ptr);
       }
-    } else {
-      throw TableFunctionError(
-          "Unsupported expression as input to table function: " + input_expr->toString() +
-          "\n Only literal constants and columns are supported!");
     }
   }
   CHECK_EQ(col_buf_ptrs.size(), exe_unit.input_exprs.size());
@@ -397,12 +389,17 @@ void TableFunctionExecutionContext::launchPreCodeOnCpu(
       nullptr,
       nullptr,  // output string dictionary proxy ptrs - not supported for pre-flights yet
       &output_row_count);
-
+  if (err == TableFunctionErrorCode::NotAnError) {
+    // table_function_entry_point does not initialize output_row_count
+    // when a UDTF returns NotAnError, so we'll set it here.
+    output_row_count = mgr->get_nrows();
+  }
   if (exe_unit.table_func.hasPreFlightOutputSizer()) {
     exe_unit.output_buffer_size_param = output_row_count;
   }
 
-  if (err == TableFunctionErrorCode::GenericError) {
+  if (err == TableFunctionErrorCode::NotAnError) {
+  } else if (err == TableFunctionErrorCode::GenericError) {
     throw UserTableFunctionError("Error executing table function pre flight check: " +
                                  std::string(mgr->get_error_message()));
   } else if (err) {
@@ -564,7 +561,11 @@ ResultSetPtr TableFunctionExecutionContext::launchCpuCode(
                                  std::string(e.what()));
   }
 
-  if (err == TableFunctionErrorCode::GenericError) {
+  if (err == TableFunctionErrorCode::NotAnError) {
+    // table_function_entry_point does not initialize output_row_count
+    // when a UDTF returns NotAnError, so we'll set it here.
+    output_row_count = mgr->get_nrows();
+  } else if (err == TableFunctionErrorCode::GenericError) {
     throw UserTableFunctionError("Error executing table function: " +
                                  std::string(mgr->get_error_message()));
   }
diff --git a/QueryEngine/TableFunctions/TableFunctionManager.h b/QueryEngine/TableFunctions/TableFunctionManager.h
index 3801140b57..eb4892156d 100644
--- a/QueryEngine/TableFunctions/TableFunctionManager.h
+++ b/QueryEngine/TableFunctions/TableFunctionManager.h
@@ -49,6 +49,9 @@ class UserTableFunctionError : public TableFunctionError {
 // negative return values
 enum TableFunctionErrorCode : int32_t {
   GenericError = -0x75BCD15,
+  NotAnError = -0x4F4B,  // used to indicate a succesful UDTF
+                         // execution without specifying the row
+                         // size for output columns
 };
 
 extern std::mutex TableFunctionManager_singleton_mutex;
diff --git a/QueryEngine/TableFunctions/TableFunctionOps.cpp b/QueryEngine/TableFunctions/TableFunctionOps.cpp
index 15e01a64d8..cb4b1d2815 100644
--- a/QueryEngine/TableFunctions/TableFunctionOps.cpp
+++ b/QueryEngine/TableFunctions/TableFunctionOps.cpp
@@ -150,6 +150,10 @@ extern "C" DEVICE RUNTIME_EXPORT int32_t table_function_error(const char* messag
   return TableFunctionManager_error_message(reinterpret_cast<int8_t*>(mgr), message);
 }
 
+extern "C" DEVICE RUNTIME_EXPORT int32_t table_function_success_code() {
+  return TableFunctionErrorCode::NotAnError;
+}
+
 /*
   TableFunctionManager_get_singleton is used internally to get the
   pointer to global singleton of TableFunctionManager, if initialized,
diff --git a/QueryEngine/TableFunctions/TableFunctionsFactory.cpp b/QueryEngine/TableFunctions/TableFunctionsFactory.cpp
index 1f68bf0748..0fc3616f01 100644
--- a/QueryEngine/TableFunctions/TableFunctionsFactory.cpp
+++ b/QueryEngine/TableFunctions/TableFunctionsFactory.cpp
@@ -90,6 +90,9 @@ SQLTypeInfo TableFunction::getOutputSQLType(const size_t idx) const {
   return ext_arg_type_to_type_info_output(output_args_[idx]);
 }
 
+// TODO(pearu): rename to countLiteralArgs as the relevant argument
+// types include TextEncodingNone, Array, etc literals that are not
+// scalars.
 int32_t TableFunction::countScalarArgs() const {
   int32_t scalar_args = 0;
   for (const auto& ext_arg : input_args_) {
diff --git a/QueryEngine/TableFunctions/TestFunctions/ArrayTestTableFunctions.cpp b/QueryEngine/TableFunctions/TestFunctions/ArrayTestTableFunctions.cpp
index f51d86ea58..8d5ceb096c 100644
--- a/QueryEngine/TableFunctions/TestFunctions/ArrayTestTableFunctions.cpp
+++ b/QueryEngine/TableFunctions/TestFunctions/ArrayTestTableFunctions.cpp
@@ -173,6 +173,29 @@ NEVER_INLINE HOST int32_t array_concat__cpu_template(TableFunctionManager& mgr,
   return size;
 }
 
+template <typename T>
+NEVER_INLINE HOST int32_t array_append__cpu_template(TableFunctionManager& mgr,
+                                                     const Column<Array<T>>& input1,
+                                                     const Array<T>& input2,
+                                                     Column<Array<T>>& output) {
+  int size = input1.size();
+  int output_values_size = input2.size() * size;
+  for (int i = 0; i < size; i++) {
+    output_values_size += input1[i].size();
+  }
+  mgr.set_output_array_values_total_number(
+      /*output column index=*/0,
+      /*upper bound to the number of items in all output arrays=*/output_values_size);
+
+  mgr.set_output_row_size(size);
+
+  for (int i = 0; i < size; i++) {
+    output.concatItem(i, input1[i]);
+    output.concatItem(i, input2);
+  }
+  return size;
+}
+
 // explicit instantiations
 template NEVER_INLINE HOST int32_t
 array_concat__cpu_template(TableFunctionManager& mgr,
@@ -207,6 +230,42 @@ array_concat__cpu_template(TableFunctionManager& mgr,
                            const ColumnList<Array<TextEncodingDict>>& inputs,
                            Column<Array<TextEncodingDict>>& output);
 
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<float>>& input1,
+                           const Array<float>& input2,
+                           Column<Array<float>>& output);
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<double>>& input1,
+                           const Array<double>& input2,
+                           Column<Array<double>>& output);
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<int8_t>>& input1,
+                           const Array<int8_t>& input2,
+                           Column<Array<int8_t>>& output);
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<int16_t>>& input1,
+                           const Array<int16_t>& input2,
+                           Column<Array<int16_t>>& output);
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<int32_t>>& input1,
+                           const Array<int32_t>& input2,
+                           Column<Array<int32_t>>& output);
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<int64_t>>& input1,
+                           const Array<int64_t>& input2,
+                           Column<Array<int64_t>>& output);
+template NEVER_INLINE HOST int32_t
+array_append__cpu_template(TableFunctionManager& mgr,
+                           const Column<Array<bool>>& input1,
+                           const Array<bool>& input2,
+                           Column<Array<bool>>& output);
+
 template <typename T>
 NEVER_INLINE HOST int32_t array_asarray__cpu_template(TableFunctionManager& mgr,
                                                       const Column<T>& input,
diff --git a/QueryEngine/TableFunctions/TestFunctions/OtherTestTableFunctions.cpp b/QueryEngine/TableFunctions/TestFunctions/OtherTestTableFunctions.cpp
index 252e348d7e..4f0be0faf1 100644
--- a/QueryEngine/TableFunctions/TestFunctions/OtherTestTableFunctions.cpp
+++ b/QueryEngine/TableFunctions/TestFunctions/OtherTestTableFunctions.cpp
@@ -970,3 +970,65 @@ EXTENSION_NOINLINE int32_t ct_test_func__cpu_2(const Column<int32_t>& input1,
   out[0] = 234;
   return 1;
 }
+
+#ifndef __CUDACC__
+template <typename T>
+TEMPLATE_NOINLINE int32_t row_repeater__cpu_template(TableFunctionManager& mgr,
+                                                     const Column<T>& input_col,
+                                                     const int reps,
+                                                     Column<T>& output_col) {
+  // Equivalent to row_copier but supports output row sizes larger
+  // than MAX_INT
+  size_t output_row_count = input_col.size() * reps;
+  mgr.set_output_row_size(output_row_count);
+
+  auto start = 0;
+  auto stop = input_col.size();
+  auto step = 1;
+
+  for (auto i = start; i < stop; i += step) {
+    for (int c = 0; c < reps; c++) {
+      output_col[i + (stop * c)] = input_col[i];
+    }
+  }
+
+  return SUCCESS;
+}
+
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<int8_t>& input_col,
+                           const int reps,
+                           Column<int8_t>& output_col);
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<int16_t>& input_col,
+                           const int reps,
+                           Column<int16_t>& output_col);
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<int32_t>& input_col,
+                           const int reps,
+                           Column<int32_t>& output_col);
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<int64_t>& input_col,
+                           const int reps,
+                           Column<int64_t>& output_col);
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<float>& input_col,
+                           const int reps,
+                           Column<float>& output_col);
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<double>& input_col,
+                           const int reps,
+                           Column<double>& output_col);
+template TEMPLATE_NOINLINE int32_t
+row_repeater__cpu_template(TableFunctionManager& mgr,
+                           const Column<bool>& input_col,
+                           const int reps,
+                           Column<bool>& output_col);
+
+#endif  // #ifndef __CUDACC__
diff --git a/QueryEngine/TableFunctions/TestFunctions/SignatureTestTableFunctions.cpp b/QueryEngine/TableFunctions/TestFunctions/SignatureTestTableFunctions.cpp
index 54142d536f..5e228d56b0 100644
--- a/QueryEngine/TableFunctions/TestFunctions/SignatureTestTableFunctions.cpp
+++ b/QueryEngine/TableFunctions/TestFunctions/SignatureTestTableFunctions.cpp
@@ -413,6 +413,14 @@ ct_require_text_collist_enc_dict__cpu_(const ColumnList<TextEncodingDict>& input
   return 1;
 }
 
+EXTENSION_NOINLINE_HOST int32_t ct_require_cursor__cpu_(const Column<int64_t>& input,
+                                                        int64_t y,
+                                                        Column<int64_t>& out) {
+  set_output_row_size(1);
+  out[0] = 12;
+  return 1;
+}
+
 EXTENSION_NOINLINE_HOST int32_t ct_test_allocator(TableFunctionManager& mgr,
                                                   const Column<int32_t>& input,
                                                   const TextEncodingNone& t,
diff --git a/QueryEngine/TableFunctions/TestFunctions/TableFunctionsTesting.h b/QueryEngine/TableFunctions/TestFunctions/TableFunctionsTesting.h
index 8a84562844..ef7efabba4 100644
--- a/QueryEngine/TableFunctions/TestFunctions/TableFunctionsTesting.h
+++ b/QueryEngine/TableFunctions/TestFunctions/TableFunctionsTesting.h
@@ -929,6 +929,7 @@ ct_union_pushdown_projection__cpu_template(TableFunctionManager& mgr,
   UDTF: ct_require_str_diff__cpu_(Column<int>, TextEncodingNone i | require="i != \"MAX\"") -> Column<int>
   UDTF: ct_require_text_enc_dict__cpu_(Cursor<Column<TextEncodingDict>>, int64_t x | require="x >= 1") -> Column<int64_t>
   UDTF: ct_require_text_collist_enc_dict__cpu_(Cursor<ColumnList<TextEncodingDict>>, int64_t x | require="x >= 1") -> Column<int64_t>
+  UDTF: ct_require_cursor__cpu_(Cursor<Column<int64_t> x | require="x.size() + y > 10">, int64_t y) -> Column<int64_t>
 */
 // clang-format on
 
@@ -974,6 +975,10 @@ ct_require_text_collist_enc_dict__cpu_(const ColumnList<TextEncodingDict>& input
                                        const int64_t x,
                                        Column<int64_t>& out);
 
+EXTENSION_NOINLINE_HOST int32_t ct_require_cursor__cpu_(const Column<int64_t>& input,
+                                                        int64_t y,
+                                                        Column<int64_t>& out);
+
 /*
   UDTF: ct_test_allocator(TableFunctionManager, Column<int32_t>, TextEncodingNone) ->
   Column<int32_t>
@@ -1196,12 +1201,19 @@ NEVER_INLINE HOST int32_t array_copier__cpu_template(TableFunctionManager& mgr,
 /*
   UDTF: array_concat__cpu_template(TableFunctionManager mgr, ColumnList<Array<T>> input) -> Column<Array<T>> | input_id=args<0>,
           T=[float, double, int8_t, int16_t, int32_t, int64_t, bool, TextEncodingDict]
+  UDTF: array_append__cpu_template(TableFunctionManager mgr, Column<Array<T>> input1, Array<T> input2) -> Column<Array<T>> | input_id=args<0>,
+          T=[float, double, int8_t, int16_t, int32_t, int64_t, bool]
 */
 // clang-format on
 template <typename T>
 NEVER_INLINE HOST int32_t array_concat__cpu_template(TableFunctionManager& mgr,
                                                      const ColumnList<Array<T>>& inputs,
                                                      Column<Array<T>>& output);
+template <typename T>
+NEVER_INLINE HOST int32_t array_append__cpu_template(TableFunctionManager& mgr,
+                                                     const Column<Array<T>>& input1,
+                                                     const Array<T>& input2,
+                                                     Column<Array<T>>& output);
 
 // clang-format off
 /*
@@ -1442,6 +1454,7 @@ ct_to_polygon__cpu_(TableFunctionManager& mgr,
   UDTF: row_copier_text(Column<TextEncodingDict>, RowMultiplier) -> Column<TextEncodingDict> | input_id=args<0>
   UDTF: row_copier_columnlist__cpu__(TableFunctionManager, ColumnList<double> cols) -> Column<double>
   UDTF: row_copier2__cpu__(Column<double>, int) -> Column<double>, Column<double>
+  UDTF: row_repeater__cpu_template(TableFunctionManager, Column<T>, int) -> Column<T>, T=[int8_t, int16_t, int32_t, int64_t, float, double, bool]
 */
 // clang-format on
 EXTENSION_NOINLINE int32_t row_copier(const Column<double>& input_col,
@@ -1460,6 +1473,12 @@ row_copier_columnlist__cpu__(TableFunctionManager& mgr,
                              const ColumnList<double>& cols,
                              Column<double>& output_col);
 
+template <typename T>
+TEMPLATE_NOINLINE int32_t row_repeater__cpu_template(TableFunctionManager& mgr,
+                                                     const Column<T>& input_col,
+                                                     const int reps,
+                                                     Column<T>& output_col);
+
 #endif  // #ifndef __CUDACC__
 
 EXTENSION_NOINLINE int32_t row_copier_text(const Column<TextEncodingDict>& input_col,
diff --git a/QueryEngine/TableOptimizer.cpp b/QueryEngine/TableOptimizer.cpp
index 21d841127c..3bd6f7e51a 100644
--- a/QueryEngine/TableOptimizer.cpp
+++ b/QueryEngine/TableOptimizer.cpp
@@ -155,8 +155,8 @@ void TableOptimizer::recomputeMetadata() const {
 
   for (const auto td : table_descriptors) {
     ScopeGuard row_set_holder = [this] { executor_->row_set_mem_owner_ = nullptr; };
-    executor_->row_set_mem_owner_ =
-        std::make_shared<RowSetMemoryOwner>(ROW_SET_SIZE, /*num_threads=*/1);
+    executor_->row_set_mem_owner_ = std::make_shared<RowSetMemoryOwner>(
+        ROW_SET_SIZE, executor_->executor_id_, /*num_threads=*/1);
     const auto table_id = td->tableId;
     auto stats = recomputeDeletedColumnMetadata(td);
 
@@ -563,8 +563,8 @@ void TableOptimizer::vacuumFragmentsAboveMinSelectivity(
       heavyai::unique_lock<heavyai::shared_mutex> executor_lock(
           executor_->execute_mutex_);
       ScopeGuard row_set_holder = [this] { executor_->row_set_mem_owner_ = nullptr; };
-      executor_->row_set_mem_owner_ =
-          std::make_shared<RowSetMemoryOwner>(ROW_SET_SIZE, /*num_threads=*/1);
+      executor_->row_set_mem_owner_ = std::make_shared<RowSetMemoryOwner>(
+          ROW_SET_SIZE, executor_->executor_id_, /*num_threads=*/1);
       deleted_column_stats =
           getDeletedColumnStats(td, getFragmentIndexes(td, fragment_ids));
       executor_->clearMetaInfoCache();
diff --git a/QueryEngine/WindowContext.cpp b/QueryEngine/WindowContext.cpp
index d2a6107606..8e67cec952 100644
--- a/QueryEngine/WindowContext.cpp
+++ b/QueryEngine/WindowContext.cpp
@@ -419,11 +419,11 @@ void index_to_partition_end(
   int64_t partition_end_handle = reinterpret_cast<int64_t>(partition_end);
   for (size_t i = 0; i < index_size; ++i) {
     if (advance_current_rank(comparator, index, i)) {
-      agg_count_distinct_bitmap(&partition_end_handle, off + i - 1, 0);
+      agg_count_distinct_bitmap(&partition_end_handle, off + i - 1, 0, 0);
     }
   }
   CHECK(index_size);
-  agg_count_distinct_bitmap(&partition_end_handle, off + index_size - 1, 0);
+  agg_count_distinct_bitmap(&partition_end_handle, off + index_size - 1, 0, 0);
 }
 
 bool pos_is_set(const int64_t bitset, const int64_t pos) {
@@ -1694,6 +1694,7 @@ int64_t* WindowFunctionContext::getNullValueEndPos() const {
 
 void WindowFunctionContext::fillPartitionStart() {
   CountDistinctDescriptor partition_start_bitmap{CountDistinctImplType::Bitmap,
+                                                 0,
                                                  0,
                                                  static_cast<int64_t>(elem_count_),
                                                  false,
@@ -1706,7 +1707,7 @@ void WindowFunctionContext::fillPartitionStart() {
   partition_start_ = static_cast<int8_t*>(checked_calloc(bitmap_sz, 1));
   int64_t partition_count = partitionCount();
   auto partition_start_handle = reinterpret_cast<int64_t>(partition_start_);
-  agg_count_distinct_bitmap(&partition_start_handle, 0, 0);
+  agg_count_distinct_bitmap(&partition_start_handle, 0, 0, 0);
   if (partition_start_offset_) {
     // if we have `partition_start_offset_`, we can reuse it for this logic
     // but note that it has partition_count + 1 elements where the first element is zero
@@ -1714,19 +1715,20 @@ void WindowFunctionContext::fillPartitionStart() {
     // and rest of them can represent values required for this logic
     for (int64_t i = 0; i < partition_count - 1; ++i) {
       agg_count_distinct_bitmap(
-          &partition_start_handle, partition_start_offset_[i + 1], 0);
+          &partition_start_handle, partition_start_offset_[i + 1], 0, 0);
     }
   } else {
     std::vector<size_t> partition_offsets(partition_count);
     std::partial_sum(counts(), counts() + partition_count, partition_offsets.begin());
     for (int64_t i = 0; i < partition_count - 1; ++i) {
-      agg_count_distinct_bitmap(&partition_start_handle, partition_offsets[i], 0);
+      agg_count_distinct_bitmap(&partition_start_handle, partition_offsets[i], 0, 0);
     }
   }
 }
 
 void WindowFunctionContext::fillPartitionEnd() {
   CountDistinctDescriptor partition_start_bitmap{CountDistinctImplType::Bitmap,
+                                                 0,
                                                  0,
                                                  static_cast<int64_t>(elem_count_),
                                                  false,
@@ -1749,10 +1751,10 @@ void WindowFunctionContext::fillPartitionEnd() {
         continue;
       }
       agg_count_distinct_bitmap(
-          &partition_end_handle, partition_start_offset_[i + 1] - 1, 0);
+          &partition_end_handle, partition_start_offset_[i + 1] - 1, 0, 0);
     }
     if (elem_count_) {
-      agg_count_distinct_bitmap(&partition_end_handle, elem_count_ - 1, 0);
+      agg_count_distinct_bitmap(&partition_end_handle, elem_count_ - 1, 0, 0);
     }
   } else {
     std::vector<size_t> partition_offsets(partition_count);
@@ -1761,10 +1763,10 @@ void WindowFunctionContext::fillPartitionEnd() {
       if (partition_offsets[i] == 0) {
         continue;
       }
-      agg_count_distinct_bitmap(&partition_end_handle, partition_offsets[i] - 1, 0);
+      agg_count_distinct_bitmap(&partition_end_handle, partition_offsets[i] - 1, 0, 0);
     }
     if (elem_count_) {
-      agg_count_distinct_bitmap(&partition_end_handle, elem_count_ - 1, 0);
+      agg_count_distinct_bitmap(&partition_end_handle, elem_count_ - 1, 0, 0);
     }
   }
 }
diff --git a/QueryEngine/cuda_mapd_rt.cu b/QueryEngine/cuda_mapd_rt.cu
index aa5cb9381f..3e1c0784de 100644
--- a/QueryEngine/cuda_mapd_rt.cu
+++ b/QueryEngine/cuda_mapd_rt.cu
@@ -1303,12 +1303,16 @@ extern "C" __device__ void linear_probabilistic_count(uint8_t* bitmap,
 extern "C" __device__ void agg_count_distinct_bitmap_gpu(int64_t* agg,
                                                          const int64_t val,
                                                          const int64_t min_val,
+                                                         const int64_t bucket_size,
                                                          const int64_t base_dev_addr,
                                                          const int64_t base_host_addr,
                                                          const uint64_t sub_bitmap_count,
                                                          const uint64_t bitmap_bytes) {
   constexpr unsigned bitmap_element_size = 8 * sizeof(uint32_t);
-  auto const bitmap_idx = static_cast<uint64_t>(val - min_val);
+  auto bitmap_idx = static_cast<uint64_t>(val - min_val);
+  if (1 < bucket_size) {
+    bitmap_idx /= static_cast<uint64_t>(bucket_size);
+  }
   uint64_t const word_idx = bitmap_idx / bitmap_element_size;
   uint32_t const bit_idx = bitmap_idx % bitmap_element_size;
   int64_t const agg_offset = *agg - base_host_addr;
@@ -1321,14 +1325,21 @@ extern "C" __device__ void agg_count_distinct_bitmap_skip_val_gpu(
     int64_t* agg,
     const int64_t val,
     const int64_t min_val,
+    const int64_t bucket_size,
     const int64_t skip_val,
     const int64_t base_dev_addr,
     const int64_t base_host_addr,
     const uint64_t sub_bitmap_count,
     const uint64_t bitmap_bytes) {
   if (val != skip_val) {
-    agg_count_distinct_bitmap_gpu(
-        agg, val, min_val, base_dev_addr, base_host_addr, sub_bitmap_count, bitmap_bytes);
+    agg_count_distinct_bitmap_gpu(agg,
+                                  val,
+                                  min_val,
+                                  bucket_size,
+                                  base_dev_addr,
+                                  base_host_addr,
+                                  sub_bitmap_count,
+                                  bitmap_bytes);
   }
 }
 
diff --git a/QueryEngine/heavydbTypes.h b/QueryEngine/heavydbTypes.h
index 85442eb99f..aa87db0057 100644
--- a/QueryEngine/heavydbTypes.h
+++ b/QueryEngine/heavydbTypes.h
@@ -73,6 +73,7 @@ EXTENSION_NOINLINE int8_t* allocate_varlen_buffer(int64_t element_count,
       .c_str()
 #define TABLE_FUNCTION_ERROR(MSG) table_function_error(ERROR_STRING(MSG))
 #define ERROR_MESSAGE(MSG) error_message(ERROR_STRING(MSG))
+#define SUCCESS table_function_success_code()
 
 EXTENSION_NOINLINE_HOST void set_output_item_values_total_number(
     int32_t index,
@@ -93,6 +94,7 @@ EXTENSION_NOINLINE_HOST void TableFunctionManager_set_output_row_size(int8_t* mg
                                                                       int64_t num_rows);
 EXTENSION_NOINLINE_HOST int8_t* TableFunctionManager_get_singleton();
 EXTENSION_NOINLINE_HOST int32_t table_function_error(const char* message);
+EXTENSION_NOINLINE_HOST int32_t table_function_success_code();
 EXTENSION_NOINLINE_HOST int32_t TableFunctionManager_error_message(int8_t* mgr_ptr,
                                                                    const char* message);
 EXTENSION_NOINLINE_HOST void TableFunctionManager_set_metadata(
@@ -1289,6 +1291,11 @@ struct Column {
     this_item += item;
   }
 
+  DEVICE inline void concatItem(int64_t index, const RowStruct& item) {
+    RowType this_item = getItem(index);
+    this_item += item;
+  }
+
   // Return row object with the specified extra number of elements.
   DEVICE RowType getItem(const int64_t index, const int64_t extra_numel) const {
     auto result = (*this)[index];
diff --git a/QueryEngine/scripts/TableFunctionsFactory_declbracket.py b/QueryEngine/scripts/TableFunctionsFactory_declbracket.py
index e4cc289b29..28f25dc491 100644
--- a/QueryEngine/scripts/TableFunctionsFactory_declbracket.py
+++ b/QueryEngine/scripts/TableFunctionsFactory_declbracket.py
@@ -209,8 +209,15 @@ def get_cpp_type(self):
         return '%s<%s<%s>>' % (clsname, subclsname, ctype)
 
     def format_cpp_type(self, idx, use_generic_arg_name=False, real_arg_name=None, is_input=True):
-        col_typs = ('Column', 'ColumnList')
-        literal_ref_typs = ('TextEncodingNone',)
+        # Arguments that types are derived from arithmetic and bool
+        # types, should be passed by value:
+        pass_by_value_typs =(
+            'bool', 'int8_t', 'int16_t', 'int32_t', 'int64_t', 'float',
+            'double', 'Timestamp', 'DayTimeInterval', 'YearMonthTimeInterval', 'TextEncodingDict')
+        # Arguments that types are of struct type, should be passed by
+        # reference:
+        pass_by_reference_typs =('Column', 'ColumnList', 'TextEncodingNone', 'Array')
+
         if use_generic_arg_name:
             arg_name = 'input' + str(idx) if is_input else 'output' + str(idx)
         elif real_arg_name is not None:
@@ -220,15 +227,20 @@ def format_cpp_type(self, idx, use_generic_arg_name=False, real_arg_name=None, i
             arg_name = 'input' + str(idx) if is_input else 'output' + str(idx)
         const = 'const ' if is_input else ''
         cpp_type = self.get_cpp_type()
-        if any(cpp_type.startswith(t) for t in col_typs + literal_ref_typs):
+        if any(cpp_type.startswith(t) for t in pass_by_reference_typs):
             return '%s%s& %s' % (const, cpp_type, arg_name), arg_name
-        else:
+        elif any(cpp_type.startswith(t) for t in pass_by_value_typs):
             return '%s %s' % (cpp_type, arg_name), arg_name
+        else:
+            msg =('Argument passing policy for argument `%s` of C++ type `%s` is unspecified.'
+                  ' Update pass_by_value_typs or pass_by_reference_typs in'
+                  ' Declaration.format_cpp_type to indicate the desired argument'
+                  ' passing policy.') %(arg_name, cpp_type)
+            raise NotImplementedError(msg)
 
     @classmethod
     def parse(cls, typ):
         """typ is a string in format NAME<ARGS> or NAME
-
         Returns Bracket instance.
         """
         i = typ.find('<')
diff --git a/QueryEngine/scripts/TableFunctionsFactory_transformers.py b/QueryEngine/scripts/TableFunctionsFactory_transformers.py
index 459c9f1070..e37c2d5de7 100644
--- a/QueryEngine/scripts/TableFunctionsFactory_transformers.py
+++ b/QueryEngine/scripts/TableFunctionsFactory_transformers.py
@@ -2,7 +2,7 @@
            'FixRowMultiplierPosArgTransformer', 'RenameNodesTransformer',
            'TextEncodingDictTransformer', 'FieldAnnotationTransformer',
            'SupportedAnnotationsTransformer', 'RangeAnnotationTransformer',
-           'AmbiguousSignatureCheckTransformer',
+           'CursorAnnotationTransformer', 'AmbiguousSignatureCheckTransformer',
            'DefaultValueAnnotationTransformer',
            'DeclBracketTransformer', 'Pipeline']
 
@@ -448,6 +448,20 @@ def visit_arg_node(self, arg_node):
         return arg_node
 
 
+class CursorAnnotationTransformer(AstTransformer):
+    """
+    * Move a "require" annotation from inside a cursor to the cursor
+    """
+
+    def visit_arg_node(self, arg_node):
+        if arg_node.type.is_cursor():
+            for inner in arg_node.type.inner:
+                for ann in inner.annotations:
+                    if ann.key == 'require':
+                        arg_node.annotations.append(ann)
+        return arg_node
+
+
 class DeclBracketTransformer(AstTransformer):
 
     def visit_udtf_node(self, udtf_node):
diff --git a/QueryEngine/scripts/generate_TableFunctionsFactory_init.py b/QueryEngine/scripts/generate_TableFunctionsFactory_init.py
index b001142cc3..a4d3e1c523 100644
--- a/QueryEngine/scripts/generate_TableFunctionsFactory_init.py
+++ b/QueryEngine/scripts/generate_TableFunctionsFactory_init.py
@@ -126,6 +126,7 @@ def find_signatures(input_file):
                     transformers.DefaultValueAnnotationTransformer,
                     transformers.SupportedAnnotationsTransformer,
                     transformers.RangeAnnotationTransformer,
+                    transformers.CursorAnnotationTransformer,
                     transformers.FixRowMultiplierPosArgTransformer,
                     transformers.RenameNodesTransformer,
                     transformers.AstPrinter)(ast)
@@ -149,6 +150,7 @@ def find_signatures(input_file):
                 transformers.DefaultValueAnnotationTransformer,
                 transformers.SupportedAnnotationsTransformer,
                 transformers.RangeAnnotationTransformer,
+                transformers.CursorAnnotationTransformer,
                 transformers.FixRowMultiplierPosArgTransformer,
                 transformers.RenameNodesTransformer,
                 transformers.DeclBracketTransformer)(ast)
diff --git a/QueryRunner/QueryRunner.cpp b/QueryRunner/QueryRunner.cpp
index e307f87f3e..68664f7cb6 100644
--- a/QueryRunner/QueryRunner.cpp
+++ b/QueryRunner/QueryRunner.cpp
@@ -379,7 +379,7 @@ RegisteredQueryHint QueryRunner::getParsedQueryHint(const std::string& query_str
 
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -404,7 +404,7 @@ std::shared_ptr<const RelAlgNode> QueryRunner::getRootNodeFromParsedQuery(
 
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -427,7 +427,7 @@ QueryRunner::getParsedQueryHints(const std::string& query_str) {
   auto executor = Executor::getExecutor(Executor::UNITARY_EXECUTOR_ID);
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -449,7 +449,7 @@ std::optional<RegisteredQueryHint> QueryRunner::getParsedGlobalQueryHints(
   auto executor = Executor::getExecutor(Executor::UNITARY_EXECUTOR_ID);
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -470,7 +470,7 @@ RaExecutionSequence QueryRunner::getRaExecutionSequence(const std::string& query
   auto executor = Executor::getExecutor(Executor::UNITARY_EXECUTOR_ID);
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -500,7 +500,7 @@ void QueryRunner::validateDDLStatement(const std::string& stmt_str_in) {
   auto& cat = session_info_->getCatalog();
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   calcite_mgr->process(query_state->createQueryStateProxy(),
@@ -518,7 +518,7 @@ std::shared_ptr<RelAlgTranslator> QueryRunner::getRelAlgTranslator(
   auto& cat = session_info_->getCatalog();
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -541,7 +541,7 @@ QueryPlanDagInfo QueryRunner::getQueryInfoForDataRecyclerTest(
   auto executor = Executor::getExecutor(Executor::UNITARY_EXECUTOR_ID);
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   const auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -580,7 +580,7 @@ std::unique_ptr<Parser::Stmt> QueryRunner::createStatement(
     const auto& cat = session_info_->getCatalog();
     auto calcite_mgr = cat.getCalciteMgr();
     const auto calciteQueryParsingOption =
-        calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+        calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
     const auto calciteOptimizationOption =
         calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
     const auto query_json = calcite_mgr
@@ -616,7 +616,7 @@ void QueryRunner::runDDLStatement(const std::string& stmt_str_in) {
     auto& cat = session_info_->getCatalog();
     auto calcite_mgr = cat.getCalciteMgr();
     const auto calciteQueryParsingOption =
-        calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+        calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
     const auto calciteOptimizationOption =
         calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
     const auto query_ra = calcite_mgr
@@ -682,6 +682,7 @@ ExecutionOptions QueryRunner::defaultExecutionOptionsForRunSQL(bool allow_loop_j
           false,
           g_gpu_mem_limit_percent,
           false,
+          0.5,
           1000,
           false};
 }
@@ -750,7 +751,7 @@ std::shared_ptr<ResultSet> QueryRunner::runSQLWithAllowingInterrupt(
           std::lock_guard<std::mutex> calcite_lock_guard(calcite_lock);
           auto calcite_mgr = cat.getCalciteMgr();
           const auto calciteQueryParsingOption =
-              calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+              calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
           const auto calciteOptimizationOption =
               calcite_mgr->getCalciteOptimizationOption(
                   false, g_enable_watchdog, {}, false);
@@ -838,7 +839,7 @@ std::shared_ptr<ExecutionResult> run_select_query_with_filter_push_down(
 
   auto calcite_mgr = cat.getCalciteMgr();
   const auto calciteQueryParsingOption =
-      calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+      calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
   auto calciteOptimizationOption =
       calcite_mgr->getCalciteOptimizationOption(false, g_enable_watchdog, {}, false);
   const auto query_ra = calcite_mgr
@@ -882,7 +883,8 @@ std::shared_ptr<ExecutionResult> run_select_query_with_filter_push_down(
 
 std::shared_ptr<ResultSet> QueryRunner::getCalcitePlan(const std::string& query_str,
                                                        bool enable_watchdog,
-                                                       bool as_json_str) const {
+                                                       bool is_explain_as_json_str,
+                                                       bool is_explain_detailed) const {
   CHECK(!Catalog_Namespace::SysCatalog::instance().isAggregator());
   CHECK(session_info_);
   const auto& cat = session_info_->getCatalog();
@@ -894,15 +896,18 @@ std::shared_ptr<ResultSet> QueryRunner::getCalcitePlan(const std::string& query_
       [&cat,
        &query_str,
        &enable_watchdog,
-       &as_json_str,
+       &is_explain_as_json_str,
+       &is_explain_detailed,
        &query_state,
        &result,
        parent_thread_local_ids = logger::thread_local_ids()](const size_t worker_id) {
         logger::LocalIdsScopeGuard lisg = parent_thread_local_ids.setNewThreadId();
         auto executor = Executor::getExecutor(worker_id);
         auto calcite_mgr = cat.getCalciteMgr();
-        const auto calciteQueryParsingOption =
-            calcite_mgr->getCalciteQueryParsingOption(true, as_json_str, false);
+        // Calcite returns its plan as a form of `json_str` by default,
+        // so we set `is_explain` to TRUE if `!is_explain_as_json_str`
+        const auto calciteQueryParsingOption = calcite_mgr->getCalciteQueryParsingOption(
+            true, !is_explain_as_json_str, false, is_explain_detailed);
         const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
             g_enable_calcite_view_optimize, enable_watchdog, {}, false);
         const auto query_ra = calcite_mgr
@@ -960,7 +965,7 @@ std::shared_ptr<ExecutionResult> QueryRunner::runSelectQuery(const std::string&
         co.explain_type = explain_type;
         auto calcite_mgr = cat.getCalciteMgr();
         const auto calciteQueryParsingOption =
-            calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+            calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
         const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
             g_enable_calcite_view_optimize, g_enable_watchdog, {}, false);
         const auto query_ra = calcite_mgr
@@ -1021,7 +1026,7 @@ std::unique_ptr<RelAlgDag> QueryRunner::getRelAlgDag(const std::string& query_st
         auto eo = ExecutionOptions::defaults();
         auto calcite_mgr = cat.getCalciteMgr();
         const auto calciteQueryParsingOption =
-            calcite_mgr->getCalciteQueryParsingOption(true, false, true);
+            calcite_mgr->getCalciteQueryParsingOption(true, false, true, false);
         const auto calciteOptimizationOption = calcite_mgr->getCalciteOptimizationOption(
             g_enable_calcite_view_optimize, g_enable_watchdog, {}, false);
         const auto query_ra = calcite_mgr
diff --git a/QueryRunner/QueryRunner.h b/QueryRunner/QueryRunner.h
index 03d6452a74..87ccfb2761 100644
--- a/QueryRunner/QueryRunner.h
+++ b/QueryRunner/QueryRunner.h
@@ -268,7 +268,8 @@ class QueryRunner {
   RaExecutionSequence getRaExecutionSequence(const std::string& query_str);
   virtual std::shared_ptr<ResultSet> getCalcitePlan(const std::string& query_str,
                                                     bool enable_watchdog,
-                                                    bool as_json_str) const;
+                                                    bool is_explain_as_json_str,
+                                                    bool is_explain_detailed) const;
 
   std::tuple<QueryPlanHash,
              std::shared_ptr<HashTable>,
diff --git a/README.md b/README.md
index 4722dac787..ff50080635 100644
--- a/README.md
+++ b/README.md
@@ -308,9 +308,9 @@ The [scripts/mapd-deps-ubuntu.sh](scripts/mapd-deps-ubuntu.sh) and [scripts/mapd
 
 ## Arch
 
-[scripts/mapd-deps-arch.sh](scripts/mapd-deps-arch.sh) is provided that will use [yay](https://aur.archlinux.org/packages/yay/) to install packages from the [Arch User Repository](https://wiki.archlinux.org/index.php/Arch_User_Repository) and a custom PKGBUILD script for Apache Arrow. If you don't have yay yet, install it first: https://github.com/Jguer/yay#installation
+[scripts/mapd-deps-arch.sh](scripts/mapd-deps-arch.sh) is provided that will use [yay](https://aur.archlinux.org/packages/yay/) to install packages from the [Arch User Repository](https://wiki.archlinux.org/index.php/Arch_User_Repository) and custom PKGBUILD scripts for a few packages listed below. If you don't have `yay` yet, install it first: https://github.com/Jguer/yay#installation
 
-Note: Apache Arrow, while available in the AUR, requires a few custom build flags in order to be used with Core. A custom PKGBUILD for it is included.
+### Package Version Requirements:
 
 ### CUDA
 
diff --git a/Shared/CallbackManager.h b/Shared/CallbackManager.h
new file mode 100644
index 0000000000..19772bf83a
--- /dev/null
+++ b/Shared/CallbackManager.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2023 HEAVY.AI, Inc.
+ *
+ * Licensed 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 <limits>
+#include <map>
+#include <type_traits>
+#include <vector>
+
+/**
+ * Template class for managing notification callbacks
+ *
+ * IDType must be an intergral type
+ * CallbackType must be a callable type: lambda, functor, etc
+ * ContextType can be any basic type, struct, or class
+ *
+ * Notification broadcast is in registration order
+ * IDs DO NOT wrap around, and a free-list is not used, so IDType must
+ * not be exhaustible
+ *
+ * Unregistering a callback during notification handling will defer
+ * unregistration until notification completes
+ *
+ * Example:
+ *
+ * struct MyContextType {
+ *   int value;
+ *   ...
+ * };
+ * using MyCallbackType = std::function<void(const MyContextType&)>;
+ * using MyCallbackID = uint32_t;
+ *
+ * class SomeClass {
+ *   CallbackManager<MyCallbackID, MyCallbackType, MyContextType> callbacks;
+ *
+ *   SomeClass() {
+ *     callbacks.registerCallback([this](const MyContextType& context) {
+ *         handleCallback(context);
+ *       }
+ *     );
+ *   }
+ *
+ *   void handleCallback(const MyContextType& context) {
+ *     doStuff(context.value);
+ *   }
+ * };
+ * */
+template <typename IDType, typename CallbackType, typename ContextType>
+class CallbackManager {
+  static_assert(std::is_integral_v<IDType>, "IDType must be and integral type");
+  static_assert(
+      std::is_invocable_v<CallbackType, const ContextType&>,
+      "CallbackType must be callable with \'const ContextType&\' as the argument");
+
+ public:
+  IDType registerCallback(CallbackType callback) {
+    CHECK_LT(next_id_, std::numeric_limits<IDType>::max());
+    callbacks_.emplace(next_id_++, callback);
+    return next_id_ - 1;
+  }
+
+  void unregisterCallback(IDType id) {
+    CHECK(callbacks_.count(id)) << "Callback id not found";
+    if (is_notifying_) {
+      // Defer unregistration until notification loop is complete
+      deferred_unregister_ids_.push_back(id);
+    } else {
+      callbacks_.erase(id);
+    }
+  }
+
+  void notify(const ContextType& context) {
+    is_notifying_ = true;
+    for (auto callback_pair : callbacks_) {
+      callback_pair.second(context);
+    }
+    is_notifying_ = false;
+
+    // Unregister any callbacks that tried to unregister during notification
+    if (!deferred_unregister_ids_.empty()) {
+      for (auto id : deferred_unregister_ids_) {
+        unregisterCallback(id);
+      }
+      deferred_unregister_ids_.clear();
+    }
+  }
+
+  bool isEmpty() const { return callbacks_.empty(); }
+
+ private:
+  // Use std::map to ensure callback iteration is in registration order
+  std::map<IDType, CallbackType> callbacks_;
+  IDType next_id_;
+  bool is_notifying_;
+  std::vector<IDType> deferred_unregister_ids_;
+};
diff --git a/StringDictionary/LruCache.hpp b/Shared/LruCache.h
similarity index 55%
rename from StringDictionary/LruCache.hpp
rename to Shared/LruCache.h
index c9def2832a..c0b8c40794 100644
--- a/StringDictionary/LruCache.hpp
+++ b/Shared/LruCache.h
@@ -5,13 +5,22 @@
  * Created on June 20, 2013, 5:09 PM
  */
 
-#ifndef STRINGDICTIONARY_LRUCACHE_HPP
-#define STRINGDICTIONARY_LRUCACHE_HPP
+#pragma once
 
 #include <cstddef>
 #include <list>
+#include <memory>
+#include <type_traits>
 #include <unordered_map>
 
+template <class T>
+struct is_shared_ptr : std::false_type {};
+
+template <class T>
+struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
+
+enum class EvictionMetricType { EntryCount, ByteSize };
+
 template <typename key_t, typename value_t, class hash_t = std::hash<key_t>>
 class LruCache {
  private:
@@ -22,23 +31,29 @@ class LruCache {
   using map_t_iterator = typename map_t::iterator;
 
  public:
-  LruCache(const size_t max_size) : max_size_(max_size) {}
+  LruCache(EvictionMetricType eviction_metric_type, const size_t max_size)
+      : eviction_metric_type_(eviction_metric_type)
+      , max_size_(max_size)
+      , total_byte_size_(0) {}
 
-  void put(const key_t& key, value_t&& value) {
+  size_t put(const key_t& key, value_t&& value) {
+    total_byte_size_ += getValueSize(value);
     auto it = cache_items_map_.find(key);
     cache_items_list_.emplace_front(key, std::forward<value_t&&>(value));
-    putCommon(it, key);
+    return putCommon(it, key);
   }
 
-  void put(const key_t& key, const value_t& value) {
+  size_t put(const key_t& key, const value_t& value) {
+    total_byte_size_ += getValueSize(value);
     auto it = cache_items_map_.find(key);
     cache_items_list_.emplace_front(key, std::forward<const value_t&&>(value));
-    putCommon(it, key);
+    return putCommon(it, key);
   }
 
   void erase(const key_t& key) {
     auto it = cache_items_map_.find(key);
     if (it != cache_items_map_.end()) {
+      total_byte_size_ -= getValueSize(it->second);
       cache_items_list_.erase(it->second);
       cache_items_map_.erase(it);
     }
@@ -70,6 +85,7 @@ class LruCache {
   void clear() {
     cache_items_list_.clear();
     cache_items_map_.clear();
+    total_byte_size_ = 0;
   }
 
   size_t computeNumEntriesToEvict(const float fraction) {
@@ -80,40 +96,67 @@ class LruCache {
         cache_items_map_.size());
   }
 
-  void evictNEntries(const size_t n) { evictCommon(n); }
+  size_t evictNEntries(const size_t n) { return evictCommon(n); }
 
-  size_t size() const { return cache_items_list_.size(); }
+  size_t size() const {
+    return eviction_metric_type_ == EvictionMetricType::EntryCount
+               ? cache_items_list_.size()
+               : total_byte_size_;
+  }
 
  private:
-  void putCommon(map_t_iterator& it, key_t const& key) {
+  size_t putCommon(map_t_iterator& it, key_t const& key) {
+    size_t entries_erased = 0;
     if (it != cache_items_map_.end()) {
+      total_byte_size_ -= getValueSize(it->second);
       cache_items_list_.erase(it->second);
       cache_items_map_.erase(it);
+      entries_erased++;
     }
     cache_items_map_[key] = cache_items_list_.begin();
 
-    if (cache_items_map_.size() > max_size_) {
+    while ((eviction_metric_type_ == EvictionMetricType::EntryCount &&
+            cache_items_map_.size() > max_size_) ||
+           (eviction_metric_type_ == EvictionMetricType::ByteSize &&
+            total_byte_size_ > max_size_)) {
       auto last = cache_items_list_.end();
       last--;
-      cache_items_map_.erase(last->first);
+      auto target_it = cache_items_map_.find(last->first);
+      total_byte_size_ -= getValueSize(target_it->second);
+      cache_items_map_.erase(target_it);
       cache_items_list_.pop_back();
+      entries_erased++;
     }
+    return entries_erased;
   }
 
-  void evictCommon(const size_t entries_to_evict) {
+  size_t evictCommon(const size_t entries_to_evict) {
     auto last = cache_items_list_.end();
     size_t entries_erased = 0;
     while (entries_erased < entries_to_evict && last != cache_items_list_.begin()) {
       last--;
+      total_byte_size_ -= getValueSize(last->second);
       cache_items_map_.erase(last->first);
       last = cache_items_list_.erase(last);
       entries_erased++;
     }
+    return entries_erased;
   }
 
+  size_t getValueSize(const value_t& value) {
+    if constexpr (std::is_pointer_v<value_t> || is_shared_ptr<value_t>::value) {
+      // 'value == nullptr' represents a call from the `get_or_wait` function
+      return value ? value->size() : 0;
+    } else {
+      return value.size();
+    }
+  }
+
+  size_t getValueSize(const list_iterator_t& it) { return getValueSize(it->second); }
+
   cache_list_t cache_items_list_;
   map_t cache_items_map_;
+  EvictionMetricType eviction_metric_type_;
   size_t max_size_;
+  size_t total_byte_size_;
 };
-
-#endif  // STRINGDICTIONARY_LRUCACHE_HPP
diff --git a/Shared/sqldefs.h b/Shared/sqldefs.h
index 8ec90c1e62..f525339e3a 100644
--- a/Shared/sqldefs.h
+++ b/Shared/sqldefs.h
@@ -112,8 +112,10 @@ enum class SqlStringOpKind {
   JSON_VALUE,
   BASE64_ENCODE,
   BASE64_DECODE,
-  TRY_STRING_CAST,  // string-to-numeric
-  POSITION,         // string-to-numeric
+  TRY_STRING_CAST,         // string-to-numeric
+  POSITION,                // string-to-numeric
+  JAROWINKLER_SIMILARITY,  // string-to-numeric
+  LEVENSHTEIN_DISTANCE,    // string-to-numeric
   INVALID
 };
 
@@ -371,6 +373,10 @@ inline std::ostream& operator<<(std::ostream& os, const SqlStringOpKind kind) {
       return os << "TRY_STRING_CAST";
     case SqlStringOpKind::POSITION:
       return os << "POSITION";
+    case SqlStringOpKind::JAROWINKLER_SIMILARITY:
+      return os << "JAROWINKLER_SIMILARITY";
+    case SqlStringOpKind::LEVENSHTEIN_DISTANCE:
+      return os << "LEVENSHTEIN_DISTANCE";
     case SqlStringOpKind::INVALID:
       return os << "INVALID";
   }
@@ -449,6 +455,12 @@ inline SqlStringOpKind name_to_string_op_kind(const std::string& func_name) {
   if (func_name == "POSITION") {
     return SqlStringOpKind::POSITION;
   }
+  if (func_name == "JAROWINKLER_SIMILARITY") {
+    return SqlStringOpKind::JAROWINKLER_SIMILARITY;
+  }
+  if (func_name == "LEVENSHTEIN_DISTANCE") {
+    return SqlStringOpKind::LEVENSHTEIN_DISTANCE;
+  }
   LOG(FATAL) << "Invalid string function " << func_name << ".";
   return SqlStringOpKind::INVALID;
 }
diff --git a/StringDictionary/StringDictionary.cpp b/StringDictionary/StringDictionary.cpp
index 0c42f4a9ba..263e7766d3 100644
--- a/StringDictionary/StringDictionary.cpp
+++ b/StringDictionary/StringDictionary.cpp
@@ -539,7 +539,6 @@ size_t StringDictionary::getBulk(const std::vector<String>& string_vec,
 
   tbb::task_arena limited_arena(thread_info.num_threads);
   limited_arena.execute([&] {
-    CHECK_LE(tbb::this_task_arena::max_concurrency(), thread_info.num_threads);
     tbb::parallel_for(
         tbb::blocked_range<int64_t>(
             0, num_lookup_strings, thread_info.num_elems_per_thread /* tbb grain_size */),
@@ -1792,7 +1791,6 @@ std::vector<std::string_view> StringDictionary::getStringViews(
     CHECK_GE(thread_info.num_elems_per_thread, 1L);
 
     tbb::task_arena limited_arena(thread_info.num_threads);
-    CHECK_LE(tbb::this_task_arena::max_concurrency(), thread_info.num_threads);
     limited_arena.execute([&] {
       tbb::parallel_for(
           tbb::blocked_range<int64_t>(
@@ -1915,7 +1913,6 @@ size_t StringDictionary::buildDictionaryTranslationMap(
   std::vector<size_t> num_strings_not_translated_per_thread(thread_info.num_threads, 0UL);
   constexpr bool short_circuit_empty_dictionary_translations{false};
   limited_arena.execute([&] {
-    CHECK_LE(tbb::this_task_arena::max_concurrency(), thread_info.num_threads);
     if (short_circuit_empty_dictionary_translations && dest_dictionary_is_empty) {
       tbb::parallel_for(
           tbb::blocked_range<int32_t>(
@@ -2050,19 +2047,21 @@ void StringDictionary::buildDictionaryNumericTranslationMap(
   // destination dictionary, but this version gets significantly better performance
   // (~2X), likely due to eliminating the overhead of writing out the string views and
   // then reading them back in (along with the associated cache misses)
-  tbb::parallel_for(
-      tbb::blocked_range<int32_t>(
-          0, num_source_strings, thread_info.num_elems_per_thread /* tbb grain_size */),
-      [&](const tbb::blocked_range<int32_t>& r) {
-        const int32_t start_idx = r.begin();
-        const int32_t end_idx = r.end();
-        for (int32_t source_string_id = start_idx; source_string_id != end_idx;
-             ++source_string_id) {
-          const std::string source_str =
-              std::string(getStringFromStorageFast(source_string_id));
-          translated_ids[source_string_id] = string_ops.numericEval(source_str);
-        }
-      });
+  limited_arena.execute([&] {
+    tbb::parallel_for(
+        tbb::blocked_range<int32_t>(
+            0, num_source_strings, thread_info.num_elems_per_thread /* tbb grain_size */),
+        [&](const tbb::blocked_range<int32_t>& r) {
+          const int32_t start_idx = r.begin();
+          const int32_t end_idx = r.end();
+          for (int32_t source_string_id = start_idx; source_string_id != end_idx;
+               ++source_string_id) {
+            const std::string source_str =
+                std::string(getStringFromStorageFast(source_string_id));
+            translated_ids[source_string_id] = string_ops.numericEval(source_str);
+          }
+        });
+  });
 }
 
 void translate_string_ids(std::vector<int32_t>& dest_ids,
diff --git a/StringOps/StringOps.cpp b/StringOps/StringOps.cpp
index 5df37d8af8..c7eb852c2f 100644
--- a/StringOps/StringOps.cpp
+++ b/StringOps/StringOps.cpp
@@ -118,6 +118,203 @@ Datum Position::numericEval(const std::string_view str) const {
   }
 }
 
+// Prefix length to consider for the Jaro-Winkler score.
+constexpr int winkler_k_prefix_length = 4;
+
+// Scaling factor for the adjustment of the score.
+constexpr double winkler_k_scaling_factor = 0.1;
+
+double compute_jaro_score(std::string_view s1, std::string_view s2) {
+  int s1_len = s1.size();
+  int s2_len = s2.size();
+
+  if (s1_len == 0 || s2_len == 0) {
+    return 0.0;
+  }
+
+  int match_distance = std::max(s1_len, s2_len) / 2 - 1;
+  std::vector<bool> s1_match(s1_len, false);
+  std::vector<bool> s2_match(s2_len, false);
+
+  int matches = 0;
+  int transpositions = 0;
+
+  for (int i = 0; i < s1_len; ++i) {
+    int start = std::max(0, i - match_distance);
+    int end = std::min(i + match_distance + 1, s2_len);
+
+    for (int j = start; j < end; ++j) {
+      if (s2_match[j]) {
+        continue;
+      }
+      if (s1[i] != s2[j]) {
+        continue;
+      }
+      s1_match[i] = true;
+      s2_match[j] = true;
+      ++matches;
+      break;
+    }
+  }
+
+  if (matches == 0) {
+    return 0.0;
+  }
+
+  int k = 0;
+  for (int i = 0; i < s1_len; ++i) {
+    if (!s1_match[i]) {
+      continue;
+    }
+    while (!s2_match[k]) {
+      ++k;
+    }
+    if (s1[i] != s2[k]) {
+      ++transpositions;
+    }
+    ++k;
+  }
+
+  double score = ((matches / (double)s1_len) + (matches / (double)s2_len) +
+                  ((matches - transpositions / 2.0) / matches)) /
+                 3.0;
+
+  return score;
+}
+
+double compute_jaro_winkler_score(std::string_view s1, std::string_view s2) {
+  double jaro_score = compute_jaro_score(s1, s2);
+
+  int l = 0;
+  int n = std::min({static_cast<int>(s1.size()),
+                    static_cast<int>(s2.size()),
+                    winkler_k_prefix_length});
+
+  for (; l < n; ++l) {
+    if (s1[l] != s2[l]) {
+      break;
+    }
+  }
+
+  double winkler_adjustment = l * winkler_k_scaling_factor * (1 - jaro_score);
+  double jaro_winkler_score = jaro_score + winkler_adjustment;
+
+  return jaro_winkler_score * 100;
+}
+
+NullableStrType JarowinklerSimilarity::operator()(const std::string& str) const {
+  UNREACHABLE() << "Invalid string output for Jarowinkler Similarity";
+  return {};
+}
+
+Datum JarowinklerSimilarity::numericEval(const std::string_view str) const {
+  if (str.empty()) {
+    return NullDatum(return_ti_);
+  }
+  const double jaro_winkler_score = compute_jaro_winkler_score(str, str_literal_);
+  Datum return_datum;
+  return_datum.bigintval = static_cast<int64_t>(std::round(jaro_winkler_score));
+  return return_datum;
+}
+
+Datum JarowinklerSimilarity::numericEval(const std::string_view str1,
+                                         const std::string_view str2) const {
+  if (str1.empty() || str2.empty()) {
+    return NullDatum(return_ti_);
+  }
+  const double jaro_winkler_score = compute_jaro_winkler_score(str1, str2);
+  Datum return_datum;
+  return_datum.bigintval = static_cast<int64_t>(std::round(jaro_winkler_score));
+  return return_datum;
+}
+
+// int64_t compute_levenshtein_distance(std::string_view s1, std::string_view s2) {
+//   const size_t len1 = s1.size(), len2 = s2.size();
+//   std::vector<std::vector<size_t>> d(len1 + 1, std::vector<size_t>(len2 + 1));
+//
+//   d[0][0] = 0;
+//   for (size_t i = 1; i <= len1; ++i) {
+//     d[i][0] = i;
+//   }
+//   for (size_t i = 1; i <= len2; ++i) {
+//     d[0][i] = i;
+//   }
+//
+//   for (size_t i = 1; i <= len1; ++i) {
+//     for (size_t j = 1; j <= len2; ++j) {
+//       d[i][j] = std::min({d[i - 1][j] + 1,
+//                           d[i][j - 1] + 1,
+//                           d[i - 1][j - 1] + (s1[i - 1] == s2[j - 1] ? 0 : 1)});
+//     }
+//   }
+//
+//   return d[len1][len2];
+// }
+
+template <typename T>
+T compute_levenshtein_distance_template(std::string_view s1, std::string_view s2) {
+  const size_t len1 = s1.size(), len2 = s2.size();
+  std::vector<std::vector<T>> d(len1 + 1, std::vector<T>(len2 + 1));
+
+  d[0][0] = 0;
+  for (size_t i = 1; i <= len1; ++i) {
+    d[i][0] = i;
+  }
+  for (size_t i = 1; i <= len2; ++i) {
+    d[0][i] = i;
+  }
+
+  for (size_t i = 1; i <= len1; ++i) {
+    for (size_t j = 1; j <= len2; ++j) {
+      d[i][j] = std::min({d[i - 1][j] + 1,
+                          d[i][j - 1] + 1,
+                          d[i - 1][j - 1] + (s1[i - 1] == s2[j - 1] ? 0 : 1)});
+    }
+  }
+
+  return d[len1][len2];
+}
+
+int64_t compute_levenshtein_distance(std::string_view s1, std::string_view s2) {
+  const size_t max_len = std::max(s1.size(), s2.size());
+
+  if (max_len < 256) {
+    return compute_levenshtein_distance_template<uint8_t>(s1, s2);
+  } else if (max_len < 65536) {
+    return compute_levenshtein_distance_template<uint16_t>(s1, s2);
+  } else if (max_len < std::numeric_limits<uint32_t>::max()) {
+    return compute_levenshtein_distance_template<uint32_t>(s1, s2);
+  } else {
+    return compute_levenshtein_distance_template<uint64_t>(s1, s2);
+  }
+}
+
+NullableStrType LevenshteinDistance::operator()(const std::string& str) const {
+  UNREACHABLE() << "Invalid string output for Levenshtein Distance";
+  return {};
+}
+
+Datum LevenshteinDistance::numericEval(const std::string_view str) const {
+  if (str.empty()) {
+    return NullDatum(return_ti_);
+  }
+  const double levenshtein_distance = compute_levenshtein_distance(str, str_literal_);
+  Datum return_datum;
+  return_datum.bigintval = static_cast<int64_t>(std::round(levenshtein_distance));
+  return return_datum;
+}
+
+Datum LevenshteinDistance::numericEval(const std::string_view str1,
+                                       const std::string_view str2) const {
+  if (str1.empty() || str2.empty()) {
+    return NullDatum(return_ti_);
+  }
+  const double levenshtein_distance = compute_levenshtein_distance(str1, str2);
+  Datum return_datum;
+  return_datum.bigintval = static_cast<int64_t>(std::round(levenshtein_distance));
+  return return_datum;
+}
+
 NullableStrType Lower::operator()(const std::string& str) const {
   std::string output_str(str);
   std::transform(
@@ -698,6 +895,16 @@ Datum StringOps::numericEval(const std::string_view str) const {
   return string_ops_.back()->numericEval(modified_str.str);
 }
 
+Datum StringOps::numericEval(const std::string_view str1,
+                             const std::string_view str2) const {
+  const auto num_string_producing_ops = string_ops_.size() - 1;
+  // All string ops should be evaluated before invoking
+  // numericEval with two non-literal string inputs, so
+  // num string producing ops should be 0 here
+  CHECK_EQ(num_string_producing_ops, 0UL);
+  return string_ops_.back()->numericEval(str1, str2);
+}
+
 std::vector<std::unique_ptr<const StringOp>> StringOps::genStringOpsFromOpInfos(
     const std::vector<StringOpInfo>& string_op_infos) const {
   // Should we handle pure literal expressions here as well
@@ -893,6 +1100,28 @@ std::unique_ptr<const StringOp> gen_string_op(const StringOpInfo& string_op_info
                                                 search_literal);
       }
     }
+    case SqlStringOpKind::JAROWINKLER_SIMILARITY: {
+      CHECK_GE(num_non_variable_literals, 0UL);
+      CHECK_LE(num_non_variable_literals, 1UL);
+      if (num_non_variable_literals == 1UL) {
+        const auto str_literal = string_op_info.getStringLiteral(1);
+        return std::make_unique<const JarowinklerSimilarity>(var_string_optional_literal,
+                                                             str_literal);
+      } else {
+        return std::make_unique<const JarowinklerSimilarity>(var_string_optional_literal);
+      }
+    }
+    case SqlStringOpKind::LEVENSHTEIN_DISTANCE: {
+      CHECK_GE(num_non_variable_literals, 0UL);
+      CHECK_LE(num_non_variable_literals, 1UL);
+      if (num_non_variable_literals == 1UL) {
+        const auto str_literal = string_op_info.getStringLiteral(1);
+        return std::make_unique<const LevenshteinDistance>(var_string_optional_literal,
+                                                           str_literal);
+      } else {
+        return std::make_unique<const LevenshteinDistance>(var_string_optional_literal);
+      }
+    }
     default: {
       UNREACHABLE();
       return std::make_unique<NullOp>(var_string_optional_literal, op_kind);
diff --git a/StringOps/StringOps.h b/StringOps/StringOps.h
index d8d72623dd..76a68cf517 100644
--- a/StringOps/StringOps.h
+++ b/StringOps/StringOps.h
@@ -93,6 +93,13 @@ struct StringOp {
     return NullDatum(SQLTypeInfo());
   }
 
+  virtual Datum numericEval(const std::string_view str1,
+                            const std::string_view str2) const {
+    UNREACHABLE() << "numericEval not allowed for this method";
+    // Make compiler happy
+    return NullDatum(SQLTypeInfo());
+  }
+
   virtual Datum numericEval() const {
     CHECK(hasVarStringLiteral());
     if (var_str_literal_.is_null) {
@@ -159,6 +166,46 @@ struct Position : public StringOp {
   const int64_t start_;
 };
 
+struct JarowinklerSimilarity : public StringOp {
+  JarowinklerSimilarity(const std::optional<std::string>& var_str_optional_literal,
+                        const std::string& str_literal)
+      : StringOp(SqlStringOpKind::JAROWINKLER_SIMILARITY,
+                 SQLTypeInfo(kBIGINT),
+                 var_str_optional_literal)
+      , str_literal_(str_literal) {}
+
+  JarowinklerSimilarity(const std::optional<std::string>& var_str_optional_literal)
+      : StringOp(SqlStringOpKind::JAROWINKLER_SIMILARITY, var_str_optional_literal) {}
+
+  NullableStrType operator()(const std::string& str) const override;
+
+  Datum numericEval(const std::string_view str) const override;
+  Datum numericEval(const std::string_view str1,
+                    const std::string_view str2) const override;
+
+  const std::string str_literal_;
+};
+
+struct LevenshteinDistance : public StringOp {
+  LevenshteinDistance(const std::optional<std::string>& var_str_optional_literal,
+                      const std::string& str_literal)
+      : StringOp(SqlStringOpKind::LEVENSHTEIN_DISTANCE,
+                 SQLTypeInfo(kBIGINT),
+                 var_str_optional_literal)
+      , str_literal_(str_literal) {}
+
+  LevenshteinDistance(const std::optional<std::string>& var_str_optional_literal)
+      : StringOp(SqlStringOpKind::LEVENSHTEIN_DISTANCE, var_str_optional_literal) {}
+
+  NullableStrType operator()(const std::string& str) const override;
+
+  Datum numericEval(const std::string_view str) const override;
+  Datum numericEval(const std::string_view str1,
+                    const std::string_view str2) const override;
+
+  const std::string str_literal_;
+};
+
 struct Lower : public StringOp {
   Lower(const std::optional<std::string>& var_str_optional_literal)
       : StringOp(SqlStringOpKind::LOWER, var_str_optional_literal) {}
@@ -565,6 +612,7 @@ class StringOps {
   std::string_view operator()(const std::string_view sv, std::string& sv_storage) const;
 
   Datum numericEval(const std::string_view str) const;
+  Datum numericEval(const std::string_view str1, const std::string_view str2) const;
 
   size_t size() const { return num_ops_; }
 
diff --git a/Tests/BufferMgrTest.cpp b/Tests/BufferMgrTest.cpp
index 5c6a34b997..1dd9f3ffc0 100644
--- a/Tests/BufferMgrTest.cpp
+++ b/Tests/BufferMgrTest.cpp
@@ -273,12 +273,15 @@ class MockCudaMgr : public CudaMgr_Namespace::CudaMgr {
  public:
   MockCudaMgr() : CudaMgr_Namespace::CudaMgr(1), fail_on_allocation_(false) {}
 
-  int8_t* allocateDeviceMem(const size_t num_bytes, const int device_num) override {
+  int8_t* allocateDeviceMem(const size_t num_bytes,
+                            const int device_num,
+                            const bool is_slab) override {
     int8_t* mem_ptr{nullptr};
     if (fail_on_allocation_) {
       throw CudaMgr_Namespace::CudaErrorException(CUDA_ERROR_OUT_OF_MEMORY);
     } else {
-      mem_ptr = CudaMgr_Namespace::CudaMgr::allocateDeviceMem(num_bytes, device_num);
+      mem_ptr =
+          CudaMgr_Namespace::CudaMgr::allocateDeviceMem(num_bytes, device_num, is_slab);
     }
     return mem_ptr;
   }
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index c66801952a..df07b882a8 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -136,6 +136,13 @@ list(APPEND SANITY_TEST_PROGRAMS CatalogMigrationTest)
 
 ##########
 
+add_executable(CodeCacheTest CodeCacheTest.cpp)
+target_link_libraries(CodeCacheTest ${THRIFT_HANDLER_TEST_LIBRARIES})
+add_test(CodeCacheTest CodeCacheTest ${TEST_ARGS})
+list(APPEND SANITY_TEST_PROGRAMS CodeCacheTest)
+
+##########
+
 add_executable(CodeGeneratorTest CodeGeneratorTest.cpp)
 target_link_libraries(CodeGeneratorTest ${EXECUTE_TEST_LIBS} ${LibAwsS3_LIBRARIES})
 add_test(CodeGeneratorTest CodeGeneratorTest ${TEST_ARGS})
@@ -586,9 +593,19 @@ else()
     $<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:stdc++fs>
     ${Boost_LIBRARIES} ${ZLIB_LIBRARIES} ${AwsS3_CURL_SUPPORT} ${CMAKE_DL_LIBS})
 endif()
-add_test(StringDictionaryTest StringDictionaryTest ${TEST_ARGS})
+
+if(ENABLE_TSAN)
+  add_test(StringDictionaryTest StringDictionaryTest ${TEST_ARGS} "--string-dictionary-size=10000")
+else()
+  add_test(StringDictionaryTest StringDictionaryTest ${TEST_ARGS})
+endif()
 list(APPEND SANITY_TEST_PROGRAMS StringDictionaryTest)
-add_test(NAME StringDictionaryHashTest COMMAND StringDictionaryTest ${TEST_ARGS} "--enable-string-dict-hash-cache")
+
+if(ENABLE_TSAN)
+  add_test(NAME StringDictionaryHashTest COMMAND StringDictionaryTest ${TEST_ARGS} "--enable-string-dict-hash-cache" "--string-dictionary-size=10000")
+else()
+  add_test(NAME StringDictionaryHashTest COMMAND StringDictionaryTest ${TEST_ARGS} "--enable-string-dict-hash-cache")
+endif()
 list(APPEND SANITY_TESTS StringDictionaryHashTest)
 
 ##########
diff --git a/Tests/CalciteOptimizeTest.cpp b/Tests/CalciteOptimizeTest.cpp
index 9246f200fe..23ab0f0e6f 100644
--- a/Tests/CalciteOptimizeTest.cpp
+++ b/Tests/CalciteOptimizeTest.cpp
@@ -94,7 +94,7 @@ TEST_F(ViewObject, BasicTest) {
   CHECK(session);
 
   auto calciteQueryParsingOption =
-      g_calcite->getCalciteQueryParsingOption(true, false, true);
+      g_calcite->getCalciteQueryParsingOption(true, false, true, false);
   auto calciteOptimizationOption =
       g_calcite->getCalciteOptimizationOption(false, false, {}, false);
 
@@ -144,7 +144,7 @@ TEST_F(ViewObject, Joins) {
   CHECK(session);
 
   auto calciteQueryParsingOption =
-      g_calcite->getCalciteQueryParsingOption(true, false, true);
+      g_calcite->getCalciteQueryParsingOption(true, false, true, false);
   auto calciteOptimizationOption =
       g_calcite->getCalciteOptimizationOption(true, false, {}, false);
 
diff --git a/Tests/CodeCacheTest.cpp b/Tests/CodeCacheTest.cpp
new file mode 100644
index 0000000000..61e3a5b0f4
--- /dev/null
+++ b/Tests/CodeCacheTest.cpp
@@ -0,0 +1,167 @@
+#include "DBHandlerTestHelpers.h"
+#include "Logger/Logger.h"
+#include "QueryEngine/Execute.h"
+#include "QueryEngine/QueryEngine.h"
+#include "QueryRunner/QueryRunner.h"
+#include "TestHelpers.h"
+
+#ifndef BASE_PATH
+#define BASE_PATH "./tmp"
+#endif
+
+extern size_t g_code_cache_max_num_items;
+extern size_t g_gpu_code_cache_max_size_in_bytes;
+extern bool g_is_test_env;
+
+using namespace TestHelpers;
+using QR = QueryRunner::QueryRunner;
+
+class BaseTestFixture : public DBHandlerTestFixture {
+ protected:
+  void SetUp() override {}
+
+  void TearDown() override {}
+
+  const char* table_schema = "(x int, y int);";
+
+  void buildTable(const std::string& table_name) {
+    sql("DROP TABLE IF EXISTS " + table_name + ";");
+    sql("CREATE TABLE " + table_name + " " + table_schema);
+    ValuesGenerator gen(table_name);
+    for (size_t i = 0; i < 10; i++) {
+      sql(gen(i, i * 10));
+    }
+  }
+};
+
+class GPUCodeCacheTest : public BaseTestFixture {
+ protected:
+  void SetUp() override {
+    dt = TExecuteMode::type::GPU;
+    try {
+      if (!getCatalog().getDataMgr().gpusPresent()) {
+        GTEST_SKIP() << "GPU not available. Skipping test.";
+      }
+      qe_instance = QueryEngine::getInstance();
+      qe_instance->gpu_code_accessor->clear();
+    } catch (...) {
+      LOG(WARNING) << "Cannot get the QueryEngine instance, aborting.";
+      std::rethrow_exception(std::current_exception());
+    }
+    BaseTestFixture::SetUp();
+    buildTable("gpu_code_cache_t");
+  }
+
+  void TearDown() override {
+    sql("DROP TABLE IF EXISTS gpu_code_cache_t;");
+    BaseTestFixture::TearDown();
+  }
+
+  TExecuteMode::type dt;
+  std::shared_ptr<QueryEngine> qe_instance;
+};
+
+TEST_F(GPUCodeCacheTest, EvictionMetricType_ByteSize) {
+  setExecuteMode(dt);
+  ScopeGuard reset = [this] {
+    qe_instance->gpu_code_accessor->resetCache(g_gpu_code_cache_max_size_in_bytes);
+  };
+  size_t const new_cache_size = 1000000;
+  qe_instance->gpu_code_accessor->resetCache(new_cache_size);
+  sql("SELECT x FROM gpu_code_cache_t WHERE y = 0;");
+  EXPECT_LE(qe_instance->gpu_code_accessor->getCacheSize(), new_cache_size);
+  auto const cache_metric1 = qe_instance->gpu_code_accessor->getCodeCacheMetric();
+  sql("SELECT x FROM gpu_code_cache_t WHERE y = 0 AND x > 0;");
+  EXPECT_LE(qe_instance->gpu_code_accessor->getCacheSize(), new_cache_size);
+  auto const cache_metric2 = qe_instance->gpu_code_accessor->getCodeCacheMetric();
+  EXPECT_LT(cache_metric1.evict_count, cache_metric2.evict_count);
+}
+
+TEST_F(GPUCodeCacheTest, DuplicatedQuery) {
+  setExecuteMode(dt);
+  sql("SELECT x FROM gpu_code_cache_t;");
+  auto const sz1 = qe_instance->gpu_code_accessor->getCacheSize();
+  sql("SELECT x FROM gpu_code_cache_t;");
+  auto const sz2 = qe_instance->gpu_code_accessor->getCacheSize();
+  EXPECT_EQ(sz1, sz2);
+  sql("SELECT x,y FROM gpu_code_cache_t group by 1,2;");
+  auto const sz3 = qe_instance->gpu_code_accessor->getCacheSize();
+  EXPECT_LT(sz2, sz3);
+}
+
+class CPUCodeCacheTest : public BaseTestFixture {
+ protected:
+  void SetUp() override {
+    dt = TExecuteMode::type::CPU;
+    try {
+      qe_instance = QueryEngine::getInstance();
+      qe_instance->cpu_code_accessor->clear();
+    } catch (...) {
+      LOG(WARNING) << "Cannot get the QueryEngine instance, aborting.";
+      std::rethrow_exception(std::current_exception());
+    }
+    BaseTestFixture::SetUp();
+    buildTable("cpu_code_cache_t");
+  }
+
+  void TearDown() override {
+    sql("DROP TABLE IF EXISTS cpu_code_cache_t;");
+    BaseTestFixture::TearDown();
+  }
+
+  TExecuteMode::type dt;
+  std::shared_ptr<QueryEngine> qe_instance;
+};
+
+TEST_F(CPUCodeCacheTest, DuplicatedQuery) {
+  setExecuteMode(dt);
+  sql("SELECT x FROM cpu_code_cache_t;");
+  auto const sz1 = qe_instance->cpu_code_accessor->getCacheSize();
+  sql("SELECT x FROM cpu_code_cache_t;");
+  auto const sz2 = qe_instance->cpu_code_accessor->getCacheSize();
+  EXPECT_EQ(sz1, sz2);
+  sql("SELECT x,y FROM cpu_code_cache_t group by 1,2;");
+  auto const sz3 = qe_instance->cpu_code_accessor->getCacheSize();
+  EXPECT_LT(sz2, sz3);
+}
+
+TEST_F(CPUCodeCacheTest, EvictionMetricType_EntryCount) {
+  setExecuteMode(dt);
+  ScopeGuard reset = [this] {
+    qe_instance->cpu_code_accessor->resetCache(g_code_cache_max_num_items);
+  };
+  qe_instance->cpu_code_accessor->resetCache(1);
+  sql("SELECT x FROM cpu_code_cache_t WHERE y = 0;");
+  EXPECT_EQ(qe_instance->cpu_code_accessor->getCacheSize(), size_t(1));
+  auto const cache_metric1 = qe_instance->cpu_code_accessor->getCodeCacheMetric();
+  sql("SELECT x FROM cpu_code_cache_t WHERE y = 0 AND x > 0;");
+  EXPECT_EQ(qe_instance->cpu_code_accessor->getCacheSize(), size_t(1));
+  auto const cache_metric2 = qe_instance->cpu_code_accessor->getCodeCacheMetric();
+  EXPECT_LT(cache_metric1.evict_count, cache_metric2.evict_count);
+}
+
+int main(int argc, char* argv[]) {
+  g_is_test_env = true;
+  ScopeGuard reset = [] { g_is_test_env = false; };
+  TestHelpers::init_logger_stderr_only(argc, argv);
+  namespace po = boost::program_options;
+  po::options_description desc("Options");
+  logger::LogOptions log_options(argv[0]);
+  log_options.max_files_ = 0;  // stderr only by default
+  desc.add(log_options.get_options());
+
+  po::variables_map vm;
+  po::store(po::command_line_parser(argc, argv).options(desc).allow_unregistered().run(),
+            vm);
+  po::notify(vm);
+
+  int err{0};
+  try {
+    testing::InitGoogleTest(&argc, argv);
+    testing::AddGlobalTestEnvironment(new DBHandlerTestEnvironment);
+    err = RUN_ALL_TESTS();
+  } catch (const std::exception& e) {
+    LOG(ERROR) << e.what();
+  }
+  return err;
+}
diff --git a/Tests/ColumnarResultsTest.cpp b/Tests/ColumnarResultsTest.cpp
index 6ae3597fad..f35a1e2e9c 100644
--- a/Tests/ColumnarResultsTest.cpp
+++ b/Tests/ColumnarResultsTest.cpp
@@ -71,7 +71,7 @@ void test_columnar_conversion(const std::vector<TargetInfo>& target_infos,
                               const size_t non_empty_step_size,
                               const bool is_parallel_conversion = false) {
   auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
-      Executor::getArenaBlockSize(), /*num_threads=*/1);
+      Executor::getArenaBlockSize(), 0, /*num_threads=*/1);
   ResultSet result_set(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
 
@@ -164,7 +164,7 @@ TEST(Construct, Empty) {
   std::vector<SQLTypeInfo> sql_type_infos;
   QueryMemoryDescriptor query_mem_desc;
   auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
-      Executor::getArenaBlockSize(), /*num_threads=*/1);
+      Executor::getArenaBlockSize(), 0, /*num_threads=*/1);
   ResultSet result_set(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
   ColumnarResultsTester columnar_results(
diff --git a/Tests/ConcurrencyTest.cpp b/Tests/ConcurrencyTest.cpp
index a2ed6370e0..907f6f43db 100644
--- a/Tests/ConcurrencyTest.cpp
+++ b/Tests/ConcurrencyTest.cpp
@@ -191,6 +191,9 @@ class ConcurrencyTestEnv : public DBHandlerTestFixture {
     fast_query_ms_warmup_future.get();
     slow_query_ms_warmup_future.get();
 
+    auto const baseline_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+                                 std::chrono::system_clock::now().time_since_epoch())
+                                 .count();
     setExecuteMode(execution_mode_slow_query);
 
     auto slow_query_ms_future = std::async(
@@ -202,8 +205,8 @@ class ConcurrencyTestEnv : public DBHandlerTestFixture {
     auto fast_query_ms_future = std::async(
         std::launch::async, get_query_return_time_epoch_ms, std::ref(fast_query_sql));
 
-    const auto fast_query_return_ms = fast_query_ms_future.get();
-    const auto slow_query_return_ms = slow_query_ms_future.get();
+    const auto fast_query_return_ms = fast_query_ms_future.get() - baseline_ms;
+    const auto slow_query_return_ms = slow_query_ms_future.get() - baseline_ms;
 
     if (fast_query_should_finish_first) {
       EXPECT_LT(fast_query_return_ms, slow_query_return_ms);
@@ -215,9 +218,9 @@ class ConcurrencyTestEnv : public DBHandlerTestFixture {
  private:
   const std::string table_name{"test_concurrency"};
   const std::string slow_query_sql =
-      "SELECT SUM(ct_sleep_us(100000)) AS res FROM test_concurrency WHERE i64 < 50;";
+      "SELECT SUM(ct_sleep_us(100000)) AS res FROM test_concurrency WHERE i64 < 10;";
   const std::string fast_query_sql =
-      "SELECT SUM(1) AS res FROM test_concurrency WHERE i64 < 50;";
+      "SELECT SUM(i64) AS res FROM test_concurrency WHERE i64 < 10;";
   const char* table_schema = R"(
     (
         i64 BIGINT,
diff --git a/Tests/CreateAndDropTableDdlTest.cpp b/Tests/CreateAndDropTableDdlTest.cpp
index 7a81fa072d..43d48ac4e8 100644
--- a/Tests/CreateAndDropTableDdlTest.cpp
+++ b/Tests/CreateAndDropTableDdlTest.cpp
@@ -23,14 +23,26 @@
 #include <gtest/gtest.h>
 #include <boost/filesystem.hpp>
 
+#include "Calcite/Calcite.h"
 #include "Catalog/ForeignTable.h"
 #include "Catalog/TableDescriptor.h"
 #include "DBHandlerTestHelpers.h"
 #include "Fragmenter/FragmentDefaultValues.h"
 #include "Shared/SysDefinitions.h"
+#include "Shared/scope.h"
 #include "TestHelpers.h"
 #include "Utils/DdlUtils.h"
 
+class MockCalcite : public Calcite {
+ public:
+  struct UpdateMetadataError : public std::runtime_error {
+    UpdateMetadataError() : std::runtime_error("UpdateMetadataError") {}
+  };
+  void updateMetadata(std::string catalog, std::string table) override {
+    throw UpdateMetadataError();
+  }
+};
+
 extern bool g_enable_fsi;
 extern bool g_enable_s3_fsi;
 
@@ -1918,6 +1930,21 @@ TEST_F(RenameTableTest, SimpleRename) {
   sql("RENAME TABLE F TO A, G TO B, H TO C;");
 }
 
+TEST_F(RenameTableTest, RenameCalciteError) {
+  // Inject a calcite conenction issue so that updating calcite metadata will throw.
+  // TODO(Misiu): Replace this with proper Catalog unit tests.
+  auto& cat = getCatalog();
+  auto old_calcite = cat.getCalciteMgr();
+  cat.setCalciteMgr(std::make_shared<MockCalcite>());
+  ScopeGuard scope = [&] { cat.setCalciteMgr(old_calcite); };
+
+  queryAndAssertException("RENAME TABLE A to F;", "UpdateMetadataError");
+  sqlAndCompareResult("SELECT count(*) FROM A WHERE Name = 'A';", {{i(1)}});
+  queryAndAssertException(
+      "SELECT count(*) from F;",
+      "SQL Error: From line 2, column 6 to line 2, column 8: Object 'F' not found");
+}
+
 TEST_F(RenameTableTest, SwapRename) {
   // swap
   sql("RENAME TABLE D to E, E to D");
diff --git a/Tests/DBHandlerTestHelpers.h b/Tests/DBHandlerTestHelpers.h
index 37c70c6375..8ad54f2034 100644
--- a/Tests/DBHandlerTestHelpers.h
+++ b/Tests/DBHandlerTestHelpers.h
@@ -411,6 +411,7 @@ class DBHandlerTestFixture : public TestHelpers::TbbPrivateServerKiller {
       const size_t render_mem_bytes{500000000};
       const size_t max_concurrent_render_sessions{500};
       const bool render_compositor_use_last_gpu{false};
+      const bool renderer_enable_slab_allocation{false};
       const size_t reserved_gpu_mem{134217728};
       const size_t num_reader_threads{0};
       const bool legacy_syntax{true};
@@ -443,6 +444,7 @@ class DBHandlerTestFixture : public TestHelpers::TbbPrivateServerKiller {
                                                 max_concurrent_render_sessions,
                                                 reserved_gpu_mem,
                                                 render_compositor_use_last_gpu,
+                                                renderer_enable_slab_allocation,
                                                 num_reader_threads,
                                                 auth_metadata_,
                                                 system_parameters_,
diff --git a/Tests/DBObjectPrivilegesTest.cpp b/Tests/DBObjectPrivilegesTest.cpp
index acb0bd543d..3ea5af686a 100644
--- a/Tests/DBObjectPrivilegesTest.cpp
+++ b/Tests/DBObjectPrivilegesTest.cpp
@@ -1383,7 +1383,7 @@ TEST_F(ViewObject, CalciteViewResolution) {
   }
 
   auto calciteQueryParsingOption =
-      g_calcite->getCalciteQueryParsingOption(true, false, true);
+      g_calcite->getCalciteQueryParsingOption(true, false, true, false);
   auto calciteOptimizationOption =
       g_calcite->getCalciteOptimizationOption(false, false, {}, false);
 
diff --git a/Tests/DumpRestoreTest.cpp b/Tests/DumpRestoreTest.cpp
index 46c6a42fb0..4d255c5c2d 100644
--- a/Tests/DumpRestoreTest.cpp
+++ b/Tests/DumpRestoreTest.cpp
@@ -627,6 +627,15 @@ TEST_F(DumpAndRestoreTest, DropRenderGroupColumnsBad) {
                std::runtime_error);
 }
 
+TEST_F(DumpAndRestoreTest, QuotedColumnNames) {
+  run_ddl_statement("CREATE TABLE test_table(\"integer\" integer);");
+  run_ddl_statement("DUMP TABLE test_table to '" + tar_ball_path +
+                    "' with (compression = 'gzip')");
+  run_ddl_statement("RESTORE TABLE test_table_2 from '" + tar_ball_path +
+                    "' WITH (compression = 'gzip');");
+  sqlAndCompareResult("SELECT COUNT(*) FROM test_table_2;", std::vector<long int>{0});
+}
+
 #ifdef HAVE_AWS_S3
 class S3RestoreTest : public DumpAndRestoreTest {
  protected:
diff --git a/Tests/ExecuteTest.cpp b/Tests/ExecuteTest.cpp
index cf3ca43687..a011277eed 100644
--- a/Tests/ExecuteTest.cpp
+++ b/Tests/ExecuteTest.cpp
@@ -20,9 +20,11 @@
 #include "../Parser/ParserNode.h"
 #include "../QueryEngine/ArrowResultSet.h"
 #include "../QueryEngine/CgenState.h"
+#include "../QueryEngine/CodeCacheAccessor.h"
 #include "../QueryEngine/Descriptors/RelAlgExecutionDescriptor.h"
 #include "../QueryEngine/Execute.h"
 #include "../QueryEngine/ExpressionRange.h"
+#include "../QueryEngine/QueryEngine.h"
 #include "../QueryEngine/ResultSetReductionJIT.h"
 #include "../QueryRunner/QueryRunner.h"
 #include "../Shared/DateConverters.h"
@@ -2879,6 +2881,75 @@ TEST_F(Select, ConstantWidthBucketExpr) {
   g_sqlite_comparator.query(drop);
 }
 
+TEST_F(Select, ConstantWidthBucketExprOnDecimalColLowerBound) {
+  // TEST 1. Check target value against edge cases related to lower_bound
+  run_ddl_statement("DROP TABLE IF EXISTS wbdct1;");
+  run_ddl_statement(
+      "CREATE TABLE wbdct1 (v1 FLOAT, v2 DOUBLE, v3 DECIMAL(9, 6) ENCODING FIXED(32), v4 "
+      "FLOAT NOT NULL, v5 DOUBLE NOT NULL, v6 DECIMAL(9, 6) NOT NULL ENCODING "
+      "FIXED(32));");
+  run_multiple_agg(
+      "INSERT INTO wbdct1 VALUES (25.88865, 25.88865, 25.88865, 25.88865, 25.88865, "
+      "25.88865);",
+      ExecutorDeviceType::CPU);
+  run_multiple_agg(
+      "INSERT INTO wbdct1 VALUES (25.88866, 25.88866, 25.88866, 25.88866, 25.88866, "
+      "25.88866);",
+      ExecutorDeviceType::CPU);
+  run_multiple_agg(
+      "INSERT INTO wbdct1 VALUES (25.888661,25.888661,25.888661, "
+      "25.888661,25.888661,25.888661);",
+      ExecutorDeviceType::CPU);
+  run_multiple_agg(
+      "INSERT INTO wbdct1 VALUES (25.88867, 25.88867, 25.88867, 25.88867, 25.88867, "
+      "25.88867);",
+      ExecutorDeviceType::CPU);
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    for (std::string col_name : {"v1", "v2", "v3", "v4", "v5", "v6"}) {
+      std::string q = "select sum(c) from (select width_bucket(" + col_name +
+                      ", 25.88866, 51.046306, 12) c from wbdct1 group by 1);";
+      // WIDTH_BUCKET should returns {0, 1} -> so, we expect 1 as a result
+      EXPECT_EQ(int64_t(1), v<int64_t>(run_simple_agg(q, dt)));
+    }
+  }
+  run_ddl_statement("DROP TABLE IF EXISTS wbdct1;");
+}
+
+TEST_F(Select, ConstantWidthBucketExprOnDecimalColUpperBound) {
+  run_ddl_statement("DROP TABLE IF EXISTS wbdct2;");
+  run_ddl_statement(
+      "CREATE TABLE wbdct2 (v1 FLOAT, v2 DOUBLE, v3 DECIMAL(9, 6) ENCODING FIXED(32), v4 "
+      "FLOAT NOT NULL, v5 DOUBLE NOT NULL, v6 DECIMAL(9, 6) NOT NULL ENCODING "
+      "FIXED(32));");
+  run_multiple_agg(
+      "INSERT INTO wbdct2 VALUES (51.046305, 51.046305, 51.046305, 51.046305, 51.046305, "
+      "51.046305);",
+      ExecutorDeviceType::CPU);
+  run_multiple_agg(
+      "INSERT INTO wbdct2 VALUES (51.046306, 51.046306, 51.046306, 51.046306, 51.046306, "
+      "51.046306);",
+      ExecutorDeviceType::CPU);
+  run_multiple_agg(
+      "INSERT INTO wbdct2 VALUES (51.0463061,51.0463061,51.0463061, "
+      "51.0463061,51.0463061,51.0463061);",
+      ExecutorDeviceType::CPU);
+  run_multiple_agg(
+      "INSERT INTO wbdct2 VALUES (51.046307,51.046307,51.04630, "
+      "51.046307,51.046307,51.04630);",
+      ExecutorDeviceType::CPU);
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    for (std::string col_name : {"v1", "v2", "v3", "v4", "v5", "v6"}) {
+      std::string q = "select sum(c) from (select width_bucket(" + col_name +
+                      ", 25.88866, 51.046306, 12) c from wbdct2 group by 1);";
+      // WIDTH_BUCKET should returns {12, 13} -> so, we expect 25 as a result
+      EXPECT_EQ(int64_t(25), v<int64_t>(run_simple_agg(q, dt)));
+    }
+  }
+  run_ddl_statement("DROP TABLE IF EXISTS wbdct2;");
+}
+
 TEST_F(Select, WidthBucketExpr) {
   auto drop = "DROP TABLE IF EXISTS wb_test;";
   auto create =
@@ -3323,8 +3394,10 @@ TEST_F(Select, CountDistinct) {
     run_ddl_statement(drop_dt_tbl_stmt);
     g_sqlite_comparator.query(drop_dt_tbl_stmt);
   };
-  ScopeGuard drop_cd_test_tables = [drop_cd_test_tables_ddl] {
-    drop_cd_test_tables_ddl();
+  ScopeGuard drop_cd_test_tables = [&] {
+    if (!g_keep_test_data) {
+      drop_cd_test_tables_ddl();
+    }
   };
   drop_cd_test_tables_ddl();
   for (const auto& p : ts_tbl_name_ddl_prefix_pair) {
@@ -3434,6 +3507,14 @@ TEST_F(Select, CountDistinct) {
                     "SELECT COUNT(DISTINCT " + col_name + ") FROM dt_cd_test_tbl WHERE " +
                         col_name + " BETWEEN date '2022-01-10' AND date '2022-01-30';",
                     dt)));
+      EXPECT_EQ(9,
+                v<int64_t>(run_simple_agg("SELECT COUNT(DISTINCT(DATE_TRUNC(month," +
+                                              col_name + "))) FROM dt_cd_test_tbl;",
+                                          dt)));
+      EXPECT_EQ(35,
+                v<int64_t>(run_simple_agg("SELECT COUNT(DISTINCT(DATE_TRUNC(week," +
+                                              col_name + "))) FROM dt_cd_test_tbl;",
+                                          dt)));
     }
   }
   {
@@ -3533,6 +3614,49 @@ TEST_F(Select, CountIf) {
     }
     run_test(test_queries);
   }
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    const auto res =
+        run_simple_agg("SELECT COUNT_IF(x > 0) - COUNT_IF(x > 0) FROM test;", dt);
+    EXPECT_EQ(v<int64_t>(res), 0);
+  }
+}
+
+TEST_F(Select, DivisionBetweenCountExprs) {
+  const std::string sqlite_query = "SELECT 1";
+  for (std::string op : {"COUNT(*)",
+                         "COUNT(DISTINCT y)",
+                         "COUNT(DISTINCT x)",
+                         "APPROX_COUNT_DISTINCT(x)",
+                         "APPROX_COUNT_DISTINCT(y)"}) {
+    std::ostringstream q1_oss, q2_oss;
+    q1_oss << "SELECT CAST(" << op << " AS DOUBLE) / CAST(" << op
+           << " AS BIGINT) FROM test";
+    q2_oss << "SELECT CAST(" << op << " AS DOUBLE) / " << op << " FROM test";
+    for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+      SKIP_NO_GPU();
+      c(q1_oss.str(), sqlite_query, dt);
+      c(q2_oss.str(), sqlite_query, dt);
+    }
+  }
+
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    c("SELECT COUNT(*) / COUNT(*) FROM test;", dt);
+    c("SELECT COUNT(*) / COUNT(*) FROM test GROUP BY x", dt);
+  }
+}
+
+TEST_F(Select, DivisionBetweenCountExprsWithGroupby) {
+  const std::string sqlite_query = "SELECT 1 FROM test GROUP BY x";
+  for (std::string op : {"COUNT(*)", "COUNT(DISTINCT x)", "APPROX_COUNT_DISTINCT(x)"}) {
+    std::ostringstream query;
+    query << "SELECT " << op << " / " << op << " FROM test GROUP BY x";
+    for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+      SKIP_NO_GPU();
+      c(query.str(), sqlite_query, dt);
+    }
+  }
 }
 
 TEST_F(Select, SumIf) {
@@ -3728,6 +3852,86 @@ TEST_F(Select, ApproxCountDistinct) {
                        false));
 }
 
+class AggDistinct
+    : public Select,
+      public testing::WithParamInterface<std::tuple<ExecutorDeviceType, SQLAgg>> {
+ public:
+  // NOTE: test names must be non-empty, unique, and may only contain ASCII alphanumeric
+  // characters. [Use underscores carefully.]
+  static std::string testName(testing::TestParamInfo<ParamType> const& info) {
+    std::ostringstream oss;
+    oss << std::get<0>(info.param) << '_' << toString(std::get<1>(info.param));
+    return oss.str();
+  }
+};
+
+class AggDistinctSupported : public AggDistinct {
+ public:
+  static void testTableColumn(ExecutorDeviceType const dt, SQLAgg const sql_agg) {
+    std::string const query = "SELECT " + toString(sql_agg) + "(DISTINCT f) FROM test;";
+    std::string sqlite_query = query;
+    if (sql_agg == kAPPROX_COUNT_DISTINCT) {
+      // Replace APPROX_COUNT_DISTINCT w/ COUNT for SQLite.
+      std::string const replace("APPROX_COUNT_DISTINCT");
+      sqlite_query.replace(sqlite_query.find(replace), replace.size(), "COUNT");
+    }
+    // This should not throw an exception, so test for correctness too.
+    c(query, sqlite_query, dt);
+  }
+};
+
+TEST_P(AggDistinctSupported, TestTableColumn) {
+  SKIP_ALL_ON_AGGREGATOR();
+  auto const [dt, sql_agg] = GetParam();
+  AggDistinctSupported::testTableColumn(dt, sql_agg);
+}
+
+// Tested agg types mirror is_distinct_supported() in QueryEngine/RelAlgTranslator.cpp
+INSTANTIATE_TEST_SUITE_P(
+    Select,
+    AggDistinctSupported,
+    testing::Combine(testing::Values(ExecutorDeviceType::CPU, ExecutorDeviceType::GPU),
+                     testing::Values(kMIN, kMAX, kCOUNT, kAPPROX_COUNT_DISTINCT)),
+    AggDistinctSupported::testName);
+
+class AggDistinctUnsupported : public AggDistinct {
+ public:
+  static std::string errorMessage(SQLAgg const sql_agg) {
+    return toString(sql_agg) + " does not currently support the DISTINCT qualifier.";
+  }
+
+  // Exception is expected to be thrown since DISTINCT is not supported.
+  static void testTableColumn(ExecutorDeviceType const dt, SQLAgg const sql_agg) {
+    // APPROX_MEDIAN is the 1-parameter form of APPROX_QUANTILE/PERCENTILE
+    std::string const func =
+        sql_agg == kAPPROX_QUANTILE ? std::string("APPROX_MEDIAN") : toString(sql_agg);
+    std::string const query = "SELECT " + func + "(DISTINCT f) FROM test;";
+    run_simple_agg(query, dt);
+  }
+};
+
+TEST_P(AggDistinctUnsupported, TestTableColumn) {
+  SKIP_ALL_ON_AGGREGATOR();
+  auto const [dt, sql_agg] = GetParam();
+  try {
+    AggDistinctUnsupported::testTableColumn(dt, sql_agg);
+    FAIL() << "Exception expected.";
+  } catch (std::runtime_error const& e) {
+    auto const expected = AggDistinctUnsupported::errorMessage(sql_agg);
+    EXPECT_EQ(expected, std::string(e.what()));
+  } catch (...) {
+    FAIL() << "Unexpected exception type thrown.";
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Select,
+    AggDistinctUnsupported,
+    testing::Combine(
+        testing::Values(ExecutorDeviceType::CPU, ExecutorDeviceType::GPU),
+        testing::Values(kAVG, kSUM, kAPPROX_QUANTILE, kSAMPLE, kSINGLE_VALUE, kMODE)),
+    AggDistinctUnsupported::testName);
+
 // Additional unit tests for APPROX_MEDIAN are in Quantile/.
 TEST_F(Select, ApproxMedianSanity) {
   auto dt = ExecutorDeviceType::CPU;
@@ -4048,9 +4252,9 @@ TEST_F(Select, ModeBasic) {
     auto const fx = select_mode<int64_t>("fx", dt);
     EXPECT_EQ(9, fx) << dt;
     query = "SELECT CAST(MODE(dd) AS DOUBLE) FROM test;";
-    EXPECT_EQ(11110 * 0.01, v<double>(run_simple_agg(query, dt))) << dt << ": " << query;
+    EXPECT_EQ(11110 / 100.0, v<double>(run_simple_agg(query, dt))) << dt << ": " << query;
     query = "SELECT CAST(MODE(dd_notnull) AS DOUBLE) FROM test;";
-    EXPECT_EQ(11110 * 0.01, v<double>(run_simple_agg(query, dt))) << dt << ": " << query;
+    EXPECT_EQ(11110 / 100.0, v<double>(run_simple_agg(query, dt))) << dt << ": " << query;
     auto const ss = boost::get<std::string>(select_mode<NullableString>("ss", dt));
     EXPECT_EQ("fish", ss) << dt;
     query = "SELECT MODE(u) IS NULL FROM test;";
@@ -5042,6 +5246,46 @@ TEST_F(Select, Strings) {
   }
 }
 
+TEST_F(Select, NotILikeDictEncodedText) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+
+    ASSERT_EQ(static_cast<int64_t>(10),
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(*) FROM test WHERE str NOT ILIKE 'F%';", dt)));
+  }
+}
+
+TEST_F(Select, NotILikeDictEncodedTextWithParenthesis) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+
+    ASSERT_EQ(static_cast<int64_t>(10),
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(*) FROM test WHERE ( str NOT ILIKE 'F%' );", dt)));
+  }
+}
+
+TEST_F(Select, NotILikeNoneEncodedText) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+
+    ASSERT_EQ(15,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(*) FROM test WHERE real_str NOT ILIKE '%BaZ%';", dt)));
+  }
+}
+
+TEST_F(Select, NotILikeNoneEncodedTextWithParenthesis) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+
+    ASSERT_EQ(15,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(*) FROM test WHERE (real_str NOT ILIKE '%bAz%');", dt)));
+  }
+}
+
 TEST_F(Select, SharedDictionary) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
@@ -5471,6 +5715,26 @@ void check_one_date_trunc_group_with_agg(const ResultSet& rows,
   ASSERT_TRUE(empty_row.empty());
 }
 
+struct DateTruncTestParam {
+  std::string date_trunc_args;
+  int64_t expected_result;
+  int64_t expected_count;
+};
+
+using DateTruncTestParams = std::vector<DateTruncTestParam>;
+void check_date_trunc_queries(const DateTruncTestParams& date_trunc_test_params,
+                              ExecutorDeviceType dt) {
+  for (auto& test_param : date_trunc_test_params) {
+    const auto one_row = run_multiple_agg(
+        "SELECT date_trunc(" + test_param.date_trunc_args +
+            ") as key0,COUNT(*) AS val FROM test group by key0 order by key0 "
+            "limit 1;",
+        dt);
+    check_one_date_trunc_group_with_agg(
+        *one_row, test_param.expected_result, test_param.expected_count);
+  }
+}
+
 // Example: "1969-12-31 23:59:59.999999" -> -1
 // The number of fractional digits must be 0, 3, 6, or 9.
 int64_t timestampToInt64(char const* timestr, ExecutorDeviceType const dt) {
@@ -5625,1390 +5889,14 @@ TEST_F(Select, TimeSyntaxCheck) {
             dt)));
     ASSERT_EQ(128885,
               v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF('sql_tsi_minute', "
-                                        "TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
-                                        "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(128885,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(sql_tsi_minute, TIMESTAMP "
-                                        "'2003-02-01 0:00:00', TIMESTAMP "
-                                        "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
-                                        dt)));
-  }
-}
-
-TEST_F(Select, Time) {
-  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
-    SKIP_NO_GPU();
-    // check DATE Formats
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE CAST('1999-09-10' AS DATE) > o;", dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE CAST('10/09/1999' AS DATE) > o;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('10/09/99' AS DATE) > o;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('10-Sep-99' AS DATE) > o;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('9/10/99' AS DATE) > o;", dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE CAST('31/Oct/2013' AS DATE) > o;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('10/31/13' AS DATE) > o;", dt)));
-    // check TIME FORMATS
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('15:13:15' AS TIME) > n;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('151315' AS TIME) > n;", dt)));
-
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE CAST('1999-09-10' AS DATE) > o;", dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE CAST('1999-09-10' AS DATE) <= o;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('15:13:15' AS TIME) > n;", dt)));
-    ASSERT_EQ(0,
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE CAST('15:13:15' AS TIME) <= n;", dt)));
-    cta("SELECT DATETIME('NOW') FROM test limit 1;", dt);
-    EXPECT_ANY_THROW(run_simple_agg("SELECT DATETIME(NULL) FROM test LIMIT 1;", dt));
-    // these next tests work because all dates are before now 2015-12-8 17:00:00
-    ASSERT_EQ(
-        static_cast<int64_t>(2 * g_num_rows),
-        v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m < NOW();", dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE o IS NULL OR o < CURRENT_DATE;", dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(2 * g_num_rows),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE o IS NULL OR o < CURRENT_DATE();", dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE m < CURRENT_TIMESTAMP;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE m < CURRENT_TIMESTAMP();", dt)));
-    ASSERT_TRUE(v<int64_t>(
-        run_simple_agg("SELECT CURRENT_DATE = CAST(CURRENT_TIMESTAMP AS DATE);", dt)));
-    ASSERT_TRUE(
-        v<int64_t>(run_simple_agg("SELECT DATEADD('day', -1, CURRENT_TIMESTAMP) < "
-                                  "CURRENT_DATE AND CURRENT_DATE <= CURRENT_TIMESTAMP;",
-                                  dt)));
-    ASSERT_TRUE(v<int64_t>(run_simple_agg(
-        "SELECT CAST(CURRENT_DATE AS TIMESTAMP) <= CURRENT_TIMESTAMP;", dt)));
-    ASSERT_TRUE(v<int64_t>(run_simple_agg(
-        "SELECT EXTRACT(YEAR FROM CURRENT_DATE) = EXTRACT(YEAR FROM CURRENT_TIMESTAMP)"
-        " AND EXTRACT(MONTH FROM CURRENT_DATE) = EXTRACT(MONTH FROM CURRENT_TIMESTAMP)"
-        " AND EXTRACT(DAY FROM CURRENT_DATE) = EXTRACT(DAY FROM CURRENT_TIMESTAMP)"
-        " AND EXTRACT(HOUR FROM CURRENT_DATE) = 0"
-        " AND EXTRACT(MINUTE FROM CURRENT_DATE) = 0"
-        " AND EXTRACT(SECOND FROM CURRENT_DATE) = 0;",
-        dt)));
-    ASSERT_TRUE(v<int64_t>(run_simple_agg(
-        "SELECT EXTRACT(HOUR FROM CURRENT_TIME()) = EXTRACT(HOUR FROM CURRENT_TIMESTAMP)"
-        " AND EXTRACT(MINUTE FROM CURRENT_TIME) = EXTRACT(MINUTE FROM CURRENT_TIMESTAMP)"
-        " AND EXTRACT(SECOND FROM CURRENT_TIME) = EXTRACT(SECOND FROM CURRENT_TIMESTAMP)"
-        ";",
-        dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE m > timestamp(0) '2014-12-13T000000';",
-                  dt)));
-    ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE CAST(o AS "
-                                        "TIMESTAMP) > timestamp(0) '1999-09-08T160000';",
-                                        dt)));
-    ASSERT_EQ(0,
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE CAST(o AS "
-                                        "TIMESTAMP) > timestamp(0) '1999-09-10T160000';",
-                                        dt)));
-    ASSERT_EQ(14185957950LL,
-              v<int64_t>(run_simple_agg(
-                  "SELECT MAX(EXTRACT(EPOCH FROM m) * 10) FROM test;", dt)));
-    ASSERT_EQ(14185152000LL,
-              v<int64_t>(run_simple_agg(
-                  "SELECT MAX(EXTRACT(DATEEPOCH FROM m) * 10) FROM test;", dt)));
-    ASSERT_EQ(20140,
-              v<int64_t>(run_simple_agg(
-                  "SELECT MAX(EXTRACT(YEAR FROM m) * 10) FROM test;", dt)));
-    ASSERT_EQ(120,
-              v<int64_t>(run_simple_agg(
-                  "SELECT MAX(EXTRACT(MONTH FROM m) * 10) FROM test;", dt)));
-    ASSERT_EQ(140,
-              v<int64_t>(
-                  run_simple_agg("SELECT MAX(EXTRACT(DAY FROM m) * 10) FROM test;", dt)));
-    ASSERT_EQ(
-        22,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(HOUR FROM m)) FROM test;", dt)));
-    ASSERT_EQ(
-        23,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(MINUTE FROM m)) FROM test;", dt)));
-    ASSERT_EQ(
-        15,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(SECOND FROM m)) FROM test;", dt)));
-    ASSERT_EQ(
-        6, v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(DOW FROM m)) FROM test;", dt)));
-    ASSERT_EQ(
-        348,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(DOY FROM m)) FROM test;", dt)));
-    ASSERT_EQ(
-        15,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(HOUR FROM n)) FROM test;", dt)));
-    ASSERT_EQ(
-        13,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(MINUTE FROM n)) FROM test;", dt)));
-    ASSERT_EQ(
-        14,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(SECOND FROM n)) FROM test;", dt)));
-    ASSERT_EQ(
-        1999,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(YEAR FROM o)) FROM test;", dt)));
-    ASSERT_EQ(
-        9,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(MONTH FROM o)) FROM test;", dt)));
-    ASSERT_EQ(
-        9, v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(DAY FROM o)) FROM test;", dt)));
-    ASSERT_EQ(4,
-              v<int64_t>(run_simple_agg(
-                  "SELECT EXTRACT(DOW FROM o) FROM test WHERE o IS NOT NULL;", dt)));
-    ASSERT_EQ(252,
-              v<int64_t>(run_simple_agg(
-                  "SELECT EXTRACT(DOY FROM o) FROM test WHERE o IS NOT NULL;", dt)));
-    ASSERT_EQ(
-        936835200LL,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(EPOCH FROM o)) FROM test;", dt)));
-    ASSERT_EQ(936835200LL,
-              v<int64_t>(run_simple_agg(
-                  "SELECT MAX(EXTRACT(DATEEPOCH FROM o)) FROM test;", dt)));
-    ASSERT_EQ(52LL,
-              v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(WEEK FROM CAST('2012-01-01 "
-                                        "20:15:12' AS TIMESTAMP))) FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(
-        1LL,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(WEEK_SUNDAY FROM CAST('2012-01-01 "
-                                  "20:15:12' AS TIMESTAMP))) FROM test limit 1;",
-                                  dt)));
-    ASSERT_EQ(1LL,
-              v<int64_t>(
-                  run_simple_agg("SELECT MAX(EXTRACT(WEEK_SATURDAY FROM CAST('2012-01-01 "
-                                 "20:15:12' AS TIMESTAMP))) FROM test limit 1;",
-                                 dt)));
-    ASSERT_EQ(10LL,
-              v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(WEEK FROM CAST('2008-03-03 "
-                                        "20:15:12' AS TIMESTAMP))) FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(
-        10LL,
-        v<int64_t>(run_simple_agg("SELECT MAX(EXTRACT(WEEK_SUNDAY FROM CAST('2008-03-03 "
-                                  "20:15:12' AS TIMESTAMP))) FROM test limit 1;",
-                                  dt)));
-    ASSERT_EQ(10LL,
-              v<int64_t>(
-                  run_simple_agg("SELECT MAX(EXTRACT(WEEK_SATURDAY FROM CAST('2008-03-03 "
-                                 "20:15:12' AS TIMESTAMP))) FROM test limit 1;",
-                                 dt)));
-    // Monday
-    ASSERT_EQ(1LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(DOW FROM CAST('2008-03-03 "
-                                        "20:15:12' AS TIMESTAMP)) FROM test limit 1;",
-                                        dt)));
-    // Monday
-    ASSERT_EQ(1LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(ISODOW FROM CAST('2008-03-03 "
-                                        "20:15:12' AS TIMESTAMP)) FROM test limit 1;",
-                                        dt)));
-    // Sunday
-    ASSERT_EQ(0LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(DOW FROM CAST('2008-03-02 "
-                                        "20:15:12' AS TIMESTAMP)) FROM test limit 1;",
-                                        dt)));
-    // Sunday
-    ASSERT_EQ(7LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(ISODOW FROM CAST('2008-03-02 "
-                                        "20:15:12' AS TIMESTAMP)) FROM test limit 1;",
-                                        dt)));
-    // Wednesday
-    ASSERT_EQ(3LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT('isodow' FROM o - INTERVAL 40 "
-                                        "years) dow FROM test ORDER BY dow LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(15000000000LL,
-              v<int64_t>(run_simple_agg(
-                  "SELECT EXTRACT(nanosecond from m) FROM test limit 1;", dt)));
-    ASSERT_EQ(15000000LL,
-              v<int64_t>(run_simple_agg(
-                  "SELECT EXTRACT(microsecond from m) FROM test limit 1;", dt)));
-    ASSERT_EQ(15000LL,
-              v<int64_t>(run_simple_agg(
-                  "SELECT EXTRACT(millisecond from m) FROM test limit 1;", dt)));
-    ASSERT_EQ(56000000000LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(nanosecond from TIMESTAMP(0) "
-                                        "'1999-03-14 23:34:56') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(56000000LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(microsecond from TIMESTAMP(0) "
-                                        "'1999-03-14 23:34:56') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(56000LL,
-              v<int64_t>(run_simple_agg("SELECT EXTRACT(millisecond from TIMESTAMP(0) "
-                                        "'1999-03-14 23:34:56') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(2005,
-              v<int64_t>(run_simple_agg("select EXTRACT(year from TIMESTAMP '2005-12-31 "
-                                        "23:59:59') from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1997,
-              v<int64_t>(run_simple_agg("select EXTRACT(year from TIMESTAMP '1997-01-01 "
-                                        "23:59:59') from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(2006,
-              v<int64_t>(run_simple_agg("select EXTRACT(year from TIMESTAMP '2006-01-01 "
-                                        "00:0:00') from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(2014,
-              v<int64_t>(run_simple_agg("select EXTRACT(year from TIMESTAMP '2014-01-01 "
-                                        "00:00:00') from test limit 1;",
-                                        dt)));
-
-    // test DATE format processing
-    ASSERT_EQ(1434844800LL,
-              v<int64_t>(run_simple_agg(
-                  "select CAST('2015-06-21' AS DATE) FROM test limit 1;", dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE o < CAST('06/21/2015' AS DATE);", dt)));
-    ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test WHERE o < CAST('21-Jun-15' AS DATE);", dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE o < CAST('21/Jun/2015' AS DATE);", dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(g_num_rows + g_num_rows / 2),
-        v<int64_t>(run_simple_agg(
-            "SELECT COUNT(*) FROM test WHERE o < CAST('1434844800' AS DATE);", dt)));
-
-    // test different input formats
-    // added new format for customer
-    ASSERT_EQ(
-        1434896116LL,
-        v<int64_t>(run_simple_agg(
-            "select CAST('2015-06-21 14:15:16' AS timestamp) FROM test limit 1;", dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                        "CAST('2015-06-21:141516' AS TIMESTAMP);",
-                                        dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(2 * g_num_rows),
-        v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= CAST('21-JUN-15 "
-                                  "2.15.16.12345 PM' AS TIMESTAMP);",
-                                  dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(2 * g_num_rows),
-        v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= CAST('21-JUN-15 "
-                                  "2.15.16.12345 AM' AS TIMESTAMP);",
-                                  dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                        "CAST('21-JUN-15 2:15:16 AM' AS TIMESTAMP);",
-                                        dt)));
-
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                        "CAST('06/21/2015 14:15:16' AS TIMESTAMP);",
-                                        dt)));
-
-    // Support ISO date offset format
-    ASSERT_EQ(
-        static_cast<int64_t>(2 * g_num_rows),
-        v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                  "CAST('21/Aug/2015:12:13:14 -0600' AS TIMESTAMP);",
-                                  dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                        "CAST('2015-08-21T12:13:14 -0600' AS TIMESTAMP);",
-                                        dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                        "CAST('21-Aug-15 12:13:14 -0600' AS TIMESTAMP);",
-                                        dt)));
-    ASSERT_EQ(
-        static_cast<int64_t>(2 * g_num_rows),
-        v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                  "CAST('21/Aug/2015:13:13:14 -0500' AS TIMESTAMP);",
-                                  dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test WHERE m <= "
-                                        "CAST('2015-08-21T18:13:14' AS TIMESTAMP);",
-                                        dt)));
-    // add test for quarterday behaviour
-    ASSERT_EQ(1LL,
-              v<int64_t>(run_simple_agg(
-                  "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T04:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(1LL,
-              v<int64_t>(run_simple_agg(
-                  "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T00:00:00' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(2LL,
-              v<int64_t>(run_simple_agg(
-                  "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T08:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(3LL,
-              v<int64_t>(run_simple_agg(
-                  "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T14:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(4LL,
-              v<int64_t>(run_simple_agg(
-                  "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T23:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(1440115200LL,
-              v<int64_t>(run_simple_agg(
-                  "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T04:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(1440136800LL,
-              v<int64_t>(run_simple_agg(
-                  "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T08:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(1440158400LL,
-              v<int64_t>(run_simple_agg(
-                  "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T13:23:11' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(1440180000LL,
-              v<int64_t>(run_simple_agg(
-                  "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T23:59:59' AS "
-                  "timestamp)) FROM test limit 1;",
-                  dt)));
-    ASSERT_EQ(2007,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('year', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(2007,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('yyyy', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        2007,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('yy', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(4,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('quarter', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        4,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('qq', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        4,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('q', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(10,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('month', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        10,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('mm', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        10,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('m', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(303,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('dayofyear', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        303,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('dy', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        303,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('y', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        30,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('day', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        30,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('dd', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        30,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('d', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(12,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('hour', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        12,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('hh', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(15,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('minute', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        15,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('mi', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        15,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('n', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(32,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('second', CAST('2007-10-30 "
-                                        "12:15:32' AS TIMESTAMP)) FROM test;",
-                                        dt)));
-    ASSERT_EQ(
-        32,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('ss', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        32,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('s', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;",
-            dt)));
-    ASSERT_EQ(
-        32,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('s', TIMESTAMP '2007-10-30 12:15:32') FROM test;", dt)));
-    ASSERT_EQ(
-        3,
-        v<int64_t>(run_simple_agg("SELECT DATEDIFF('year', CAST('2006-01-07 00:00:00' as "
-                                  "TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
-                                  "TIMESTAMP)) FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        36,
-        v<int64_t>(run_simple_agg("SELECT DATEDIFF('month', CAST('2006-01-07 00:00:00' "
-                                  "as TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
-                                  "TIMESTAMP)) FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1096,
-        v<int64_t>(run_simple_agg("SELECT DATEDIFF('day', CAST('2006-01-07 00:00:00' as "
-                                  "TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
-                                  "TIMESTAMP)) FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        12,
-        v<int64_t>(run_simple_agg("SELECT DATEDIFF('quarter', CAST('2006-01-07 00:00:00' "
-                                  "as TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
-                                  "TIMESTAMP)) FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('day', DATE '2009-2-28', DATE "
-                                        "'2009-03-01') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(2,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('day', DATE '2008-2-28', DATE "
-                                        "'2008-03-01') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(-425,
-              v<int64_t>(run_simple_agg("select DATEDIFF('day', DATE '1971-03-02', DATE "
-                                        "'1970-01-01') from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('day', o, o + INTERVAL '1' DAY) FROM TEST LIMIT 1;", dt)));
-    ASSERT_EQ(15,
-              v<int64_t>(run_simple_agg("SELECT count(*) from test where DATEDIFF('day', "
-                                        "CAST (m AS DATE), o) < -5570;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('second', m, TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:16') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1000,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('millisecond', m, TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:16') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(44000000,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('microsecond', m, TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:59') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(34000000000,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('nanosecond', m, TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:49') FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(-1000,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('millisecond', TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:16', m) FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(-44000000,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('microsecond', TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:59', m) FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(-34000000000,
-              v<int64_t>(run_simple_agg("SELECT DATEDIFF('nanosecond', TIMESTAMP(0) "
-                                        "'2014-12-13 22:23:49', m) FROM test limit 1;",
-                                        dt)));
-    // DATEADD tests
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('day', 1, CAST('2017-05-31' AS DATE)) "
-                                  "= TIMESTAMP '2017-06-01 0:00:00' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('day', 2, DATE '2017-05-31') = "
-                                  "TIMESTAMP '2017-06-02 0:00:00' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('day', -1, CAST('2017-05-31' AS DATE)) "
-                                  "= TIMESTAMP '2017-05-30 0:00:00' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('day', -2, DATE '2017-05-31') = "
-                                  "TIMESTAMP '2017-05-29 0:00:00' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('hour', 1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-                  "'2017-05-31 2:11:11' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('hour', 10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 11:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('hour', -1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 0:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('hour', -10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-30 15:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('minute', 1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:12:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('minute', 10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:21:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('minute', -1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:10:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('minute', -10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:01:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('second', 1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:11:12' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('second', 10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:11:21' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('second', -1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:11:10' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('second', -10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
-            "'2017-05-31 1:11:01' from test limit 1;",
-            dt)));
-
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('month', 1, DATE '2017-01-10') = TIMESTAMP "
-                  "'2017-02-10 0:00:00' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('month', 10, DATE '2017-01-10') = TIMESTAMP "
-                  "'2017-11-10 0:00:00' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('month', 1, DATE '2009-01-30') = TIMESTAMP "
-                  "'2009-02-28 0:00:00' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('month', 1, DATE '2008-01-30') = TIMESTAMP "
-                  "'2008-02-29 0:00:00' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('month', 1, TIMESTAMP '2009-01-30 1:11:11') = TIMESTAMP "
-            "'2009-02-28 1:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('month', -1, TIMESTAMP '2009-03-30 1:11:11') = TIMESTAMP "
-            "'2009-02-28 1:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('month', -4, TIMESTAMP '2009-03-30 1:11:11') = TIMESTAMP "
-            "'2008-11-30 1:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEADD('month', 5, TIMESTAMP '2009-01-31 1:11:11') = TIMESTAMP "
-            "'2009-6-30 1:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('year', 1, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
-                  "'2009-02-28 1:11:11' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPADD(YEAR, 1, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
-            "'2009-02-28 1:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPADD(YEAR, -8, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
-            "'2000-02-29 1:11:11' from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPADD(YEAR, -8, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
-            "'2000-02-29 1:11:11' from test limit 1;",
-            dt)));
-
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT m = TIMESTAMP '2014-12-13 22:23:15' from test limit 1;", dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', 1, m) = TIMESTAMP "
-                                        "'2014-12-14 22:23:15' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', -1, m) = TIMESTAMP "
-                                        "'2014-12-12 22:23:15' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', 1, m) = TIMESTAMP "
-                                        "'2014-12-14 22:23:15' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', -1, m) = TIMESTAMP "
-                                        "'2014-12-12 22:23:15' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(
-                  run_simple_agg("SELECT o = DATE '1999-09-09' from test limit 1;", dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', 1, o) = TIMESTAMP "
-                                        "'1999-09-10 0:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', -3, o) = TIMESTAMP "
-                                        "'1999-09-06 0:00:00' from test limit 1;",
-                                        dt)));
-    /* DATE ADD subseconds to default timestamp(0) */
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('millisecond', 1000, m) = TIMESTAMP "
-                                  "'2014-12-13 22:23:16' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('microsecond', 1000000, m) = TIMESTAMP "
-                                  "'2014-12-13 22:23:16' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('nanosecond', 1000000000, m) = TIMESTAMP "
-                  "'2014-12-13 22:23:16' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT DATEADD('millisecond', 5123, m) = TIMESTAMP "
-                                  "'2014-12-13 22:23:20' from test limit 1;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('microsecond', 86400000000, m) = TIMESTAMP "
-                  "'2014-12-14 22:23:15' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT DATEADD('nanosecond', 86400000000123, m) = TIMESTAMP "
-                  "'2014-12-14 22:23:15' from test limit 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('weekday', -3, o) = TIMESTAMP "
-                                        "'1999-09-06 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('decade', 3, o) = TIMESTAMP "
-                                        "'2029-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('week', 1, o) = TIMESTAMP "
-                                        "'1999-09-16 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(DAY, 1, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-03 1:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(DAY, -1, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-01 1:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(DAY, 15, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-17 1:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(DAY, -15, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-02-15 1:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(HOUR, 1, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-02 2:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(HOUR, -1, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-02 0:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(HOUR, 15, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-02 16:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(HOUR, -15, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-01 10:23:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(MINUTE, 15, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-02 1:38:45' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(
-                  run_simple_agg("SELECT TIMESTAMPADD(MINUTE, -15, TIMESTAMP '2009-03-02 "
-                                 "1:23:45') = TIMESTAMP '2009-03-02 1:08:45' "
-                                 "FROM TEST LIMIT 1;",
-                                 dt)));
-    ASSERT_EQ(
-        1,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPADD(SECOND, 15, TIMESTAMP '2009-03-02 "
-                                  "1:23:45') = TIMESTAMP '2009-03-02 1:24:00' "
-                                  "FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(
-                  run_simple_agg("SELECT TIMESTAMPADD(SECOND, -15, TIMESTAMP '2009-03-02 "
-                                 "1:23:45') = TIMESTAMP '2009-03-02 1:23:30' "
-                                 "FROM TEST LIMIT 1;",
-                                 dt)));
-
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(DAY, 1, m) = TIMESTAMP '2014-12-14 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(DAY, -1, m) = TIMESTAMP '2014-12-12 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(DAY, 15, m) = TIMESTAMP '2014-12-28 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(DAY, -15, m) = TIMESTAMP '2014-11-28 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(HOUR, 1, m) = TIMESTAMP '2014-12-13 23:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(HOUR, -1, m) = TIMESTAMP '2014-12-13 21:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(HOUR, 15, m) = TIMESTAMP '2014-12-14 13:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(HOUR, -15, m) = TIMESTAMP '2014-12-13 7:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(MINUTE, 15, m) = TIMESTAMP '2014-12-13 22:38:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(MINUTE, -15, m) = TIMESTAMP '2014-12-13 22:08:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(SECOND, 15, m) = TIMESTAMP '2014-12-13 22:23:30' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(SECOND, -15, m) = TIMESTAMP '2014-12-13 22:23:00' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(MONTH, 1, m) = TIMESTAMP '2015-01-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(MONTH, -1, m) = TIMESTAMP '2014-11-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(MONTH, 5, m) = TIMESTAMP '2015-05-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(DAY, -5, m) = TIMESTAMP '2014-12-08 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(YEAR, 1, m) = TIMESTAMP '2015-12-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(YEAR, -1, m) = TIMESTAMP '2013-12-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(YEAR, 5, m) = TIMESTAMP '2019-12-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPADD(YEAR, -5, m) = TIMESTAMP '2009-12-13 22:23:15' "
-                  "FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg("select count(*) from test where TIMESTAMPADD(YEAR, "
-                                  "15, CAST(o AS TIMESTAMP)) > m;",
-                                  dt)));
-    ASSERT_EQ(
-        15,
-        v<int64_t>(run_simple_agg("select count(*) from test where TIMESTAMPADD(YEAR, "
-                                  "16, CAST(o AS TIMESTAMP)) > m;",
-                                  dt)));
-
-    ASSERT_EQ(
-        128885,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPDIFF(minute, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
-            "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
-            dt)));
-    ASSERT_EQ(2148,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPDIFF(hour, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
-                  "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(89,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPDIFF(day, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
-                  "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(3,
-              v<int64_t>(run_simple_agg(
-                  "SELECT TIMESTAMPDIFF(month, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
-                  "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
-                  dt)));
-    ASSERT_EQ(
-        -3,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPDIFF(month, TIMESTAMP '2003-05-01 12:05:55', TIMESTAMP "
-            "'2003-02-01 0:00:00') FROM TEST LIMIT 1;",
-            dt)));
-    ASSERT_EQ(
-        5,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPDIFF(month, m, m + INTERVAL '5' MONTH) FROM TEST LIMIT 1;",
-            dt)));
-    ASSERT_EQ(
-        -5,
-        v<int64_t>(run_simple_agg(
-            "SELECT TIMESTAMPDIFF(month, m, m - INTERVAL '5' MONTH) FROM TEST LIMIT 1;",
-            dt)));
-    ASSERT_EQ(
-        15,
-        v<int64_t>(run_simple_agg("select count(*) from test where TIMESTAMPDIFF(YEAR, "
-                                  "m, CAST(o AS TIMESTAMP)) < 0;",
-                                  dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(year, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(14,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(month, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(426,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(day, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(60,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(week, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(
-        60,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(week_sunday, DATE '2018-01-02', "
-                                  "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(
-        60,
-        v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(week_saturday, DATE "
-                                  "'2018-01-02', DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                  dt)));
-    ASSERT_EQ(613440,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(minute, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(10224,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(hour, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(36806400,
-              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(second, DATE '2018-01-02', "
-                                        "DATE '2019-03-04') FROM TEST LIMIT 1;",
-                                        dt)));
-
-    ASSERT_EQ(
-        1418428800LL,
-        v<int64_t>(run_simple_agg("SELECT CAST(m AS date) FROM test LIMIT 1;", dt)));
-    ASSERT_EQ(1336435200LL,
-              v<int64_t>(run_simple_agg("SELECT CAST(CAST('2012-05-08 20:15:12' AS "
-                                        "TIMESTAMP) AS DATE) FROM test LIMIT 1;",
-                                        dt)));
-    ASSERT_EQ(15,
-              v<int64_t>(run_simple_agg(
-                  "SELECT COUNT(*) FROM test GROUP BY CAST(m AS date);", dt)));
-    const auto rows = run_multiple_agg(
-        "SELECT DATE_TRUNC(month, CAST(o AS TIMESTAMP(0))) AS key0, str AS key1, "
-        "COUNT(*) AS val FROM test GROUP BY "
-        "key0, key1 ORDER BY val DESC, key1;",
-        dt);
-    check_date_trunc_groups(*rows);
-    const auto one_row = run_multiple_agg(
-        "SELECT DATE_TRUNC(year, CASE WHEN str = 'foo' THEN m END) dt FROM test GROUP BY "
-        "dt HAVING dt IS NOT NULL;",
-        dt);
-    check_one_date_trunc_group(*one_row, 1388534400);
-    ASSERT_EQ(0,
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test where "
-                                        "DATE '2017-05-30' = DATE '2017-05-31' OR "
-                                        "DATE '2017-05-31' = DATE '2017-05-30';",
-                                        dt)));
-    ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
-              v<int64_t>(run_simple_agg("SELECT COUNT(*) FROM test where "
-                                        "EXTRACT(DOW from TIMESTAMPADD(HOUR, -5, "
-                                        "TIMESTAMP '2017-05-31 1:11:11')) = 1 OR "
-                                        "EXTRACT(DOW from TIMESTAMPADD(HOUR, -5, "
-                                        "TIMESTAMP '2017-05-31 1:11:11')) = 2;",
-                                        dt)));
-
-    std::vector<std::tuple<std::string, int64_t, int64_t>> date_trunc_queries{
-        /*TIMESTAMP(0) */
-        std::make_tuple("year, m", 1388534400LL, 20),
-        std::make_tuple("month, m", 1417392000LL, 20),
-        std::make_tuple("day, m", 1418428800LL, 15),
-        std::make_tuple("hour, m", 1418508000LL, 15),
-        std::make_tuple("minute, m", 1418509380LL, 15),
-        std::make_tuple("second, m", 1418509395LL, 15),
-        std::make_tuple("millennium, m", 978307200LL, 20),
-        std::make_tuple("century, m", 978307200LL, 20),
-        std::make_tuple("decade, m", 1262304000LL, 20),
-        std::make_tuple("week, m", 1417996800LL, 20),
-        std::make_tuple("week_sunday, m", 1417910400LL, 15),
-        std::make_tuple("week_saturday, m", 1418428800LL, 20),
-        std::make_tuple("nanosecond, m", 1418509395LL, 15),
-        std::make_tuple("microsecond, m", 1418509395LL, 15),
-        std::make_tuple("millisecond, m", 1418509395LL, 15),
-        /* TIMESTAMP(3) */
-        std::make_tuple("year, m_3", 1388534400000LL, 20),
-        std::make_tuple("month, m_3", 1417392000000LL, 20),
-        std::make_tuple("day, m_3", 1418428800000LL, 15),
-        std::make_tuple("hour, m_3", 1418508000000LL, 15),
-        std::make_tuple("minute, m_3", 1418509380000LL, 15),
-        std::make_tuple("second, m_3", 1418509395000LL, 15),
-        std::make_tuple("millennium, m_3", 978307200000LL, 20),
-        std::make_tuple("century, m_3", 978307200000LL, 20),
-        std::make_tuple("decade, m_3", 1262304000000LL, 20),
-        std::make_tuple("week, m_3", 1417996800000LL, 20),
-        std::make_tuple("week_sunday, m_3", 1417910400000LL, 15),
-        std::make_tuple("week_saturday, m_3", 1418428800000LL, 20),
-        std::make_tuple("nanosecond, m_3", 1418509395323LL, 15),
-        std::make_tuple("microsecond, m_3", 1418509395323LL, 15),
-        std::make_tuple("millisecond, m_3", 1418509395323LL, 15),
-        /* TIMESTAMP(6) */
-        std::make_tuple("year, m_6", 915148800000000LL, 10),
-        std::make_tuple("month, m_6", 930787200000000LL, 10),
-        std::make_tuple("day, m_6", 931651200000000LL, 10),
-        std::make_tuple("hour, m_6", 931701600000000LL, 10),
-        /* std::make_tuple("minute, m_6", 931701720000000LL, 10), // Exception with sort
-           watchdog */
-        std::make_tuple("second, m_6", 931701773000000LL, 10),
-        std::make_tuple("millennium, m_6", -30578688000000000LL, 10),
-        std::make_tuple("century, m_6", -2177452800000000LL, 10),
-        std::make_tuple("decade, m_6", 631152000000000LL, 10),
-        std::make_tuple("week, m_6", 931132800000000LL, 10),
-        std::make_tuple("week_sunday, m_6", 931651200000000LL, 10),
-        std::make_tuple("week_saturday, m_6", 931564800000000LL, 10),
-        std::make_tuple("nanosecond, m_6", 931701773874533LL, 10),
-        std::make_tuple("microsecond, m_6", 931701773874533LL, 10),
-        std::make_tuple("millisecond, m_6", 931701773874000LL, 10),
-        /* TIMESTAMP(9) */
-        std::make_tuple("year, m_9", 1136073600000000000LL, 10),
-        std::make_tuple("month, m_9", 1143849600000000000LL, 10),
-        std::make_tuple("day, m_9", 1146009600000000000LL, 10),
-        std::make_tuple("hour, m_9", 1146020400000000000LL, 10),
-        /* std::make_tuple("minute, m_9", 1146023340000000000LL, 10), // Exception with
-           sort watchdog */
-        std::make_tuple("second, m_9", 1146023344000000000LL, 10),
-        std::make_tuple("millennium, m_9", 978307200000000000LL, 20),
-        std::make_tuple("century, m_9", 978307200000000000LL, 20),
-        std::make_tuple("decade, m_9", 946684800000000000LL, 10),
-        std::make_tuple("week, m_9", 1145836800000000000LL, 10),
-        std::make_tuple("week_sunday, m_9", 1145750400000000000LL, 10),
-        std::make_tuple("week_saturday, m_9", 1145664000000000000LL, 10),
-        std::make_tuple("nanosecond, m_9", 1146023344607435125LL, 10),
-        std::make_tuple("microsecond, m_9", 1146023344607435000LL, 10),
-        std::make_tuple("millisecond, m_9", 1146023344607000000LL, 10)};
-    for (auto& query : date_trunc_queries) {
-      const auto one_row = run_multiple_agg(
-          "SELECT date_trunc(" + std::get<0>(query) +
-              ") as key0,COUNT(*) AS val FROM test group by key0 order by key0 "
-              "limit 1;",
-          dt);
-      check_one_date_trunc_group_with_agg(
-          *one_row, std::get<1>(query), std::get<2>(query));
-    }
-    // Compressed DATE - limits test
-    ASSERT_EQ(4708022400LL,
-              v<int64_t>(run_simple_agg(
-                  "select CAST('2119-03-12' AS DATE) FROM test limit 1;", dt)));
-    ASSERT_EQ(7998912000LL,
-              v<int64_t>(run_simple_agg("select CAST(CAST('2223-06-24 23:13:57' AS "
-                                        "TIMESTAMP) AS DATE) FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('year', 411, o) = TIMESTAMP "
-                                        "'2410-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('year', -399, o) = TIMESTAMP "
-                                        "'1600-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('month', 6132, o) = TIMESTAMP "
-                                        "'2510-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('month', -1100, o) = TIMESTAMP "
-                                        "'1908-01-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', 312456, o) = TIMESTAMP "
-                                        "'2855-03-01 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', -23674, o) = TIMESTAMP "
-                                        "'1934-11-15 00:00:00' from test limit 1 ;",
-                                        dt)));
-    ASSERT_EQ(
-        -302,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('year', DATE '2302-04-21', o) from test limit 1;", dt)));
-    ASSERT_EQ(
-        501,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('year', o, DATE '2501-04-21') from test limit 1;", dt)));
-    ASSERT_EQ(
-        -4895,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('month', DATE '2407-09-01', o) from test limit 1;", dt)));
-    ASSERT_EQ(
-        3817,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('month', o, DATE '2317-11-01') from test limit 1;", dt)));
-    ASSERT_EQ(
-        -86972,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('day', DATE '2237-10-23', o) from test limit 1;", dt)));
-    ASSERT_EQ(
-        86972,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('day', o, DATE '2237-10-23') from test limit 1;", dt)));
-    ASSERT_EQ(
-        2617,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('year', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        12,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('month', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        23,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('day', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('hour', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('minute', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('second', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        6,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('weekday', CAST ('2011-12-31' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(365,
-              v<int64_t>(run_simple_agg("SELECT DATEPART('dayofyear', CAST ('2011-12-31' "
-                                        "as DATE)) from test limit 1;",
-                                        dt)));
-    // Compressed DATE - limits test
-    ASSERT_EQ(4708022400LL,
-              v<int64_t>(run_simple_agg(
-                  "select CAST('2119-03-12' AS DATE) FROM test limit 1;", dt)));
-    ASSERT_EQ(7998912000LL,
-              v<int64_t>(run_simple_agg("select CAST(CAST('2223-06-24 23:13:57' AS "
-                                        "TIMESTAMP) AS DATE) FROM test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('year', 411, o) = TIMESTAMP "
-                                        "'2410-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('year', -399, o) = TIMESTAMP "
-                                        "'1600-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('month', 6132, o) = TIMESTAMP "
-                                        "'2510-09-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('month', -1100, o) = TIMESTAMP "
-                                        "'1908-01-09 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', 312456, o) = TIMESTAMP "
-                                        "'2855-03-01 00:00:00' from test limit 1;",
-                                        dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg("SELECT DATEADD('day', -23674, o) = TIMESTAMP "
-                                        "'1934-11-15 00:00:00' from test limit 1 ;",
-                                        dt)));
-    ASSERT_EQ(
-        -302,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('year', DATE '2302-04-21', o) from test limit 1;", dt)));
-    ASSERT_EQ(
-        501,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('year', o, DATE '2501-04-21') from test limit 1;", dt)));
-    ASSERT_EQ(
-        -4895,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('month', DATE '2407-09-01', o) from test limit 1;", dt)));
-    ASSERT_EQ(
-        3817,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('month', o, DATE '2317-11-01') from test limit 1;", dt)));
-    ASSERT_EQ(
-        -86972,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('day', DATE '2237-10-23', o) from test limit 1;", dt)));
-    ASSERT_EQ(
-        86972,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEDIFF('day', o, DATE '2237-10-23') from test limit 1;", dt)));
-    ASSERT_EQ(
-        2617,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('year', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        12,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('month', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        23,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('day', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('hour', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('minute', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    ASSERT_EQ(
-        0,
-        v<int64_t>(run_simple_agg(
-            "SELECT DATEPART('second', CAST ('2617-12-23' as DATE)) from test limit 1;",
-            dt)));
-    /* Compressed Date ColumnarResults fetch tests*/
-    ASSERT_EQ(1999,
-              v<int64_t>(run_simple_agg("select yr from (SELECT EXTRACT(year from o) as "
-                                        "yr, o from test order by x) limit 1;",
-                                        dt)));
-    ASSERT_EQ(936835200,
-              v<int64_t>(run_simple_agg("select dy from (SELECT DATE_TRUNC(day, o) as "
-                                        "dy, o from test order by x) limit 1;",
+                                        "TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
+                                        "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
                                         dt)));
-    ASSERT_EQ(936921600,
-              v<int64_t>(run_simple_agg("select dy from (SELECT DATEADD('day', 1, o) as "
-                                        "dy, o from test order by x) limit 1;",
+    ASSERT_EQ(128885,
+              v<int64_t>(run_simple_agg("SELECT TIMESTAMPDIFF(sql_tsi_minute, TIMESTAMP "
+                                        "'2003-02-01 0:00:00', TIMESTAMP "
+                                        "'2003-05-01 12:05:55') FROM TEST LIMIT 1;",
                                         dt)));
-    ASSERT_EQ(1,
-              v<int64_t>(run_simple_agg(
-                  "select dy from (SELECT DATEDIFF('day', o, DATE '1999-09-10') as dy, o "
-                  "from test order by x) limit 1;",
-                  dt)));
-
-    // range tests
-    ASSERT_EQ(
-        1417392000,
-        v<int64_t>(run_simple_agg("SELECT date_trunc(month, m) as key0 FROM "
-                                  "test WHERE (m >= TIMESTAMP(3) '1970-01-01 "
-                                  "00:00:00.000') GROUP BY key0 ORDER BY key0 LIMIT 1;",
-                                  dt)));
-    EXPECT_NO_THROW(run_multiple_agg(
-        "SELECT date_trunc(second, ne) AS sec, AVG(x) FROM test GROUP BY sec;", dt));
   }
 }
 
@@ -7270,6 +6158,27 @@ TEST_F(Select, DateTruncate) {
             R"(SELECT EXTRACT('epoch' FROM date_trunc('quarter', dt)) FROM test_date_time ORDER BY dt;)",
             dt),
         {-213148800, -55296000, 0, 339206400});
+    ASSERT_EQ(2,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(second,m))) FROM test;", dt)));
+    ASSERT_EQ(2,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(minute,m))) FROM test;", dt)));
+    ASSERT_EQ(2,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(hour,m))) FROM test;", dt)));
+    ASSERT_EQ(2,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(day,m))) FROM test;", dt)));
+    ASSERT_EQ(2,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(day,m_3))) FROM test;", dt)));
+    ASSERT_EQ(3,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(day,m_6))) FROM test;", dt)));
+    ASSERT_EQ(3,
+              v<int64_t>(run_simple_agg(
+                  "SELECT COUNT(DISTINCT(DATE_TRUNC(day,m_9))) FROM test;", dt)));
   }
 }
 
@@ -9571,6 +8480,77 @@ TEST_F(Select, SkipFragments) {
     ExecutorDeviceType::CPU);
 }
 
+TEST_F(Select, PerDeviceCardinality) {
+  SKIP_ALL_ON_AGGREGATOR();
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    size_t num_fragments{0};
+    if (dt == ExecutorDeviceType::GPU) {
+      num_fragments = QR::get()->getExecutor()->deviceCount(dt);
+    } else {
+      num_fragments = 2;
+    }
+    run_ddl_statement("DROP TABLE IF EXISTS pdc_test;");
+    run_ddl_statement("CREATE TABLE pdc_test (v1 int, v2 int) WITH (FRAGMENT_SIZE = 4);");
+    for (size_t i = 0; i < num_fragments; i++) {
+      for (size_t j = 1; j <= 4; j++) {
+        run_multiple_agg("INSERT INTO pdc_test VALUES(1, " + std::to_string(j) + ");",
+                         ExecutorDeviceType::CPU);
+      }
+    }
+    std::shared_ptr<ResultSet> res =
+        run_multiple_agg("SELECT v1 FROM pdc_test WHERE v2 <= 2;", dt);
+    CHECK_EQ(res->entryCount(), num_fragments * 2)
+        << "Expected: " << num_fragments * 2 << ", Actual: " << res->entryCount();
+  }
+  run_ddl_statement("DROP TABLE IF EXISTS pdc_test;");
+}
+
+TEST_F(Select, PerDeviceCardinalityShardedTable) {
+  size_t num_fragments = 4;
+  size_t frag_size = 4;
+  run_ddl_statement("DROP TABLE IF EXISTS pdc_test;");
+  run_ddl_statement(
+      "CREATE TABLE pdc_test (v1 int, v2 int, shard key(v1)) WITH (SHARD_COUNT = 2, "
+      "FRAGMENT_SIZE = 4);");
+  for (size_t i = 1; i <= num_fragments; i++) {
+    for (size_t j = 1; j <= frag_size; j++) {
+      run_multiple_agg("INSERT INTO pdc_test VALUES(" + std::to_string(i) + ", " +
+                           std::to_string(j) + ");",
+                       ExecutorDeviceType::CPU);
+    }
+  }
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    for (size_t i = 1; i <= frag_size; i++) {
+      std::string q =
+          "SELECT COUNT(1) FROM (SELECT v1 FROM pdc_test WHERE v2 <= 2) WHERE v1 = " +
+          std::to_string(i) + ";";
+      EXPECT_EQ(v<int64_t>(run_simple_agg(q, dt)), int64_t(2)) << q;
+    }
+  }
+  run_ddl_statement("DROP TABLE IF EXISTS pdc_test;");
+}
+
+TEST_F(Select, FilteredCountallWithLimit) {
+  ScopeGuard reset = [old_val = g_preflight_count_query_threshold] {
+    g_preflight_count_query_threshold = old_val;
+    run_ddl_statement("DROP TABLE IF EXISTS fcwl_test;");
+  };
+  g_preflight_count_query_threshold = 10;
+  run_ddl_statement("DROP TABLE IF EXISTS fcwl_test;");
+  run_ddl_statement("CREATE TABLE fcwl_test (v INT) WITH (FRAGMENT_SIZE=20);");
+  for (size_t i = 0; i < 40; i++) {
+    run_multiple_agg("INSERT INTO fcwl_test VALUES(1);", ExecutorDeviceType::CPU);
+  }
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    std::shared_ptr<ResultSet> res =
+        run_multiple_agg("SELECT v FROM fcwl_test LIMIT 11", dt);
+    EXPECT_EQ(res->entryCount(), size_t(11));
+  }
+}
+
 TEST_F(Select, UnsupportedNodes) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
@@ -10012,6 +8992,50 @@ void import_hash_join_with_composite_text_cols_test() {
                    ExecutorDeviceType::CPU);
 }
 
+void import_switch_to_baseline_test(bool sharded) {
+  auto drop_table = [&](std::string const& table_name) {
+    std::ostringstream oss;
+    oss << "DROP TABLE IF EXISTS " << table_name << ";";
+    run_ddl_statement(oss.str());
+  };
+  auto create_table = [&](std::string const& table_name) {
+    std::ostringstream oss;
+    oss << "CREATE TABLE " << table_name << "(id int";
+    if (sharded) {
+      oss << ", SHARD KEY (id)) WITH (SHARD_COUNT=2);";
+    } else {
+      oss << ");";
+    }
+    run_ddl_statement(oss.str());
+  };
+  std::vector<std::string> tbl_names = {"tjs1", "tjs2", "tjs3", "tjs"};
+  if (sharded) {
+    for (size_t i = 0; i < tbl_names.size(); i++) {
+      tbl_names[i] = tbl_names[i] + "s";
+    }
+  }
+  for (std::string tbl_name : tbl_names) {
+    drop_table(tbl_name);
+    create_table(tbl_name);
+  }
+  for (int v : {30, 2, 2, 4, 1, 3}) {
+    std::ostringstream oss1;
+    oss1 << "INSERT INTO " << tbl_names[0] << " VALUES (" << v << ");";
+    std::ostringstream oss2;
+    oss2 << "INSERT INTO " << tbl_names[2] << " VALUES (" << v << ");";
+    run_multiple_agg(oss1.str(), ExecutorDeviceType::CPU);
+    run_multiple_agg(oss2.str(), ExecutorDeviceType::CPU);
+  }
+  run_multiple_agg("INSERT INTO " + tbl_names[1] + " VALUES (2);",
+                   ExecutorDeviceType::CPU);
+  run_multiple_agg("INSERT INTO " + tbl_names[3] + " VALUES (2);",
+                   ExecutorDeviceType::CPU);
+  run_multiple_agg("INSERT INTO " + tbl_names[1] + " VALUES (10000000);",
+                   ExecutorDeviceType::CPU);
+  run_multiple_agg("INSERT INTO " + tbl_names[3] + " VALUES (20);",
+                   ExecutorDeviceType::CPU);
+}
+
 void import_hash_join_decimal_test() {
   const std::string drop_old_test{"DROP TABLE IF EXISTS hash_join_decimal_test;"};
   run_ddl_statement(drop_old_test);
@@ -14252,7 +13276,7 @@ TEST_F(Select, CalciteNullRejectionRuleCastIssue) {
   std::string query{
       "SELECT v FROM (SELECT R.a v FROM outer_join_foo R LEFT JOIN left_join_geo1 S "
       "ON (R.a = S.x) WHERE ST_X(loc) IS NOT NULL);"};
-  auto const calcite_plan = QR::get()->getCalcitePlan(query, false, false);
+  auto const calcite_plan = QR::get()->getCalcitePlan(query, false, false, false);
   EXPECT_EQ(size_t(1), calcite_plan->rowCount(false));
   const auto crt_row = calcite_plan->getNextRow(true, false);
   EXPECT_EQ(size_t(1), crt_row.size());
@@ -14413,61 +13437,6 @@ TEST_F(Select, Joins_ComplexQueries) {
   }
 }
 
-TEST_F(Select, Joins_TimeAndDate) {
-  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
-    SKIP_NO_GPU();
-
-    // Inner joins
-    THROW_ON_AGGREGATOR(c("SELECT COUNT(*) FROM test a, test b WHERE a.m = b.m;", dt));
-    THROW_ON_AGGREGATOR(c("SELECT COUNT(*) FROM test a, test b WHERE a.n = b.n;", dt));
-    THROW_ON_AGGREGATOR(c("SELECT COUNT(*) FROM test a, test b WHERE a.o = b.o;", dt));
-
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.m = b.ts;", dt);
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt;", dt);
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt32;", dt);
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt16;", dt);
-
-    // Empty
-    c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.m = b.m;", dt);
-    c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.n = b.n;", dt);
-    c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.o = b.o;", dt);
-    c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.o1 = b.o1;", dt);
-    c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.o2 = b.o2;", dt);
-
-    // Bitwise path addition
-    c("SELECT COUNT(*) FROM test a, test_inner b where a.m = b.ts or (a.m is null and "
-      "b.ts is null);",
-      dt);
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt or (a.o is null and "
-      "b.dt is null);",
-      dt);
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt32 or (a.o is null and "
-      "b.dt32 is null);",
-      dt);
-    c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt16 or (a.o is null and "
-      "b.dt16 is null);",
-      dt);
-
-    // Inner joins across types
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt = b.dt;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt32 = b.dt;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt16 = b.dt;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt = b.dt32;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt32 = b.dt32;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt = b.dt16;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt32 = b.dt16;", dt);
-    c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt16 = b.dt16;", dt);
-
-    // Outer joins
-    c("SELECT a.x, a.o, b.dt FROM test a JOIN test_inner b ON a.o = b.dt;", dt);
-
-    if (dt == ExecutorDeviceType::CPU) {
-      // Clear CPU memory and hash table caches
-      QR::get()->clearCpuMemory();
-    }
-  }
-}
-
 TEST_F(Select, Joins_OneOuterExpression) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
@@ -14640,6 +13609,79 @@ TEST_F(JoinTest, EmptyJoinTables) {
   }
 }
 
+class QRExecutorDeviceParamTest : public ExecutorDeviceParameterizedTest {
+ protected:
+  bool gpusPresent() override { return QR::get()->gpusPresent(); }
+
+  TargetValue runSimpleAgg(const std::string& query) {
+    return run_simple_agg(query, device_type_);
+  }
+
+  void c(const std::string& query) { ::c(query, device_type_); }
+};
+
+class DateAndTimeJoinTest : public QRExecutorDeviceParamTest {
+ protected:
+  void TearDown() override {
+    if (device_type_ == ExecutorDeviceType::CPU) {
+      // Clear CPU memory and hash table caches
+      QR::get()->clearCpuMemory();
+    }
+    QRExecutorDeviceParamTest::TearDown();
+  }
+};
+
+TEST_P(DateAndTimeJoinTest, InnerJoin) {
+  THROW_ON_AGGREGATOR(c("SELECT COUNT(*) FROM test a, test b WHERE a.m = b.m;"));
+  THROW_ON_AGGREGATOR(c("SELECT COUNT(*) FROM test a, test b WHERE a.n = b.n;"));
+  THROW_ON_AGGREGATOR(c("SELECT COUNT(*) FROM test a, test b WHERE a.o = b.o;"));
+
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.m = b.ts;");
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt;");
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt32;");
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt16;");
+}
+
+TEST_P(DateAndTimeJoinTest, EmptyTable) {
+  c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.m = b.m;");
+  c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.n = b.n;");
+  c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.o = b.o;");
+  c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.o1 = b.o1;");
+  c("SELECT COUNT(*) FROM test a, test_empty b WHERE a.o2 = b.o2;");
+}
+
+TEST_P(DateAndTimeJoinTest, BitwisePathAddition) {
+  c("SELECT COUNT(*) FROM test a, test_inner b where a.m = b.ts or (a.m is null and "
+    "b.ts is null);");
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt or (a.o is null and "
+    "b.dt is null);");
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt32 or (a.o is null and "
+    "b.dt32 is null);");
+  c("SELECT COUNT(*) FROM test a, test_inner b WHERE a.o = b.dt16 or (a.o is null and "
+    "b.dt16 is null);");
+}
+
+TEST_P(DateAndTimeJoinTest, InnerJoinAcrossTypes) {
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt = b.dt;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt32 = b.dt;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt16 = b.dt;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt = b.dt32;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt32 = b.dt32;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt = b.dt16;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt32 = b.dt16;");
+  c("SELECT COUNT(*) FROM test_inner a, test_inner b WHERE a.dt16 = b.dt16;");
+}
+
+TEST_P(DateAndTimeJoinTest, OuterJoin) {
+  c("SELECT a.x, a.o, b.dt FROM test a JOIN test_inner b ON a.o = b.dt;");
+}
+
+INSTANTIATE_TEST_SUITE_P(CpuAndGpuExecutorDevices,
+                         DateAndTimeJoinTest,
+                         ::testing::Values(ExecutorDeviceType::CPU,
+                                           ExecutorDeviceType::GPU),
+                         ::testing::PrintToStringParamName());
+
 TEST_F(Select, Joins_MultipleOuterExpressions) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
@@ -21856,43 +20898,53 @@ TEST(Join, BuildHashTable) {
   }
 }
 
-TEST(Join, SwitchToBaselineJoin) {
+TEST(Join, SwitchToBaselineHashJoin) {
+  SKIP_ALL_ON_AGGREGATOR();
+  // check the query using CPU to verify we can switch it to baseline join correctly
+  ScopeGuard reset_flag = [orig =
+                               g_ratio_num_hash_entry_to_num_tuple_switch_to_baseline]() {
+    g_ratio_num_hash_entry_to_num_tuple_switch_to_baseline = orig;
+  };
+  g_ratio_num_hash_entry_to_num_tuple_switch_to_baseline = 5;
+  std::set<QueryPlanHash> visited;
+  ASSERT_EQ(
+      int64_t(2),
+      v<int64_t>(run_simple_agg("SELECT COUNT(1) FROM tjs1 a JOIN tjs2 b ON a.id = b.id;",
+                                ExecutorDeviceType::CPU)));
+
+  auto cached_item = QR::get()->getCachedHashtableWithoutCacheKey(
+      visited, CacheItemType::BASELINE_HT, DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  auto cached_ht = std::get<1>(cached_item);
+  auto expected_ht = dynamic_cast<BaselineHashTable*>(cached_ht.get());
+  EXPECT_TRUE(expected_ht);
+  QR::get()->clearCpuMemory();
+}
+
+TEST(Join, KeepPerfectHashJoin) {
+  SKIP_ALL_ON_AGGREGATOR();
+  // check the query using CPU to verify we can switch it to baseline join correctly
+  std::set<QueryPlanHash> visited;
+  ASSERT_EQ(
+      int64_t(2),
+      v<int64_t>(run_simple_agg("SELECT COUNT(1) FROM tjs1 a JOIN tjs2 b ON a.id = b.id;",
+                                ExecutorDeviceType::CPU)));
+  auto cached_item = QR::get()->getCachedHashtableWithoutCacheKey(
+      visited, CacheItemType::PERFECT_HT, DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  auto cached_ht = std::get<1>(cached_item);
+  auto expected_ht = dynamic_cast<PerfectHashTable*>(cached_ht.get());
+  EXPECT_TRUE(expected_ht);
+  QR::get()->clearCpuMemory();
+}
+
+TEST(Join, DISABLED_SwitchToBaselineHashJoinOnShardedTable) {
   SKIP_ALL_ON_AGGREGATOR();
   auto const dt = ExecutorDeviceType::GPU;
   if (skip_tests(dt)) {
     return;
   }
-  auto drop_table = [&](std::string const& table_name) {
-    std::ostringstream oss;
-    oss << "DROP TABLE IF EXISTS " << table_name << ";";
-    run_ddl_statement(oss.str());
-  };
-  auto create_table = [&](std::string const& table_name) {
-    std::ostringstream oss;
-    oss << "CREATE TABLE " << table_name
-        << "(id int, SHARD KEY (id)) WITH (SHARD_COUNT=2);";
-    run_ddl_statement(oss.str());
-  };
-  for (std::string tbl_name : {"tjs1", "tjs2", "tjs3", "tjs4"}) {
-    drop_table(tbl_name);
-    create_table(tbl_name);
-  }
-  for (int v : {30, 2, 2, 4, 1, 3}) {
-    std::ostringstream oss1;
-    oss1 << "INSERT INTO tjs1 VALUES (" << v << ");";
-    std::ostringstream oss2;
-    oss2 << "INSERT INTO tjs3 VALUES (" << v << ");";
-    run_multiple_agg(oss1.str(), ExecutorDeviceType::CPU);
-    run_multiple_agg(oss2.str(), ExecutorDeviceType::CPU);
-  }
-  run_multiple_agg("INSERT INTO tjs2 VALUES (2);", ExecutorDeviceType::CPU);
-  run_multiple_agg("INSERT INTO tjs4 VALUES (2);", ExecutorDeviceType::CPU);
-  run_multiple_agg("INSERT INTO tjs2 VALUES (10000000);", ExecutorDeviceType::CPU);
-  run_multiple_agg("INSERT INTO tjs4 VALUES (20);", ExecutorDeviceType::CPU);
-
   ASSERT_EQ(int64_t(2),
             v<int64_t>(run_simple_agg(
-                "SELECT COUNT(1) FROM tjs1 a JOIN tjs2 b ON a.id = b.id;", dt)));
+                "SELECT COUNT(1) FROM tjs1s a JOIN tjs2s b ON a.id = b.id;", dt)));
   ScopeGuard reset_flag = [orig =
                                g_ratio_num_hash_entry_to_num_tuple_switch_to_baseline]() {
     g_ratio_num_hash_entry_to_num_tuple_switch_to_baseline = orig;
@@ -21900,10 +20952,7 @@ TEST(Join, SwitchToBaselineJoin) {
   g_ratio_num_hash_entry_to_num_tuple_switch_to_baseline = 5;
   ASSERT_EQ(int64_t(2),
             v<int64_t>(run_simple_agg(
-                "SELECT COUNT(1) FROM tjs3 a JOIN tjs4 b ON a.id = b.id;", dt)));
-  for (std::string tbl_name : {"tjs1", "tjs2", "tjs3", "tjs4"}) {
-    drop_table(tbl_name);
-  }
+                "SELECT COUNT(1) FROM tjs3s a JOIN tjs4s b ON a.id = b.id;", dt)));
 }
 
 TEST(Join, ComplexQueries) {
@@ -22083,7 +21132,6 @@ TEST(Delete, MultiDelete) {
 TEST(Delete, Joins_ImplicitJoins) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
-    c("DELETE FROM test WHERE test.x = 8;", dt);
 
     c("SELECT COUNT(*) FROM test, test_inner WHERE test.x = test_inner.x;", dt);
     c("SELECT COUNT(*) FROM test, hash_join_test WHERE test.t = hash_join_test.t;", dt);
@@ -22252,7 +21300,7 @@ TEST_F(Select, TemporarilyDisableInClauseDecorrelationInDistMode) {
       g_enable_watchdog = watchdog_flag;
       for (const auto& test_cond : test_query) {
         const auto query_explain_result =
-            QR::get()->getCalcitePlan(test_cond.query, g_enable_watchdog, false);
+            QR::get()->getCalcitePlan(test_cond.query, g_enable_watchdog, true, false);
         EXPECT_EQ(size_t(1), query_explain_result->rowCount(false));
         const auto crt_row = query_explain_result->getNextRow(true, false);
         EXPECT_EQ(size_t(1), crt_row.size());
@@ -22311,7 +21359,7 @@ TEST_F(Select, InClauseDecorrelationUnderWatchdog) {
     // defined between hash joinable columns, i.e., integer type columns
     for (const auto& test_cond : test_query) {
       const auto query_explain_result =
-          QR::get()->getCalcitePlan(test_cond.query, g_enable_watchdog, false);
+          QR::get()->getCalcitePlan(test_cond.query, g_enable_watchdog, true, false);
       EXPECT_EQ(size_t(1), query_explain_result->rowCount(false));
       const auto crt_row = query_explain_result->getNextRow(true, false);
       EXPECT_EQ(size_t(1), crt_row.size());
@@ -23058,13 +22106,11 @@ TEST_F(Select, WindowFunctionEmptyPartitions) {
       // TODO(todd): Rework to also run in SQLite, which doesn't support DATE_TRUNC
       std::string query =
           "SELECT DATE_TRUNC(DAY, d) AS binned_day, COUNT(*) AS n, SUM(x) AS sum_x, "
-          "COUNT(*) - LAG(COUNT(*)) OVER ( ORDER BY DATE_TRUNC(DAY, d) ) AS "
+          "(x*2+1) - LAG((x*2+1)) OVER ( ORDER BY DATE_TRUNC(DAY, d) ) AS "
           "lag_n_order_by_d, SUM(x) / SUM(SUM(x+1)) OVER ( ORDER BY DATE_TRUNC(DAY, d)) "
-          "AS "
-          "sum_over_lag_sum_x FROM " +
+          "AS sum_over_lag_sum_x FROM " +
           table_name +
-          " GROUP BY DATE_TRUNC(DAY, d) ORDER BY "
-          "DATE_TRUNC(DAY, d) NULLS FIRST;";
+          " GROUP BY DATE_TRUNC(DAY, d), x ORDER BY DATE_TRUNC(DAY, d) NULLS FIRST;";
       EXPECT_NO_THROW(run_multiple_agg(query, dt));
     }
   }
@@ -25591,7 +24637,7 @@ TEST_F(Select, GroupEmptyBlank) {
 TEST_F(Select, UnionAll) {
   bool enable_union = true;
   std::swap(g_enable_union, enable_union);
-  for (auto dt : {ExecutorDeviceType::CPU /*, ExecutorDeviceType::GPU*/}) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
     c("SELECT COUNT(*) FROM ("
       " SELECT a4 FROM union_all_a"
@@ -25885,6 +24931,33 @@ TEST_F(Select, UnionAll) {
   g_enable_union = enable_union;
 }
 
+TEST_F(Select, UnionAllIgnorePerSubqueryCardinality) {
+  ScopeGuard reset = [old1 = g_enable_union, old2 = g_preflight_count_query_threshold] {
+    g_enable_union = old1;
+    g_preflight_count_query_threshold = old2;
+  };
+  g_enable_union = true;
+  g_preflight_count_query_threshold = 1;
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    c("SELECT a0, a1, a2, a3 FROM union_all_a"
+      " WHERE a0 BETWEEN 111 AND 115"
+      " UNION ALL"
+      " SELECT b0, b1, b2, b3 FROM union_all_b"
+      " WHERE b0 BETWEEN 211 AND 217"
+      " ORDER BY a1;",
+      dt);
+    c("SELECT COUNT(*) FROM ("
+      " SELECT a4 FROM union_all_a"
+      " UNION ALL"
+      " SELECT b4 FROM union_all_b"
+      " UNION ALL"
+      " SELECT c4 FROM union_all_c"
+      ");",
+      dt);
+  }
+}
+
 TEST_F(Select, VariableLengthAggs) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
@@ -26177,6 +25250,18 @@ TEST_F(Select, ProjectVarlenColWithLazyFetching) {
     EXPECT_TRUE(!result_rows2->isEmpty());
     auto crt_row2 = result_rows2->getNextRow(true, true);
     EXPECT_EQ(size_t(2), crt_row2.size());
+
+    for (std::string col :
+         {"col_point_1", "col_linestring_1", "col_polygon_1", "col_multipolygon_1"}) {
+      std::ostringstream oss;
+      oss << "SELECT " << col << ", ST_NPoints(" << col << ") FROM data_types_basic3;";
+      std::ostringstream oss2;
+      oss2 << "SELECT ST_NPoints(" << col << "), " << col << " FROM data_types_basic3;";
+      auto rs1 = run_multiple_agg(oss.str(), dt);
+      EXPECT_TRUE(!rs1->isEmpty());
+      auto rs2 = run_multiple_agg(oss2.str(), dt);
+      EXPECT_TRUE(!rs2->isEmpty());
+    }
   }
 }
 
@@ -26655,6 +25740,27 @@ TEST_F(Select, AutoQueryCaching) {
   }
 }
 
+TEST_F(Select, RedundantProjectionRemoving) {
+  // in dist mode, we cannot get RaExecutionSequence from QR
+  SKIP_ALL_ON_AGGREGATOR();
+  auto perform_test = [](std::string query) {
+    auto const ra_exec_seq = QR::get()->getRaExecutionSequence(query);
+    EXPECT_EQ(ra_exec_seq.size(), size_t(1));
+  };
+  std::string q1 =
+      "select fn, f from (select R.f, abs(R.ff) v, R.fn from test R, test_inner S where "
+      "R.x = "
+      "S.x and S.str is not null) where f > 0";
+  std::string q2 = q1 + " order by 1";
+  std::string q3 =
+      "select f, fn from (select fn, abs(ff), f from (select R.f, R.ff, abs(R.fn) fn "
+      "from test R, test_inner S where R.x = S.x and S.str is not null) where fn > 0) "
+      "where f > 0";
+  perform_test(q1);
+  perform_test(q2);
+  perform_test(q3);
+}
+
 class SubqueryTestEnv : public ::testing::Test {
  protected:
   void SetUp() override {
@@ -26857,6 +25963,70 @@ TEST_F(Select, Explain_Query_Session) {
   EXPECT_EQ(executor->getNumCurentSessionsEnrolled(), static_cast<size_t>(0));
 }
 
+// Test EXPLAIN CALCITE and EXPLAIN CALCITE DETAILED.
+class CalcitePlan : public Select, public testing::WithParamInterface<bool> {
+  // Erase substrings between '\t' and '\n'. Keep the '\n'.
+  static void eraseDetails(std::string& explain) {
+    size_t tab_pos = 0u;
+    while ((tab_pos = explain.find('\t', tab_pos)) != std::string::npos) {
+      size_t newline_pos = explain.find('\n', tab_pos);  // ok if npos
+      explain.erase(tab_pos, newline_pos - tab_pos);
+    }
+  }
+
+ public:
+  static void test(bool const is_detail, std::string const& query, std::string expected) {
+    auto const calcite_plan = QR::get()->getCalcitePlan(query, false, false, is_detail);
+    EXPECT_EQ(1u, calcite_plan->rowCount(false));
+    auto const crt_row = calcite_plan->getNextRow(true, false);
+    EXPECT_EQ(1u, crt_row.size());
+    auto actual = boost::get<std::string>(v<NullableString>(crt_row[0]));
+    if (!is_detail) {
+      EXPECT_EQ(std::string::npos, actual.find("->db:")) << actual;
+      eraseDetails(expected);
+    }
+    EXPECT_EQ(expected, actual);
+  }
+};
+
+TEST_P(CalcitePlan, Simple) {
+  bool const is_detail = GetParam();
+  char const* query = "SELECT x FROM test";
+  char const* expected =
+      "LogicalProject(x=[$0])\t{[$0->db:heavyai,tableName:test,colName:x]}\n"
+      "  LogicalTableScan(table=[[heavyai, test]])\n";
+  CalcitePlan::test(is_detail, query, expected);
+}
+
+TEST_P(CalcitePlan, FilterCrossJoin) {
+  bool const is_detail = GetParam();
+  char const* query = "SELECT a.x, b.y, a.z FROM test a, test b wHERE a.x=b.y";
+  char const* expected =
+      // clang-format off
+      "LogicalProject(x=[$0], y=[$42], z=[$3])\t{[$0->db:heavyai,tableName:test,colName:x], [$3->db:heavyai,tableName:test,colName:z], [$42->db:heavyai,tableName:test,colName:y]}\n"
+      "  LogicalFilter(condition=[=($0, $42)])\t{[$0->db:heavyai,tableName:test,colName:x], [$42->db:heavyai,tableName:test,colName:y]}\n"
+      "    LogicalJoin(condition=[true], joinType=[inner])\n"
+      "      LogicalTableScan(table=[[heavyai, test]])\n"
+      "      LogicalTableScan(table=[[heavyai, test]])\n";
+  // clang-format on
+  CalcitePlan::test(is_detail, query, expected);
+}
+
+TEST_P(CalcitePlan, InnerJoin) {
+  bool const is_detail = GetParam();
+  char const* query = "SELECT a.x, b.y, a.z FROM test a JOIN test b ON a.x=b.y";
+  char const* expected =
+      // clang-format off
+      "LogicalProject(x=[$0], y=[$42], z=[$3])\t{[$0->db:heavyai,tableName:test,colName:x], [$3->db:heavyai,tableName:test,colName:z], [$42->db:heavyai,tableName:test,colName:y]}\n"
+      "  LogicalJoin(condition=[=($0, $42)], joinType=[inner])\t{[$0->db:heavyai,tableName:test,colName:x], [$42->db:heavyai,tableName:test,colName:y]}\n"
+      "    LogicalTableScan(table=[[heavyai, test]])\n"
+      "    LogicalTableScan(table=[[heavyai, test]])\n";
+  // clang-format on
+  CalcitePlan::test(is_detail, query, expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(Test, CalcitePlan, testing::Bool() /* is_detail */);
+
 TEST_F(Select, ProjectMoreThan1MVarlenTypeColumn) {
   // this test checks that we can safely fallback to row-wise output mode
   // when target expression (in a projection target list) generates geo point
@@ -26922,6 +26092,1196 @@ TEST_F(Select, InfNanTest) {
   }
 }
 
+// Use a non-empty session_id to test call to Executor::removeFromQuerySessionList().
+// This test verifies QE-952 fixes a valgrind error.
+TEST_F(Select, RemoveFromQuerySessionList) {
+  SKIP_ALL_ON_AGGREGATOR();
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    char const* query = "SELECT COUNT(*) FROM test;";
+    char const* session_id = "0123456789ABCDEF0123456789ABCDEF";  // len=32
+    QR::get()->runSQLWithAllowingInterrupt(query, session_id, dt);
+  }
+}
+
+class DateAndTimeFunctionsTest : public QRExecutorDeviceParamTest {};
+
+TEST_P(DateAndTimeFunctionsTest, CastLiteralToDate) {
+  // check DATE Formats
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('1999-09-10' AS DATE) > o;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('10/09/1999' AS DATE) > o;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('10/09/99' AS DATE) > o;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('10-Sep-99' AS DATE) > o;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('9/10/99' AS DATE) > o;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('31/Oct/2013' AS DATE) > o;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('10/31/13' AS DATE) > o;")));
+
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('1999-09-10' AS DATE) > o;")));
+  ASSERT_EQ(0,
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('1999-09-10' AS DATE) <= o;")));
+
+  //  test DATE format processing
+  ASSERT_EQ(
+      1434844800LL,
+      v<int64_t>(runSimpleAgg("select CAST('2015-06-21' AS DATE) FROM test limit 1;")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE o < CAST('06/21/2015' AS DATE);")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE o < CAST('21-Jun-15' AS DATE);")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE o < CAST('21/Jun/2015' AS DATE);")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE o < CAST('1434844800' AS DATE);")));
+
+  ASSERT_EQ(0,
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test where "
+                                    "DATE '2017-05-30' = DATE '2017-05-31' OR "
+                                    "DATE '2017-05-31' = DATE '2017-05-30';")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CastTimestampToDate) {
+  ASSERT_EQ(1418428800LL,
+            v<int64_t>(runSimpleAgg("SELECT CAST(m AS date) FROM test LIMIT 1;")));
+
+  ASSERT_EQ(
+      15,
+      v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test GROUP BY CAST(m AS date);")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CastLiteralToTime) {
+  // check TIME FORMATS
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('15:13:15' AS TIME) > n;")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('151315' AS TIME) > n;")));
+
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('15:13:15' AS TIME) > n;")));
+  ASSERT_EQ(0,
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE CAST('15:13:15' AS TIME) <= n;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CastLiteralToTimestamp_CustomFormat) {
+  //  test different input formats
+  //  added new format for customer
+  ASSERT_EQ(1434896116LL,
+            v<int64_t>(runSimpleAgg(
+                "select CAST('2015-06-21 14:15:16' AS timestamp) FROM test limit 1;")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('2015-06-21:141516' AS TIMESTAMP);")));
+  ASSERT_EQ(
+      static_cast<int64_t>(2 * g_num_rows),
+      v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= CAST('21-JUN-15 "
+                              "2.15.16.12345 PM' AS TIMESTAMP);")));
+  ASSERT_EQ(
+      static_cast<int64_t>(2 * g_num_rows),
+      v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= CAST('21-JUN-15 "
+                              "2.15.16.12345 AM' AS TIMESTAMP);")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('21-JUN-15 2:15:16 AM' AS TIMESTAMP);")));
+
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('06/21/2015 14:15:16' AS TIMESTAMP);")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CastLiteralToTimestamp_IsoFormat) {
+  //  Support ISO date offset format
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('21/Aug/2015:12:13:14 -0600' AS TIMESTAMP);")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('2015-08-21T12:13:14 -0600' AS TIMESTAMP);")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('21-Aug-15 12:13:14 -0600' AS TIMESTAMP);")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('21/Aug/2015:13:13:14 -0500' AS TIMESTAMP);")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m <= "
+                                    "CAST('2015-08-21T18:13:14' AS TIMESTAMP);")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CastLiteralToTimestamp_IsoFormat_AlternateSyntax) {
+  ASSERT_EQ(
+      static_cast<int64_t>(2 * g_num_rows),
+      v<int64_t>(runSimpleAgg(
+          "SELECT COUNT(*) FROM test WHERE m > timestamp(0) '2014-12-13T000000';")));
+  ASSERT_EQ(static_cast<int64_t>(g_num_rows + g_num_rows / 2),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE CAST(o AS "
+                                    "TIMESTAMP) > timestamp(0) '1999-09-08T160000';")));
+  ASSERT_EQ(0,
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE CAST(o AS "
+                                    "TIMESTAMP) > timestamp(0) '1999-09-10T160000';")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, NestedCast) {
+  ASSERT_EQ(1336435200LL,
+            v<int64_t>(runSimpleAgg("SELECT CAST(CAST('2012-05-08 20:15:12' AS "
+                                    "TIMESTAMP) AS DATE) FROM test LIMIT 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_EqualityTest) {
+  ASSERT_TRUE(v<int64_t>(runSimpleAgg(
+      "SELECT EXTRACT(YEAR FROM CURRENT_DATE) = EXTRACT(YEAR FROM CURRENT_TIMESTAMP)"
+      " AND EXTRACT(MONTH FROM CURRENT_DATE) = EXTRACT(MONTH FROM CURRENT_TIMESTAMP)"
+      " AND EXTRACT(DAY FROM CURRENT_DATE) = EXTRACT(DAY FROM CURRENT_TIMESTAMP)"
+      " AND EXTRACT(HOUR FROM CURRENT_DATE) = 0"
+      " AND EXTRACT(MINUTE FROM CURRENT_DATE) = 0"
+      " AND EXTRACT(SECOND FROM CURRENT_DATE) = 0;")));
+
+  ASSERT_TRUE(v<int64_t>(runSimpleAgg(
+      "SELECT EXTRACT(HOUR FROM CURRENT_TIME()) = EXTRACT(HOUR FROM CURRENT_TIMESTAMP)"
+      " AND EXTRACT(MINUTE FROM CURRENT_TIME) = EXTRACT(MINUTE FROM CURRENT_TIMESTAMP)"
+      " AND EXTRACT(SECOND FROM CURRENT_TIME) = EXTRACT(SECOND FROM CURRENT_TIMESTAMP)"
+      ";")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Epoch) {
+  ASSERT_EQ(
+      14185957950LL,
+      v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(EPOCH FROM m) * 10) FROM test;")));
+
+  ASSERT_EQ(936835200LL,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(EPOCH FROM o)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_DateEpoch) {
+  ASSERT_EQ(
+      14185152000LL,
+      v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(DATEEPOCH FROM m) * 10) FROM test;")));
+
+  ASSERT_EQ(936835200LL,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(DATEEPOCH FROM o)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Year) {
+  ASSERT_EQ(20140,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(YEAR FROM m) * 10) FROM test;")));
+
+  ASSERT_EQ(1999,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(YEAR FROM o)) FROM test;")));
+
+  ASSERT_EQ(2005,
+            v<int64_t>(runSimpleAgg("select EXTRACT(year from TIMESTAMP '2005-12-31 "
+                                    "23:59:59') from test limit 1;")));
+  ASSERT_EQ(1997,
+            v<int64_t>(runSimpleAgg("select EXTRACT(year from TIMESTAMP '1997-01-01 "
+                                    "23:59:59') from test limit 1;")));
+  ASSERT_EQ(2006,
+            v<int64_t>(runSimpleAgg("select EXTRACT(year from TIMESTAMP '2006-01-01 "
+                                    "00:0:00') from test limit 1;")));
+  ASSERT_EQ(2014,
+            v<int64_t>(runSimpleAgg("select EXTRACT(year from TIMESTAMP '2014-01-01 "
+                                    "00:00:00') from test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Month) {
+  ASSERT_EQ(
+      120, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(MONTH FROM m) * 10) FROM test;")));
+
+  ASSERT_EQ(9, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(MONTH FROM o)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Day) {
+  ASSERT_EQ(140,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(DAY FROM m) * 10) FROM test;")));
+
+  ASSERT_EQ(9, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(DAY FROM o)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Hour) {
+  ASSERT_EQ(22, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(HOUR FROM m)) FROM test;")));
+
+  ASSERT_EQ(15, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(HOUR FROM n)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Week) {
+  ASSERT_EQ(52LL,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(WEEK FROM CAST('2012-01-01 "
+                                    "20:15:12' AS TIMESTAMP))) FROM test limit 1;")));
+
+  ASSERT_EQ(10LL,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(WEEK FROM CAST('2008-03-03 "
+                                    "20:15:12' AS TIMESTAMP))) FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_WeekSunday) {
+  ASSERT_EQ(
+      1LL,
+      v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(WEEK_SUNDAY FROM CAST('2012-01-01 "
+                              "20:15:12' AS TIMESTAMP))) FROM test limit 1;")));
+
+  ASSERT_EQ(
+      10LL,
+      v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(WEEK_SUNDAY FROM CAST('2008-03-03 "
+                              "20:15:12' AS TIMESTAMP))) FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_WeekSaturday) {
+  ASSERT_EQ(
+      1LL,
+      v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(WEEK_SATURDAY FROM CAST('2012-01-01 "
+                              "20:15:12' AS TIMESTAMP))) FROM test limit 1;")));
+
+  ASSERT_EQ(
+      10LL,
+      v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(WEEK_SATURDAY FROM CAST('2008-03-03 "
+                              "20:15:12' AS TIMESTAMP))) FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Minute) {
+  ASSERT_EQ(23,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(MINUTE FROM m)) FROM test;")));
+
+  ASSERT_EQ(13,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(MINUTE FROM n)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Second) {
+  ASSERT_EQ(15,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(SECOND FROM m)) FROM test;")));
+
+  ASSERT_EQ(14,
+            v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(SECOND FROM n)) FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Millisecond) {
+  ASSERT_EQ(
+      15000LL,
+      v<int64_t>(runSimpleAgg("SELECT EXTRACT(millisecond from m) FROM test limit 1;")));
+
+  ASSERT_EQ(56000LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(millisecond from TIMESTAMP(0) "
+                                    "'1999-03-14 23:34:56') FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Microsecond) {
+  ASSERT_EQ(
+      15000000LL,
+      v<int64_t>(runSimpleAgg("SELECT EXTRACT(microsecond from m) FROM test limit 1;")));
+
+  ASSERT_EQ(56000000LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(microsecond from TIMESTAMP(0) "
+                                    "'1999-03-14 23:34:56') FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Nanosecond) {
+  ASSERT_EQ(
+      15000000000LL,
+      v<int64_t>(runSimpleAgg("SELECT EXTRACT(nanosecond from m) FROM test limit 1;")));
+
+  ASSERT_EQ(56000000000LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(nanosecond from TIMESTAMP(0) "
+                                    "'1999-03-14 23:34:56') FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Doy) {
+  ASSERT_EQ(348, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(DOY FROM m)) FROM test;")));
+
+  ASSERT_EQ(252,
+            v<int64_t>(runSimpleAgg(
+                "SELECT EXTRACT(DOY FROM o) FROM test WHERE o IS NOT NULL;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_Dow) {
+  ASSERT_EQ(6, v<int64_t>(runSimpleAgg("SELECT MAX(EXTRACT(DOW FROM m)) FROM test;")));
+
+  ASSERT_EQ(4,
+            v<int64_t>(runSimpleAgg(
+                "SELECT EXTRACT(DOW FROM o) FROM test WHERE o IS NOT NULL;")));
+
+  // Monday
+  ASSERT_EQ(1LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(DOW FROM CAST('2008-03-03 "
+                                    "20:15:12' AS TIMESTAMP)) FROM test limit 1;")));
+  // Monday
+  ASSERT_EQ(1LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(ISODOW FROM CAST('2008-03-03 "
+                                    "20:15:12' AS TIMESTAMP)) FROM test limit 1;")));
+  // Sunday
+  ASSERT_EQ(0LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(DOW FROM CAST('2008-03-02 "
+                                    "20:15:12' AS TIMESTAMP)) FROM test limit 1;")));
+  // Sunday
+  ASSERT_EQ(7LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT(ISODOW FROM CAST('2008-03-02 "
+                                    "20:15:12' AS TIMESTAMP)) FROM test limit 1;")));
+  // Wednesday
+  ASSERT_EQ(3LL,
+            v<int64_t>(runSimpleAgg("SELECT EXTRACT('isodow' FROM o - INTERVAL 40 "
+                                    "years) dow FROM test ORDER BY dow LIMIT 1;")));
+
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test where "
+                                    "EXTRACT(DOW from TIMESTAMPADD(HOUR, -5, "
+                                    "TIMESTAMP '2017-05-31 1:11:11')) = 1 OR "
+                                    "EXTRACT(DOW from TIMESTAMPADD(HOUR, -5, "
+                                    "TIMESTAMP '2017-05-31 1:11:11')) = 2;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, Extract_QuarterDay) {
+  //  add test for quarterday behaviour
+  ASSERT_EQ(1LL,
+            v<int64_t>(runSimpleAgg(
+                "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T04:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(1LL,
+            v<int64_t>(runSimpleAgg(
+                "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T00:00:00' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(2LL,
+            v<int64_t>(runSimpleAgg(
+                "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T08:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(3LL,
+            v<int64_t>(runSimpleAgg(
+                "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T14:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(4LL,
+            v<int64_t>(runSimpleAgg(
+                "select EXTRACT (QUARTERDAY FROM CAST('2015-08-21T23:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(1440115200LL,
+            v<int64_t>(runSimpleAgg(
+                "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T04:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(1440136800LL,
+            v<int64_t>(runSimpleAgg(
+                "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T08:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(1440158400LL,
+            v<int64_t>(runSimpleAgg(
+                "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T13:23:11' AS "
+                "timestamp)) FROM test limit 1;")));
+  ASSERT_EQ(1440180000LL,
+            v<int64_t>(runSimpleAgg(
+                "select DATE_TRUNC (QUARTERDAY, CAST('2015-08-21T23:59:59' AS "
+                "timestamp)) FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CurrentDateTime) {
+  cta("SELECT DATETIME('NOW') FROM test limit 1;", device_type_);
+  EXPECT_ANY_THROW(runSimpleAgg("SELECT DATETIME(NULL) FROM test LIMIT 1;"));
+  // these next tests work because all dates are before now 2015-12-8 17:00:00
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m < NOW();")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE o IS NULL OR o < CURRENT_DATE;")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE o IS NULL OR o < CURRENT_DATE();")));
+  ASSERT_EQ(
+      static_cast<int64_t>(2 * g_num_rows),
+      v<int64_t>(runSimpleAgg("SELECT COUNT(*) FROM test WHERE m < CURRENT_TIMESTAMP;")));
+  ASSERT_EQ(static_cast<int64_t>(2 * g_num_rows),
+            v<int64_t>(runSimpleAgg(
+                "SELECT COUNT(*) FROM test WHERE m < CURRENT_TIMESTAMP();")));
+  ASSERT_TRUE(
+      v<int64_t>(runSimpleAgg("SELECT CURRENT_DATE = CAST(CURRENT_TIMESTAMP AS DATE);")));
+  ASSERT_TRUE(
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -1, CURRENT_TIMESTAMP) < "
+                              "CURRENT_DATE AND CURRENT_DATE <= CURRENT_TIMESTAMP;")));
+  ASSERT_TRUE(v<int64_t>(
+      runSimpleAgg("SELECT CAST(CURRENT_DATE AS TIMESTAMP) <= CURRENT_TIMESTAMP;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, DatePart) {
+  ASSERT_EQ(2007,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('year', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(2007,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('yyyy', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      2007,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('yy', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(4,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('quarter', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      4,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('qq', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      4,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('q', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(10,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('month', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      10,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('mm', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      10,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('m', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(303,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('dayofyear', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      303,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('dy', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      303,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('y', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(30,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('day', CAST('2007-10-30 12:15:32' "
+                                    "AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      30,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('dd', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      30,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('d', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(12,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('hour', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      12,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('hh', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(15,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('minute', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      15,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('mi', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      15,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('n', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(32,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('second', CAST('2007-10-30 "
+                                    "12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      32,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('ss', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(
+      32,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('s', CAST('2007-10-30 12:15:32' AS TIMESTAMP)) FROM test;")));
+  ASSERT_EQ(32,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEPART('s', TIMESTAMP '2007-10-30 12:15:32') FROM test;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateDiff) {
+  ASSERT_EQ(
+      3,
+      v<int64_t>(runSimpleAgg("SELECT DATEDIFF('year', CAST('2006-01-07 00:00:00' as "
+                              "TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
+                              "TIMESTAMP)) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(36,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('month', CAST('2006-01-07 00:00:00' "
+                                    "as TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
+                                    "TIMESTAMP)) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      1096,
+      v<int64_t>(runSimpleAgg("SELECT DATEDIFF('day', CAST('2006-01-07 00:00:00' as "
+                              "TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
+                              "TIMESTAMP)) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      12,
+      v<int64_t>(runSimpleAgg("SELECT DATEDIFF('quarter', CAST('2006-01-07 00:00:00' "
+                              "as TIMESTAMP), CAST('2009-01-07 00:00:00' AS "
+                              "TIMESTAMP)) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('day', DATE '2009-2-28', DATE "
+                                    "'2009-03-01') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(2,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('day', DATE '2008-2-28', DATE "
+                                    "'2008-03-01') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(-425,
+            v<int64_t>(runSimpleAgg("select DATEDIFF('day', DATE '1971-03-02', DATE "
+                                    "'1970-01-01') from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('day', o, o + INTERVAL '1' DAY) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(15,
+            v<int64_t>(runSimpleAgg("SELECT count(*) from test where DATEDIFF('day', "
+                                    "CAST (m AS DATE), o) < -5570;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('second', m, TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:16') FROM test limit 1;")));
+  ASSERT_EQ(1000,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('millisecond', m, TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:16') FROM test limit 1;")));
+  ASSERT_EQ(44000000,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('microsecond', m, TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:59') FROM test limit 1;")));
+  ASSERT_EQ(34000000000,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('nanosecond', m, TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:49') FROM test limit 1;")));
+  ASSERT_EQ(-1000,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('millisecond', TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:16', m) FROM test limit 1;")));
+  ASSERT_EQ(-44000000,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('microsecond', TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:59', m) FROM test limit 1;")));
+  ASSERT_EQ(-34000000000,
+            v<int64_t>(runSimpleAgg("SELECT DATEDIFF('nanosecond', TIMESTAMP(0) "
+                                    "'2014-12-13 22:23:49', m) FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateAdd) {
+  //  DATEADD tests
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('day', 1, CAST('2017-05-31' AS DATE)) "
+                              "= TIMESTAMP '2017-06-01 0:00:00' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('day', 2, DATE '2017-05-31') = "
+                              "TIMESTAMP '2017-06-02 0:00:00' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -1, CAST('2017-05-31' AS DATE)) "
+                              "= TIMESTAMP '2017-05-30 0:00:00' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -2, DATE '2017-05-31') = "
+                              "TIMESTAMP '2017-05-29 0:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('hour', 1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+                "'2017-05-31 2:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('hour', 10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+                "'2017-05-31 11:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('hour', -1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+                "'2017-05-31 0:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('hour', -10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+                "'2017-05-30 15:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('minute', 1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+                "'2017-05-31 1:12:11' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEADD('minute', 10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+          "'2017-05-31 1:21:11' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEADD('minute', -1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+          "'2017-05-31 1:10:11' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEADD('minute', -10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+          "'2017-05-31 1:01:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('second', 1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+                "'2017-05-31 1:11:12' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEADD('second', 10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+          "'2017-05-31 1:11:21' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEADD('second', -1, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+          "'2017-05-31 1:11:10' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEADD('second', -10, TIMESTAMP '2017-05-31 1:11:11') = TIMESTAMP "
+          "'2017-05-31 1:11:01' from test limit 1;")));
+
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('month', 1, DATE '2017-01-10') = TIMESTAMP "
+                              "'2017-02-10 0:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(
+                runSimpleAgg("SELECT DATEADD('month', 10, DATE '2017-01-10') = TIMESTAMP "
+                             "'2017-11-10 0:00:00' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('month', 1, DATE '2009-01-30') = TIMESTAMP "
+                              "'2009-02-28 0:00:00' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('month', 1, DATE '2008-01-30') = TIMESTAMP "
+                              "'2008-02-29 0:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('month', 1, TIMESTAMP '2009-01-30 1:11:11') = TIMESTAMP "
+                "'2009-02-28 1:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('month', -1, TIMESTAMP '2009-03-30 1:11:11') = TIMESTAMP "
+                "'2009-02-28 1:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('month', -4, TIMESTAMP '2009-03-30 1:11:11') = TIMESTAMP "
+                "'2008-11-30 1:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('month', 5, TIMESTAMP '2009-01-31 1:11:11') = TIMESTAMP "
+                "'2009-6-30 1:11:11' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('year', 1, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
+                "'2009-02-28 1:11:11' from test limit 1;")));
+
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT m = TIMESTAMP '2014-12-13 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', 1, m) = TIMESTAMP "
+                                    "'2014-12-14 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -1, m) = TIMESTAMP "
+                                    "'2014-12-12 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', 1, m) = TIMESTAMP "
+                                    "'2014-12-14 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -1, m) = TIMESTAMP "
+                                    "'2014-12-12 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT o = DATE '1999-09-09' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', 1, o) = TIMESTAMP "
+                                    "'1999-09-10 0:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -3, o) = TIMESTAMP "
+                                    "'1999-09-06 0:00:00' from test limit 1;")));
+  /* DATE ADD subseconds to default timestamp(0) */
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('millisecond', 1000, m) = TIMESTAMP "
+                                    "'2014-12-13 22:23:16' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('microsecond', 1000000, m) = TIMESTAMP "
+                              "'2014-12-13 22:23:16' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('nanosecond', 1000000000, m) = TIMESTAMP "
+                              "'2014-12-13 22:23:16' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('millisecond', 5123, m) = TIMESTAMP "
+                                    "'2014-12-13 22:23:20' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT DATEADD('microsecond', 86400000000, m) = TIMESTAMP "
+                              "'2014-12-14 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEADD('nanosecond', 86400000000123, m) = TIMESTAMP "
+                "'2014-12-14 22:23:15' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('weekday', -3, o) = TIMESTAMP "
+                                    "'1999-09-06 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('decade', 3, o) = TIMESTAMP "
+                                    "'2029-09-09 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('week', 1, o) = TIMESTAMP "
+                                    "'1999-09-16 00:00:00' from test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, TimestampAdd) {
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT TIMESTAMPADD(YEAR, 1, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
+          "'2009-02-28 1:11:11' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT TIMESTAMPADD(YEAR, -8, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
+          "'2000-02-29 1:11:11' from test limit 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg(
+          "SELECT TIMESTAMPADD(YEAR, -8, TIMESTAMP '2008-02-29 1:11:11') = TIMESTAMP "
+          "'2000-02-29 1:11:11' from test limit 1;")));
+
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(DAY, 1, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-03-03 1:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(DAY, -1, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-03-01 1:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(DAY, 15, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-03-17 1:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(DAY, -15, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-02-15 1:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(HOUR, 1, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-03-02 2:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(HOUR, -1, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-03-02 0:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(HOUR, 15, TIMESTAMP '2009-03-02 "
+                                    "1:23:45') = TIMESTAMP '2009-03-02 16:23:45' "
+                                    "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(HOUR, -15, TIMESTAMP '2009-03-02 "
+                              "1:23:45') = TIMESTAMP '2009-03-01 10:23:45' "
+                              "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(MINUTE, 15, TIMESTAMP '2009-03-02 "
+                              "1:23:45') = TIMESTAMP '2009-03-02 1:38:45' "
+                              "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(MINUTE, -15, TIMESTAMP '2009-03-02 "
+                              "1:23:45') = TIMESTAMP '2009-03-02 1:08:45' "
+                              "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(SECOND, 15, TIMESTAMP '2009-03-02 "
+                              "1:23:45') = TIMESTAMP '2009-03-02 1:24:00' "
+                              "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      1,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPADD(SECOND, -15, TIMESTAMP '2009-03-02 "
+                              "1:23:45') = TIMESTAMP '2009-03-02 1:23:30' "
+                              "FROM TEST LIMIT 1;")));
+
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(DAY, 1, m) = TIMESTAMP '2014-12-14 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(DAY, -1, m) = TIMESTAMP '2014-12-12 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(DAY, 15, m) = TIMESTAMP '2014-12-28 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(DAY, -15, m) = TIMESTAMP '2014-11-28 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(HOUR, 1, m) = TIMESTAMP '2014-12-13 23:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(HOUR, -1, m) = TIMESTAMP '2014-12-13 21:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(HOUR, 15, m) = TIMESTAMP '2014-12-14 13:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(HOUR, -15, m) = TIMESTAMP '2014-12-13 7:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(MINUTE, 15, m) = TIMESTAMP '2014-12-13 22:38:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(MINUTE, -15, m) = TIMESTAMP '2014-12-13 22:08:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(SECOND, 15, m) = TIMESTAMP '2014-12-13 22:23:30' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(SECOND, -15, m) = TIMESTAMP '2014-12-13 22:23:00' "
+                "FROM TEST LIMIT 1;")));
+
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(MONTH, 1, m) = TIMESTAMP '2015-01-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(MONTH, -1, m) = TIMESTAMP '2014-11-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(MONTH, 5, m) = TIMESTAMP '2015-05-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(DAY, -5, m) = TIMESTAMP '2014-12-08 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(YEAR, 1, m) = TIMESTAMP '2015-12-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(YEAR, -1, m) = TIMESTAMP '2013-12-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(YEAR, 5, m) = TIMESTAMP '2019-12-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPADD(YEAR, -5, m) = TIMESTAMP '2009-12-13 22:23:15' "
+                "FROM TEST LIMIT 1;")));
+  ASSERT_EQ(0,
+            v<int64_t>(runSimpleAgg("select count(*) from test where TIMESTAMPADD(YEAR, "
+                                    "15, CAST(o AS TIMESTAMP)) > m;")));
+  ASSERT_EQ(15,
+            v<int64_t>(runSimpleAgg("select count(*) from test where TIMESTAMPADD(YEAR, "
+                                    "16, CAST(o AS TIMESTAMP)) > m;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, TimestampDiff) {
+  ASSERT_EQ(128885,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPDIFF(minute, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
+                "'2003-05-01 12:05:55') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(2148,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPDIFF(hour, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
+                "'2003-05-01 12:05:55') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(89,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPDIFF(day, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
+                "'2003-05-01 12:05:55') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(3,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPDIFF(month, TIMESTAMP '2003-02-01 0:00:00', TIMESTAMP "
+                "'2003-05-01 12:05:55') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(-3,
+            v<int64_t>(runSimpleAgg(
+                "SELECT TIMESTAMPDIFF(month, TIMESTAMP '2003-05-01 12:05:55', TIMESTAMP "
+                "'2003-02-01 0:00:00') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      5,
+      v<int64_t>(runSimpleAgg(
+          "SELECT TIMESTAMPDIFF(month, m, m + INTERVAL '5' MONTH) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      -5,
+      v<int64_t>(runSimpleAgg(
+          "SELECT TIMESTAMPDIFF(month, m, m - INTERVAL '5' MONTH) FROM TEST LIMIT 1;")));
+  ASSERT_EQ(15,
+            v<int64_t>(runSimpleAgg("select count(*) from test where TIMESTAMPDIFF(YEAR, "
+                                    "m, CAST(o AS TIMESTAMP)) < 0;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(year, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(14,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(month, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(426,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(day, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(60,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(week, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      60,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(week_sunday, DATE '2018-01-02', "
+                              "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(
+      60,
+      v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(week_saturday, DATE "
+                              "'2018-01-02', DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(613440,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(minute, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(10224,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(hour, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+  ASSERT_EQ(36806400,
+            v<int64_t>(runSimpleAgg("SELECT TIMESTAMPDIFF(second, DATE '2018-01-02', "
+                                    "DATE '2019-03-04') FROM TEST LIMIT 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_SecondsPrecision) {
+  DateTruncTestParams date_trunc_queries{/*TIMESTAMP(0) */
+                                         {"year, m", 1388534400LL, 20},
+                                         {"month, m", 1417392000LL, 20},
+                                         {"day, m", 1418428800LL, 15},
+                                         {"hour, m", 1418508000LL, 15},
+                                         {"minute, m", 1418509380LL, 15},
+                                         {"second, m", 1418509395LL, 15},
+                                         {"millennium, m", 978307200LL, 20},
+                                         {"century, m", 978307200LL, 20},
+                                         {"decade, m", 1262304000LL, 20},
+                                         {"week, m", 1417996800LL, 20},
+                                         {"week_sunday, m", 1417910400LL, 15},
+                                         {"week_saturday, m", 1418428800LL, 20},
+                                         {"nanosecond, m", 1418509395LL, 15},
+                                         {"microsecond, m", 1418509395LL, 15},
+                                         {"millisecond, m", 1418509395LL, 15}};
+
+  check_date_trunc_queries(date_trunc_queries, device_type_);
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_MillisecondsPrecision) {
+  DateTruncTestParams date_trunc_queries{/* TIMESTAMP(3) */
+                                         {"year, m_3", 1388534400000LL, 20},
+                                         {"month, m_3", 1417392000000LL, 20},
+                                         {"day, m_3", 1418428800000LL, 15},
+                                         {"hour, m_3", 1418508000000LL, 15},
+                                         {"minute, m_3", 1418509380000LL, 15},
+                                         {"second, m_3", 1418509395000LL, 15},
+                                         {"millennium, m_3", 978307200000LL, 20},
+                                         {"century, m_3", 978307200000LL, 20},
+                                         {"decade, m_3", 1262304000000LL, 20},
+                                         {"week, m_3", 1417996800000LL, 20},
+                                         {"week_sunday, m_3", 1417910400000LL, 15},
+                                         {"week_saturday, m_3", 1418428800000LL, 20},
+                                         {"nanosecond, m_3", 1418509395323LL, 15},
+                                         {"microsecond, m_3", 1418509395323LL, 15},
+                                         {"millisecond, m_3", 1418509395323LL, 15}};
+
+  check_date_trunc_queries(date_trunc_queries, device_type_);
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_MicrosecondsPrecision) {
+  DateTruncTestParams date_trunc_queries{/* TIMESTAMP(6} */
+                                         {"year, m_6", 915148800000000LL, 10},
+                                         {"month, m_6", 930787200000000LL, 10},
+                                         {"day, m_6", 931651200000000LL, 10},
+                                         {"hour, m_6", 931701600000000LL, 10},
+                                         /* {"minute, m_6", 931701720000000LL, 10}, //
+                                            Exception with sort watchdog */
+                                         {"second, m_6", 931701773000000LL, 10},
+                                         {"millennium, m_6", -30578688000000000LL, 10},
+                                         {"century, m_6", -2177452800000000LL, 10},
+                                         {"decade, m_6", 631152000000000LL, 10},
+                                         {"week, m_6", 931132800000000LL, 10},
+                                         {"week_sunday, m_6", 931651200000000LL, 10},
+                                         {"week_saturday, m_6", 931564800000000LL, 10},
+                                         {"nanosecond, m_6", 931701773874533LL, 10},
+                                         {"microsecond, m_6", 931701773874533LL, 10},
+                                         {"millisecond, m_6", 931701773874000LL, 10}};
+
+  check_date_trunc_queries(date_trunc_queries, device_type_);
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_NanosecondsPrecision) {
+  DateTruncTestParams date_trunc_queries{
+      /* TIMESTAMP(9} */
+      {"year, m_9", 1136073600000000000LL, 10},
+      {"month, m_9", 1143849600000000000LL, 10},
+      {"day, m_9", 1146009600000000000LL, 10},
+      {"hour, m_9", 1146020400000000000LL, 10},
+      /* {"minute, m_9", 1146023340000000000LL, 10}, // Exception with
+         sort watchdog */
+      {"second, m_9", 1146023344000000000LL, 10},
+      {"millennium, m_9", 978307200000000000LL, 20},
+      {"century, m_9", 978307200000000000LL, 20},
+      {"decade, m_9", 946684800000000000LL, 10},
+      {"week, m_9", 1145836800000000000LL, 10},
+      {"week_sunday, m_9", 1145750400000000000LL, 10},
+      {"week_saturday, m_9", 1145664000000000000LL, 10},
+      {"nanosecond, m_9", 1146023344607435125LL, 10},
+      {"microsecond, m_9", 1146023344607435000LL, 10},
+      {"millisecond, m_9", 1146023344607000000LL, 10}};
+
+  check_date_trunc_queries(date_trunc_queries, device_type_);
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_CastedArgument) {
+  const auto rows = run_multiple_agg(
+      "SELECT DATE_TRUNC(month, CAST(o AS TIMESTAMP(0))) AS key0, str AS key1, "
+      "COUNT(*) AS val FROM test GROUP BY "
+      "key0, key1 ORDER BY val DESC, key1;",
+      device_type_);
+  check_date_trunc_groups(*rows);
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_CaseExpressionArgument) {
+  const auto one_row = run_multiple_agg(
+      "SELECT DATE_TRUNC(year, CASE WHEN str = 'foo' THEN m END) dt FROM test "
+      "GROUP BY "
+      "dt HAVING dt IS NOT NULL;",
+      device_type_);
+  check_one_date_trunc_group(*one_row, 1388534400);
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_RangeProjection) {
+  //  range tests
+  ASSERT_EQ(
+      1417392000,
+      v<int64_t>(runSimpleAgg("SELECT date_trunc(month, m) as key0 FROM "
+                              "test WHERE (m >= TIMESTAMP(3) '1970-01-01 "
+                              "00:00:00.000') GROUP BY key0 ORDER BY key0 LIMIT 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, DateTrunc_GroupBy) {
+  EXPECT_NO_THROW(run_multiple_agg(
+      "SELECT date_trunc(second, ne) AS sec, AVG(x) FROM test GROUP BY sec;",
+      device_type_));
+}
+
+//  Compressed DATE - limits test
+TEST_P(DateAndTimeFunctionsTest, CompressedDateLimit_Cast) {
+  ASSERT_EQ(
+      4708022400LL,
+      v<int64_t>(runSimpleAgg("select CAST('2119-03-12' AS DATE) FROM test limit 1;")));
+  ASSERT_EQ(7998912000LL,
+            v<int64_t>(runSimpleAgg("select CAST(CAST('2223-06-24 23:13:57' AS "
+                                    "TIMESTAMP) AS DATE) FROM test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CompressedDateLimit_DateAdd) {
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('year', 411, o) = TIMESTAMP "
+                                    "'2410-09-09 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('year', -399, o) = TIMESTAMP "
+                                    "'1600-09-09 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('month', 6132, o) = TIMESTAMP "
+                                    "'2510-09-09 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('month', -1100, o) = TIMESTAMP "
+                                    "'1908-01-09 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', 312456, o) = TIMESTAMP "
+                                    "'2855-03-01 00:00:00' from test limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg("SELECT DATEADD('day', -23674, o) = TIMESTAMP "
+                                    "'1934-11-15 00:00:00' from test limit 1 ;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CompressedDateLimit_DateDiff) {
+  ASSERT_EQ(-302,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('year', DATE '2302-04-21', o) from test limit 1;")));
+  ASSERT_EQ(501,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('year', o, DATE '2501-04-21') from test limit 1;")));
+  ASSERT_EQ(-4895,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('month', DATE '2407-09-01', o) from test limit 1;")));
+  ASSERT_EQ(3817,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('month', o, DATE '2317-11-01') from test limit 1;")));
+  ASSERT_EQ(-86972,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('day', DATE '2237-10-23', o) from test limit 1;")));
+  ASSERT_EQ(86972,
+            v<int64_t>(runSimpleAgg(
+                "SELECT DATEDIFF('day', o, DATE '2237-10-23') from test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CompressedDateLimit_DatePart) {
+  ASSERT_EQ(
+      2617,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('year', CAST ('2617-12-23' as DATE)) from test limit 1;")));
+  ASSERT_EQ(
+      12,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('month', CAST ('2617-12-23' as DATE)) from test limit 1;")));
+  ASSERT_EQ(
+      23,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('day', CAST ('2617-12-23' as DATE)) from test limit 1;")));
+  ASSERT_EQ(
+      0,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('hour', CAST ('2617-12-23' as DATE)) from test limit 1;")));
+  ASSERT_EQ(
+      0,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('minute', CAST ('2617-12-23' as DATE)) from test limit 1;")));
+  ASSERT_EQ(
+      0,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('second', CAST ('2617-12-23' as DATE)) from test limit 1;")));
+  ASSERT_EQ(
+      6,
+      v<int64_t>(runSimpleAgg(
+          "SELECT DATEPART('weekday', CAST ('2011-12-31' as DATE)) from test limit 1;")));
+  ASSERT_EQ(365,
+            v<int64_t>(runSimpleAgg("SELECT DATEPART('dayofyear', CAST ('2011-12-31' "
+                                    "as DATE)) from test limit 1;")));
+}
+
+TEST_P(DateAndTimeFunctionsTest, CompressedDate_ColumnarResult) {
+  /* Compressed Date ColumnarResults fetch tests*/
+  ASSERT_EQ(1999,
+            v<int64_t>(runSimpleAgg("select yr from (SELECT EXTRACT(year from o) as "
+                                    "yr, o from test order by x) limit 1;")));
+  ASSERT_EQ(936835200,
+            v<int64_t>(runSimpleAgg("select dy from (SELECT DATE_TRUNC(day, o) as "
+                                    "dy, o from test order by x) limit 1;")));
+  ASSERT_EQ(936921600,
+            v<int64_t>(runSimpleAgg("select dy from (SELECT DATEADD('day', 1, o) as "
+                                    "dy, o from test order by x) limit 1;")));
+  ASSERT_EQ(1,
+            v<int64_t>(runSimpleAgg(
+                "select dy from (SELECT DATEDIFF('day', o, DATE '1999-09-10') as dy, o "
+                "from test order by x) limit 1;")));
+}
+
+INSTANTIATE_TEST_SUITE_P(CpuAndGpuExecutorDevices,
+                         DateAndTimeFunctionsTest,
+                         ::testing::Values(ExecutorDeviceType::CPU,
+                                           ExecutorDeviceType::GPU),
+                         ::testing::PrintToStringParamName());
+
 class ValuesTest : public ::testing::Test {
  protected:
   static void check_result_set(std::shared_ptr<ResultSet> rs,
@@ -28417,6 +28777,18 @@ int create_and_populate_tables(const bool use_temporary_tables,
     LOG(ERROR) << "Unknown error in import_window_function_framing_navigation():"
                << e.what();
   }
+  try {
+    import_switch_to_baseline_test(false);
+  } catch (std::exception const& e) {
+    LOG(ERROR) << "Unknown error in import_switch_to_baseline_test(sharded=false):"
+               << e.what();
+  }
+  try {
+    import_switch_to_baseline_test(true);
+  } catch (std::exception const& e) {
+    LOG(ERROR) << "Unknown error in import_switch_to_baseline_test(sharded=true):"
+               << e.what();
+  }
   {
     std::string insert_query{"INSERT INTO test_in_bitmap VALUES('a');"};
     run_multiple_agg(insert_query, ExecutorDeviceType::CPU);
@@ -28453,7 +28825,7 @@ int create_and_populate_tables(const bool use_temporary_tables,
   for (bool is_multi_frag : {false, true}) {
     const size_t rand_seed = is_multi_frag ? 23 : 42;
     int err = create_and_populate_large_window_func_table(
-        is_multi_frag, g_shard_count, 40000, rand_seed);
+        is_multi_frag, g_shard_count, 4000, rand_seed);
     if (err) {
       return err;
     }
@@ -28730,6 +29102,10 @@ void drop_tables() {
   for (std::string tbl : {"CTX1", "CTX2", "CTX3", "CTX4"}) {
     run_ddl_statement("DROP TABLE IF EXISTS " + tbl + ";");
   }
+  for (std::string tbl :
+       {"tjs1", "tjs2", "tjs3", "tjs4", "tjs1s", "tjs2s", "tjs3s", "tjs4s"}) {
+    run_ddl_statement("DROP TABLE IF EXISTS " + tbl + ";");
+  }
 }
 
 void drop_views() {
diff --git a/Tests/ForeignTableDmlTest.cpp b/Tests/ForeignTableDmlTest.cpp
index 4cc7e5f94d..f885d8e5bd 100644
--- a/Tests/ForeignTableDmlTest.cpp
+++ b/Tests/ForeignTableDmlTest.cpp
@@ -5207,6 +5207,21 @@ TEST_F(SelectQueryTest, ParquetFixedLengthArrayUnsignedIntegerTypes) {
   // clang-format on
 }
 
+TEST_F(SelectQueryTest, ParquetIncorrectArrayMapping) {
+  const auto& query = getCreateForeignTableQuery(
+      "( index INT, b BOOLEAN[], t TINYINT[], s SMALLINT[], i INTEGER, bi BIGINT[], f "
+      "FLOAT[], tm TIME[], tp TIMESTAMP[], d DATE[], txt TEXT[], fixedpoint "
+      "DECIMAL(10,5)[] )",
+      "array_types",
+      "parquet");
+  sql(query);
+  queryAndAssertPartialException(
+      "SELECT COUNT(*) FROM " + default_table_name,
+      "Unsupported mapping detected. Column 'int.list.item' detected to be a parquet "
+      "list but HeavyDB mapped column 'i' is not an array. Parquet column: "
+      "int.list.item, HeavyDB column: i");
+}
+
 TEST_P(DataTypeFragmentSizeAndDataWrapperTest, ArrayTypes) {
   auto [fragment_size, data_wrapper_type, extension] = GetParam();
   if (is_odbc(data_wrapper_type)) {
diff --git a/Tests/GeospatialTest.cpp b/Tests/GeospatialTest.cpp
index 5869a9b42c..dd0d5c61e8 100644
--- a/Tests/GeospatialTest.cpp
+++ b/Tests/GeospatialTest.cpp
@@ -103,7 +103,8 @@ TargetValue run_simple_agg(const std::string& query_str,
                            const ExecutorDeviceType device_type,
                            const bool geo_return_geo_tv = true,
                            const bool allow_loop_joins = true) {
-  auto rows = QR::get()->runSQL(query_str, device_type, allow_loop_joins);
+  auto rows =
+      QR::get()->runSQL(query_str, device_type, g_hoist_literals, allow_loop_joins);
   if (geo_return_geo_tv) {
     rows->setGeoReturnType(ResultSet::GeoReturnType::GeoTargetValue);
   }
@@ -2957,8 +2958,8 @@ TEST_P(GeoSpatialJoinTablesFixture, GeoJoins) {
         "SELECT a.id FROM geospatial_test a INNER JOIN geospatial_inner_join_test "
         "b ON ST_Contains(b.poly, a.p);",
         dt,
-        true,
-        false));
+        true,     // geo_return_geo_tv
+        false));  // allow_loop_joins
 
     SKIP_ON_AGGREGATOR(ASSERT_EQ(
         static_cast<int64_t>(1),
@@ -2966,8 +2967,8 @@ TEST_P(GeoSpatialJoinTablesFixture, GeoJoins) {
             "SELECT a.id FROM geospatial_test a INNER JOIN geospatial_inner_join_test "
             "b ON ST_Contains(b.poly, a.p) WHERE b.id = 2;",
             dt,
-            true,
-            false))));
+            true,       // geo_return_geo_tv
+            false))));  // allow_loop_joins
 
     const auto trivial_loop_join_state = g_trivial_loop_join_threshold;
     g_trivial_loop_join_threshold = 1;
@@ -3505,6 +3506,294 @@ TEST_P(GeoSpatialTestTablesFixture, Cast) {
   }
 }
 
+using TestParamType =
+    std::tuple<ExecutorDeviceType, std::string, std::string, std::string>;
+class GeoFunctionsParamTypeTest : public ::testing::TestWithParam<TestParamType> {
+ public:
+  static void SetUpTestSuite() {
+    std::string create_table_statement;
+    for (const auto& column_name_and_type : column_names_and_types_) {
+      auto column_name = column_name_and_type[0], column_type = column_name_and_type[1];
+      if (create_table_statement.empty()) {
+        create_table_statement =
+            "CREATE TABLE geo_table (" + column_name + " " + column_type;
+      } else {
+        create_table_statement += "," + column_name + " " + column_type;
+      }
+    }
+    create_table_statement += ");";
+    run_ddl_statement(create_table_statement);
+
+    run_multiple_agg(
+        "INSERT INTO geo_table VALUES "
+        "  ('POINT(2 1)', 'MULTIPOINT(2 1)', 'LINESTRING(1 1, 2 1, 3 1)', "
+        "   'MULTILINESTRING((1 1, 2 1, 3 1))', 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))', "
+        "   'MULTIPOLYGON(((0 0, 4 0, 4 4, 0 4, 0 0)))'), "
+        "  ('POINT(0 0)', 'MULTIPOINT(10 10)', 'LINESTRING(50 50, 60 50, 70 50)', "
+        "   'MULTILINESTRING((50 100, 60 100, 70 100))', "
+        "   'POLYGON((50 150, 60 150, 60 200, 50 200, 50 150))', "
+        "   'MULTIPOLYGON(((50 300, 60 300, 60 350, 50 350, 50 300)))');",
+        ExecutorDeviceType::CPU);
+  }
+
+  static void TearDownTestSuite() { run_ddl_statement("DROP TABLE geo_table;"); }
+
+  void SetUp() override {
+    auto device_type = getDeviceType();
+#ifdef HAVE_CUDA
+    bool skip_test =
+        (device_type == ExecutorDeviceType::GPU && !QR::get()->gpusPresent());
+#else
+    bool skip_test = (device_type == ExecutorDeviceType::GPU);
+#endif
+    if (skip_test) {
+      GTEST_SKIP() << "Unsupported device type: " << device_type;
+    }
+  }
+
+  std::string getGeoPredicate() {
+    const auto& [_, function_name, left_operand_type, right_operand_type] = GetParam();
+    auto predicate = function_name + "(" + getColumnName(left_operand_type) + "," +
+                     getColumnName(right_operand_type) + ")";
+    if (function_name == "ST_DISTANCE") {
+      predicate += " < 1.0";
+    }
+    return predicate;
+  }
+
+  int64_t getResultCount() {
+    int64_t count;
+    if (mapContainsParam(true_result_mapping_)) {
+      count = 2;
+    } else if (mapContainsParam(false_result_mapping_)) {
+      count = 0;
+    } else {
+      // Table rows were created in such a way that most queries should return a count
+      // of 1.
+      count = 1;
+    }
+    return count;
+  }
+
+  using ParamMapType =
+      std::map<std::string, std::map<std::string, std::vector<std::string>>>;
+  bool mapContainsParam(const ParamMapType& param_map) {
+    const auto& [_, function_name, left_operand_type, right_operand_type] = GetParam();
+    if (param_map.count(function_name) > 0) {
+      const auto& type_map = shared::get_from_map(param_map, function_name);
+      if (type_map.count(left_operand_type) > 0) {
+        auto right_operand_types = shared::get_from_map(type_map, left_operand_type);
+        return shared::contains(right_operand_types, right_operand_type);
+      }
+    }
+    return false;
+  }
+
+  std::string getColumnName(const std::string& column_type) {
+    for (const auto& column_name_and_type : column_names_and_types_) {
+      if (column_name_and_type[1] == column_type) {
+        return column_name_and_type[0];
+      }
+    }
+    UNREACHABLE() << "Could not find column name for type: " << column_type;
+    return {};
+  }
+
+  ExecutorDeviceType getDeviceType() {
+    return std::get<0>(GetParam());
+  }
+
+  static std::vector<std::string> getGeoColumnTypes() {
+    std::vector<std::string> column_types;
+    for (const auto& column_name_and_type : column_names_and_types_) {
+      column_types.emplace_back(column_name_and_type[1]);
+    }
+    return column_types;
+  }
+
+  static std::string printTestParams(
+      const ::testing::TestParamInfo<TestParamType>& info) {
+    const auto& param = info.param;
+    std::stringstream ss;
+    ss << std::get<0>(param) << "_" << std::get<1>(param) << "_" << std::get<2>(param)
+       << "_" << std::get<3>(param);
+    return ss.str();
+  }
+
+  static inline const std::vector<std::vector<std::string>> column_names_and_types_{
+      {"pt1", "POINT"},
+      {"pt2", "MULTIPOINT"},
+      {"ls1", "LINESTRING"},
+      {"ls2", "MULTILINESTRING"},
+      {"py1", "POLYGON"},
+      {"py2", "MULTIPOLYGON"}};
+
+  // Use cases/geo type permutations that are currently supported.
+  static inline const ParamMapType supported_mapping_{
+      {"ST_CONTAINS",
+       {{"POINT", {"POINT", "LINESTRING", "POLYGON"}},
+        {"LINESTRING", {"POINT", "POLYGON"}},
+        {"POLYGON", {"POINT", "LINESTRING", "POLYGON"}},
+        {"MULTIPOLYGON", {"POINT", "LINESTRING"}}}},
+      {"ST_WITHIN",
+       {{"POINT", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"LINESTRING", {"POINT", "POLYGON", "MULTIPOLYGON"}},
+        {"POLYGON", {"POINT", "LINESTRING", "POLYGON"}}}},
+      {"ST_DISTANCE",
+       {{"POINT",
+         {"POINT",
+          "MULTIPOINT",
+          "LINESTRING",
+          "MULTILINESTRING",
+          "POLYGON",
+          "MULTIPOLYGON"}},
+        {"MULTIPOINT", {"POINT", "MULTIPOINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"LINESTRING", {"POINT", "MULTIPOINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"MULTILINESTRING", {"POINT"}},
+        {"POLYGON", {"POINT", "MULTIPOINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"MULTIPOLYGON",
+         {"POINT", "MULTIPOINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}}}},
+      {"ST_DISJOINT",
+       {{"POINT", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"LINESTRING", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"POLYGON", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"MULTIPOLYGON", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}}}},
+      {"ST_INTERSECTS",
+       {{"POINT", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"LINESTRING", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"POLYGON", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}},
+        {"MULTIPOLYGON", {"POINT", "LINESTRING", "POLYGON", "MULTIPOLYGON"}}}}};
+
+  // Use cases that always return true.
+  static inline const ParamMapType true_result_mapping_{
+      {"ST_CONTAINS", {{"POINT", {"POINT"}}}},  // Always true for the same point.
+      {"ST_WITHIN", {{"POINT", {"POINT"}}}},    // Always true for the same point.
+      {"ST_DISTANCE",  // Same values will always have a distance of 0.
+       {{"POINT", {"POINT"}},
+        {"MULTIPOINT", {"MULTIPOINT"}},
+        {"LINESTRING", {"LINESTRING"}},
+        {"MULTILINESTRING", {"MULTILINESTRING"}},
+        {"POLYGON", {"POLYGON"}},
+        {"MULTIPOLYGON", {"MULTIPOLYGON"}}}},
+      {"ST_INTERSECTS",  // Values always intersect with themselves.
+       {{"POINT", {"POINT"}},
+        {"LINESTRING", {"LINESTRING"}},
+        {"POLYGON", {"POLYGON"}},
+        {"MULTIPOLYGON", {"MULTIPOLYGON"}}}}};
+
+  // Use cases that always return false.
+  static inline const ParamMapType false_result_mapping_{
+      {"ST_CONTAINS",  // Smaller geo types cannot contain larger geo types.
+       {{"POINT", {"LINESTRING", "POLYGON"}},
+        {"LINESTRING", {"POLYGON"}},
+        {"POLYGON", {"POLYGON"}}}},  // TODO: This case should return true
+      {"ST_WITHIN",                  // Smaller geo types cannot contain larger geo types.
+       {{"LINESTRING", {"POINT"}},
+        {"POLYGON", {"POINT", "LINESTRING", "POLYGON"}}}},  // TODO: POLYGON/POLYGON case
+                                                            // should return true
+      {"ST_DISJOINT",  // Values cannot be disjoint with themselves.
+       {{"POINT", {"POINT"}},
+        {"LINESTRING", {"LINESTRING"}},
+        {"POLYGON", {"POLYGON"}},
+        {"MULTIPOLYGON", {"MULTIPOLYGON"}}}}};
+};
+
+class SupportedGeoFunctionsParamTypeTest : public GeoFunctionsParamTypeTest {
+ public:
+  static std::vector<TestParamType> getTestParams() {
+    std::vector<TestParamType> result;
+    for (auto device_type : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+      for (const auto& [function_name, type_map] : supported_mapping_) {
+        for (const auto& [type, values] : type_map) {
+          for (const auto& value : values) {
+            result.emplace_back(device_type, function_name, type, value);
+          }
+        }
+      }
+    }
+    return result;
+  }
+};
+
+TEST_P(SupportedGeoFunctionsParamTypeTest, AllSupportedTypes) {
+  auto query = "SELECT count(*) FROM geo_table WHERE " + getGeoPredicate() + ";";
+  ASSERT_EQ(getResultCount(), v<int64_t>(run_simple_agg(query, getDeviceType())));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    OperatorsAndColumnTypes,
+    SupportedGeoFunctionsParamTypeTest,
+    ::testing::ValuesIn(SupportedGeoFunctionsParamTypeTest::getTestParams()),
+    GeoFunctionsParamTypeTest::printTestParams);
+
+class UnsupportedGeoFunctionsParamTypeTest : public GeoFunctionsParamTypeTest {
+ public:
+  static std::vector<TestParamType> getTestParams() {
+    auto column_types = getGeoColumnTypes();
+    std::vector<TestParamType> result;
+    for (auto device_type : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+      for (const auto& [function_name, type_map] : supported_mapping_) {
+        for (const auto& left_operand_type : column_types) {
+          if (type_map.find(left_operand_type) == type_map.end()) {
+            // Include all geo column types for the right operand in the test, if the geo
+            // function does not support this column type.
+            for (const auto& right_operand_type : column_types) {
+              result.emplace_back(
+                  device_type, function_name, left_operand_type, right_operand_type);
+            }
+          } else {
+            const auto& right_operand_types =
+                shared::get_from_map(type_map, left_operand_type);
+            for (const auto& right_operand_type : column_types) {
+              // Include geo column types that are missing from the supported types list.
+              if (!shared::contains(right_operand_types, right_operand_type)) {
+                result.emplace_back(
+                    device_type, function_name, left_operand_type, right_operand_type);
+              }
+            }
+          }
+        }
+      }
+    }
+    return result;
+  }
+};
+
+TEST_P(UnsupportedGeoFunctionsParamTypeTest, AllUnsupportedTypes) {
+  try {
+    auto query = "SELECT count(*) FROM geo_table WHERE " + getGeoPredicate() + ";";
+    run_simple_agg(query, getDeviceType());
+    FAIL() << "An exception should have been thrown for this test case. Query: " << query;
+  } catch (const std::runtime_error& e) {
+    const auto& [_, function_name, left_operand_type, right_operand_type] = GetParam();
+    // In certain cases, ST_WITHIN is converted to ST_CONTAINS, and ST_DISJOINT is
+    // converted to ST_INTERSECT. See RelAlgTranslator::translateBinaryGeoFunction.
+    std::string executed_function_name;
+    if (function_name == "ST_WITHIN") {
+      executed_function_name = "ST_CONTAINS";
+    } else if (function_name == "ST_DISJOINT") {
+      executed_function_name = "ST_INTERSECTS";
+    } else {
+      executed_function_name = function_name;
+    }
+    auto error_regex =
+        "(" + (".*Function\\s" + executed_function_name + ".*not\\ssupported.*") + "|" +
+        (".*" + function_name +
+         "\\scurrently\\sdoesn't\\ssupport\\sthis\\sargument\\scombination.*") +
+        ")";
+    std::string actual_error{e.what()};
+    ASSERT_TRUE(
+        std::regex_match(actual_error, std::regex{error_regex, std::regex::icase}))
+        << "Error: " << actual_error << ", Regex: " << error_regex;
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    OperatorsAndColumnTypes,
+    UnsupportedGeoFunctionsParamTypeTest,
+    ::testing::ValuesIn(UnsupportedGeoFunctionsParamTypeTest::getTestParams()),
+    GeoFunctionsParamTypeTest::printTestParams);
+
 int main(int argc, char** argv) {
   g_is_test_env = true;
 
diff --git a/Tests/GpuSharedMemoryTest.cpp b/Tests/GpuSharedMemoryTest.cpp
index 08e7c692a6..f165ca401f 100644
--- a/Tests/GpuSharedMemoryTest.cpp
+++ b/Tests/GpuSharedMemoryTest.cpp
@@ -326,8 +326,8 @@ void perform_test_and_verify_results(TestInputData input) {
       "v32:32:32-v64:64:64-v128:128:128-n16:32:64");
   module->setTargetTriple("nvptx64-nvidia-cuda");
   auto cuda_mgr = std::make_unique<CudaMgr_Namespace::CudaMgr>(1);
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), executor->getExecutorId());
   auto query_mem_desc = perfect_hash_one_col_desc(
       input.target_infos, input.suggested_agg_widths, input.min_entry, input.max_entry);
   if (input.keyless_hash) {
diff --git a/Tests/ProfileTest.cpp b/Tests/ProfileTest.cpp
index 60db577b22..51bcebdab6 100644
--- a/Tests/ProfileTest.cpp
+++ b/Tests/ProfileTest.cpp
@@ -1309,8 +1309,8 @@ TEST(Reduction, Baseline) {
 #else
   const bool has_multi_gpus = false;
 #endif  // HAVE_CUDA
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(g_arena_block_size, /*num_worker_threads=*/1);
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      g_arena_block_size, 0, /*num_worker_threads=*/1);
   std::vector<std::unique_ptr<ResultSet>> results;
   for (size_t i = 0; i < result_count; ++i) {
     auto rs = std::make_unique<ResultSet>(
@@ -1576,7 +1576,7 @@ TEST(Reduction, PerfectHash) {
   const bool has_multi_gpus = false;
 #endif  // HAVE_CUDA
   const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(g_arena_block_size, /*num_threads=*/1);
+      std::make_shared<RowSetMemoryOwner>(g_arena_block_size, 0, /*num_threads=*/1);
   std::vector<std::unique_ptr<ResultSet>> results;
   for (size_t i = 0; i < result_count; ++i) {
     auto rs = std::make_unique<ResultSet>(
diff --git a/Tests/ResultSetBaselineRadixSortTest.cpp b/Tests/ResultSetBaselineRadixSortTest.cpp
index 8fd0ae8b81..6afe92681f 100644
--- a/Tests/ResultSetBaselineRadixSortTest.cpp
+++ b/Tests/ResultSetBaselineRadixSortTest.cpp
@@ -222,7 +222,7 @@ void SortBaselineIntegersTestImpl(const bool desc) {
   const auto target_infos = get_sort_int_target_infos();
   const auto query_mem_desc = baseline_sort_desc(target_infos, 400, sizeof(K));
   const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(), 0);
   const int64_t upper_bound = 200;
   const int64_t lower_bound = 1;
   std::unique_ptr<ResultSet> rs(new ResultSet(
@@ -260,7 +260,7 @@ TEST(SortBaseline, Floats) {
       const auto target_infos = get_sort_fp_target_infos();
       const auto query_mem_desc = baseline_sort_desc(target_infos, 400, 8);
       const auto row_set_mem_owner =
-          std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+          std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(), 0);
       const int64_t upper_bound = 200;
       std::unique_ptr<ResultSet> rs(new ResultSet(target_infos,
                                                   ExecutorDeviceType::CPU,
@@ -289,7 +289,7 @@ TEST(SortBaseline, FloatsNotNull) {
       const auto target_infos = get_sort_notnull_fp_target_infos();
       const auto query_mem_desc = baseline_sort_desc(target_infos, 400, 8);
       const auto row_set_mem_owner =
-          std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+          std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(), 0);
       const int64_t upper_bound = 200;
       std::unique_ptr<ResultSet> rs(new ResultSet(target_infos,
                                                   ExecutorDeviceType::CPU,
diff --git a/Tests/ResultSetTest.cpp b/Tests/ResultSetTest.cpp
index 7cda9d6938..01c137719e 100644
--- a/Tests/ResultSetTest.cpp
+++ b/Tests/ResultSetTest.cpp
@@ -65,7 +65,8 @@ TEST(Construct, Allocate) {
   ResultSet result_set(target_infos,
                        ExecutorDeviceType::CPU,
                        query_mem_desc,
-                       std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize()),
+                       std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize(),
+                                                           Executor::UNITARY_EXECUTOR_ID),
                        0,
                        0);
   result_set.allocateStorage();
@@ -880,8 +881,8 @@ std::shared_ptr<StringDictionary> g_sd =
 void test_iterate(const std::vector<TargetInfo>& target_infos,
                   const QueryMemoryDescriptor& query_mem_desc) {
   SQLTypeInfo double_ti(kDOUBLE, false);
-  auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   StringDictionaryProxy* sdp =
       row_set_mem_owner->addStringDict(g_sd, {0, 1}, g_sd->storageEntryCount());
   ResultSet result_set(
@@ -998,8 +999,8 @@ void run_reduction(const std::vector<TargetInfo>& target_infos,
                    const int step) {
   const ResultSetStorage* storage1{nullptr};
   const ResultSetStorage* storage2{nullptr};
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   row_set_mem_owner->addStringDict(g_sd, {1, 1}, g_sd->storageEntryCount());
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
@@ -1025,8 +1026,8 @@ void test_reduce(const std::vector<TargetInfo>& target_infos,
                  const bool sort) {
   const ResultSetStorage* storage1{nullptr};
   const ResultSetStorage* storage2{nullptr};
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   row_set_mem_owner->addStringDict(g_sd, {1, 1}, g_sd->storageEntryCount());
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
@@ -1118,8 +1119,8 @@ void test_reduce_random_groups(const std::vector<TargetInfo>& target_infos,
   const ResultSetStorage* storage2{nullptr};
   std::unique_ptr<ResultSet> rs1;
   std::unique_ptr<ResultSet> rs2;
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   switch (query_mem_desc.getQueryDescriptionType()) {
     case QueryDescriptionType::GroupByPerfectHash: {
       rs1.reset(new ResultSet(target_infos,
@@ -1864,8 +1865,8 @@ TEST(MoreReduce, MissingValues) {
   target_infos.push_back(TargetInfo{true, kCOUNT, bigint_ti, null_ti, true, false});
   auto query_mem_desc = perfect_hash_one_col_desc(target_infos, 8, 7, 9);
   query_mem_desc.setHasKeylessHash(false);
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
   const auto storage1 = rs1->allocateStorage();
@@ -1928,8 +1929,8 @@ TEST(MoreReduce, MissingValuesKeyless) {
   target_infos.push_back(TargetInfo{true, kCOUNT, bigint_ti, null_ti, true, false});
   auto query_mem_desc = perfect_hash_one_col_desc(target_infos, 8, 7, 9);
   query_mem_desc.setHasKeylessHash(true);
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
   const auto storage1 = rs1->allocateStorage();
@@ -1988,8 +1989,8 @@ TEST(MoreReduce, OffsetRewrite) {
   target_infos.push_back(TargetInfo{true, kSAMPLE, real_str_ti, null_ti, true, false});
   auto query_mem_desc = perfect_hash_one_col_desc(target_infos, 8, 7, 9);
   query_mem_desc.setHasKeylessHash(false);
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
   const auto storage1 = rs1->allocateStorage();
@@ -2083,8 +2084,8 @@ TEST(MoreReduce, OffsetRewriteGeo) {
 
   auto query_mem_desc = perfect_hash_one_col_desc(target_infos, 8, 7, 9);
   query_mem_desc.setHasKeylessHash(false);
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
   const auto storage1 = rs1->allocateStorage();
@@ -2186,8 +2187,8 @@ TEST(MoreReduce, OffsetRewriteGeoKeyless) {
   auto query_mem_desc = perfect_hash_one_col_desc(target_infos, 8, 7, 9);
   query_mem_desc.setHasKeylessHash(true);
   query_mem_desc.setTargetIdxForKey(0);
-  const auto row_set_mem_owner =
-      std::make_shared<RowSetMemoryOwner>(Executor::getArenaBlockSize());
+  const auto row_set_mem_owner = std::make_shared<RowSetMemoryOwner>(
+      Executor::getArenaBlockSize(), Executor::UNITARY_EXECUTOR_ID);
   const auto rs1 = std::make_unique<ResultSet>(
       target_infos, ExecutorDeviceType::CPU, query_mem_desc, row_set_mem_owner, 0, 0);
   const auto storage1 = rs1->allocateStorage();
diff --git a/Tests/SQLHintTest.cpp b/Tests/SQLHintTest.cpp
index 6c8494fad3..5839b4c293 100644
--- a/Tests/SQLHintTest.cpp
+++ b/Tests/SQLHintTest.cpp
@@ -64,6 +64,19 @@ std::shared_ptr<ResultSet> run_query(const std::string& query_str,
   return QR::get()->runSQL(query_str, device_type, true, true);
 }
 
+std::optional<RegisteredQueryHint> get_hint_registered(std::string const& query,
+                                                       QueryHint query_hint) {
+  auto const rel_alg_dag = QR::get()->getRelAlgDag(query);
+  for (const auto& kv : rel_alg_dag.get()->getQueryHints()) {
+    for (auto& kv2 : kv.second) {
+      if (kv2.second.isHintRegistered(query_hint)) {
+        return kv2.second;
+      }
+    }
+  }
+  return std::nullopt;
+}
+
 bool is_hint_registered(
     std::unordered_map<size_t, std::unordered_map<unsigned, RegisteredQueryHint>>& hints,
     QueryHint expected_hint) {
@@ -82,6 +95,17 @@ bool is_hint_globally_registered(const RegisteredQueryHint& hints,
   return hints.isHintRegistered(expected_hint);
 }
 
+void check_hint_registered(std::string const& query,
+                           QueryHint query_hint,
+                           bool is_global) {
+  auto const rel_alg_dag = QR::get()->getRelAlgDag(query);
+  if (is_global) {
+    EXPECT_TRUE(is_hint_globally_registered(rel_alg_dag->getGlobalHints(), query_hint));
+  } else {
+    EXPECT_TRUE(is_hint_registered(rel_alg_dag->getQueryHints(), query_hint));
+  }
+}
+
 struct QueryHintInfo {
   QueryHint hint;
   bool is_global;
@@ -1254,16 +1278,6 @@ TEST(QueryHint, Subquery) {
 TEST(QueryHint, HashJoinSpec) {
   // this join originally a target for perfect join hash table w/ OneToOne hash table
   // layout
-  auto check_registered_hint = [](std::string const& query,
-                                  QueryHint query_hint,
-                                  bool is_global) {
-    auto const rel_alg_dag = QR::get()->getRelAlgDag(query);
-    if (is_global) {
-      EXPECT_TRUE(is_hint_globally_registered(rel_alg_dag->getGlobalHints(), query_hint));
-    } else {
-      EXPECT_TRUE(is_hint_registered(rel_alg_dag->getQueryHints(), query_hint));
-    }
-  };
   std::string q1 =
       "select /*+ force_baseline_hash_join */ count(1) from JOIN_HINT_TEST R, "
       "JOIN_HINT_TEST S where R.v = S.v;";
@@ -1276,10 +1290,10 @@ TEST(QueryHint, HashJoinSpec) {
   std::string q2_g =
       "select /*+ g_force_one_to_many_hash_join */ count(1) from JOIN_HINT_TEST R, "
       "JOIN_HINT_TEST S where R.v = S.v;";
-  check_registered_hint(q1, QueryHint::kforceBaselineHashJoin, false);
-  check_registered_hint(q1_g, QueryHint::kforceBaselineHashJoin, true);
-  check_registered_hint(q2, QueryHint::kforceOneToManyHashJoin, false);
-  check_registered_hint(q2_g, QueryHint::kforceOneToManyHashJoin, true);
+  check_hint_registered(q1, QueryHint::kforceBaselineHashJoin, false);
+  check_hint_registered(q1_g, QueryHint::kforceBaselineHashJoin, true);
+  check_hint_registered(q2, QueryHint::kforceOneToManyHashJoin, false);
+  check_hint_registered(q2_g, QueryHint::kforceOneToManyHashJoin, true);
 
   std::set<QueryPlanHash> visited;
   QR::get()->runSQL(q1, ExecutorDeviceType::CPU);
@@ -1299,6 +1313,60 @@ TEST(QueryHint, HashJoinSpec) {
               HashType::OneToMany);
 }
 
+TEST(QueryHint, ProjectionScanLimit) {
+  std::string watchdog_q1 =
+      "SELECT /*+ watchdog, watchdog_max_projected_rows_per_device(1) */ v FROM "
+      "JOIN_HINT_TEST";
+  std::string watchdog_q1_g =
+      "SELECT /*+ g_watchdog, g_watchdog_max_projected_rows_per_device(2) */ v FROM "
+      "JOIN_HINT_TEST";
+  std::string projection_q1 =
+      "SELECT /*+ preflight_count_query_threshold(1) */ v FROM JOIN_HINT_TEST";
+  std::string projection_q1_g =
+      "SELECT /*+ g_preflight_count_query_threshold(2) */ v FROM JOIN_HINT_TEST";
+
+  check_hint_registered(
+      watchdog_q1, QueryHint::kWatchdogMaxProjectedRowsPerDevice, false);
+  auto watchdog_q1_hint =
+      get_hint_registered(watchdog_q1, QueryHint::kWatchdogMaxProjectedRowsPerDevice);
+  EXPECT_TRUE(watchdog_q1_hint.has_value());
+  EXPECT_EQ(1UL, watchdog_q1_hint->watchdog_max_projected_rows_per_device);
+  EXPECT_ANY_THROW(run_query(watchdog_q1, ExecutorDeviceType::CPU));
+
+  check_hint_registered(
+      watchdog_q1_g, QueryHint::kWatchdogMaxProjectedRowsPerDevice, true);
+  auto watchdog_q1_g_hint =
+      get_hint_registered(watchdog_q1_g, QueryHint::kWatchdogMaxProjectedRowsPerDevice);
+  EXPECT_TRUE(watchdog_q1_g_hint.has_value());
+  EXPECT_EQ(2UL, watchdog_q1_g_hint->watchdog_max_projected_rows_per_device);
+
+  check_hint_registered(projection_q1, QueryHint::kPreflightCountQueryThreshold, false);
+  auto projection_q1_hint =
+      get_hint_registered(projection_q1, QueryHint::kPreflightCountQueryThreshold);
+  EXPECT_TRUE(projection_q1_hint.has_value());
+  EXPECT_EQ(1UL, projection_q1_hint->preflight_count_query_threshold);
+
+  check_hint_registered(projection_q1_g, QueryHint::kPreflightCountQueryThreshold, true);
+  auto projection_q1_g_hint =
+      get_hint_registered(projection_q1_g, QueryHint::kPreflightCountQueryThreshold);
+  EXPECT_TRUE(projection_q1_g_hint.has_value());
+  EXPECT_EQ(2UL, projection_q1_g_hint->preflight_count_query_threshold);
+
+  std::string watchdog_q2 =
+      "SELECT /*+ watchdog, watchdog_max_projected_rows_per_device(-1) */ v FROM "
+      "JOIN_HINT_TEST LIMIT "
+      "2";
+  auto watchdog_q2_hint =
+      get_hint_registered(watchdog_q2, QueryHint::kWatchdogMaxProjectedRowsPerDevice);
+  EXPECT_FALSE(watchdog_q2_hint.has_value());
+
+  std::string projection_q2 =
+      "SELECT /*+ preflight_count_query_threshold(-1) */ v FROM JOIN_HINT_TEST LIMIT 2";
+  auto projection_q2_hint =
+      get_hint_registered(projection_q2, QueryHint::kPreflightCountQueryThreshold);
+  EXPECT_FALSE(projection_q2_hint.has_value());
+}
+
 int main(int argc, char** argv) {
   TestHelpers::init_logger_stderr_only(argc, argv);
   testing::InitGoogleTest(&argc, argv);
diff --git a/Tests/ShowCommandsDdlTest.cpp b/Tests/ShowCommandsDdlTest.cpp
index 538c779126..48103e012a 100644
--- a/Tests/ShowCommandsDdlTest.cpp
+++ b/Tests/ShowCommandsDdlTest.cpp
@@ -49,6 +49,7 @@ extern size_t g_logs_system_tables_max_files_count;
 std::string test_binary_file_path;
 std::string test_source_path;
 bool g_run_odbc{false};
+bool g_custom_odbc_inst{false};  // Are tests being run with a custom odbcinst.ini file?
 
 namespace bf = boost::filesystem;
 namespace po = boost::program_options;
@@ -4545,7 +4546,7 @@ TEST_F(SystemTablesTest, MemoryDetailsSystemTableCpu) {
     sqlAndCompareResult("SELECT * FROM memory_details WHERE device_type = 'CPU' ORDER BY page_count;",
                         {{"Server", db_id, shared::kDefaultDbName, table_id, "test_table_1",
                           i(1), "i", array({db_id, table_id, i(1), i(0)}),
-                          i(0), "CPU", "USED", i(1), getCpuPageSize(), i(0), i(0), i(1)},
+                          i(0), "CPU", "USED", i(1), getCpuPageSize(), i(0), i(0), i(0)},
                           {"Server", Null, Null, Null, Null, Null, Null, Null,
                           i(0), "CPU", "FREE", getAllocatedCpuPageCount() - 1,
                           getCpuPageSize(), i(0), i(1), i(0)}});
@@ -5610,8 +5611,11 @@ int main(int argc, char** argv) {
 
   po::options_description desc("Options");
   desc.add_options()("run-odbc-tests", "Run ODBC related tests.");
+  desc.add_options()("run-altered-odbcinst-tests",
+                     "Run ODBC tests that depend on a custom odbcinst.ini file.");
   po::variables_map vm = DBHandlerTestFixture::initTestArgs(argc, argv, desc);
   g_run_odbc = (vm.count("run-odbc-tests"));
+  g_custom_odbc_inst = (vm.count("run-altered-odbcinst-tests"));
 
   int err{0};
   try {
diff --git a/Tests/StringDictionaryTest.cpp b/Tests/StringDictionaryTest.cpp
index 843e9e8ca5..0635d3c1d5 100644
--- a/Tests/StringDictionaryTest.cpp
+++ b/Tests/StringDictionaryTest.cpp
@@ -39,6 +39,7 @@
 #endif
 
 extern bool g_cache_string_hash;
+int g_op_count{250000};
 
 class StringDictionaryTest : public TestHelpers::TbbPrivateServerKiller {};
 class StringDictionaryProxyTest : public TestHelpers::TbbPrivateServerKiller {};
@@ -97,8 +98,6 @@ TEST_F(StringDictionaryTest, RecoverZero) {
   ASSERT_EQ(static_cast<size_t>(0), num_strings);
 }
 
-const int g_op_count{250000};
-
 TEST_F(StringDictionaryTest, ManyAddsAndGets) {
   const DictRef dict_ref(-1, 1);
   StringDictionary string_dict(dict_ref, BASE_PATH1, false, false, g_cache_string_hash);
@@ -352,7 +351,7 @@ TEST_F(StringDictionaryProxyTest, GetOrAddTransient) {
       }
     }
 
-    for (size_t i = 0; i < g_op_count; ++i) {
+    for (int i = 0; i < g_op_count; ++i) {
       string_ids[i] = string_dict_proxy.getOrAddTransient(strings[i]);
     }
 
@@ -378,7 +377,7 @@ TEST_F(StringDictionaryProxyTest, GetOrAddTransient) {
       }
     }
 
-    for (size_t i = 0; i < g_op_count; ++i) {
+    for (int i = 0; i < g_op_count; ++i) {
       string_ids[i] = string_dict_proxy.getOrAddTransient(strings[i]);
     }
 
@@ -565,7 +564,7 @@ TEST_F(StringDictionaryProxyTest, BuildIntersectionTranslationMapToOtherProxy) {
 
     constexpr int32_t num_source_proxy_transient_ids{64};
     constexpr int32_t num_dest_proxy_transient_ids{32};
-    constexpr int32_t proxy_transient_start{g_op_count * 2};
+    int proxy_transient_start{g_op_count * 2};
     for (int i = proxy_transient_start;
          i < proxy_transient_start + num_source_proxy_transient_ids;
          ++i) {
@@ -946,6 +945,10 @@ int main(int argc, char** argv) {
           ->implicit_value(true),
       "Cache string hash values in the string dictionary server during import.");
 
+  desc.add_options()("string-dictionary-size",
+                     po::value<int>(&g_op_count)->default_value(g_op_count),
+                     "The size of string dictionaries used during the test");
+
   logger::LogOptions log_options(argv[0]);
   log_options.severity_ = logger::Severity::FATAL;
   log_options.set_options();  // update default values
diff --git a/Tests/StringFunctionsTest.cpp b/Tests/StringFunctionsTest.cpp
index 572d7afc6a..b81f3a2a96 100644
--- a/Tests/StringFunctionsTest.cpp
+++ b/Tests/StringFunctionsTest.cpp
@@ -2030,6 +2030,188 @@ TEST_F(StringFunctionTest, Position) {
   }
 }
 
+TEST_F(StringFunctionTest, JarowinklerSimilarity) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    {
+      // Literal similarity
+      // Identical strings should score 100 similarity
+      auto result_set = sql("select jarowinkler_similarity('hi', 'hi');", dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{{int64_t(100)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Literal similarity
+      // Completely dissimilar strings should score 0 similarity
+      // Note that Jaro-Winkler similarity is case sensitive
+      auto result_set = sql("select jarowinkler_similarity('hi', 'HI');", dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{{int64_t(0)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-literal similarity, literal last
+      auto result_set =
+          sql("select jarowinkler_similarity(personal_motto, 'one for all') from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(63)}, {int64_t(45)}, {int64_t(48)}, {int64_t(59)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-literal similarity, literal first
+      auto result_set =
+          sql("select jarowinkler_similarity('one for all', personal_motto) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(63)}, {int64_t(45)}, {int64_t(48)}, {int64_t(59)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var similarity
+      auto result_set =
+          sql("select jarowinkler_similarity(personal_motto, raw_email) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(56)}, {int64_t(43)}, {int64_t(42)}, {int64_t(60)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var similarity, same string
+      auto result_set =
+          sql("select jarowinkler_similarity(personal_motto, personal_motto) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(100)}, {int64_t(100)}, {int64_t(100)}, {int64_t(100)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var similarity, one argument encoding none
+      auto result_set =
+          sql("select jarowinkler_similarity(personal_motto, last_name) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(0)}, {int64_t(49)}, {int64_t(69)}, {int64_t(38)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var similarity, both arguments encoding none
+      auto result_set =
+          sql("select jarowinkler_similarity(last_name, last_name) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(100)}, {int64_t(100)}, {int64_t(100)}, {int64_t(100)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var similarity, nested LOWER operator
+      auto result_set =
+          sql("select jarowinkler_similarity(lower(first_name), lower(full_name)) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(88)}, {int64_t(88)}, {int64_t(87)}, {int64_t(84)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+  }
+}
+
+TEST_F(StringFunctionTest, LevenshteinDistance) {
+  for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
+    SKIP_NO_GPU();
+    {
+      // Literal distance
+      // Identical strings should have 0 distance
+      auto result_set = sql("select levenshtein_distance('hi', 'hi');", dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{{int64_t(0)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Literal distance
+      // Completely dissimilar strings should have distance equal to length of strings
+      // Note that Levenshtein Distance is case sensitive
+      auto result_set = sql("select levenshtein_distance('hi', 'HI');", dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{{int64_t(2)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-literal distance, literal last
+      auto result_set =
+          sql("select levenshtein_distance(personal_motto, 'one for all') from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(17)}, {int64_t(24)}, {int64_t(31)}, {int64_t(73)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-literal distance, literal first
+      auto result_set =
+          sql("select levenshtein_distance('one for all', personal_motto) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(17)}, {int64_t(24)}, {int64_t(31)}, {int64_t(73)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var distance
+      auto result_set =
+          sql("select levenshtein_distance(personal_motto, raw_email) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(37)}, {int64_t(27)}, {int64_t(36)}, {int64_t(74)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var distance, same string
+      auto result_set =
+          sql("select levenshtein_distance(personal_motto, personal_motto) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(0)}, {int64_t(0)}, {int64_t(0)}, {int64_t(0)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var distance, one argument encoding none
+      auto result_set =
+          sql("select levenshtein_distance(personal_motto, last_name) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(28)}, {int64_t(30)}, {int64_t(34)}, {int64_t(80)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var distance, both arguments encoding none
+      auto result_set =
+          sql("select levenshtein_distance(last_name, last_name) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(0)}, {int64_t(0)}, {int64_t(0)}, {int64_t(0)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+    {
+      // Var-var distance, nested LOWER operator
+      auto result_set =
+          sql("select levenshtein_distance(lower(first_name), lower(full_name)) from "
+              "string_function_test_people order by id;",
+              dt);
+      std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+          {int64_t(6)}, {int64_t(6)}, {int64_t(7)}, {int64_t(6)}};
+      compare_result_set(expected_result_set, result_set);
+    }
+  }
+}
+
 TEST_F(StringFunctionTest, ExplicitCastToNumeric) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
@@ -2062,7 +2244,7 @@ TEST_F(StringFunctionTest, ExplicitCastToNumeric) {
   }
 }
 
-TEST_F(StringFunctionTest, ImplictCastToNumeric) {
+TEST_F(StringFunctionTest, ImplicitCastToNumeric) {
   for (auto dt : {ExecutorDeviceType::CPU, ExecutorDeviceType::GPU}) {
     SKIP_NO_GPU();
     {
@@ -3584,6 +3766,20 @@ TEST_F(StringFunctionTest, StrtokToArray_UDF) {
   }
 }
 
+TEST_F(StringFunctionTest, UDFConcat) {
+  for (auto dt : {ExecutorDeviceType::CPU, /* ExecutorDeviceType::GPU */}) {
+    SKIP_NO_GPU();
+    const auto result_set =
+        sql("select 'hello ' || udf_identity(name) from text_enc_test;", dt);
+    std::vector<std::vector<ScalarTargetValue>> expected_result_set{
+        {"hello United States"},
+        {"hello Canada"},
+        {"hello United Kingdom"},
+        {"hello Germany"}};
+    compare_result_set(expected_result_set, result_set);
+  }
+}
+
 TEST_F(StringFunctionTest, AlterTable_RuntimeFunction) {
   for (auto dt : {ExecutorDeviceType::CPU, /* ExecutorDeviceType::GPU */}) {
     SKIP_NO_GPU();
diff --git a/Tests/SystemTableFunctionsTest.cpp b/Tests/SystemTableFunctionsTest.cpp
index b474bcabea..3ab0efc770 100644
--- a/Tests/SystemTableFunctionsTest.cpp
+++ b/Tests/SystemTableFunctionsTest.cpp
@@ -222,13 +222,13 @@ TEST_F(SystemTFs, GenerateSeries) {
                 static_cast<int64_t>(500000L));  // avg of 500,000
     }
 
-    // Outputs of more than 2^30 rows are not allowed
+    // Outputs of rows that buffers sizes exceed 16 TiB, are not allowed
     {
       EXPECT_THROW(
           run_multiple_agg(
               "SELECT COUNT(*) AS n, MIN(generate_series) AS min_series, "
               "MAX(generate_series) AS max_series, AVG(generate_series) as avg_series "
-              "FROM (SELECT * FROM TABLE(generate_series(0, 2000000000, 1)));",
+              "FROM (SELECT * FROM TABLE(generate_series(0, 100000000000000, 1)));",
               dt),
           UserTableFunctionError);
     }
diff --git a/Tests/TableFunctionsTest.cpp b/Tests/TableFunctionsTest.cpp
index b605259aef..8a42d0970d 100644
--- a/Tests/TableFunctionsTest.cpp
+++ b/Tests/TableFunctionsTest.cpp
@@ -184,11 +184,17 @@ class TableFunctions : public ::testing::Test {
           "CREATE TABLE arr_test ("
           "barr BOOLEAN[], sum_along_row_barr BOOLEAN, concat_barr BOOLEAN[], "
           "carr TINYINT[], sum_along_row_carr TINYINT, concat_carr TINYINT[], "
+          "concat_carr_literal TINYINT[], "
           "sarr SMALLINT[], sum_along_row_sarr SMALLINT, concat_sarr SMALLINT[], "
-          "iarr INT[], sum_along_row_iarr INT, concat_iarr INT[], "
+          "concat_sarr_literal SMALLINT[], "
+          "iarr INT[], sum_along_row_iarr INT, concat_iarr INT[], concat_iarr_literal "
+          "INT[], "
           "larr BIGINT[], sum_along_row_larr BIGINT, concat_larr  BIGINT[], "
+          "concat_larr_literal BIGINT[], "
           "farr FLOAT[], sum_along_row_farr FLOAT, concat_farr FLOAT[], "
+          "concat_farr_literal FLOAT[], "
           "darr DOUBLE[], sum_along_row_darr DOUBLE, concat_darr DOUBLE[], "
+          "concat_darr_literal DOUBLE[], "
           "tarr TEXT[] ENCODING DICT(32), sum_along_row_tarr TEXT ENCODING DICT(32), "
           "concat_tarr TEXT[] ENCODING DICT(32), "
           "l BIGINT, asarray_l BIGINT[],"
@@ -203,21 +209,27 @@ class TableFunctions : public ::testing::Test {
                            "{1, 2}",
                            3,
                            "{1, 2, 1, 2}",
+                           "{1, 2, NULL, 99, 88}",
                            "{1, 2}",
                            3,
                            "{1, 2, 1, 2}",
+                           "{1, 2, NULL, 99, 88}",
                            "{1, 2}",
                            3,
                            "{1, 2, 1, 2}",
+                           "{1, 2, NULL, 99, 88}",
                            "{1, 2}",
                            3,
                            "{1, 2, 1, 2}",
+                           "{1, 2, NULL, 99, 88}",
                            "{1.0, 2.0}",
                            "3.0",
                            "{1.0, 2.0, 1.0, 2.0}",
+                           "{1.0, 2.0, NULL, 99.0, 88.0}",
                            "{1.0, 2.0}",
                            "3.0",
                            "{1.0, 2.0, 1.0, 2.0}",
+                           "{1.0, 2.0, NULL, 99.0, 88.0}",
                            "{'a', 'bc'}",
                            "'abc'",
                            "{'a', 'bc', 'a', 'bc'}",
@@ -232,21 +244,27 @@ class TableFunctions : public ::testing::Test {
                            "{1, NULL, 2}",
                            3,
                            "{1, NULL, 2, 1, NULL, 2}",
+                           "{1, NULL, 2, NULL, 99, 88}",
                            "{1, NULL, 2}",
                            3,
                            "{1, NULL, 2, 1, NULL, 2}",
+                           "{1, NULL, 2, NULL, 99, 88}",
                            "{1, NULL, 2}",
                            3,
                            "{1, NULL, 2, 1, NULL, 2}",
+                           "{1, NULL, 2, NULL, 99, 88}",
                            "{1, NULL, 2}",
                            3,
                            "{1, NULL, 2, 1, NULL, 2}",
+                           "{1, NULL, 2, NULL, 99, 88}",
                            "{1.0, NULL, 2.0}",
                            "3.0",
                            "{1.0, NULL, 2.0, 1.0, NULL, 2.0}",
+                           "{1.0, NULL, 2.0, NULL, 99.0, 88.0}",
                            "{1.0, NULL, 2.0}",
                            "3.0",
                            "{1.0, NULL, 2.0, 1.0, NULL, 2.0}",
+                           "{1.0, NULL, 2.0, NULL, 99.0, 88.0}",
                            "{'a', NULL, 'bc'}",
                            "'abc'",
                            "{'a', NULL, 'bc', 'a', NULL, 'bc'}",
@@ -261,21 +279,27 @@ class TableFunctions : public ::testing::Test {
                            "{}",
                            "0",
                            "{}",
+                           "{NULL, 99, 88}",
                            "{}",
                            "0",
                            "{}",
+                           "{NULL, 99, 88}",
                            "{}",
                            "0",
                            "{}",
+                           "{NULL, 99, 88}",
                            "{}",
                            "0",
                            "{}",
+                           "{NULL, 99, 88}",
                            "{}",
                            "0.0",
                            "{}",
+                           "{NULL, 99.0, 88.0}",
                            "{}",
                            "0.0",
                            "{}",
+                           "{NULL, 99.0, 88.0}",
                            "{}",
                            "''",
                            "{}",
@@ -291,21 +315,27 @@ class TableFunctions : public ::testing::Test {
                            "NULL",
                            "NULL",
                            "NULL",
+                           "{NULL, 99, 88}",
                            "NULL",
                            "NULL",
                            "NULL",
+                           "{NULL, 99, 88}",
                            "NULL",
                            "NULL",
                            "NULL",
+                           "{NULL, 99, 88}",
                            "NULL",
                            "NULL",
                            "NULL",
+                           "{NULL, 99, 88}",
                            "NULL",
                            "NULL",
                            "NULL",
+                           "{NULL, 99.0, 88.0}",
                            "NULL",
                            "NULL",
                            "NULL",
+                           "{NULL, 99.0, 88.0}",
                            "NULL",
                            "NULL",
                            "NULL",
@@ -887,6 +917,16 @@ TEST_F(TableFunctions, BasicProjection) {
       auto v = TestHelpers::v<int64_t>(row[0]);
       ASSERT_EQ(v, 11);
     }
+    {
+      const auto rows = run_multiple_agg(
+          "SELECT * FROM TABLE(ct_require_cursor(cursor(SELECT x"
+          " FROM tf_test), 6));",
+          dt);
+      ASSERT_EQ(rows->rowCount(), size_t(1));
+      auto row = rows->getNextRow(true, false);
+      auto v = TestHelpers::v<int64_t>(row[0]);
+      ASSERT_EQ(v, 12);
+    }
     {
       const auto rows = run_multiple_agg(
           "SELECT * FROM TABLE(ct_test_allocator(cursor(SELECT x"
@@ -2013,6 +2053,13 @@ TEST_F(TableFunctions, ThrowingTests) {
                        dt),
                    TableFunctionError);
     }
+    {
+      EXPECT_THROW(
+          run_multiple_agg("SELECT * FROM TABLE(ct_require_cursor(cursor(SELECT x"
+                           " FROM tf_test), 0));",
+                           dt),
+          TableFunctionError);
+    }
     {
       if (dt == ExecutorDeviceType::GPU) {
         EXPECT_THROW(
@@ -3704,6 +3751,37 @@ TEST_F(TableFunctions, ColumnArrayConcat) {
   }
 }
 
+TEST_F(TableFunctions, ColumnArrayAppendLiteralArray) {
+  for (auto dt : {ExecutorDeviceType::CPU /*, ExecutorDeviceType::GPU*/}) {
+    SKIP_NO_GPU();
+#define COLUMNARRAYCONCATLITERALARRAYTEST(COLNAME, CTYPE, SQLTYPE)                     \
+  {                                                                                    \
+    const auto rows =                                                                  \
+        run_multiple_agg("SELECT out0 FROM TABLE(array_append(cursor(SELECT " #COLNAME \
+                         " FROM arr_test), ARRAY[CAST(NULL AS " #SQLTYPE               \
+                         "), CAST(99 AS " #SQLTYPE "), CAST(88 AS " #SQLTYPE ")]));",  \
+                         dt);                                                          \
+    const auto result_rows =                                                           \
+        run_multiple_agg("SELECT concat_" #COLNAME "_literal FROM arr_test;", dt);     \
+    assert_equal<CTYPE>(rows, result_rows);                                            \
+  }
+    COLUMNARRAYCONCATLITERALARRAYTEST(carr, int8_t, TINYINT);
+    COLUMNARRAYCONCATLITERALARRAYTEST(sarr, int16_t, SMALLINT);
+    COLUMNARRAYCONCATLITERALARRAYTEST(iarr, int32_t, INT);
+    COLUMNARRAYCONCATLITERALARRAYTEST(larr, int64_t, BIGINT);
+    COLUMNARRAYCONCATLITERALARRAYTEST(farr, float, FLOAT);
+    COLUMNARRAYCONCATLITERALARRAYTEST(darr, double, DOUBLE);
+    {
+      const auto rows = run_multiple_agg(
+          "SELECT out0 FROM TABLE(array_append(cursor(SELECT barr FROM arr_test), "
+          "ARRAY[]));",
+          dt);
+      const auto result_rows = run_multiple_agg("SELECT barr FROM arr_test;", dt);
+      assert_equal<bool>(rows, result_rows);
+    }
+  }
+}
+
 TEST_F(TableFunctions, ColumnArrayCopierOneRow) {
   for (auto dt : {ExecutorDeviceType::CPU /*, ExecutorDeviceType::GPU*/}) {
     SKIP_NO_GPU();
@@ -4241,8 +4319,6 @@ TEST_F(TableFunctions, ColumnTextEncodingNoneConcat) {
       assert_equal<double>(rows, expected_rows);
     }
     {
-      // FIXME: Warning: UDTF has no CURSOR field subtype data. Proceeding assuming CURSOR
-      // typechecks.
       std::string q1 = "SELECT t1abc FROM bytes_test;";
       std::string q2 =
           "SELECT outputs FROM TABLE(CT_CONCAT(CURSOR(SELECT t1 FROM bytes_test), "
@@ -4385,6 +4461,19 @@ TEST_F(TableFunctions, RuntimeLibTestTableFunctions) {
 }
 #endif
 
+TEST_F(TableFunctions, RowRepeater) {
+  for (auto dt : {ExecutorDeviceType::CPU /*, ExecutorDeviceType::GPU*/}) {
+    SKIP_NO_GPU();
+    {
+      const auto rows = run_multiple_agg(
+          "SELECT * FROM TABLE(ROW_REPEATER(CURSOR(SELECT generate_series FROM "
+          "TABLE(generate_series(1, 1000))), 30));",
+          dt);
+      ASSERT_EQ(rows->rowCount(), size_t(30000));
+    }
+  }
+}
+
 int main(int argc, char** argv) {
   TestHelpers::init_logger_stderr_only(argc, argv);
   testing::InitGoogleTest(&argc, argv);
diff --git a/ThriftHandler/CommandLineOptions.cpp b/ThriftHandler/CommandLineOptions.cpp
index c51e1a41d2..f1e6df4623 100644
--- a/ThriftHandler/CommandLineOptions.cpp
+++ b/ThriftHandler/CommandLineOptions.cpp
@@ -68,9 +68,12 @@ extern bool g_columnar_large_projections;
 extern size_t g_columnar_large_projections_threshold;
 extern bool g_enable_system_tables;
 extern bool g_allow_system_dashboard_update;
+extern bool g_allow_memory_status_log;
 extern bool g_enable_logs_system_tables;
 extern size_t g_logs_system_tables_max_files_count;
 extern bool g_uniform_request_ids_per_thrift_call;
+extern size_t g_gpu_code_cache_max_size_in_bytes;
+
 #ifdef ENABLE_MEMKIND
 extern std::string g_pmem_path;
 #endif
@@ -420,6 +423,16 @@ void CommandLineOptions::fillOptions() {
                          ->default_value(enable_watchdog)
                          ->implicit_value(true),
                      "Enable watchdog.");
+  desc.add_options()("watchdog-max-projected-rows-per-device",
+                     po::value<size_t>(&watchdog_max_projected_rows_per_device)
+                         ->default_value(watchdog_max_projected_rows_per_device),
+                     "Max number of rows allowed to be projected when running a query "
+                     "with watchdog enabled.");
+  desc.add_options()(
+      "preflight-count-query-threshold",
+      po::value<size_t>(&preflight_count_query_threshold)
+          ->default_value(preflight_count_query_threshold),
+      "Threshold to run pre-flight count query which computes # output rows accurately.");
   desc.add_options()("watchdog-none-encoded-string-translation-limit",
                      po::value<size_t>(&watchdog_none_encoded_string_translation_limit)
                          ->default_value(watchdog_none_encoded_string_translation_limit),
@@ -966,6 +979,12 @@ void CommandLineOptions::fillDeveloperOptions() {
                          ->default_value(std::string("")),
                      "SSL Validated public certficate.");
 
+  desc.add_options()(
+      "gpu-code-cache-max-size-in-bytes",
+      po::value<size_t>(&g_gpu_code_cache_max_size_in_bytes)
+          ->default_value(g_gpu_code_cache_max_size_in_bytes),
+      "The maximum size of cached compiled codes for the gpu code cache in bytes.");
+
   desc.add_options()("ssl-private-key",
                      po::value<std::string>(&system_parameters.ssl_key_file)
                          ->default_value(std::string("")),
@@ -1128,6 +1147,12 @@ void CommandLineOptions::fillDeveloperOptions() {
       "Threshold (in minimum number of rows) to prefer columnar output for projections. "
       "Requires --columnar-large-projections to be set.");
 
+  desc.add_options()(
+      "allow-memory-status-log",
+      po::value<bool>(&g_allow_memory_status_log)
+          ->default_value(g_allow_memory_status_log),
+      "Allow CPU (and GPU if necessary) memory status before/after the query execution.");
+
   desc.add_options()(
       "allow-query-step-cpu-retry",
       po::value<bool>(&g_allow_query_step_cpu_retry)
@@ -1740,6 +1765,8 @@ boost::optional<int> CommandLineOptions::parse_command_line(
     g_enable_watchdog = enable_watchdog;
     g_watchdog_none_encoded_string_translation_limit =
         watchdog_none_encoded_string_translation_limit;
+    g_watchdog_max_projected_rows_per_device = watchdog_max_projected_rows_per_device;
+    g_preflight_count_query_threshold = preflight_count_query_threshold;
     g_enable_dynamic_watchdog = enable_dynamic_watchdog;
     g_dynamic_watchdog_time_limit = dynamic_watchdog_time_limit;
     g_enable_runtime_query_interrupt = enable_runtime_query_interrupt;
@@ -1766,22 +1793,6 @@ boost::optional<int> CommandLineOptions::parse_command_line(
           CacheItemType::BBOX_INTERSECT_HT, g_max_cacheable_hashtable_size_bytes);
     }
     g_optimize_cuda_block_and_grid_sizes = optimize_cuda_block_and_grid_sizes;
-
-    if (g_multi_instance) {
-      LOG(INFO) << "Disabling FSI and Disk Cache as they are not currently supported "
-                   "with multi-instance.";
-      // Fsi & disk cache currently unsupported in multi-instance.  The other option
-      // configs are required because they require fsi support.
-      g_enable_fsi = false;
-      g_enable_fsi_regex_import = false;
-      g_enable_system_tables = false;
-      g_enable_legacy_delimited_import = true;
-#ifdef ENABLE_IMPORT_PARQUET
-      g_enable_legacy_parquet_import = true;
-#endif
-      disk_cache_level = "none";
-    }
-
   } catch (po::error& e) {
     std::cerr << "Usage Error: " << e.what() << std::endl;
     return 1;
diff --git a/ThriftHandler/CommandLineOptions.h b/ThriftHandler/CommandLineOptions.h
index 63b64d7dbd..7626f2d895 100644
--- a/ThriftHandler/CommandLineOptions.h
+++ b/ThriftHandler/CommandLineOptions.h
@@ -37,6 +37,9 @@ namespace po = boost::program_options;
 
 class LeafHostInfo;
 
+extern size_t g_watchdog_max_projected_rows_per_device;
+extern size_t g_preflight_count_query_threshold;
+
 class CommandLineOptions {
  public:
   CommandLineOptions(char const* argv0, bool dist_v5_ = false)
@@ -73,9 +76,13 @@ class CommandLineOptions {
   bool renderer_prefer_igpu = false;
   unsigned renderer_vulkan_timeout_ms = 60000;  // in milliseconds
   bool renderer_use_parallel_executors = true;
+  bool renderer_enable_slab_allocation = false;  // until proven fully working
   bool enable_watchdog = true;
   bool enable_dynamic_watchdog = false;
   size_t watchdog_none_encoded_string_translation_limit = 1000000;
+  size_t watchdog_max_projected_rows_per_device =
+      g_watchdog_max_projected_rows_per_device;
+  size_t preflight_count_query_threshold = g_preflight_count_query_threshold;
   bool enable_runtime_query_interrupt = true;
   bool enable_non_kernel_time_query_interrupt = true;
   bool use_estimator_result_cache = true;
@@ -169,6 +176,8 @@ extern bool g_enable_dynamic_watchdog;
 extern unsigned g_dynamic_watchdog_time_limit;
 extern unsigned g_trivial_loop_join_threshold;
 extern size_t g_watchdog_none_encoded_string_translation_limit;
+extern size_t g_watchdog_max_projected_rows_per_device;
+extern size_t g_preflight_count_query_threshold;
 extern bool g_from_table_reordering;
 extern bool g_enable_filter_push_down;
 extern bool g_allow_cpu_retry;
@@ -214,6 +223,7 @@ extern bool g_enable_ml_functions;
 extern bool g_restrict_ml_model_metadata_to_superusers;
 extern bool g_enable_dev_table_functions;
 extern bool g_enable_geo_ops_on_uncompressed_coords;
+extern bool g_allow_memory_status_log;
 
 extern size_t g_max_memory_allocation_size;
 extern double g_bump_allocator_step_reduction;
diff --git a/ThriftHandler/DBHandler.cpp b/ThriftHandler/DBHandler.cpp
index 7a0ecfc047..4e37aa0759 100644
--- a/ThriftHandler/DBHandler.cpp
+++ b/ThriftHandler/DBHandler.cpp
@@ -123,6 +123,7 @@ extern bool g_allow_s3_server_privileges;
 extern bool g_enable_system_tables;
 bool g_allow_system_dashboard_update{false};
 bool g_uniform_request_ids_per_thrift_call{true};
+extern bool g_allow_memory_status_log;
 
 using Catalog_Namespace::Catalog;
 using Catalog_Namespace::SysCatalog;
@@ -183,6 +184,7 @@ DBHandler::DBHandler(const std::vector<LeafHostInfo>& db_leaves,
                      const size_t max_concurrent_render_sessions,
                      const size_t reserved_gpu_mem,
                      const bool render_compositor_use_last_gpu,
+                     const bool renderer_enable_slab_allocation,
                      const size_t num_reader_threads,
                      const AuthMetadata& authMetadata,
                      SystemParameters& system_parameters,
@@ -225,10 +227,11 @@ DBHandler::DBHandler(const std::vector<LeafHostInfo>& db_leaves,
     , renderer_use_parallel_executors_(renderer_use_parallel_executors)
     , enable_auto_clear_render_mem_(enable_auto_clear_render_mem)
     , render_oom_retry_threshold_(render_oom_retry_threshold)
+    , render_mem_bytes_(render_mem_bytes)
     , max_concurrent_render_sessions_(max_concurrent_render_sessions)
     , reserved_gpu_mem_(reserved_gpu_mem)
     , render_compositor_use_last_gpu_(render_compositor_use_last_gpu)
-    , render_mem_bytes_(render_mem_bytes)
+    , renderer_enable_slab_allocation_{renderer_enable_slab_allocation}
     , num_reader_threads_(num_reader_threads)
 #ifdef ENABLE_GEOS
     , libgeos_so_filename_(libgeos_so_filename)
@@ -553,7 +556,8 @@ void DBHandler::initialize(const bool is_new_db) {
                                               renderer_prefer_igpu_,
                                               renderer_vulkan_timeout_,
                                               renderer_use_parallel_executors_,
-                                              system_parameters_));
+                                              system_parameters_,
+                                              renderer_enable_slab_allocation_));
     } catch (const std::exception& e) {
       LOG(ERROR) << "Backend rendering disabled: " << e.what();
     }
@@ -3160,6 +3164,74 @@ std::vector<int> column_ids_by_names(const std::list<const ColumnDescriptor*>& d
   return desc_to_column_ids;
 }
 
+void log_cache_size(const Catalog_Namespace::Catalog& cat) {
+  std::ostringstream oss;
+  oss << "Cache size information {";
+  auto executor = Executor::getExecutor(Executor::UNITARY_EXECUTOR_ID);
+  // 1. Data recycler
+  // 1.a Resultset Recycler
+  auto resultset_cache_size =
+      executor->getResultSetRecyclerHolder()
+          .getResultSetRecycler()
+          ->getResultSetRecyclerMetricTracker()
+          .getCurrentCacheSize(DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  if (resultset_cache_size) {
+    oss << "\"query_resultset\": " << *resultset_cache_size << " bytes, ";
+  }
+
+  // 1.b Join Hash Table Recycler
+  auto perfect_join_ht_cache_size =
+      PerfectJoinHashTable::getHashTableCache()->getCurrentCacheSizeForDevice(
+          CacheItemType::PERFECT_HT, DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  auto baseline_join_ht_cache_size =
+      BaselineJoinHashTable::getHashTableCache()->getCurrentCacheSizeForDevice(
+          CacheItemType::BASELINE_HT, DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  auto bbox_intersect_ht_cache_size =
+      BoundingBoxIntersectJoinHashTable::getHashTableCache()
+          ->getCurrentCacheSizeForDevice(CacheItemType::BBOX_INTERSECT_HT,
+                                         DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  auto bbox_intersect_ht_tuner_cache_size =
+      BoundingBoxIntersectJoinHashTable::getBoundingBoxIntersectTuningParamCache()
+          ->getCurrentCacheSizeForDevice(CacheItemType::BBOX_INTERSECT_AUTO_TUNER_PARAM,
+                                         DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  auto sum_hash_table_cache_size =
+      perfect_join_ht_cache_size + baseline_join_ht_cache_size +
+      bbox_intersect_ht_cache_size + bbox_intersect_ht_tuner_cache_size;
+  oss << "\"hash_tables\": " << sum_hash_table_cache_size << " bytes, ";
+
+  // 1.c Chunk Metadata Recycler
+  auto chunk_metadata_cache_size =
+      executor->getResultSetRecyclerHolder()
+          .getChunkMetadataRecycler()
+          ->getCurrentCacheSizeForDevice(CacheItemType::CHUNK_METADATA,
+                                         DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
+  oss << "\"chunk_metadata\": " << chunk_metadata_cache_size << " bytes, ";
+
+  // 2. Query Plan Dag
+  auto query_plan_dag_cache_size =
+      executor->getQueryPlanDagCache().getCurrentNodeMapSize();
+  oss << "\"query_plan_dag\": " << query_plan_dag_cache_size << " bytes, ";
+
+  // 3. Compiled (GPU) Code
+  oss << "\"compiled_GPU code\": "
+      << QueryEngine::getInstance()->gpu_code_accessor->getCacheSize() << " bytes, ";
+
+  // 4. String Dictionary
+  oss << "\"string_dictionary\": " << cat.getTotalMemorySizeForDictionariesForDatabase()
+      << " bytes";
+  oss << "}";
+  LOG(INFO) << oss.str();
+}
+
+void log_system_cpu_memory_status(std::string const& query,
+                                  const Catalog_Namespace::Catalog& cat) {
+  if (g_allow_memory_status_log) {
+    std::ostringstream oss;
+    oss << query << "\n" << cat.getDataMgr().getSystemMemoryUsage();
+    LOG(INFO) << oss.str();
+    log_cache_size(cat);
+  }
+}
 }  // namespace
 
 void DBHandler::fillGeoColumns(
@@ -3233,6 +3305,22 @@ void DBHandler::fillMissingBuffers(
   }
 }
 
+namespace {
+std::string get_load_tag(const std::string& load_tag, const std::string& table_name) {
+  std::ostringstream oss;
+  oss << load_tag << "(" << table_name << ")";
+  return oss.str();
+}
+
+std::string get_import_tag(const std::string& import_tag,
+                           const std::string& table_name,
+                           const std::string& file_path) {
+  std::ostringstream oss;
+  oss << import_tag << "(" << table_name << ", file_path:" << file_path << ")";
+  return oss.str();
+}
+}  // namespace
+
 void DBHandler::load_table_binary(const TSessionId& session_id_or_json,
                                   const std::string& table_name,
                                   const std::vector<TRow>& rows,
@@ -3264,6 +3352,11 @@ void DBHandler::load_table_binary(const TSessionId& session_id_or_json,
     auto desc_id_to_column_id = column_ids_by_names(col_descs, column_names);
 
     size_t rows_completed = 0;
+    auto const load_tag = get_load_tag("load_table_binary", table_name);
+    log_system_cpu_memory_status("start_" + load_tag, session_ptr->getCatalog());
+    ScopeGuard cleanup = [&load_tag, &session_ptr]() {
+      log_system_cpu_memory_status("finish_" + load_tag, session_ptr->getCatalog());
+    };
     for (auto const& row : rows) {
       size_t col_idx = 0;
       try {
@@ -3399,6 +3492,11 @@ void DBHandler::load_table_binary_columnar(const TSessionId& session_id_or_json,
   size_t num_rows = get_column_size(cols.front());
   size_t import_idx = 0;  // index into the TColumn vector being loaded
   size_t col_idx = 0;     // index into column description vector
+  auto const load_tag = get_load_tag("load_table_binary_columnar", table_name);
+  log_system_cpu_memory_status("start_" + load_tag, session_ptr->getCatalog());
+  ScopeGuard cleanup = [&load_tag, &session_ptr]() {
+    log_system_cpu_memory_status("finish_" + load_tag, session_ptr->getCatalog());
+  };
   try {
     size_t skip_physical_cols = 0;
     for (auto cd : loader->get_column_descs()) {
@@ -3544,7 +3642,11 @@ void DBHandler::load_table_binary_arrow(const TSessionId& session_id_or_json,
 
   // col_idx indexes "desc_id_to_column_id"
   size_t col_idx = 0;
-
+  auto const load_tag = get_load_tag("load_table_binary_arrow", table_name);
+  log_system_cpu_memory_status("start_" + load_tag, session_ptr->getCatalog());
+  ScopeGuard cleanup = [&load_tag, &session_ptr]() {
+    log_system_cpu_memory_status("finish_" + load_tag, session_ptr->getCatalog());
+  };
   try {
     for (auto cd : loader->get_column_descs()) {
       if (cd->isGeoPhyCol) {
@@ -3620,7 +3722,11 @@ void DBHandler::load_table(const TSessionId& session_id_or_json,
     if (rows.empty()) {
       THROW_DB_EXCEPTION("No rows to insert");
     }
-
+    auto const load_tag = get_load_tag("load_table", table_name);
+    log_system_cpu_memory_status("start_" + load_tag, session_ptr->getCatalog());
+    ScopeGuard cleanup = [&load_tag, &session_ptr]() {
+      log_system_cpu_memory_status("finish_" + load_tag, session_ptr->getCatalog());
+    };
     const auto execute_read_lock = legacylockmgr::getExecuteReadLock();
     std::unique_ptr<import_export::Loader> loader;
     std::vector<std::unique_ptr<import_export::TypedImportBuffer>> import_buffers;
@@ -5238,7 +5344,11 @@ void DBHandler::import_table(const TSessionId& session_id_or_json,
       }
       copy_from_source = file_path.string();
     }
-
+    auto const load_tag = get_import_tag("import_table", table_name, copy_from_source);
+    log_system_cpu_memory_status("start_" + load_tag, session_ptr->getCatalog());
+    ScopeGuard cleanup = [&load_tag, &session_ptr]() {
+      log_system_cpu_memory_status("finish_" + load_tag, session_ptr->getCatalog());
+    };
     const auto insert_data_lock = lockmgr::InsertDataLockMgr::getWriteLockForTable(
         session_ptr->getCatalog(), table_name);
     std::unique_ptr<import_export::AbstractImporter> importer;
@@ -5407,7 +5517,11 @@ void DBHandler::importGeoTableSingle(const TSessionId& session_id,
   VLOG(1) << "import_geo_table: Original filename: " << file_name_in;
   VLOG(1) << "import_geo_table: Actual filename: " << file_name;
   VLOG(1) << "import_geo_table: Raster: " << is_raster;
-
+  auto const load_tag = get_import_tag("import_geo_table", table_name, file_name);
+  log_system_cpu_memory_status("start_" + load_tag, session_ptr->getCatalog());
+  ScopeGuard cleanup = [&load_tag, &session_ptr]() {
+    log_system_cpu_memory_status("finish_" + load_tag, session_ptr->getCatalog());
+  };
   // use GDAL to check the primary file exists (even if on S3 and/or in archive)
   auto file_path = boost::filesystem::path(file_name);
   if (!import_export::Importer::gdalFileOrDirectoryExists(file_name, copy_params)) {
@@ -6087,69 +6201,6 @@ void DBHandler::set_execution_mode_nolock(Catalog_Namespace::SessionInfo* sessio
   }
 }
 
-namespace {
-
-void log_cache_size(const Catalog_Namespace::Catalog& cat) {
-  std::ostringstream oss;
-  oss << "Cache size information {";
-  auto executor = Executor::getExecutor(Executor::UNITARY_EXECUTOR_ID);
-  // 1. Data recycler
-  // 1.a Resultset Recycler
-  auto resultset_cache_size =
-      executor->getResultSetRecyclerHolder()
-          .getResultSetRecycler()
-          ->getResultSetRecyclerMetricTracker()
-          .getCurrentCacheSize(DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
-  if (resultset_cache_size) {
-    oss << "\"query_resultset\": " << *resultset_cache_size << " bytes, ";
-  }
-
-  // 1.b Join Hash Table Recycler
-  auto perfect_join_ht_cache_size =
-      PerfectJoinHashTable::getHashTableCache()->getCurrentCacheSizeForDevice(
-          CacheItemType::PERFECT_HT, DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
-  auto baseline_join_ht_cache_size =
-      BaselineJoinHashTable::getHashTableCache()->getCurrentCacheSizeForDevice(
-          CacheItemType::BASELINE_HT, DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
-  auto bbox_intersect_ht_cache_size =
-      BoundingBoxIntersectJoinHashTable::getHashTableCache()
-          ->getCurrentCacheSizeForDevice(CacheItemType::BBOX_INTERSECT_HT,
-                                         DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
-  auto bbox_intersect_ht_tuner_cache_size =
-      BoundingBoxIntersectJoinHashTable::getBoundingBoxIntersectTuningParamCache()
-          ->getCurrentCacheSizeForDevice(CacheItemType::BBOX_INTERSECT_AUTO_TUNER_PARAM,
-                                         DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
-  auto sum_hash_table_cache_size =
-      perfect_join_ht_cache_size + baseline_join_ht_cache_size +
-      bbox_intersect_ht_cache_size + bbox_intersect_ht_tuner_cache_size;
-  oss << "\"hash_tables\": " << sum_hash_table_cache_size << " bytes, ";
-
-  // 1.c Chunk Metadata Recycler
-  auto chunk_metadata_cache_size =
-      executor->getResultSetRecyclerHolder()
-          .getChunkMetadataRecycler()
-          ->getCurrentCacheSizeForDevice(CacheItemType::CHUNK_METADATA,
-                                         DataRecyclerUtil::CPU_DEVICE_IDENTIFIER);
-  oss << "\"chunk_metadata\": " << chunk_metadata_cache_size << " bytes, ";
-
-  // 2. Query Plan Dag
-  auto query_plan_dag_cache_size =
-      executor->getQueryPlanDagCache().getCurrentNodeMapSize();
-  oss << "\"query_plan_dag\": " << query_plan_dag_cache_size << " bytes, ";
-
-  // 3. Compiled Code
-  oss << "\"compiled_GPU code\": " << QueryEngine::getInstance()->getTotalCacheKeySize()
-      << " bytes, ";
-
-  // 4. String Dictionary
-  oss << "\"string_dictionary\": " << cat.getTotalMemorySizeForDictionariesForDatabase()
-      << " bytes";
-  oss << "}";
-  LOG(INFO) << oss.str();
-}
-
-}  // namespace
-
 std::vector<PushedDownFilterInfo> DBHandler::execute_rel_alg(
     ExecutionResult& _return,
     QueryStateProxy query_state_proxy,
@@ -6163,11 +6214,8 @@ std::vector<PushedDownFilterInfo> DBHandler::execute_rel_alg(
     const ExplainInfo& explain_info,
     const std::optional<size_t> executor_index) const {
   query_state::Timer timer = query_state_proxy.createTimer(__func__);
-
   VLOG(1) << "Table Schema Locks:\n" << lockmgr::TableSchemaLockMgr::instance();
   VLOG(1) << "Table Data Locks:\n" << lockmgr::TableDataLockMgr::instance();
-
-  auto& cat = query_state_proxy->getConstSessionInfo()->getCatalog();
   auto executor = Executor::getExecutor(
       executor_index ? *executor_index : Executor::UNITARY_EXECUTOR_ID,
       jit_debug_ ? "/tmp" : "",
@@ -6217,8 +6265,6 @@ std::vector<PushedDownFilterInfo> DBHandler::execute_rel_alg(
     execution_time_ms -= rs->getQueueTime();
   }
   _return.setExecutionTime(execution_time_ms);
-  log_cache_size(cat);
-  VLOG(1) << cat.getDataMgr().getSystemMemoryUsage();
   const auto& filter_push_down_info = _return.getPushedDownFilterInfo();
   if (!filter_push_down_info.empty()) {
     return filter_push_down_info;
@@ -6409,11 +6455,18 @@ void DBHandler::sql_execute_impl(ExecutionResult& _return,
   auto session_ptr = query_state_proxy->getConstSessionInfo();
   // Call to DistributedValidate() below may change cat.
   auto& cat = session_ptr->getCatalog();
-
   legacylockmgr::ExecutorWriteLock execute_write_lock;
   legacylockmgr::ExecutorReadLock execute_read_lock;
 
   ParserWrapper pw{query_str};
+  auto [query_substr, post_fix] = ::substring(query_str, g_max_log_length);
+  std::ostringstream oss;
+  oss << query_substr << post_fix;
+  auto const reduced_query_str = oss.str();
+  log_system_cpu_memory_status("Start query execution: " + reduced_query_str, cat);
+  ScopeGuard cpu_system_memory_logging = [&cat, &reduced_query_str]() {
+    log_system_cpu_memory_status("Finish query execution: " + reduced_query_str, cat);
+  };
 
   // test to see if db/catalog is writable before execution of a writable SQL/DDL command
   //   TODO: move to execute() (?)
@@ -6821,8 +6874,11 @@ TPlanResult DBHandler::processCalciteRequest(
   ExplainInfo explain(query_str);
   std::string const actual_query{explain.isSelectExplain() ? explain.ActualQuery()
                                                            : query_str};
-  auto query_parsing_option = calcite_->getCalciteQueryParsingOption(
-      legacy_syntax_, explain.isCalciteExplain(), check_privileges);
+  auto query_parsing_option =
+      calcite_->getCalciteQueryParsingOption(legacy_syntax_,
+                                             explain.isCalciteExplain(),
+                                             check_privileges,
+                                             explain.isCalciteExplainDetail());
   auto optimization_option = calcite_->getCalciteOptimizationOption(
       system_parameters.enable_calcite_view_optimize,
       g_enable_watchdog,
diff --git a/ThriftHandler/DBHandler.h b/ThriftHandler/DBHandler.h
index 9d05e43ea4..545e08cc43 100644
--- a/ThriftHandler/DBHandler.h
+++ b/ThriftHandler/DBHandler.h
@@ -182,6 +182,7 @@ class DBHandler : public HeavyIf {
             const size_t max_concurrent_render_sessions,
             const size_t reserved_gpu_mem,
             const bool render_compositor_use_last_gpu,
+            const bool renderer_enable_slab_allocation,
             const size_t num_reader_threads,
             const AuthMetadata& authMetadata,
             SystemParameters& system_parameters,
@@ -964,10 +965,11 @@ class DBHandler : public HeavyIf {
   const bool renderer_use_parallel_executors_;
   const bool enable_auto_clear_render_mem_;
   const int render_oom_retry_threshold_;
+  const size_t render_mem_bytes_;
   const size_t max_concurrent_render_sessions_;
   const size_t reserved_gpu_mem_;
   const bool render_compositor_use_last_gpu_;
-  const size_t render_mem_bytes_;
+  const bool renderer_enable_slab_allocation_;
   const size_t num_reader_threads_;
 #ifdef ENABLE_GEOS
   const std::string& libgeos_so_filename_;
diff --git a/ThriftHandler/RenderHandler.h b/ThriftHandler/RenderHandler.h
index 9b2dcd11d1..c0139e86ee 100644
--- a/ThriftHandler/RenderHandler.h
+++ b/ThriftHandler/RenderHandler.h
@@ -57,7 +57,8 @@ class RenderHandler {
                          const bool renderer_prefer_igpu,
                          const uint32_t renderer_vulkan_timeout_ms,
                          const bool renderer_use_parallel_executors,
-                         const SystemParameters system_parameters);
+                         const SystemParameters system_parameters,
+                         const bool renderer_enable_slab_allocation);
   ~RenderHandler();
 
  private:
diff --git a/ThriftHandler/os/RenderHandler.cpp b/ThriftHandler/os/RenderHandler.cpp
index 8538ff4b7a..cad9dcf578 100644
--- a/ThriftHandler/os/RenderHandler.cpp
+++ b/ThriftHandler/os/RenderHandler.cpp
@@ -34,7 +34,8 @@ RenderHandler::RenderHandler(DBHandler* db_handler,
                              const bool renderer_prefer_igpu,
                              const uint32_t renderer_vulkan_timeout_ms,
                              const bool renderer_use_parallel_executors,
-                             const SystemParameters system_parameters)
+                             const SystemParameters system_parameters,
+                             const bool renderer_enable_slab_allocation)
     : impl_(nullptr) {
   throw std::runtime_error(
       "Rendering is only supported in the Enterprise and Community Editions");
diff --git a/config/tsan.suppressions b/config/tsan.suppressions
index 4c8f7a0430..3eda9f74fe 100644
--- a/config/tsan.suppressions
+++ b/config/tsan.suppressions
@@ -26,3 +26,10 @@ race:ShowModelFeatureDetailsDdlTest::TearDown
 race:ResultSet::parallelTop
 # QE-874
 race:rml::internal::BootStrapBlocks::allocate
+# QE-891
+race:daal::*
+# 2023-06-13 Ignore issues reported in third-party ODBC drivers
+race:libSnowflake.so
+deadlock:libSnowflake.so
+deadlock:libamazonredshiftodbc64.so
+deadlock:libclouderahiveodbc64.so
diff --git a/initdb.cpp b/initdb.cpp
index 1051d6017f..0cd3720974 100644
--- a/initdb.cpp
+++ b/initdb.cpp
@@ -38,7 +38,7 @@ static const std::array<std::string, 3> SampleGeoTableNames{"heavyai_us_states",
                                                             "heavyai_us_counties",
                                                             "heavyai_countries"};
 
-bool g_enable_thrift_logs{false};
+extern bool g_enable_thrift_logs;
 
 static void loadGeo(std::string base_path) {
   TSessionId session_id{};
@@ -75,6 +75,7 @@ static void loadGeo(std::string base_path) {
   const size_t render_mem_bytes{500000000};
   const size_t max_concurrent_render_sessions{500};
   const bool render_compositor_use_last_gpu{false};
+  const bool renderer_enable_slab_allocation{true};
   const size_t reserved_gpu_mem{134217728};
   const size_t num_reader_threads{0};
   const bool legacy_syntax{true};
@@ -112,6 +113,7 @@ static void loadGeo(std::string base_path) {
                                                 max_concurrent_render_sessions,
                                                 reserved_gpu_mem,
                                                 render_compositor_use_last_gpu,
+                                                renderer_enable_slab_allocation,
                                                 num_reader_threads,
                                                 auth_metadata,
                                                 system_parameters,
diff --git a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParser.java b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParser.java
index 1829fcccd4..56db65938c 100644
--- a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParser.java
+++ b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParser.java
@@ -40,8 +40,11 @@
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.RelShuttleImpl;
+import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.core.TableModify.Operation;
+import org.apache.calcite.rel.externalize.HeavyDBRelWriterImpl;
+import org.apache.calcite.rel.externalize.RelWriterImpl;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalTableModify;
 import org.apache.calcite.rel.rules.CoreRules;
@@ -80,6 +83,8 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.reflect.Field;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
@@ -491,14 +496,19 @@ public String processSql(
 
     final RelRoot sqlRel = convertSqlToRelNode(sqlNode, planner, parserOptions);
     RelNode project = sqlRel.project();
-
-    if (parserOptions.isExplain()) {
+    if (project == null) {
+      throw new RuntimeException("Cannot convert the sql to AST");
+    }
+    if (parserOptions.isExplainDetail()) {
+      StringWriter sw = new StringWriter();
+      RelWriter planWriter = new HeavyDBRelWriterImpl(
+              new PrintWriter(sw), SqlExplainLevel.EXPPLAN_ATTRIBUTES, false);
+      project.explain(planWriter);
+      return sw.toString();
+    } else if (parserOptions.isExplain()) {
       return RelOptUtil.toString(sqlRel.project());
     }
-
-    String res = HeavyDBSerializer.toString(project);
-
-    return res;
+    return HeavyDBSerializer.toString(project);
   }
 
   public HeavyDBPlanner.CompletionResult getCompletionHints(
diff --git a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParserOptions.java b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParserOptions.java
index 5083dda68b..322919001f 100644
--- a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParserOptions.java
+++ b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBParserOptions.java
@@ -28,6 +28,7 @@ public final class HeavyDBParserOptions {
   private List<FilterPushDownInfo> filterPushDownInfo;
   private boolean legacySyntax;
   private boolean isExplain;
+  private boolean isExplainDetail;
   private boolean isViewOptimizeEnabled;
   private boolean isWatchdogEnabled;
   private boolean distributedMode;
@@ -35,12 +36,14 @@ public final class HeavyDBParserOptions {
   public HeavyDBParserOptions(List<FilterPushDownInfo> inFilterPushDownInfo,
           boolean inLegacySyntax,
           boolean inIsExplain,
+          boolean inIsExplainDetail,
           boolean inIsViewOptimzeEnabled,
           boolean inWatchdogEnabled,
           boolean inDistributedMode) {
     filterPushDownInfo = inFilterPushDownInfo;
     legacySyntax = inLegacySyntax;
     isExplain = inIsExplain;
+    isExplainDetail = inIsExplainDetail;
     isViewOptimizeEnabled = inIsViewOptimzeEnabled;
     isWatchdogEnabled = inWatchdogEnabled;
     distributedMode = inDistributedMode;
@@ -50,6 +53,7 @@ public HeavyDBParserOptions() {
     filterPushDownInfo = new ArrayList<>();
     legacySyntax = true;
     isExplain = false;
+    isExplainDetail = false;
     isViewOptimizeEnabled = false;
     isWatchdogEnabled = false;
     distributedMode = false;
@@ -90,6 +94,10 @@ public boolean isExplain() {
     return isExplain;
   }
 
+  public boolean isExplainDetail() {
+    return isExplainDetail;
+  }
+
   /**
    * @param isExplain the isExplain to set
    */
diff --git a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBSqlOperatorTable.java b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBSqlOperatorTable.java
index 42867e88f2..052a8fc93a 100644
--- a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBSqlOperatorTable.java
+++ b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBSqlOperatorTable.java
@@ -233,6 +233,8 @@ public void addUDF(final Map<String, ExtensionFunction> extSigs) {
     addOperator(new RegexpMatch());
     addOperator(new Base64Encode());
     addOperator(new Base64Decode());
+    addOperator(new JarowinklerSimilarity());
+    addOperator(new LevenshteinDistance());
     addOperator(new Likely());
     addOperator(new Unlikely());
     addOperator(new Sign());
@@ -1485,6 +1487,58 @@ public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
     }
   }
 
+  public static class JarowinklerSimilarity extends SqlFunction {
+    public JarowinklerSimilarity() {
+      super("JAROWINKLER_SIMILARITY",
+              SqlKind.OTHER_FUNCTION,
+              null,
+              null,
+              OperandTypes.family(getSignatureFamilies()),
+              SqlFunctionCategory.STRING);
+    }
+
+    private static java.util.List<SqlTypeFamily> getSignatureFamilies() {
+      java.util.ArrayList<SqlTypeFamily> families =
+              new java.util.ArrayList<SqlTypeFamily>();
+      families.add(SqlTypeFamily.STRING);
+      families.add(SqlTypeFamily.STRING);
+      return families;
+    }
+
+    @Override
+    public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
+      assert opBinding.getOperandCount() == 2;
+      final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+      return typeFactory.createSqlType(SqlTypeName.BIGINT);
+    }
+  }
+
+  public static class LevenshteinDistance extends SqlFunction {
+    public LevenshteinDistance() {
+      super("LEVENSHTEIN_DISTANCE",
+              SqlKind.OTHER_FUNCTION,
+              null,
+              null,
+              OperandTypes.family(getSignatureFamilies()),
+              SqlFunctionCategory.STRING);
+    }
+
+    private static java.util.List<SqlTypeFamily> getSignatureFamilies() {
+      java.util.ArrayList<SqlTypeFamily> families =
+              new java.util.ArrayList<SqlTypeFamily>();
+      families.add(SqlTypeFamily.STRING);
+      families.add(SqlTypeFamily.STRING);
+      return families;
+    }
+
+    @Override
+    public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
+      assert opBinding.getOperandCount() == 2;
+      final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+      return typeFactory.createSqlType(SqlTypeName.BIGINT);
+    }
+  }
+
   public static class TryCast extends SqlFunction {
     //~ Instance fields --------------------------------------------------------
 
@@ -2834,7 +2888,9 @@ static class CountIf extends SqlAggFunction {
 
     @Override
     public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
-      return opBinding.getOperandType(0);
+      final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+      return typeFactory.createTypeWithNullability(
+              typeFactory.createSqlType(SqlTypeName.BIGINT), false);
     }
   }
 
diff --git a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBTypeCoercion.java b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBTypeCoercion.java
index 845c76097b..b3b12891c0 100644
--- a/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBTypeCoercion.java
+++ b/java/calcite/src/main/java/com/mapd/calcite/parser/HeavyDBTypeCoercion.java
@@ -3,6 +3,7 @@
 import static com.mapd.parser.server.ExtensionFunction.*;
 
 import com.mapd.calcite.parser.HeavyDBSqlOperatorTable.ExtTableFunction;
+import com.mapd.parser.server.ExtensionFunction.ExtArgumentType;
 
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -109,6 +110,12 @@ private int calculateScoreForCursorOperand(SqlNode cursorOperand,
     List<ExtArgumentType> formalFieldTypes =
             udtf.getCursorFieldTypes().get(formalOperandName);
 
+    // If we are typechecking a CURSOR parameter for an overload where this parameter is
+    // not a CURSOR/Column type, then it can never typecheck.
+    if (udtf.getArgTypes().get(index) != ExtArgumentType.Cursor) {
+      return -1;
+    }
+
     if (formalFieldTypes == null || formalFieldTypes.size() == 0) {
       System.out.println(
               "Warning: UDTF has no CURSOR field subtype data. Proceeding assuming CURSOR typechecks.");
diff --git a/java/calcite/src/main/java/com/mapd/parser/hint/HeavyDBHintStrategyTable.java b/java/calcite/src/main/java/com/mapd/parser/hint/HeavyDBHintStrategyTable.java
index b4d082ede0..19c320906a 100644
--- a/java/calcite/src/main/java/com/mapd/parser/hint/HeavyDBHintStrategyTable.java
+++ b/java/calcite/src/main/java/com/mapd/parser/hint/HeavyDBHintStrategyTable.java
@@ -39,6 +39,8 @@ static HintStrategyTable createHintStrategies(HintStrategyTable.Builder builder)
     supportedHints.add("max_join_hashtable_size");
     supportedHints.add("force_baseline_hash_join");
     supportedHints.add("force_one_to_many_hash_join");
+    supportedHints.add("watchdog_max_projected_rows_per_device");
+    supportedHints.add("preflight_count_query_threshold");
 
     for (String hint_name : supportedHints) {
       // add local / global hints, e.., cpu_mode / g_cpu_mode
diff --git a/java/calcite/src/main/java/com/mapd/parser/server/CalciteServerHandler.java b/java/calcite/src/main/java/com/mapd/parser/server/CalciteServerHandler.java
index 827d084701..9539b0e5bb 100644
--- a/java/calcite/src/main/java/com/mapd/parser/server/CalciteServerHandler.java
+++ b/java/calcite/src/main/java/com/mapd/parser/server/CalciteServerHandler.java
@@ -202,6 +202,7 @@ public TPlanResult process(String user,
       HeavyDBParserOptions parserOptions = new HeavyDBParserOptions(filterPushDownInfo,
               queryParsingOption.legacy_syntax,
               queryParsingOption.is_explain,
+              queryParsingOption.is_explain_detail,
               optimizationOption.is_view_optimize,
               optimizationOption.enable_watchdog,
               optimizationOption.distributed_mode);
diff --git a/java/calcite/src/main/java/org/apache/calcite/rel/externalize/HeavyDBRelWriterImpl.java b/java/calcite/src/main/java/org/apache/calcite/rel/externalize/HeavyDBRelWriterImpl.java
new file mode 100644
index 0000000000..df39ad0a0e
--- /dev/null
+++ b/java/calcite/src/main/java/org/apache/calcite/rel/externalize/HeavyDBRelWriterImpl.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2023 HEAVY.AI, Inc.
+ *
+ * Licensed 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 org.apache.calcite.rel.externalize;
+
+import com.google.common.collect.ImmutableList;
+
+import org.apache.calcite.avatica.util.Spacer;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rel.metadata.RelColumnOrigin;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.util.Pair;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.io.PrintWriter;
+import java.util.*;
+
+/**
+ * Implementation of {@link org.apache.calcite.rel.RelWriter}.
+ */
+public class HeavyDBRelWriterImpl implements RelWriter {
+  //~ Instance fields --------------------------------------------------------
+
+  protected final PrintWriter pw;
+  protected final SqlExplainLevel detailLevel;
+  protected final boolean withIdPrefix;
+  protected final Spacer spacer = new Spacer();
+  private final List<Pair<String, @Nullable Object>> values = new ArrayList<>();
+
+  //~ Constructors -----------------------------------------------------------
+
+  public HeavyDBRelWriterImpl(PrintWriter pw) {
+    this(pw, SqlExplainLevel.EXPPLAN_ATTRIBUTES, true);
+  }
+
+  public HeavyDBRelWriterImpl(
+          PrintWriter pw, SqlExplainLevel detailLevel, boolean withIdPrefix) {
+    this.pw = pw;
+    this.detailLevel = detailLevel;
+    this.withIdPrefix = withIdPrefix;
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  public Set<String> collectRelColumnOrigin(
+          RelMetadataQuery mq, RelNode rel, int targetColumnIndex) {
+    Set<RelColumnOrigin> columnOrigins = new HashSet<>();
+    Map<String, Set<String>> originInfoMap = new HashMap<>();
+    Set<String> originInfo = new HashSet<>();
+    try {
+      if (!rel.getInputs().isEmpty()) {
+        RelNode sourceNode = rel.getInputs().size() == 1 ? rel.getInput(0) : rel;
+        columnOrigins = mq.getColumnOrigins(sourceNode, targetColumnIndex);
+      }
+      for (RelColumnOrigin rco : columnOrigins) {
+        RelOptTable relOptTable = rco.getOriginTable();
+        String dbName = relOptTable.getQualifiedName().get(0);
+        String tableName = relOptTable.getQualifiedName().get(1);
+        String colName = relOptTable.getRowType()
+                                 .getFieldList()
+                                 .get(rco.getOriginColumnOrdinal())
+                                 .getName();
+        String key = "$" + targetColumnIndex;
+        String info = "db:" + dbName + ",tableName:" + tableName + ",colName:" + colName;
+        if (originInfoMap.containsKey(key)) {
+          originInfoMap.get(key).add(info);
+        } else {
+          Set<String> originList = new HashSet<>();
+          originList.add(info);
+          originInfoMap.put(key, originList);
+        }
+      }
+    } catch (Exception ex) {
+      throw new RuntimeException("EXPLAIN CALCITE DETAILED error: " + ex.getMessage());
+    }
+
+    for (Map.Entry<String, Set<String>> entry : originInfoMap.entrySet()) {
+      int index = 1;
+      for (String colInfo : entry.getValue()) {
+        if (entry.getValue().size() > 1) {
+          originInfo.add(entry.getKey() + "-" + index + "->" + colInfo);
+        } else {
+          originInfo.add(entry.getKey() + "->" + colInfo);
+        }
+        index++;
+      }
+    }
+    return originInfo;
+  }
+
+  protected void explain_(RelNode rel, List<Pair<String, @Nullable Object>> values) {
+    List<RelNode> inputs = rel.getInputs();
+    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+    if (!mq.isVisibleInExplain(rel, detailLevel)) {
+      // render children in place of this, at same level
+      explainInputs(inputs);
+      return;
+    }
+
+    StringBuilder s = new StringBuilder();
+    spacer.spaces(s);
+    if (withIdPrefix) {
+      s.append(rel.getId()).append(":");
+    }
+    s.append(rel.getRelTypeName());
+    if (detailLevel != SqlExplainLevel.NO_ATTRIBUTES) {
+      int j = 0;
+      for (Pair<String, @Nullable Object> value : values) {
+        if (value.right instanceof RelNode) {
+          continue;
+        }
+        if (j++ == 0) {
+          s.append("(");
+        } else {
+          s.append(", ");
+        }
+        s.append(value.left).append("=[").append(value.right).append("]");
+      }
+      if (j > 0) {
+        s.append(")");
+      }
+    }
+    switch (detailLevel) {
+      case ALL_ATTRIBUTES:
+        s.append(": rowcount = ")
+                .append(mq.getRowCount(rel))
+                .append(", cumulative cost = ")
+                .append(mq.getCumulativeCost(rel));
+        break;
+      default:
+        break;
+    }
+    switch (detailLevel) {
+      case NON_COST_ATTRIBUTES:
+      case ALL_ATTRIBUTES:
+        if (!withIdPrefix) {
+          // If we didn't print the rel id at the start of the line, print
+          // it at the end.
+          s.append(", id = ").append(rel.getId());
+        }
+        break;
+      default:
+        break;
+    }
+    Set<String> originInfo = new HashSet<>();
+    if (rel instanceof LogicalAggregate) {
+      LogicalAggregate aggregate = (LogicalAggregate) rel;
+      for (AggregateCall aggCall : aggregate.getAggCallList()) {
+        for (int expressionIndex : aggCall.getArgList()) {
+          originInfo.addAll(collectRelColumnOrigin(mq, rel, expressionIndex));
+        }
+      }
+    } else {
+      rel.accept(new RexShuttle() {
+        @Override
+        public RexNode visitInputRef(final RexInputRef inputRef) {
+          originInfo.addAll(collectRelColumnOrigin(mq, rel, inputRef.getIndex()));
+          return inputRef;
+        }
+      });
+    }
+    if (!originInfo.isEmpty()) {
+      s.append("\t{");
+      int index = 1;
+      for (String info : originInfo) {
+        s.append("[").append(info).append("]");
+        if (index < originInfo.size()) {
+          s.append(", ");
+        }
+        index++;
+      }
+      s.append("}");
+    }
+    pw.println(s);
+    spacer.add(2);
+    explainInputs(inputs);
+    spacer.subtract(2);
+  }
+
+  private void explainInputs(List<RelNode> inputs) {
+    for (RelNode input : inputs) {
+      input.explain(this);
+    }
+  }
+
+  @Override
+  public final void explain(RelNode rel, List<Pair<String, @Nullable Object>> valueList) {
+    explain_(rel, valueList);
+  }
+
+  @Override
+  public SqlExplainLevel getDetailLevel() {
+    return detailLevel;
+  }
+
+  @Override
+  public RelWriter item(String term, @Nullable Object value) {
+    values.add(Pair.of(term, value));
+    return this;
+  }
+
+  @Override
+  public RelWriter done(RelNode node) {
+    assert checkInputsPresentInExplain(node);
+    final List<Pair<String, @Nullable Object>> valuesCopy = ImmutableList.copyOf(values);
+    values.clear();
+    explain_(node, valuesCopy);
+    pw.flush();
+    return this;
+  }
+
+  private boolean checkInputsPresentInExplain(RelNode node) {
+    int i = 0;
+    if (values.size() > 0 && values.get(0).left.equals("subset")) {
+      ++i;
+    }
+    for (RelNode input : node.getInputs()) {
+      assert values.get(i).right == input;
+      ++i;
+    }
+    return true;
+  }
+
+  /**
+   * Converts the collected terms and values to a string. Does not write to
+   * the parent writer.
+   */
+  public String simple() {
+    final StringBuilder buf = new StringBuilder("(");
+    for (Ord<Pair<String, @Nullable Object>> ord : Ord.zip(values)) {
+      if (ord.i > 0) {
+        buf.append(", ");
+      }
+      buf.append(ord.e.left).append("=[").append(ord.e.right).append("]");
+    }
+    buf.append(")");
+    return buf.toString();
+  }
+}
diff --git a/java/thrift/calciteserver.thrift b/java/thrift/calciteserver.thrift
index 17460ed76d..0df1057fed 100644
--- a/java/thrift/calciteserver.thrift
+++ b/java/thrift/calciteserver.thrift
@@ -20,7 +20,7 @@ struct TPlanResult {
   2: i64 execution_time_ms;
      // these are the primary objects accessed in this query without resolving views
   3: TAccessedQueryObjects primary_accessed_objects;
-     // these are the accessed objects during this query after resolving all views 
+     // these are the accessed objects during this query after resolving all views
   4: TAccessedQueryObjects resolved_accessed_objects;
 }
 
@@ -41,6 +41,7 @@ struct TQueryParsingOption {
  1: bool legacy_syntax;
  2: bool is_explain;
  3: bool check_privileges;
+ 4: bool is_explain_detail;
 }
 
 struct TOptimizationOption {
diff --git a/java/utility/src/main/java/com/mapd/utility/SQLImporter.java b/java/utility/src/main/java/com/mapd/utility/SQLImporter.java
index 8daa566652..0cb1f93462 100644
--- a/java/utility/src/main/java/com/mapd/utility/SQLImporter.java
+++ b/java/utility/src/main/java/com/mapd/utility/SQLImporter.java
@@ -229,6 +229,11 @@ void printHelpMessage() {
                             + "use it only when casting is impossible")
                     .longOpt("AllowIntegerNarrowing")
                     .build());
+
+    options.addOption(Option.builder("nlj")
+                              .desc("Omit JDBC connection string from logs.")
+                              .longOpt("no-log-jdbc-connection-string")
+                              .build());
   }
 
   private Option setOptionRequired(Option option) {
@@ -352,7 +357,11 @@ void executeQuery() {
 
     try {
       // Open a connection
-      LOGGER.info("Connecting to database url :" + cmd.getOptionValue("jdbcConnect"));
+      if (cmd.hasOption("nlj")) {
+        LOGGER.info("Connecting to source database.");
+      } else {
+        LOGGER.info("Connecting to database url :" + cmd.getOptionValue("jdbcConnect"));
+      }
       conn = DriverManager.getConnection(cmd.getOptionValue("jdbcConnect"),
               cmd.getOptionValue("sourceUser"),
               cmd.getOptionValue("sourcePasswd"));
diff --git a/scripts/arch/arrow/PKGBUILD b/scripts/arch/arrow/PKGBUILD
index 27b944ce69..9184af8f61 100644
--- a/scripts/arch/arrow/PKGBUILD
+++ b/scripts/arch/arrow/PKGBUILD
@@ -1,66 +1,70 @@
-# Maintainer: Guillaume Horel <guillaume.horel@gmail.com>
+# Maintainer: Bruno Pagani <archange@archlinux.org>
+# Contributor: Guillaume Horel <guillaume.horel@gmail.com>
 
-pkgname='arrow'
-pkgver=5.0.0
-pkgrel=1
-pkgdesc="A columnar in-memory analytics layer for big data."
-arch=('x86_64')
+pkgname=arrow
+pkgver=13.0.0
+pkgrel=2
+pkgdesc="Columnar in-memory analytics layer for big data."
+arch=(x86_64)
 url="https://arrow.apache.org"
-license=('Apache')
-depends=('boost-libs' 'double-conversion' 'c-ares' 'gflags' 'google-glog' 'libutf8proc' 'lz4' 'protobuf' 'rapidjson' 'thrift' 'uriparser' 'zstd')
-checkdepends=('git')
-optdepends=()
-provides=('parquet-cpp')
-conflicts=('parquet-cpp')
-makedepends=('boost' 'cmake' 'flatbuffers' 'python-numpy')
-source=("https://github.com/apache/arrow/archive/apache-arrow-$pkgver.tar.gz")
-sha256sums=('ec3bdae6ccc08de5b5adcf9cbe7cbc085cab0ba06c8e6d3abfc3ed1cd4c1c9a2')
+license=(Apache)
+depends=(apache-orc brotli bzip2 gflags grpc google-glog libutf8proc
+         lz4 openssl protobuf re2 snappy thrift zlib zstd)
+provides=(parquet-cpp)
+conflicts=(parquet-cpp)
+makedepends=(boost clang cmake flatbuffers git gmock rapidjson xsimd)
+source=(https://archive.apache.org/dist/${pkgname}/${pkgname}-${pkgver}/apache-${pkgname}-${pkgver}.tar.gz{,.asc}
+        git+https://github.com/apache/parquet-testing.git
+        git+https://github.com/apache/arrow-testing.git)
+sha512sums=('3314d79ef20ac2cfc63f2c16fafb30c3f6187c10c6f5ea6ff036f6db766621d7c65401d85bf1e979bd0ecf831fbb0a785467642792d6bf77016f9807243c064e'
+            'SKIP'
+            'SKIP'
+            'SKIP')
+validpgpkeys=(265F80AB84FE03127E14F01125BCCA5220D84079  # Krisztian Szucs (apache) <szucs.krisztian@gmail.com>
+              08D3564B7C6A9CAFBFF6A66791D18FCF079F8007) # Kouhei Sutou <kou@cozmixng.org>
 
 build(){
-  cd "$srcdir"
-  patch -p1 $pkgname-apache-$pkgname-$pkgver/cpp/src/arrow/util/tdigest.cc < ../add-missing-limits-include.patch
-  mkdir -p build
-  cd "$srcdir/build"
-  ARROW_BUILD_TOOLCHAIN=/usr DOUBLE_CONVERSION_HOME=/usr cmake \
-    ../$pkgname-apache-$pkgname-$pkgver/cpp -DARROW_DEPENDENCY_SOURCE=SYSTEM \
-                                      -DCMAKE_GENERATOR="Unix Makefiles" \
-                                      -DARROW_PYTHON=ON \
-                                      -DCMAKE_BUILD_TYPE=Release \
-                                      -DBUILD_SHARED_LIBS=ON \
-                                      -DARROW_BUILD_TESTS=OFF \
-                                      -DARROW_ALTIVEC=OFF \
-                                      -DCMAKE_INSTALL_PREFIX="/usr" \
-                                      -DCMAKE_INSTALL_LIBDIR="lib" \
-                                      -DARROW_COMPUTE=ON \
-                                      -DARROW_IPC=ON \
-                                      -DARROW_JEMALLOC=ON \
-                                      -DARROW_ORC=OFF \
-                                      -DARROW_PARQUET=ON \
-                                      -DARROW_PLASMA=ON \
-                                      -DARROW_TENSORFLOW=ON \
-                                      -DARROW_FLIGHT=OFF \
-                                      -DARROW_GANDIVA=OFF \
-                                      -DARROW_PROTOBUF_USE_SHARED=ON \
-                                      -DARROW_GFLAGS_USE_SHARED=ON \
-                                      -DARROW_USE_GLOG=OFF \
-                                      -DARROW_WITH_ZSTD=BUNDLED \
-                                      -DARROW_WITH_SNAPPY=BUNDLED \
-                                      -DARROW_WITH_BROTLI=BUNDLED \
-                                      -DARROW_WITH_ZLIB=BUNDLED \
-                                      -DGTest_SOURCE=BUNDLED \
-                                      -DgRPC_SOURCE=BUNDLED \
-                                      -DARROW_S3=OFF \
-                                      -DARROW_FILESYSTEM=OFF \
-                                      -DARROW_CUDA=ON
+  CC=clang \
+  CXX=clang++ \
+  cmake \
+    -B build -S apache-${pkgname}-${pkgver}/cpp \
+    -DCMAKE_INSTALL_PREFIX="/usr" \
+    -DCMAKE_INSTALL_LIBDIR="lib" \
+    -DCMAKE_BUILD_TYPE=Release \
+    -DARROW_BUILD_STATIC=OFF \
+    -DARROW_DEPENDENCY_SOURCE=SYSTEM \
+    -DARROW_BUILD_TESTS=ON \
+    -DARROW_COMPUTE=ON \
+    -DARROW_CSV=ON \
+    -DARROW_SUBSTRAIT=ON \
+    -DARROW_FLIGHT=ON \
+    -DARROW_FLIGHT_SQL=ON \
+    -DARROW_GANDIVA=OFF \
+    -DARROW_HDFS=ON \
+    -DARROW_IPC=ON \
+    -DARROW_JEMALLOC=ON \
+    -DARROW_ORC=ON \
+    -DARROW_PARQUET=ON \
+    -DARROW_TENSORFLOW=ON \
+    -DARROW_USE_GLOG=ON \
+    -DARROW_WITH_BROTLI=ON \
+    -DARROW_WITH_BZ2=ON \
+    -DARROW_WITH_LZ4=ON \
+    -DARROW_WITH_SNAPPY=ON \
+    -DARROW_WITH_ZLIB=ON \
+    -DARROW_WITH_ZSTD=ON \
+    -DPARQUET_REQUIRE_ENCRYPTION=ON \
+    -Wno-dev
+  make -C build
+}
 
-  make -j $(nproc)
+check(){
+  PARQUET_TEST_DATA="${srcdir}"/parquet-testing/data \
+  ARROW_TEST_DATA="${srcdir}"/arrow-testing/data \
+  ctest --test-dir build --output-on-failure
 }
 
 package(){
-  cd "$srcdir/build"
-  make -j $(nproc) DESTDIR="${pkgdir}" install
-  find "${pkgdir}/usr/lib/" -name "*testing*" -delete
+  DESTDIR="$pkgdir" cmake --install build
+  find "${pkgdir}"/usr/lib/ -name '*testing*' -delete
 }
-
-
-# vim:ts=2:sw=2:et:
diff --git a/scripts/arch/arrow/add-missing-limits-include.patch b/scripts/arch/arrow/add-missing-limits-include.patch
deleted file mode 100644
index bd79446602..0000000000
--- a/scripts/arch/arrow/add-missing-limits-include.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 68a6ef128fd1fccfdb512a57c6e304364cb510c7 Mon Sep 17 00:00:00 2001
-From: Alex Baden <alex.baden@gmail.com>
-Date: Thu, 8 Jul 2021 12:22:01 -0600
-Subject: [PATCH] [ARROW-13290] Add missing include
-
----
- cpp/src/arrow/util/tdigest.cc | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/cpp/src/arrow/util/tdigest.cc b/cpp/src/arrow/util/tdigest.cc
-index b23bca397..1021f6e86 100644
---- a/cpp/src/arrow/util/tdigest.cc
-+++ b/cpp/src/arrow/util/tdigest.cc
-@@ -23,6 +23,7 @@
- #include <queue>
- #include <tuple>
- #include <vector>
-+#include <limits>
- 
- #include "arrow/status.h"
- 
--- 
-2.32.0
diff --git a/scripts/arch/arrow/heavyai.patch b/scripts/arch/arrow/heavyai.patch
new file mode 100644
index 0000000000..4706a9e971
--- /dev/null
+++ b/scripts/arch/arrow/heavyai.patch
@@ -0,0 +1,69 @@
+diff --git a/scripts/arch/arrow/PKGBUILD b/scripts/arch/arrow/PKGBUILD
+index 9184af8f6..b8e122df8 100644
+--- a/scripts/arch/arrow/PKGBUILD
++++ b/scripts/arch/arrow/PKGBUILD
+@@ -3,27 +3,27 @@
+ 
+ pkgname=arrow
+ pkgver=13.0.0
+-pkgrel=2
++pkgrel=100
+ pkgdesc="Columnar in-memory analytics layer for big data."
+ arch=(x86_64)
+ url="https://arrow.apache.org"
+ license=(Apache)
+-depends=(apache-orc brotli bzip2 gflags grpc google-glog libutf8proc
++depends=(brotli bzip2 gflags grpc libutf8proc
+          lz4 openssl protobuf re2 snappy thrift zlib zstd)
+ provides=(parquet-cpp)
+ conflicts=(parquet-cpp)
+-makedepends=(boost clang cmake flatbuffers git gmock rapidjson xsimd)
+-source=(https://archive.apache.org/dist/${pkgname}/${pkgname}-${pkgver}/apache-${pkgname}-${pkgver}.tar.gz{,.asc}
++makedepends=(boost cmake flatbuffers git gmock rapidjson xsimd)
++source=(https://archive.apache.org/dist/${pkgname}/${pkgname}-${pkgver}/apache-${pkgname}-${pkgver}.tar.gz
+         git+https://github.com/apache/parquet-testing.git
+         git+https://github.com/apache/arrow-testing.git)
+ sha512sums=('3314d79ef20ac2cfc63f2c16fafb30c3f6187c10c6f5ea6ff036f6db766621d7c65401d85bf1e979bd0ecf831fbb0a785467642792d6bf77016f9807243c064e'
+-            'SKIP'
+             'SKIP'
+             'SKIP')
+ validpgpkeys=(265F80AB84FE03127E14F01125BCCA5220D84079  # Krisztian Szucs (apache) <szucs.krisztian@gmail.com>
+               08D3564B7C6A9CAFBFF6A66791D18FCF079F8007) # Kouhei Sutou <kou@cozmixng.org>
+ 
+ build(){
++  export PATH=/usr/lib/llvm14/bin:$PATH
+   CC=clang \
+   CXX=clang++ \
+   cmake \
+@@ -37,16 +37,16 @@ build(){
+     -DARROW_COMPUTE=ON \
+     -DARROW_CSV=ON \
+     -DARROW_SUBSTRAIT=ON \
+-    -DARROW_FLIGHT=ON \
+-    -DARROW_FLIGHT_SQL=ON \
++    -DARROW_FLIGHT=OFF \
++    -DARROW_FLIGHT_SQL=OFF \
+     -DARROW_GANDIVA=OFF \
+     -DARROW_HDFS=ON \
+     -DARROW_IPC=ON \
+     -DARROW_JEMALLOC=ON \
+-    -DARROW_ORC=ON \
++    -DARROW_ORC=OFF \
+     -DARROW_PARQUET=ON \
+     -DARROW_TENSORFLOW=ON \
+-    -DARROW_USE_GLOG=ON \
++    -DARROW_USE_GLOG=OFF \
+     -DARROW_WITH_BROTLI=ON \
+     -DARROW_WITH_BZ2=ON \
+     -DARROW_WITH_LZ4=ON \
+@@ -54,6 +54,10 @@ build(){
+     -DARROW_WITH_ZLIB=ON \
+     -DARROW_WITH_ZSTD=ON \
+     -DPARQUET_REQUIRE_ENCRYPTION=ON \
++    -DARROW_ALTIVEC=OFF \
++    -DARROW_CUDA=ON \
++    -DARROW_JSON=ON \
++    -DBUILD_SHARED_LIBS=ON \
+     -Wno-dev
+   make -C build
+ }
diff --git a/scripts/arch/bison++/PKGBUILD b/scripts/arch/bison++/PKGBUILD
new file mode 100644
index 0000000000..9df8df3fad
--- /dev/null
+++ b/scripts/arch/bison++/PKGBUILD
@@ -0,0 +1,24 @@
+pkgname='bison++'
+pkgver=1.21
+pkgrel=1
+pkgdesc='Bison for C++'
+arch=('x86_64')
+url='https://code.google.com/archive/p/flexpp-bisonpp/'
+license=('GNU GPL v2')
+source=('https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/flexpp-bisonpp/bisonpp-1.21-45.tar.gz')
+sha256sums=('a6d2b04aa22c91c8b09cb2205c0a4148e289ea8e4127d8f05fb2e4c5b45ed791')
+
+build() {
+  cd "$srcdir/$pkgname-$pkgver"
+
+  ./configure
+  make
+}
+
+package() {
+  cd "$srcdir/$pkgname-$pkgver"
+
+  make prefix="$pkgdir/usr/" install
+}
+
+# vim:ts=2:sw=2:et:
diff --git a/scripts/arch/onedal/PKGBUILD b/scripts/arch/onedal/PKGBUILD
index 42b398528a..5f0f63a2ae 100644
--- a/scripts/arch/onedal/PKGBUILD
+++ b/scripts/arch/onedal/PKGBUILD
@@ -1,5 +1,5 @@
 pkgname='onedal'
-pkgver=2023.0.1
+pkgver=2023.1.1
 pkgrel=1
 pkgdesc="oneAPI Data Analytics Library (oneDAL)"
 arch=('x86_64')
@@ -11,42 +11,46 @@ optdepends=()
 makedepends=('cmake')
 options=('staticlibs')
 source=("https://github.com/oneapi-src/oneDAL/archive/refs/tags/$pkgver.tar.gz")
-sha256sums=('251e206d4568a09784838b73979b4e326f32091e36108bfe55b048359204b698')
+sha256sums=('38e4e6513a17382f994fb5934b3f1b4f4b291c4663e2e904c6871681ed44da48')
 
 prepare() {
   cd "$srcdir/oneDAL-$pkgver"
   ./dev/download_micromkl.sh
 
   # Remove #include of some headers marked as DAAL_DEPRECATED
-  sed -i '/^#include ".*\/bzip2compression\.h"$/d' cpp/daal/include/daal.h
-  sed -i '/^#include ".*\/zlibcompression\.h"$/d' cpp/daal/include/daal.h
+  #sed -i '/^#include ".*\/bzip2compression\.h"$/d' cpp/daal/include/daal.h
+  #sed -i '/^#include ".*\/zlibcompression\.h"$/d' cpp/daal/include/daal.h
 }
 
 build() {
   cd "$srcdir/oneDAL-$pkgver"
 
+  # building oneAPI triggers deprecated implicit copy constructor warnings, which fail the build
+  # due to -Werror, so add a -Wno-error=deprecated-copy flag to compilation command
+  sed -ie "/-Wreturn-type/s/$/ -Wno-error=deprecated-copy -Wno-error=dangling-reference/" dev/make/cmplr.gnu.mk
+
+  # Add -Wno-error=dangling-reference due to:
+  # ./cpp/oneapi/dal/algo/svm/detail/kernel_function.hpp:146:17: error: possibly dangling reference to a temporary [-Werror=dangling-reference]
+  #   146 |     const auto& kernel = kernel_function_accessor{}.get_kernel_impl(std::forward<Descriptor>(desc));
+  #       |                 ^~~~~~
+  # ./cpp/oneapi/dal/algo/svm/detail/kernel_function.hpp:146:68: note: the temporary was destroyed at the end of the full expression ‘oneapi::dal::svm::detail::v1::kernel_function_accessor().oneapi::dal::svm::detail::v1::kernel_function_accessor::get_kernel_impl<const oneapi::dal::svm::detail::v1::descriptor_base<oneapi::dal::svm::task::v1::nu_regression>&>((* & std::forward<const oneapi::dal::svm::detail::v1::descriptor_base<oneapi::dal::svm::task::v1::nu_regression>&>((* & desc))))’
+
   JAVA_HOME=/usr/lib/jvm/default \
   CPATH=$JAVA_HOME/include:$JAVA_HOME/include/linux \
   LIBRARY_PATH=/usr/lib \
   LD_LIBRARY_PATH=/usr/lib \
   TBBROOT=/usr \
-    make -f makefile _daal PLAT=lnx32e REQCPU="avx2" COMPILER=gnu -j
+    make -f makefile daal_c oneapi_c PLAT=lnx32e REQCPU="avx2 avx512" COMPILER=gnu -j
 }
 
 package() {
   cd "$srcdir/oneDAL-$pkgver"
 
-  find __work_gnu/md/lnx32e/daal/lib -name '*.d' -delete
-
   mkdir $pkgdir/usr
-  mv __release_lnx_gnu/daal/latest/include \
-     __work_gnu/md/lnx32e/daal/lib \
-     $pkgdir/usr/
-
-  # Install cmake files
-  CMAKE_DIR=$pkgdir/usr/lib/cmake/oneDAL
-  mkdir -p $CMAKE_DIR
-  cmake -D INSTALL_DIR=$CMAKE_DIR -P cmake/scripts/generate_config.cmake
+  mv __release_lnx_gnu/daal/latest/include $pkgdir/usr/include
+  mv __release_lnx_gnu/daal/latest/lib/intel64 $pkgdir/usr/lib
+  mv __release_lnx_gnu/daal/latest/lib/cmake $pkgdir/usr/lib/cmake
+  # __release_lnx_gnu/tbb/latest/lib/intel64 is also available but we use the arch onetbb package instead.
 }
 
 # vim:ts=2:sw=2:et:
diff --git a/scripts/common-functions.sh b/scripts/common-functions.sh
index 51cfd10185..cdebc2418c 100755
--- a/scripts/common-functions.sh
+++ b/scripts/common-functions.sh
@@ -99,7 +99,7 @@ function download_make_install() {
     check_artifact_cleanup $artifact_name $build_dir
 }
 
-CMAKE_VERSION=3.16.5
+CMAKE_VERSION=3.25.2
 
 function install_cmake() {
   CXXFLAGS="-pthread" CFLAGS="-pthread" download_make_install ${HTTP_DEPS}/cmake-${CMAKE_VERSION}.tar.gz
@@ -556,7 +556,7 @@ EOF
   fi
 }
 
-TBB_VERSION=2021.8.0
+TBB_VERSION=2021.9.0
 
 function install_tbb() {
   patch_old_thrust_tbb
@@ -673,34 +673,44 @@ function install_blosc() {
   check_artifact_cleanup  v${BLOSC_VERSION}.tar.gz $BDIR
 }
 
-oneDAL_VERSION=2023.0.1
+oneDAL_VERSION=2023.1.1
 function install_onedal() {
   download https://github.com/oneapi-src/oneDAL/archive/refs/tags/${oneDAL_VERSION}.tar.gz
   extract ${oneDAL_VERSION}.tar.gz
   pushd oneDAL-${oneDAL_VERSION}
   ./dev/download_micromkl.sh
-  if [ "$ID" == "ubuntu"  ] ; then
-    ONEDAL_LIBDIR="lib"
-  else
-    ONEDAL_LIBDIR="lib64"
+  if [ "$ID" != "ubuntu"  ] ; then
     # oneDAL makefile only detects libTBB built as shared lib, so we hack it to allow static built
     sed -i -E s/libtbb.so\(\.[1-9]+\)?/libtbb\.a/g makefile
     sed -i -E s/libtbbmalloc.so\(\.[1-9]+\)?/libtbbmalloc\.a/g makefile
 
+    # do not release shared library targets on CentOS
+    sed -i '/foreach t,\$(releasetbb\.LIBS_Y)/d' makefile
+    sed -i 's/$(core_y) \\//g' makefile
+    sed -i 's/:= $(oneapi_y)/:= /g' makefile
+    sed -i '/$(thr_$(i)_y))/d' makefile
+
     # oneDAL always builds static and shared versions of its libraries by default, so hack the
-    # makefile again to remove shared library building
-    sed -i 's/$(WORKDIR\.lib)\/$(core_y)//g' makefile
-    sed -i 's/$(WORKDIR\.lib)\/$(thr_tbb_y)//g' makefile
-    sed -i 's/\?= a y/\?= a/g' makefile
+    # makefile again to remove shared library building (the preceding spaces matter here)
+    sed -i 's/ $(WORKDIR\.lib)\/$(core_y)//g' makefile
+    sed -i 's/ $(WORKDIR\.lib)\/$(thr_tbb_y)//g' makefile
   fi
 
+  # oneDAL's makefile hardcodes its libTBB directory to /gcc4.8/, make it so it looks in the
+  # root PREFIX (where we install TBB)
+  sed -i 's/$(_IA)\/gcc4\.8//g' makefile
+
+  # building oneAPI triggers deprecated implicit copy constructor warnings, which fail the build
+  # due to -Werror, so add a -Wno-error=deprecated-copy flag to compilation command
+  sed -i -E s/\-Wreturn\-type/\-Wreturn\-type\ \-Wno\-error=deprecated\-copy/g dev/make/cmplr.gnu.mk
+
   # these exports will only be valid in the subshell that builds oneDAL
   (export TBBROOT=${PREFIX}; \
-   export LD_LIBRARY_PATH="${PREFIX}/${ONEDAL_LIBDIR}:${LD_LIBRARY_PATH}"; \
-   export LIBRARY_PATH="${PREFIX}/${ONEDAL_LIBDIR}:${LIBRARY_PATH}"; \
+   export LD_LIBRARY_PATH="${PREFIX}/lib64:${PREFIX}/lib:${LD_LIBRARY_PATH}"; \
+   export LIBRARY_PATH="${PREFIX}/lib64:${PREFIX}/lib:${LIBRARY_PATH}"; \
    export CPATH="${PREFIX}/include:${CPATH}"; \
    export PATH="${PREFIX}/bin:${PATH}"; \
-   make -f makefile _daal PLAT=lnx32e REQCPU="avx2 avx" COMPILER=gnu -j)
+   make -f makefile daal_c oneapi_c PLAT=lnx32e REQCPU="avx2 avx512" COMPILER=gnu -j)
 
   # remove deprecated compression methods as they generate DEPRECATED warnings/errors
   sed -i '/bzip2compression\.h/d' __release_lnx_gnu/daal/latest/include/daal.h
@@ -708,10 +718,9 @@ function install_onedal() {
 
   mkdir -p $PREFIX/include
   cp -r __release_lnx_gnu/daal/latest/include/* $PREFIX/include
-  cp -r __work_gnu/md/lnx32e/daal/lib/* $PREFIX/lib
-  cmake -P cmake/scripts/generate_config.cmake
+  cp -r __release_lnx_gnu/daal/latest/lib/intel64/* $PREFIX/lib
   mkdir -p ${PREFIX}/lib/cmake/oneDAL
-  cp cmake/*.cmake ${PREFIX}/lib/cmake/oneDAL/.
+  cp __release_lnx_gnu/daal/latest/lib/cmake/oneDAL/*.cmake ${PREFIX}/lib/cmake/oneDAL/.
   popd
   check_artifact_cleanup ${oneDAL_VERSION}.tar.gz oneDAL-${oneDAL_VERSION}
 }
diff --git a/scripts/ee/start_heavyiq.sh b/scripts/ee/start_heavyiq.sh
index 1cdff25558..3836b24402 100755
--- a/scripts/ee/start_heavyiq.sh
+++ b/scripts/ee/start_heavyiq.sh
@@ -35,11 +35,12 @@ if [[ "$HAS_PYTHON_3_10" != "false" ]]; then
     source .venv/bin/activate
   fi
   GUNICORN_LOG_FILE=${HEAVYIQ_LOG_PREFIX}_gunicorn.log
-  gunicorn --preload --log-file $GUNICORN_LOG_FILE --capture-output -t 0 -w 4 -b :$MAPD_HEAVYIQ_PORT \
-    "heavynl.api:get_app(config_path=\"$CONFIG_FILE_ABS_PATH\")" & # TODO: Update module to heavyiq
+  gunicorn --preload --log-file $GUNICORN_LOG_FILE --capture-output -t 0 -w 4 -k uvicorn.workers.UvicornWorker \
+  -b :$MAPD_HEAVYIQ_PORT "heavyiq.api:create_app(config_path=\"$CONFIG_FILE_ABS_PATH\")" &
   PID3=$!
   sleep 5
   if [[ $(ps -h -p $PID3) ]]; then
+    echo "HeavyIQ HTTP:  localhost:${MAPD_HEAVYIQ_PORT}"
     echo "- heavy_iq $PID3 started"
   else
     echo "Warning: An error occurred when starting up HeavyIQ. See the $GUNICORN_LOG_FILE logs for more details."
diff --git a/scripts/innerstartheavy b/scripts/innerstartheavy
index 5e962af64d..e889a3737c 100755
--- a/scripts/innerstartheavy
+++ b/scripts/innerstartheavy
@@ -32,6 +32,8 @@ export MAPD_HEAVYIQ_PORT=${MAPD_HEAVYIQ_PORT:=6275}
 
 export MAPD_DATA=${MAPD_DATA:="storage"}
 
+FORWARDED_ARGS=()
+
 while (( $# )); do
     case "$1" in
         --read-only)
@@ -73,7 +75,8 @@ while (( $# )); do
         --verbose)
             VERBOSE="--verbose" ;;
         *)
-            break ;;
+            FORWARDED_ARGS+=("$1")
+            ;;
     esac
     shift
 done
@@ -82,7 +85,6 @@ echo "Backend TCP:  localhost:${MAPD_TCP_PORT}"
 echo "Backend HTTP: localhost:${MAPD_HTTP_PORT}"
 echo "Frontend Web: localhost:${MAPD_WEB_PORT}"
 echo "Calcite TCP:  localhost:${MAPD_CALCITE_PORT}"
-echo "HeavyIQ HTTP:  localhost:${MAPD_HEAVYIQ_PORT}"
 
 if [ ! -d $MAPD_DATA ]; then
     mkdir -p $MAPD_DATA
@@ -126,7 +128,7 @@ else
     export VK_ICD_FILENAMES=$icd_path
 fi
 
-./bin/heavydb $MAPD_DATA $RO --port $MAPD_TCP_PORT --http-port $MAPD_HTTP_PORT --calcite-port $MAPD_CALCITE_PORT $CONFIG $VERBOSE $* &
+./bin/heavydb $MAPD_DATA $RO --port $MAPD_TCP_PORT --http-port $MAPD_HTTP_PORT --calcite-port $MAPD_CALCITE_PORT $CONFIG $VERBOSE ${FORWARDED_ARGS[@]} &
 PID1=$!
 echo "- heavydb $PID1 started"
 
@@ -171,7 +173,7 @@ if [ -d frontend ] && [ -e ./bin/heavy_web_server ]; then
     fi
 fi
 
-if [[ -d heavyiq ]]; then
+if [[ -d heavyiq && -f "$CONFIG_FILE" ]] && grep -qF "[iq]" "$CONFIG_FILE"; then
   # Execute the HeavyIQ startup script in a subshell in order to avoid
   # possible conflicts from virtual environment activation.
   ($DIR/scripts/ee/start_heavyiq.sh)
diff --git a/scripts/mapd-deps-arch.sh b/scripts/mapd-deps-arch.sh
index cc0e48ac7c..5433cb2e30 100755
--- a/scripts/mapd-deps-arch.sh
+++ b/scripts/mapd-deps-arch.sh
@@ -38,8 +38,9 @@ yay -Suy \
     blosc \
     boost \
     c-ares \
-    clang \
+    clang14 \
     cmake \
+    compiler-rt14 \
     cuda \
     doxygen \
     flex \
@@ -55,7 +56,7 @@ yay -Suy \
     jdk-openjdk \
     libiodbc \
     librdkafka \
-    llvm \
+    llvm14 \
     lz4 \
     maven \
     ninja \
@@ -70,6 +71,7 @@ yay -Suy \
 
 # Install Arrow
 ( cd arch/arrow
+  patch -p4 < heavyai.patch
   makepkg -cis
 )
 
@@ -78,11 +80,7 @@ yay -Suy \
   makepkg -cis
 )
 
-# Install Bison++ from source
-wget --continue https://dependencies.mapd.com/thirdparty/bisonpp-1.21-45.tar.gz
-tar xvf bisonpp-1.21-45.tar.gz
-pushd bison++-1.21
-./configure
-make -j $(nproc)
-sudo make install
-popd
+# Install Bison++
+( cd arch/bison++
+  makepkg -cis
+)
diff --git a/scripts/mapd-deps-centos.sh b/scripts/mapd-deps-centos.sh
index 408f23e95b..73dbc91d38 100755
--- a/scripts/mapd-deps-centos.sh
+++ b/scripts/mapd-deps-centos.sh
@@ -182,7 +182,7 @@ download_make_install ${HTTP_DEPS}/libpng-$VERS.tar.xz
 
 install_snappy
  
-VERS=3.52.15
+VERS=3.52.16
 CFLAGS="-fPIC" CXXFLAGS="-fPIC" download_make_install ${HTTP_DEPS}/libiodbc-${VERS}.tar.gz
 
 # c-blosc
diff --git a/scripts/mapd-deps-prebuilt.sh b/scripts/mapd-deps-prebuilt.sh
index aa68495b2b..b443ed12e2 100755
--- a/scripts/mapd-deps-prebuilt.sh
+++ b/scripts/mapd-deps-prebuilt.sh
@@ -51,12 +51,6 @@ done
 if [ "$ID" == "ubuntu" ] ; then
   sudo $PACKAGER update
 
-  # required for gcc-11 on Ubuntu < 22.04
-  if [ "$VERSION_ID" == "20.04" ] || [ "$VERSION_ID" == "19.10" ] || [ "$VERSION_ID" == "19.04" ] || [ "$VERSION_ID" == "18.04" ]; then
-    DEBIAN_FRONTEND=noninteractive sudo apt install -y software-properties-common
-    DEBIAN_FRONTEND=noninteractive sudo add-apt-repository ppa:ubuntu-toolchain-r/test
-  fi
-
   sudo $PACKAGER install \
       software-properties-common \
       build-essential \
@@ -64,8 +58,6 @@ if [ "$ID" == "ubuntu" ] ; then
       git \
       wget \
       curl \
-      gcc-11 \
-      g++-11 \
       libboost-all-dev \
       golang \
       libssl-dev \
@@ -108,6 +100,15 @@ if [ "$ID" == "ubuntu" ] ; then
       libxerces-c-dev \
       swig
 
+  # required for gcc-11 on Ubuntu < 22.04
+  if [ "$VERSION_ID" == "20.04" ] || [ "$VERSION_ID" == "19.04" ] || [ "$VERSION_ID" == "18.04" ]; then
+    DEBIAN_FRONTEND=noninteractive sudo add-apt-repository ppa:ubuntu-toolchain-r/test
+  fi
+
+  sudo $PACKAGER install \
+      gcc-11 \
+      g++-11
+
 # Set up gcc-11 as default gcc
 sudo update-alternatives \
   --install /usr/bin/gcc gcc /usr/bin/gcc-11 1100 \
diff --git a/scripts/mapd-deps-ubuntu.sh b/scripts/mapd-deps-ubuntu.sh
index ae71fe7448..3927b00270 100755
--- a/scripts/mapd-deps-ubuntu.sh
+++ b/scripts/mapd-deps-ubuntu.sh
@@ -78,12 +78,6 @@ DEBIAN_FRONTEND=noninteractive sudo apt-key adv --fetch-keys https://developer.d
 
 DEBIAN_FRONTEND=noninteractive sudo apt update
 
-# required for gcc-11 on Ubuntu < 22.04
-if [ "$VERSION_ID" == "20.04" ] || [ "$VERSION_ID" == "19.10" ] || [ "$VERSION_ID" == "19.04" ] || [ "$VERSION_ID" == "18.04" ]; then
-  DEBIAN_FRONTEND=noninteractive sudo apt install -y software-properties-common
-  DEBIAN_FRONTEND=noninteractive sudo add-apt-repository ppa:ubuntu-toolchain-r/test
-fi
-
 DEBIAN_FRONTEND=noninteractive sudo apt install -y \
     software-properties-common \
     build-essential \
@@ -91,8 +85,6 @@ DEBIAN_FRONTEND=noninteractive sudo apt install -y \
     git \
     wget \
     curl \
-    gcc-11 \
-    g++-11 \
     libboost-all-dev \
     libssl-dev \
     libevent-dev \
@@ -141,6 +133,15 @@ DEBIAN_FRONTEND=noninteractive sudo apt install -y \
     libxcursor-dev \
     libxi-dev
 
+# required for gcc-11 on Ubuntu < 22.04
+if [ "$VERSION_ID" == "20.04" ] || [ "$VERSION_ID" == "19.04" ] || [ "$VERSION_ID" == "18.04" ]; then
+  DEBIAN_FRONTEND=noninteractive sudo add-apt-repository ppa:ubuntu-toolchain-r/test
+fi
+
+sudo $PACKAGER install \
+  gcc-11 \
+  g++-11
+
 # Set up gcc-11 as default gcc
 sudo update-alternatives \
   --install /usr/bin/gcc gcc /usr/bin/gcc-11 1100 \
@@ -189,7 +190,7 @@ install_awscpp -j $(nproc)
 # thrift
 install_thrift
 
-VERS=3.52.15
+VERS=3.52.16
 CFLAGS="-fPIC" CXXFLAGS="-fPIC" download_make_install ${HTTP_DEPS}/libiodbc-${VERS}.tar.gz
 
 install_folly


Test patch:
diff --git a/QueryEngine/scripts/test_udtf_signatures.hpp b/QueryEngine/scripts/test_udtf_signatures.hpp
index 57b2ad3cd7..cad004ebf5 100644
--- a/QueryEngine/scripts/test_udtf_signatures.hpp
+++ b/QueryEngine/scripts/test_udtf_signatures.hpp
@@ -162,5 +162,8 @@
   UDTF: foo_54(Cursor<ColumnList<T>, Column<T>>, Cursor<ColumnList<T>>) -> Column<T>, T=[int64_t] $=>$
         foo_54(Cursor<ColumnListInt64, ColumnInt64> | fields=[field0,field1], Cursor<ColumnListInt64> | fields=[field0]) -> ColumnInt64
 
+  UDTF: foo_55(Cursor<Column<int> x | require="x.size() > 2">) -> Column<int> $=>$
+        foo_55(Cursor<ColumnInt32 | name=x | require="x.size() > 2"> | fields=[x] | require="x.size() > 2") -> ColumnInt32
+
  */
 // clang-format on
diff --git a/config/odbc-fsi-test.asan.suppressions b/config/odbc-fsi-test.asan.suppressions
index 8b2bd25a68..ad8aa85a55 100644
--- a/config/odbc-fsi-test.asan.suppressions
+++ b/config/odbc-fsi-test.asan.suppressions
@@ -2,3 +2,4 @@
 leak:libamazonredshiftodbc64
 leak:liblzma
 leak:libpgodbc
+leak:libclouderahiveodbc64
diff --git a/java/calcite/src/test/java/com/heavydb/parser/extension/ddl/DDLTest.java b/java/calcite/src/test/java/com/heavydb/parser/extension/ddl/DDLTest.java
index 35ff26fe7f..fc6eaae23c 100644
--- a/java/calcite/src/test/java/com/heavydb/parser/extension/ddl/DDLTest.java
+++ b/java/calcite/src/test/java/com/heavydb/parser/extension/ddl/DDLTest.java
@@ -37,6 +37,7 @@ TPlanResult processDdlCommand(final String ddlCommand) throws Exception {
     queryParsingOption.legacy_syntax = false;
     queryParsingOption.is_explain = false;
     queryParsingOption.check_privileges = false;
+    queryParsingOption.is_explain_detail = false;
 
     TOptimizationOption optimizationOption = new TOptimizationOption();
     optimizationOption.is_view_optimize = false;
diff --git a/java/calcite/src/test/java/com/heavydb/parser/server/test/TestServer.java b/java/calcite/src/test/java/com/heavydb/parser/server/test/TestServer.java
index 14e2f77a2b..47bdd42f20 100644
--- a/java/calcite/src/test/java/com/heavydb/parser/server/test/TestServer.java
+++ b/java/calcite/src/test/java/com/heavydb/parser/server/test/TestServer.java
@@ -252,6 +252,7 @@ private void callCalciteCheck(String query, String result) {
       queryParsingOption.legacy_syntax = false;
       queryParsingOption.is_explain = false;
       queryParsingOption.check_privileges = false;
+      queryParsingOption.is_explain_detail = false;
 
       TOptimizationOption optimizationOption = new TOptimizationOption();
       optimizationOption.is_view_optimize = false;
diff --git a/java/utility/src/main/java/com/mapd/tests/CatalogConcurrencyTest.java b/java/utility/src/main/java/com/mapd/tests/CatalogConcurrencyTest.java
index 7df3f33bbd..7eed959b55 100644
--- a/java/utility/src/main/java/com/mapd/tests/CatalogConcurrencyTest.java
+++ b/java/utility/src/main/java/com/mapd/tests/CatalogConcurrencyTest.java
@@ -18,9 +18,14 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
+import java.util.*;
+
+import ai.heavy.thrift.server.*;
 
 public class CatalogConcurrencyTest {
+  final static String defPwd = "HyperInteractive", local = "localhost", defDb = "heavyai",
+                      admin = "admin";
+  final static int port = 6274;
   final static Logger logger = LoggerFactory.getLogger(CatalogConcurrencyTest.class);
 
   public static void main(String[] args) throws Exception {
@@ -28,33 +33,39 @@ public static void main(String[] args) throws Exception {
     test.testCatalogConcurrency();
   }
 
-  private void run_test(
-          HeavyDBTestClient dba, HeavyDBTestClient user, String prefix, int max)
-          throws Exception {
+  private void run_test(HeavyDBTestClient dba,
+          HeavyDBTestClient user,
+          String prefix,
+          int max,
+          List<Integer> dashboardIds) throws Exception {
     final String sharedTableName = "table_shared";
     for (int i = 0; i < max; i++) {
-      String tableName = "table_" + prefix + "_" + i;
-      String viewName = "view_" + prefix + "_" + i;
-      String dashName = "dash_" + prefix + "_" + i;
-      long tid = Thread.currentThread().getId();
+      final long tid = Thread.currentThread().getId();
+      final String threadPrefix = "[" + tid + "] ",
+                   tableName = "table_" + prefix + "_" + i,
+                   viewName = "view_" + prefix + "_" + i,
+                   dashName = "dash_" + prefix + "_" + i;
+
+      // Modify the fixed id dashboards in parallel.
+      for (int id : dashboardIds) {
+        TDashboard board = dba.get_dashboard(id);
+        logger.info("REPLACE DASHBOARD id (" + id + ") " + board.dashboard_name);
+        dba.replace_dashboard(board.dashboard_id, board.dashboard_name + "_", admin);
+      }
 
-      logger.info("[" + tid + "]"
-              + "CREATE " + tableName);
+      logger.info(threadPrefix + "CREATE TABLE " + tableName);
       user.runSql("CREATE TABLE " + tableName + " (id text);");
       HeavyDBAsserts.assertEqual(true, null != dba.get_table_details(tableName));
-      logger.info("[" + tid + "]"
-              + "INSERT INTO " + tableName);
+      logger.info(threadPrefix + "INSERT INTO " + tableName);
       user.runSql("INSERT INTO " + tableName + " VALUES(1);");
       dba.runSql("GRANT SELECT ON TABLE " + tableName + " TO bob;");
 
-      logger.info("[" + tid + "]"
-              + "CREATE " + viewName);
+      logger.info(threadPrefix + "CREATE VIEW " + viewName);
       user.runSql("CREATE VIEW " + viewName + " AS SELECT * FROM " + tableName + ";");
       HeavyDBAsserts.assertEqual(true, null != dba.get_table_details(viewName));
       dba.runSql("GRANT SELECT ON VIEW " + viewName + " TO bob;");
 
-      logger.info("[" + tid + "]"
-              + "CREATE " + dashName);
+      logger.info(threadPrefix + "CREATE DASHBOARD " + dashName);
       int dash_id = user.create_dashboard(dashName);
       HeavyDBAsserts.assertEqual(true, null != dba.get_dashboard(dash_id));
       dba.runSql("GRANT VIEW ON DASHBOARD " + dash_id + " TO bob;");
@@ -63,31 +74,28 @@ private void run_test(
       dba.runSql("REVOKE SELECT ON VIEW " + viewName + " FROM bob;");
       dba.runSql("REVOKE SELECT ON TABLE " + tableName + " FROM bob;");
 
-      logger.info("[" + tid + "]"
-              + "DROP " + dashName);
+      logger.info(threadPrefix + "DELETE DASHBOARD " + dashName);
       dba.delete_dashboard(dash_id);
-      logger.info("[" + tid + "]"
-              + "DROP " + viewName);
+      logger.info(threadPrefix + "DROP VIEW " + viewName);
       dba.runSql("DROP VIEW " + viewName + ";");
-      logger.info("[" + tid + "]"
-              + "DROP " + tableName);
+      logger.info(threadPrefix + "DROP TABLE " + tableName);
       dba.runSql("DROP TABLE " + tableName + ";");
 
-      logger.info("[" + tid + "]"
-              + "CREATE IF NOT EXISTS " + sharedTableName);
+      logger.info(threadPrefix + "CREATE IF NOT EXISTS " + sharedTableName);
       dba.runSql("CREATE TABLE IF NOT EXISTS " + sharedTableName + " (id INTEGER);");
 
-      logger.info("[" + tid + "]"
-              + "DROP IF EXISTS " + sharedTableName);
+      logger.info(threadPrefix + "DROP IF EXISTS " + sharedTableName);
       dba.runSql("DROP TABLE IF EXISTS " + sharedTableName + ";");
     }
   }
 
-  private void runTest(
-          String db, String dbaUser, String dbaPassword, String dbUser, String dbPassword)
-          throws Exception {
-    int num_threads = 5;
-    final int runs = 25;
+  private void runTest(String db,
+          String dbaUser,
+          String dbaPassword,
+          String dbUser,
+          String dbPassword,
+          List<Integer> dashboardIds) throws Exception {
+    final int num_threads = 5, runs = 25;
     Exception exceptions[] = new Exception[num_threads];
 
     ArrayList<Thread> threads = new ArrayList<>();
@@ -99,11 +107,11 @@ private void runTest(
         @Override
         public void run() {
           try {
-            HeavyDBTestClient dba = HeavyDBTestClient.getClient(
-                    "localhost", 6274, db, dbaUser, dbaPassword);
-            HeavyDBTestClient user = HeavyDBTestClient.getClient(
-                    "localhost", 6274, db, dbUser, dbPassword);
-            run_test(dba, user, prefix, runs);
+            HeavyDBTestClient dba =
+                    HeavyDBTestClient.getClient(local, port, db, dbaUser, dbaPassword);
+            HeavyDBTestClient user =
+                    HeavyDBTestClient.getClient(local, port, db, dbUser, dbPassword);
+            run_test(dba, user, prefix, runs, dashboardIds);
           } catch (Exception e) {
             logger.error("[" + Thread.currentThread().getId() + "]"
                             + "Caught Exception: " + e.getMessage(),
@@ -131,8 +139,12 @@ public void run() {
   public void testCatalogConcurrency() throws Exception {
     logger.info("testCatalogConcurrency()");
 
-    HeavyDBTestClient su = HeavyDBTestClient.getClient(
-            "localhost", 6274, "heavyai", "admin", "HyperInteractive");
+    HeavyDBTestClient su = HeavyDBTestClient.getClient(local, port, defDb, admin, defPwd);
+
+    su.runSql("DROP USER IF EXISTS bob;");
+    su.runSql("DROP USER IF EXISTS dba;");
+    su.runSql("DROP DATABASE IF EXISTS db1;");
+
     su.runSql("CREATE USER dba (password = 'password', is_super = 'true');");
     su.runSql("CREATE USER bob (password = 'password', is_super = 'false');");
 
@@ -159,18 +171,37 @@ public void testCatalogConcurrency() throws Exception {
     su.runSql("GRANT ACCESS on database db1 TO dba;");
     su.runSql("GRANT ACCESS on database db1 TO bob;");
 
-    runTest("db1", "admin", "HyperInteractive", "admin", "HyperInteractive");
-    runTest("db1", "admin", "HyperInteractive", "dba", "password");
-    runTest("db1", "admin", "HyperInteractive", "bob", "password");
-    runTest("db1", "dba", "password", "admin", "HyperInteractive");
-    runTest("db1", "dba", "password", "bob", "password");
-
-    runTest("heavyai", "admin", "HyperInteractive", "admin", "HyperInteractive");
-    runTest("heavyai", "admin", "HyperInteractive", "dba", "password");
-    runTest("heavyai", "admin", "HyperInteractive", "bob", "password");
-    runTest("heavyai", "dba", "password", "admin", "HyperInteractive");
-    runTest("heavyai", "dba", "password", "bob", "password");
-
+    // We create a series of dashboards with fixed ids to be modified in parallel.
+    HeavyDBTestClient dba =
+            HeavyDBTestClient.getClient(local, port, "db1", admin, defPwd);
+    for (TDashboard board : dba.get_dashboards()) {
+      logger.info("DROP DASHBOARD " + board.dashboard_name);
+      dba.delete_dashboard(board.dashboard_id);
+    }
+    ArrayList<Integer> dashboardIds = new ArrayList<>();
+    for (int i = 0; i < 5; ++i) {
+      String dashName = "dash_" + i;
+      logger.info("CREATE DASHBOARD " + dashName);
+      dashboardIds.add(dba.create_dashboard(dashName));
+    }
+    HeavyDBAsserts.assertEqual(5, dba.get_dashboards().size());
+
+    runTest("db1", admin, defPwd, admin, defPwd, dashboardIds);
+    runTest("db1", admin, defPwd, "dba", "password", dashboardIds);
+    runTest("db1", admin, defPwd, "bob", "password", dashboardIds);
+    runTest("db1", "dba", "password", admin, defPwd, dashboardIds);
+    runTest("db1", "dba", "password", "bob", "password", dashboardIds);
+
+    runTest(defDb, admin, defPwd, admin, defPwd, dashboardIds);
+    runTest(defDb, admin, defPwd, "dba", "password", dashboardIds);
+    runTest(defDb, admin, defPwd, "bob", "password", dashboardIds);
+    runTest(defDb, "dba", "password", admin, defPwd, dashboardIds);
+    runTest(defDb, "dba", "password", "bob", "password", dashboardIds);
+
+    for (TDashboard board : dba.get_dashboards()) {
+      logger.info("DROP DASHBOARD " + board.dashboard_name);
+      dba.delete_dashboard(board.dashboard_id);
+    }
     su.runSql("DROP DATABASE db1;");
     su.runSql("DROP USER bob;");
     su.runSql("DROP USER dba;");