You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by st...@apache.org on 2020/10/16 09:02:38 UTC

[impala] 03/03: IMPALA-10233: zorder sort node should output rows in lexical order of partition keys

This is an automated email from the ASF dual-hosted git repository.

stigahuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit faa2d398e647c0347f81af08d7e4fa4d4a7acf72
Author: stiga-huang <hu...@gmail.com>
AuthorDate: Tue Oct 13 17:50:23 2020 +0800

    IMPALA-10233: zorder sort node should output rows in lexical order of partition keys
    
    When inserting to a partitioned hdfs table, the planner will add a sort
    node on top of the plan, depending on the clustered/noclustered plan
    hint and on the 'sort.columns' table property. If clustering is enabled
    in insertStmt or additional columns are specified in the 'sort.columns'
    table property, then the ordering columns will start with the clustering
    columns, so that partitions can be written sequentially in the table
    sink. Any additional non-clustering columns specified by the
    'sort.columns' property will be added to the ordering columns and after
    any clustering columns.
    
    For Z-order sort type, we should deal with these ordering columns
    separately. The clustering columns should still be sorted lexically, and
    only the remaining ordering columns be sorted in Z-order. So we can
    still insert partitions one by one and avoid hitting the DCHECK as
    described in the JIRA.
    
    Tests
     - Add tests for inserting to a partitioned table with zorder.
    
    Change-Id: I30cbad711167b8b63c81837e497b36fd41be9b54
    Reviewed-on: http://gerrit.cloudera.org:8080/16590
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 be/src/runtime/dml-exec-state.cc                   |  3 +-
 be/src/util/tuple-row-compare.cc                   | 40 +++++++----
 be/src/util/tuple-row-compare.h                    | 26 ++++++-
 common/thrift/PlanNodes.thrift                     |  4 ++
 .../java/org/apache/impala/analysis/SortInfo.java  | 11 ++-
 .../org/apache/impala/planner/ExchangeNode.java    |  2 +-
 .../java/org/apache/impala/planner/PlanNode.java   | 17 ++++-
 .../java/org/apache/impala/planner/Planner.java    |  8 ++-
 .../java/org/apache/impala/planner/SortNode.java   |  4 +-
 .../queries/PlannerTest/insert-sort-by-zorder.test | 82 +++++++++++++++++++---
 .../functional-query/queries/QueryTest/insert.test | 59 ++++++++++++++++
 11 files changed, 220 insertions(+), 36 deletions(-)

diff --git a/be/src/runtime/dml-exec-state.cc b/be/src/runtime/dml-exec-state.cc
index d66455e..d9a4880 100644
--- a/be/src/runtime/dml-exec-state.cc
+++ b/be/src/runtime/dml-exec-state.cc
@@ -429,7 +429,8 @@ void DmlExecState::ToTDmlResult(TDmlResult* dml_result) {
 void DmlExecState::AddPartition(
     const string& name, int64_t id, const string* base_dir) {
   lock_guard<mutex> l(lock_);
-  DCHECK(per_partition_status_.find(name) == per_partition_status_.end());
+  DCHECK(per_partition_status_.find(name) == per_partition_status_.end())
+      << "Partition status of " << name << " already exists";
   DmlPartitionStatusPB status;
   status.set_num_modified_rows(0L);
   status.set_id(id);
diff --git a/be/src/util/tuple-row-compare.cc b/be/src/util/tuple-row-compare.cc
index ddc0ea4..61ec6bd 100644
--- a/be/src/util/tuple-row-compare.cc
+++ b/be/src/util/tuple-row-compare.cc
@@ -42,6 +42,7 @@ TupleRowComparatorConfig::TupleRowComparatorConfig(
     const TSortInfo& tsort_info, const std::vector<ScalarExpr*>& ordering_exprs)
   : sorting_order_(tsort_info.sorting_order),
     ordering_exprs_(ordering_exprs),
+    num_lexical_keys_(tsort_info.num_lexical_keys_in_zorder),
     is_asc_(tsort_info.is_asc_order) {
   if (sorting_order_ == TSortingOrder::ZORDER) return;
   for (bool null_first : tsort_info.nulls_first) {
@@ -353,17 +354,26 @@ int TupleRowZOrderComparator::CompareInterpreted(const TupleRow* lhs,
   DCHECK_EQ(ordering_exprs_.size(), ordering_expr_evals_lhs_.size());
   DCHECK_EQ(ordering_expr_evals_lhs_.size(), ordering_expr_evals_rhs_.size());
 
-  // The algorithm requires all values having a common type, without loss of data.
-  // This means we have to find the biggest type.
-  int max_size = ordering_exprs_[0]->type().GetByteSize();
-  for (int i = 1; i < ordering_exprs_.size(); ++i) {
-    if (ordering_exprs_[i]->type().GetByteSize() > max_size) {
-      max_size = ordering_exprs_[i]->type().GetByteSize();
-    }
+  // Sort the partition keys lexically. The PreInsert SortNode uses ASC order and NULLS
+  // LAST. See Planner.createPreInsertSort() in FE.
+  for (int i = 0; i < num_lexical_keys_; ++i) {
+    void* lhs_value = ordering_expr_evals_lhs_[i]->GetValue(lhs);
+    void* rhs_value = ordering_expr_evals_rhs_[i]->GetValue(rhs);
+
+    // The sort order of NULLs is independent of asc/desc.
+    if (lhs_value == NULL && rhs_value == NULL) continue;
+    if (lhs_value == NULL) return 1;
+    if (rhs_value == NULL) return -1;
+
+    int result = RawValue::Compare(lhs_value, rhs_value, ordering_exprs_[i]->type());
+    if (result != 0) return result;
+    // Otherwise, try the next Expr
   }
-  if (max_size <= 4) {
+
+  // Sort the remaining keys in Z-order.
+  if (max_col_size_ <= 4) {
     return CompareBasedOnSize<uint32_t>(lhs, rhs);
-  } else if (max_size <= 8) {
+  } else if (max_col_size_ <= 8) {
     return CompareBasedOnSize<uint64_t>(lhs, rhs);
   } else {
     return CompareBasedOnSize<uint128_t>(lhs, rhs);
@@ -374,13 +384,13 @@ template<typename U>
 int TupleRowZOrderComparator::CompareBasedOnSize(const TupleRow* lhs,
     const TupleRow* rhs) const {
   auto less_msb = [](U x, U y) { return x < y && x < (x ^ y); };
-  ColumnType type = ordering_exprs_[0]->type();
+  ColumnType type = ordering_exprs_[num_lexical_keys_]->type();
   // Values of the most significant dimension from both sides.
-  U msd_lhs = GetSharedRepresentation<U>(ordering_expr_evals_lhs_[0]->GetValue(lhs),
-      type);
-  U msd_rhs = GetSharedRepresentation<U>(ordering_expr_evals_rhs_[0]->GetValue(rhs),
-      type);
-  for (int i = 1; i < ordering_exprs_.size(); ++i) {
+  U msd_lhs = GetSharedRepresentation<U>(
+      ordering_expr_evals_lhs_[num_lexical_keys_]->GetValue(lhs), type);
+  U msd_rhs = GetSharedRepresentation<U>(
+      ordering_expr_evals_rhs_[num_lexical_keys_]->GetValue(rhs), type);
+  for (int i = num_lexical_keys_ + 1; i < ordering_exprs_.size(); ++i) {
     type = ordering_exprs_[i]->type();
     void* lhs_v = ordering_expr_evals_lhs_[i]->GetValue(lhs);
     void* rhs_v = ordering_expr_evals_rhs_[i]->GetValue(rhs);
diff --git a/be/src/util/tuple-row-compare.h b/be/src/util/tuple-row-compare.h
index 6bcb863..25b9f4d 100644
--- a/be/src/util/tuple-row-compare.h
+++ b/be/src/util/tuple-row-compare.h
@@ -64,6 +64,10 @@ class TupleRowComparatorConfig {
   /// TupleRowComparatorConfig.
   const std::vector<ScalarExpr*>& ordering_exprs_;
 
+  /// Number of leading keys in the ordering exprs that should be sorted lexically.
+  /// Only used in Z-order pre-insert sort node to sort rows lexically on partition keys.
+  int num_lexical_keys_;
+
   /// Indicates, for each ordering expr, whether it enforces an ascending order or not.
   /// Not Owned.
   const std::vector<bool>& is_asc_;
@@ -198,13 +202,26 @@ class TupleRowLexicalComparator : public TupleRowComparator {
   int CompareInterpreted(const TupleRow* lhs, const TupleRow* rhs) const override;
 };
 
-/// Compares two TupleRows based on a set of exprs, in Z-order.
+/// Compares two TupleRows based on a set of exprs. The fisrt 'num_lexical_keys' exprs
+/// are compared lexically, while the remaining exprs are compared in Z-order.
 class TupleRowZOrderComparator : public TupleRowComparator {
  public:
   /// 'ordering_exprs': the ordering expressions for tuple comparison.
   TupleRowZOrderComparator(const TupleRowComparatorConfig& config)
-    : TupleRowComparator(config) {
+    : TupleRowComparator(config),
+      num_lexical_keys_(config.num_lexical_keys_) {
     DCHECK(config.sorting_order_ == TSortingOrder::ZORDER);
+    DCHECK_GE(num_lexical_keys_, 0);
+    DCHECK_LT(num_lexical_keys_, ordering_exprs_.size());
+
+    // The algorithm requires all values having a common type, without loss of data.
+    // This means we have to find the biggest type.
+    max_col_size_ = ordering_exprs_[num_lexical_keys_]->type().GetByteSize();
+    for (int i = num_lexical_keys_ + 1; i < ordering_exprs_.size(); ++i) {
+      if (ordering_exprs_[i]->type().GetByteSize() > max_col_size_) {
+        max_col_size_ = ordering_exprs_[i]->type().GetByteSize();
+      }
+    }
   }
 
  private:
@@ -242,6 +259,11 @@ class TupleRowZOrderComparator : public TupleRowComparator {
   U inline GetSharedIntRepresentation(const T val, U mask) const;
   template <typename U, typename T>
   U inline GetSharedFloatRepresentation(void* val, U mask) const;
+
+  /// Number of leading keys that should be sorted lexically.
+  int num_lexical_keys_;
+  /// The biggest type size of the ordering slots.
+  int max_col_size_;
 };
 
 /// Compares the equality of two Tuples, going slot by slot.
diff --git a/common/thrift/PlanNodes.thrift b/common/thrift/PlanNodes.thrift
index e06d1da..a2fd952 100644
--- a/common/thrift/PlanNodes.thrift
+++ b/common/thrift/PlanNodes.thrift
@@ -452,6 +452,10 @@ struct TSortInfo {
   4: optional list<Exprs.TExpr> sort_tuple_slot_exprs
   // The sorting order used in SORT BY clauses.
   5: required Types.TSortingOrder sorting_order
+  // Number of the leading lexical keys in the ordering exprs. Only used in Z-order
+  // for the pre-insert sort node to sort rows lexically on partition keys first, and
+  // sort the remaining columns in Z-order.
+  6: optional i32 num_lexical_keys_in_zorder
 }
 
 enum TSortType {
diff --git a/fe/src/main/java/org/apache/impala/analysis/SortInfo.java b/fe/src/main/java/org/apache/impala/analysis/SortInfo.java
index ab2babf..8821406 100644
--- a/fe/src/main/java/org/apache/impala/analysis/SortInfo.java
+++ b/fe/src/main/java/org/apache/impala/analysis/SortInfo.java
@@ -62,7 +62,11 @@ public class SortInfo {
   private final List<Expr> materializedExprs_;
   // Maps from exprs materialized into the sort tuple to their corresponding SlotRefs.
   private final ExprSubstitutionMap outputSmap_;
-  private TSortingOrder sortingOrder_;
+  private final TSortingOrder sortingOrder_;
+  // Number of leading keys that should be sorted lexically in sortExprs_. Only used in
+  // pre-insert sort node that uses Z-order. So the Z-order sort node can sort rows
+  // lexically on partition keys, and sort the remaining keys in Z-order.
+  private int numLexicalKeysInZOrder_ = 0;
 
   public SortInfo(List<Expr> sortExprs, List<Boolean> isAscOrder,
       List<Boolean> nullsFirstParams) {
@@ -93,6 +97,7 @@ public class SortInfo {
     sortTupleDesc_ = other.sortTupleDesc_;
     outputSmap_ = other.outputSmap_.clone();
     sortingOrder_ = other.sortingOrder_;
+    numLexicalKeysInZOrder_ = other.numLexicalKeysInZOrder_;
   }
 
   public List<Expr> getSortExprs() { return sortExprs_; }
@@ -103,6 +108,10 @@ public class SortInfo {
   public TupleDescriptor getSortTupleDescriptor() { return sortTupleDesc_; }
   public ExprSubstitutionMap getOutputSmap() { return outputSmap_; }
   public TSortingOrder getSortingOrder() { return sortingOrder_; }
+  public int getNumLexicalKeysInZOrder() { return numLexicalKeysInZOrder_; }
+  public void setNumLexicalKeysInZOrder(int numLexicalKeysInZOrder) {
+    numLexicalKeysInZOrder_ = numLexicalKeysInZOrder;
+  }
 
   /**
    * Gets the list of booleans indicating whether nulls come first or last, independent
diff --git a/fe/src/main/java/org/apache/impala/planner/ExchangeNode.java b/fe/src/main/java/org/apache/impala/planner/ExchangeNode.java
index ce16d1d..29ce4f6 100644
--- a/fe/src/main/java/org/apache/impala/planner/ExchangeNode.java
+++ b/fe/src/main/java/org/apache/impala/planner/ExchangeNode.java
@@ -134,7 +134,7 @@ public class ExchangeNode extends PlanNode {
       output.append(detailPrefix + "order by: ");
       output.append(getSortingOrderExplainString(mergeInfo_.getSortExprs(),
           mergeInfo_.getIsAscOrder(), mergeInfo_.getNullsFirstParams(),
-          mergeInfo_.getSortingOrder()));
+          mergeInfo_.getSortingOrder(), mergeInfo_.getNumLexicalKeysInZOrder()));
     }
     return output.toString();
   }
diff --git a/fe/src/main/java/org/apache/impala/planner/PlanNode.java b/fe/src/main/java/org/apache/impala/planner/PlanNode.java
index 30dc5a4..f0b2c09 100644
--- a/fe/src/main/java/org/apache/impala/planner/PlanNode.java
+++ b/fe/src/main/java/org/apache/impala/planner/PlanNode.java
@@ -666,7 +666,7 @@ abstract public class PlanNode extends TreeNode<PlanNode> {
 
   protected String getSortingOrderExplainString(List<? extends Expr> exprs,
       List<Boolean> isAscOrder, List<Boolean> nullsFirstParams,
-      TSortingOrder sortingOrder) {
+      TSortingOrder sortingOrder, int numLexicalKeysInZOrder) {
     StringBuilder output = new StringBuilder();
     switch (sortingOrder) {
     case LEXICAL:
@@ -682,9 +682,20 @@ abstract public class PlanNode extends TreeNode<PlanNode> {
       }
       break;
     case ZORDER:
+      if (numLexicalKeysInZOrder > 0) {
+        Preconditions.checkElementIndex(numLexicalKeysInZOrder, exprs.size());
+        output.append("LEXICAL: ");
+        for (int i = 0; i < numLexicalKeysInZOrder; ++i) {
+          if (i > 0) output.append(", ");
+          // Pre-insert sorting uses ASC and NULLS LAST. See more in
+          // Planner.createPreInsertSort().
+          output.append(exprs.get(i).toSql()).append(" ASC NULLS LAST");
+        }
+        output.append(", ");
+      }
       output.append("ZORDER: ");
-      for (int i = 0; i < exprs.size(); ++i) {
-        if (i > 0) output.append(", ");
+      for (int i = numLexicalKeysInZOrder; i < exprs.size(); ++i) {
+        if (i > numLexicalKeysInZOrder) output.append(", ");
         output.append(exprs.get(i).toSql());
       }
       break;
diff --git a/fe/src/main/java/org/apache/impala/planner/Planner.java b/fe/src/main/java/org/apache/impala/planner/Planner.java
index 5c3ac01..c02cc59 100644
--- a/fe/src/main/java/org/apache/impala/planner/Planner.java
+++ b/fe/src/main/java/org/apache/impala/planner/Planner.java
@@ -711,6 +711,7 @@ public class Planner {
     List<Expr> orderingExprs = new ArrayList<>();
 
     boolean partialSort = false;
+    int numPartitionKeys = 0;
     if (insertStmt.getTargetTable() instanceof FeKuduTable) {
       // Always sort if the 'clustered' hint is present. Otherwise, don't sort if either
       // the 'noclustered' hint is present, or this is a single node exec, or if the
@@ -723,7 +724,11 @@ public class Planner {
         partialSort = true;
       }
     } else if (insertStmt.requiresClustering()) {
-      orderingExprs.addAll(insertStmt.getPartitionKeyExprs());
+      List<Expr> partKeys = insertStmt.getPartitionKeyExprs();
+      orderingExprs.addAll(partKeys);
+      // Ignore constants. Only dynamic partition inserts have non constant keys.
+      Expr.removeConstants(orderingExprs);
+      numPartitionKeys = orderingExprs.size();
     }
     orderingExprs.addAll(insertStmt.getSortExprs());
     // Ignore constants for the sake of clustering.
@@ -736,6 +741,7 @@ public class Planner {
     List<Boolean> nullsFirstParams = Collections.nCopies(orderingExprs.size(), false);
     SortInfo sortInfo = new SortInfo(orderingExprs, isAscOrder, nullsFirstParams,
         insertStmt.getSortingOrder());
+    sortInfo.setNumLexicalKeysInZOrder(numPartitionKeys);
     sortInfo.createSortTupleInfo(insertStmt.getResultExprs(), analyzer);
     sortInfo.getSortTupleDescriptor().materializeSlots();
     insertStmt.substituteResultExprs(sortInfo.getOutputSmap(), analyzer);
diff --git a/fe/src/main/java/org/apache/impala/planner/SortNode.java b/fe/src/main/java/org/apache/impala/planner/SortNode.java
index f8645ab..ff78ab1 100644
--- a/fe/src/main/java/org/apache/impala/planner/SortNode.java
+++ b/fe/src/main/java/org/apache/impala/planner/SortNode.java
@@ -232,6 +232,7 @@ public class SortNode extends PlanNode {
     msg.node_type = TPlanNodeType.SORT_NODE;
     TSortInfo sort_info = new TSortInfo(Expr.treesToThrift(info_.getSortExprs()),
         info_.getIsAscOrder(), info_.getNullsFirst(), info_.getSortingOrder());
+    sort_info.setNum_lexical_keys_in_zorder(info_.getNumLexicalKeysInZOrder());
     Preconditions.checkState(tupleIds_.size() == 1,
         "Incorrect size for tupleIds_ in SortNode");
     sort_info.setSort_tuple_slot_exprs(Expr.treesToThrift(resolvedTupleExprs_));
@@ -250,7 +251,8 @@ public class SortNode extends PlanNode {
     if (detailLevel.ordinal() >= TExplainLevel.STANDARD.ordinal()) {
       output.append(detailPrefix + "order by: ");
       output.append(getSortingOrderExplainString(info_.getSortExprs(),
-          info_.getIsAscOrder(), info_.getNullsFirstParams(), info_.getSortingOrder()));
+          info_.getIsAscOrder(), info_.getNullsFirstParams(), info_.getSortingOrder(),
+          info_.getNumLexicalKeysInZOrder()));
     }
 
     if (detailLevel.ordinal() >= TExplainLevel.EXTENDED.ordinal()) {
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/insert-sort-by-zorder.test b/testdata/workloads/functional-planner/queries/PlannerTest/insert-sort-by-zorder.test
index 25fdc82..76c8a81 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/insert-sort-by-zorder.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/insert-sort-by-zorder.test
@@ -7,7 +7,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 01:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=7.30K
 |
 00:SCAN HDFS [functional.alltypes]
@@ -18,7 +18,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 02:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=7.30K
 |
 01:EXCHANGE [HASH(`year`,`month`)]
@@ -36,7 +36,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 01:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=7.30K
 |
 00:SCAN HDFS [functional.alltypes]
@@ -47,7 +47,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 01:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=7.30K
 |
 00:SCAN HDFS [functional.alltypes]
@@ -64,7 +64,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 01:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=7.30K
 |
 00:SCAN HDFS [functional.alltypes]
@@ -75,7 +75,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 02:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=7.30K
 |
 01:EXCHANGE [HASH(`year`,`month`)]
@@ -84,6 +84,66 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
    HDFS partitions=24/24 files=24 size=478.45KB
    row-size=17B cardinality=7.30K
 ====
+# IMPALA-10233: INSERT statement sorted by ZORDER using both static and dynamic
+# partitioning. We only need to lexically sort the dynamic partition columns.
+insert into table test_sort_by_zorder.t partition(year=2010, month)
+select id, int_col, bool_col, month from functional.alltypes where year = 2010;
+---- PLAN
+WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(2010,month)]
+|  partitions=12
+|
+01:SORT
+|  order by: LEXICAL: month ASC NULLS LAST, ZORDER: int_col, bool_col
+|  row-size=13B cardinality=3.65K
+|
+00:SCAN HDFS [functional.alltypes]
+   partition predicates: `year` = 2010
+   HDFS partitions=12/24 files=12 size=239.77KB
+   row-size=13B cardinality=3.65K
+---- DISTRIBUTEDPLAN
+WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(2010,month)]
+|  partitions=12
+|
+02:SORT
+|  order by: LEXICAL: month ASC NULLS LAST, ZORDER: int_col, bool_col
+|  row-size=13B cardinality=3.65K
+|
+01:EXCHANGE [HASH(`month`)]
+|
+00:SCAN HDFS [functional.alltypes]
+   partition predicates: `year` = 2010
+   HDFS partitions=12/24 files=12 size=239.77KB
+   row-size=13B cardinality=3.65K
+====
+# IMPALA-10233: Test static partition INSERTs sorted by ZORDER. In this case we don't
+# need sort expressions for the partition columns.
+insert into table test_sort_by_zorder.t partition(year=2010, month=1)
+select id, int_col, bool_col from functional.alltypes where year = 2010 and month = 1;
+---- PLAN
+WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(2010,1)]
+|  partitions=1
+|
+01:SORT
+|  order by: ZORDER: int_col, bool_col
+|  row-size=9B cardinality=310
+|
+00:SCAN HDFS [functional.alltypes]
+   partition predicates: `year` = 2010, `month` = 1
+   HDFS partitions=1/24 files=1 size=20.36KB
+   row-size=9B cardinality=310
+---- DISTRIBUTEDPLAN
+WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(2010,1)]
+|  partitions=1
+|
+01:SORT
+|  order by: ZORDER: int_col, bool_col
+|  row-size=9B cardinality=310
+|
+00:SCAN HDFS [functional.alltypes]
+   partition predicates: `year` = 2010, `month` = 1
+   HDFS partitions=1/24 files=1 size=20.36KB
+   row-size=9B cardinality=310
+====
 # IMPALA-4166: insert into tables with sort.columns property adds sort node.
 insert into table test_sort_by_zorder.t_nopart /*+ shuffle */
 select id, int_col, bool_col from functional.alltypes
@@ -205,7 +265,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 04:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=10
 |
 03:TOP-N [LIMIT=10]
@@ -230,7 +290,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(year,mont
 |  partitions=24
 |
 07:SORT
-|  order by: ZORDER: year, month, int_col, bool_col
+|  order by: LEXICAL: year ASC NULLS LAST, month ASC NULLS LAST, ZORDER: int_col, bool_col
 |  row-size=17B cardinality=10
 |
 06:MERGING-EXCHANGE [UNPARTITIONED]
@@ -269,7 +329,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(b.`year`,
 |  partitions=24
 |
 04:SORT
-|  order by: ZORDER: b.`year`, a.`month`, max(b.int_col), min(a.bool_col)
+|  order by: LEXICAL: b.`year` ASC NULLS LAST, a.`month` ASC NULLS LAST, ZORDER: max(b.int_col), min(a.bool_col)
 |  row-size=17B cardinality=7.30K
 |
 03:AGGREGATE [FINALIZE]
@@ -295,7 +355,7 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(b.`year`,
 |  partitions=24
 |
 09:SORT
-|  order by: ZORDER: b.`year`, a.`month`, max(b.int_col), min(a.bool_col)
+|  order by: LEXICAL: b.`year` ASC NULLS LAST, a.`month` ASC NULLS LAST, ZORDER: max(b.int_col), min(a.bool_col)
 |  row-size=17B cardinality=7.30K
 |
 08:EXCHANGE [HASH(b.`year`,a.`month`)]
@@ -328,4 +388,4 @@ WRITE TO HDFS [test_sort_by_zorder.t, OVERWRITE=false, PARTITION-KEYS=(b.`year`,
 01:SCAN HDFS [functional.alltypes b]
    HDFS partitions=24/24 files=24 size=478.45KB
    runtime filters: RF000 -> b.id
-   row-size=12B cardinality=7.30K
\ No newline at end of file
+   row-size=12B cardinality=7.30K
diff --git a/testdata/workloads/functional-query/queries/QueryTest/insert.test b/testdata/workloads/functional-query/queries/QueryTest/insert.test
index 8971835..551469c 100644
--- a/testdata/workloads/functional-query/queries/QueryTest/insert.test
+++ b/testdata/workloads/functional-query/queries/QueryTest/insert.test
@@ -913,3 +913,62 @@ where v.id = 0
 ---- RESULTS
 year=0/month=1/: 1
 ====
+---- QUERY
+# IMPALA-10233: Inserting to a partitioned table with zorder should work.
+create table partitioned_zorder_tbl sort by zorder (id, string_col) like functional.alltypes;
+insert into partitioned_zorder_tbl partition (year, month) select * from functional.alltypes;
+---- RESULTS
+year=2009/month=1/: 310
+year=2009/month=10/: 310
+year=2009/month=11/: 300
+year=2009/month=12/: 310
+year=2009/month=2/: 280
+year=2009/month=3/: 310
+year=2009/month=4/: 300
+year=2009/month=5/: 310
+year=2009/month=6/: 300
+year=2009/month=7/: 310
+year=2009/month=8/: 310
+year=2009/month=9/: 300
+year=2010/month=1/: 310
+year=2010/month=10/: 310
+year=2010/month=11/: 300
+year=2010/month=12/: 310
+year=2010/month=2/: 280
+year=2010/month=3/: 310
+year=2010/month=4/: 300
+year=2010/month=5/: 310
+year=2010/month=6/: 300
+year=2010/month=7/: 310
+year=2010/month=8/: 310
+year=2010/month=9/: 300
+====
+---- QUERY
+# IMPALA-10233: Inserting to a partitioned table with zorder should work.
+insert overwrite partitioned_zorder_tbl partition(year=2010, month)
+select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, float_col, double_col, date_string_col, string_col, timestamp_col, month
+from functional.alltypes
+where year = 2010
+---- RESULTS
+year=2010/month=1/: 310
+year=2010/month=10/: 310
+year=2010/month=11/: 300
+year=2010/month=12/: 310
+year=2010/month=2/: 280
+year=2010/month=3/: 310
+year=2010/month=4/: 300
+year=2010/month=5/: 310
+year=2010/month=6/: 300
+year=2010/month=7/: 310
+year=2010/month=8/: 310
+year=2010/month=9/: 300
+====
+---- QUERY
+# IMPALA-10233: Inserting to a partitioned table with zorder should work.
+insert overwrite partitioned_zorder_tbl partition(year=2010, month=1)
+select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, float_col, double_col, date_string_col, string_col, timestamp_col
+from functional.alltypes
+where year = 2010 and month = 1
+---- RESULTS
+year=2010/month=1/: 310
+====