You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@orc.apache.org by om...@apache.org on 2017/05/02 18:13:29 UTC

orc git commit: ORC-87: Fix timestamp statistics

Repository: orc
Updated Branches:
  refs/heads/master 9175b3e22 -> dd464406f


ORC-87: Fix timestamp statistics

Fixes #110

Signed-off-by: Owen O'Malley <om...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/orc/repo
Commit: http://git-wip-us.apache.org/repos/asf/orc/commit/dd464406
Tree: http://git-wip-us.apache.org/repos/asf/orc/tree/dd464406
Diff: http://git-wip-us.apache.org/repos/asf/orc/diff/dd464406

Branch: refs/heads/master
Commit: dd464406f7cb86b34e79576a50312fff367581e1
Parents: 9175b3e
Author: Deepak Majeti <de...@hpe.com>
Authored: Tue Apr 18 16:59:44 2017 -0400
Committer: Owen O'Malley <om...@apache.org>
Committed: Tue May 2 11:13:03 2017 -0700

----------------------------------------------------------------------
 c++/include/orc/Statistics.hh       | 26 +++++++++
 c++/src/Reader.cc                   | 38 ++++++++-----
 c++/src/Reader.hh                   |  4 +-
 c++/src/Statistics.cc               | 93 +++++++++++++++++++++-----------
 c++/src/Statistics.hh               | 92 +++++++++++++++++++++++++++----
 c++/src/Timezone.cc                 |  2 -
 c++/src/Timezone.hh                 |  3 ++
 c++/test/CMakeLists.txt             |  1 +
 c++/test/TestTimestampStatistics.cc | 60 +++++++++++++++++++++
 9 files changed, 261 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/include/orc/Statistics.hh
----------------------------------------------------------------------
diff --git a/c++/include/orc/Statistics.hh b/c++/include/orc/Statistics.hh
index c65eb3c..9643ec6 100644
--- a/c++/include/orc/Statistics.hh
+++ b/c++/include/orc/Statistics.hh
@@ -321,6 +321,32 @@ namespace orc {
      * @return maximum value
      */
     virtual int64_t getMaximum() const = 0;
+
+    /**
+     * check whether column has a lowerBound
+     * @return true if column has a lowerBound
+     */
+    virtual bool hasLowerBound() const = 0;
+
+    /**
+     * check whether column has an upperBound
+     * @return true if column has an upperBound
+     */
+    virtual bool hasUpperBound() const = 0;
+
+    /**
+     * Get the lowerBound value for the column.
+     * @return lowerBound value
+     */
+    virtual int64_t getLowerBound() const = 0;
+
+    /**
+     * Get the upperBound value for the column.
+     * @return upperBound value
+     */
+    virtual int64_t getUpperBound() const = 0;
+
+
   };
 
   class Statistics {

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/src/Reader.cc
----------------------------------------------------------------------
diff --git a/c++/src/Reader.cc b/c++/src/Reader.cc
index 034586d..9c423bd 100644
--- a/c++/src/Reader.cc
+++ b/c++/src/Reader.cc
@@ -339,20 +339,20 @@ namespace orc {
     return forcedScaleOnHive11Decimal;
   }
 
-  proto::StripeFooter RowReaderImpl::getStripeFooter
-       (const proto::StripeInformation& info) {
+  proto::StripeFooter getStripeFooter(const proto::StripeInformation& info,
+                                      const FileContents& contents) {
     uint64_t stripeFooterStart = info.offset() + info.indexlength() +
       info.datalength();
     uint64_t stripeFooterLength = info.footerlength();
     std::unique_ptr<SeekableInputStream> pbStream =
-      createDecompressor(contents->compression,
+      createDecompressor(contents.compression,
                          std::unique_ptr<SeekableInputStream>
-                         (new SeekableFileInputStream(contents->stream.get(),
+                         (new SeekableFileInputStream(contents.stream.get(),
                                                       stripeFooterStart,
                                                       stripeFooterLength,
-                                                      *contents->pool)),
-                         contents->blockSize,
-                         *contents->pool);
+                                                      *contents.pool)),
+                         contents.blockSize,
+                         *contents.pool);
     proto::StripeFooter result;
     if (!result.ParseFromZeroCopyStream(pbStream.get())) {
       throw ParseError(std::string("bad StripeFooter from ") +
@@ -526,16 +526,25 @@ namespace orc {
     if (metadata.get() == nullptr) {
       throw std::logic_error("No stripe statistics in file");
     }
+    proto::StripeInformation currentStripeInfo =
+        footer->stripes(static_cast<int>(stripeIndex));
+    proto::StripeFooter currentStripeFooter =
+        getStripeFooter(currentStripeInfo, *contents.get());
+
+    const Timezone& writerTZ =
+      currentStripeFooter.has_writertimezone() ?
+        getTimezoneByName(currentStripeFooter.writertimezone()) :
+        getLocalTimezone();
+    StatContext statContext(hasCorrectStatistics(), &writerTZ);
     return std::unique_ptr<Statistics>
       (new StatisticsImpl(metadata->stripestats
-                          (static_cast<int>(stripeIndex)),
-                          hasCorrectStatistics()));
+                          (static_cast<int>(stripeIndex)), statContext));
   }
 
   std::unique_ptr<Statistics> ReaderImpl::getStatistics() const {
+    StatContext statContext(hasCorrectStatistics());
     return std::unique_ptr<Statistics>
-      (new StatisticsImpl(*footer,
-                          hasCorrectStatistics()));
+      (new StatisticsImpl(*footer, statContext));
   }
 
   std::unique_ptr<ColumnStatistics>
@@ -545,8 +554,9 @@ namespace orc {
     }
     proto::ColumnStatistics col =
       footer->statistics(static_cast<int32_t>(index));
-    return std::unique_ptr<ColumnStatistics> (convertColumnStatistics
-                                              (col, hasCorrectStatistics()));
+
+    StatContext statContext(hasCorrectStatistics());
+    return std::unique_ptr<ColumnStatistics> (convertColumnStatistics(col, statContext));
   }
 
   void ReaderImpl::readMetadata() const {
@@ -763,7 +773,7 @@ namespace orc {
   void RowReaderImpl::startNextStripe() {
     reader.reset(); // ColumnReaders use lots of memory; free old memory first
     currentStripeInfo = footer->stripes(static_cast<int>(currentStripe));
-    currentStripeFooter = getStripeFooter(currentStripeInfo);
+    currentStripeFooter = getStripeFooter(currentStripeInfo, *contents.get());
     rowsInCurrentStripe = currentStripeInfo.numberofrows();
     const Timezone& writerTimezone =
       currentStripeFooter.has_writertimezone() ?

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/src/Reader.hh
----------------------------------------------------------------------
diff --git a/c++/src/Reader.hh b/c++/src/Reader.hh
index 040d60a..02f2260 100644
--- a/c++/src/Reader.hh
+++ b/c++/src/Reader.hh
@@ -46,6 +46,9 @@ namespace orc {
     std::ostream *errorStream;
   };
 
+  proto::StripeFooter getStripeFooter(const proto::StripeInformation& info,
+                                      const FileContents& contents);
+
   class ReaderImpl;
 
   class ColumnSelector {
@@ -116,7 +119,6 @@ namespace orc {
     std::unique_ptr<ColumnReader> reader;
 
     // internal methods
-    proto::StripeFooter getStripeFooter(const proto::StripeInformation& info);
     void startNextStripe();
 
   public:

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/src/Statistics.cc
----------------------------------------------------------------------
diff --git a/c++/src/Statistics.cc b/c++/src/Statistics.cc
index 083b722..54da928 100644
--- a/c++/src/Statistics.cc
+++ b/c++/src/Statistics.cc
@@ -25,39 +25,37 @@
 namespace orc {
 
   ColumnStatistics* convertColumnStatistics(const proto::ColumnStatistics& s,
-                                            bool correctStats) {
+                                            const StatContext& statContext) {
     if (s.has_intstatistics()) {
       return new IntegerColumnStatisticsImpl(s);
     } else if (s.has_doublestatistics()) {
       return new DoubleColumnStatisticsImpl(s);
     } else if (s.has_stringstatistics()) {
-      return new StringColumnStatisticsImpl(s, correctStats);
+      return new StringColumnStatisticsImpl(s, statContext);
     } else if (s.has_bucketstatistics()) {
-      return new BooleanColumnStatisticsImpl(s, correctStats);
+      return new BooleanColumnStatisticsImpl(s, statContext);
     } else if (s.has_decimalstatistics()) {
-      return new DecimalColumnStatisticsImpl(s, correctStats);
+      return new DecimalColumnStatisticsImpl(s, statContext);
     } else if (s.has_timestampstatistics()) {
-      return new TimestampColumnStatisticsImpl(s, correctStats);
+      return new TimestampColumnStatisticsImpl(s, statContext);
     } else if (s.has_datestatistics()) {
-      return new DateColumnStatisticsImpl(s, correctStats);
+      return new DateColumnStatisticsImpl(s, statContext);
     } else if (s.has_binarystatistics()) {
-      return new BinaryColumnStatisticsImpl(s, correctStats);
+      return new BinaryColumnStatisticsImpl(s, statContext);
     } else {
       return new ColumnStatisticsImpl(s);
     }
   }
 
-  StatisticsImpl::StatisticsImpl(const proto::StripeStatistics& stripeStats, bool correctStats) {
+  StatisticsImpl::StatisticsImpl(const proto::StripeStatistics& stripeStats, const StatContext& statContext) {
     for(int i = 0; i < stripeStats.colstats_size(); i++) {
-      colStats.push_back(convertColumnStatistics
-          (stripeStats.colstats(i), correctStats));
+      colStats.push_back(convertColumnStatistics(stripeStats.colstats(i), statContext));
     }
   }
 
-  StatisticsImpl::StatisticsImpl(const proto::Footer& footer, bool correctStats) {
+  StatisticsImpl::StatisticsImpl(const proto::Footer& footer, const StatContext& statContext) {
     for(int i = 0; i < footer.statistics_size(); i++) {
-      colStats.push_back(convertColumnStatistics
-          (footer.statistics(i), correctStats));
+      colStats.push_back(convertColumnStatistics(footer.statistics(i), statContext));
     }
   }
 
@@ -151,9 +149,9 @@ namespace orc {
   }
 
   BinaryColumnStatisticsImpl::BinaryColumnStatisticsImpl
-  (const proto::ColumnStatistics& pb, bool correctStats){
+  (const proto::ColumnStatistics& pb, const StatContext& statContext){
     valueCount = pb.numberofvalues();
-    if (!pb.has_binarystatistics() || !correctStats) {
+    if (!pb.has_binarystatistics() || !statContext.correctStats) {
       _hasTotalLength = false;
 
       totalLength = 0;
@@ -164,9 +162,9 @@ namespace orc {
   }
 
   BooleanColumnStatisticsImpl::BooleanColumnStatisticsImpl
-  (const proto::ColumnStatistics& pb, bool correctStats){
+  (const proto::ColumnStatistics& pb, const StatContext& statContext){
     valueCount = pb.numberofvalues();
-    if (!pb.has_bucketstatistics() || !correctStats) {
+    if (!pb.has_bucketstatistics() || !statContext.correctStats) {
       _hasCount = false;
       trueCount = 0;
     }else{
@@ -176,9 +174,9 @@ namespace orc {
   }
 
   DateColumnStatisticsImpl::DateColumnStatisticsImpl
-  (const proto::ColumnStatistics& pb, bool correctStats){
+  (const proto::ColumnStatistics& pb, const StatContext& statContext){
     valueCount = pb.numberofvalues();
-    if (!pb.has_datestatistics() || !correctStats) {
+    if (!pb.has_datestatistics() || !statContext.correctStats) {
       _hasMinimum = false;
       _hasMaximum = false;
 
@@ -193,9 +191,9 @@ namespace orc {
   }
 
   DecimalColumnStatisticsImpl::DecimalColumnStatisticsImpl
-  (const proto::ColumnStatistics& pb, bool correctStats){
+  (const proto::ColumnStatistics& pb, const StatContext& statContext){
     valueCount = pb.numberofvalues();
-    if (!pb.has_decimalstatistics() || !correctStats) {
+    if (!pb.has_decimalstatistics() || !statContext.correctStats) {
       _hasMinimum = false;
       _hasMaximum = false;
       _hasSum = false;
@@ -258,9 +256,9 @@ namespace orc {
   }
 
   StringColumnStatisticsImpl::StringColumnStatisticsImpl
-  (const proto::ColumnStatistics& pb, bool correctStats){
+  (const proto::ColumnStatistics& pb, const StatContext& statContext){
     valueCount = pb.numberofvalues();
-    if (!pb.has_stringstatistics() || !correctStats) {
+    if (!pb.has_stringstatistics() || !statContext.correctStats) {
       _hasMinimum = false;
       _hasMaximum = false;
       _hasTotalLength = false;
@@ -279,20 +277,55 @@ namespace orc {
   }
 
   TimestampColumnStatisticsImpl::TimestampColumnStatisticsImpl
-  (const proto::ColumnStatistics& pb, bool correctStats) {
+  (const proto::ColumnStatistics& pb, const StatContext& statContext) {
     valueCount = pb.numberofvalues();
-    if (!pb.has_timestampstatistics() || !correctStats) {
+    if (!pb.has_timestampstatistics() || !statContext.correctStats) {
       _hasMinimum = false;
       _hasMaximum = false;
+      _hasLowerBound = false;
+      _hasUpperBound = false;
       minimum = 0;
       maximum = 0;
+      lowerBound = 0;
+      upperBound = 0;
     }else{
       const proto::TimestampStatistics& stats = pb.timestampstatistics();
-      _hasMinimum = stats.has_minimum();
-      _hasMaximum = stats.has_maximum();
-
-      minimum = stats.minimum();
-      maximum = stats.maximum();
+      _hasMinimum = stats.has_minimumutc() || (stats.has_minimum() && (statContext.writerTimezone != NULL));
+      _hasMaximum = stats.has_maximumutc() || (stats.has_maximum() && (statContext.writerTimezone != NULL));
+      _hasLowerBound = stats.has_minimumutc() || stats.has_minimum();
+      _hasUpperBound = stats.has_maximumutc() || stats.has_maximum();
+
+      // Timestamp stats are stored in milliseconds
+      if (stats.has_minimumutc()) {
+        minimum = stats.minimumutc();
+        lowerBound = minimum;
+      } else if (statContext.writerTimezone) {
+        int64_t writerTimeSec = stats.minimum() / 1000;
+        // multiply the offset by 1000 to convert to millisecond
+        minimum = stats.minimum() + (statContext.writerTimezone->getVariant(writerTimeSec).gmtOffset) * 1000;
+        lowerBound = minimum;
+      } else {
+        minimum = 0;
+        // subtract 1 day 1 hour (25 hours) in milliseconds to handle unknown TZ and daylight savings
+        lowerBound = stats.minimum() - (25 * SECONDS_PER_HOUR * 1000);
+      }
+
+      // Timestamp stats are stored in milliseconds
+      if (stats.has_maximumutc()) {
+        maximum = stats.maximumutc();
+        upperBound = maximum;
+      } else if (statContext.writerTimezone) {
+        int64_t writerTimeSec = stats.maximum() / 1000;
+        // multiply the offset by 1000 to convert to millisecond
+        maximum = stats.maximum() + (statContext.writerTimezone->getVariant(writerTimeSec).gmtOffset) * 1000;
+        upperBound = maximum;
+      } else {
+        maximum = 0;
+        // add 1 day 1 hour (25 hours) in milliseconds to handle unknown TZ and daylight savings
+        upperBound = stats.maximum() +  (25 * SECONDS_PER_HOUR * 1000);
+      }
+      // Add 1 millisecond to account for microsecond precision of values
+      upperBound += 1;
     }
   }
 

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/src/Statistics.hh
----------------------------------------------------------------------
diff --git a/c++/src/Statistics.hh b/c++/src/Statistics.hh
index 1d1006e..de9c9f3 100644
--- a/c++/src/Statistics.hh
+++ b/c++/src/Statistics.hh
@@ -23,11 +23,24 @@
 #include "orc/OrcFile.hh"
 #include "orc/Reader.hh"
 
+#include "Timezone.hh"
 #include "TypeImpl.hh"
 
 namespace orc {
 
 /**
+ * StatContext contains fields required to compute statistics
+ */
+
+  struct StatContext {
+    const bool correctStats;
+    const Timezone* const writerTimezone;
+    StatContext() : correctStats(false), writerTimezone(NULL) {}
+    StatContext(bool cStat, const Timezone* const timezone = NULL) :
+        correctStats(cStat), writerTimezone(timezone) {}
+  };
+
+/**
  * ColumnStatistics Implementation
  */
 
@@ -58,7 +71,7 @@ namespace orc {
 
   public:
     BinaryColumnStatisticsImpl(const proto::ColumnStatistics& stats,
-                               bool correctStats);
+                               const StatContext& statContext);
     virtual ~BinaryColumnStatisticsImpl();
 
     bool hasTotalLength() const override {
@@ -96,7 +109,7 @@ namespace orc {
     uint64_t trueCount;
 
   public:
-    BooleanColumnStatisticsImpl(const proto::ColumnStatistics& stats, bool correctStats);
+    BooleanColumnStatisticsImpl(const proto::ColumnStatistics& stats, const StatContext& statContext);
     virtual ~BooleanColumnStatisticsImpl();
 
     bool hasCount() const override {
@@ -147,7 +160,7 @@ namespace orc {
     int32_t maximum;
 
   public:
-    DateColumnStatisticsImpl(const proto::ColumnStatistics& stats, bool correctStats);
+    DateColumnStatisticsImpl(const proto::ColumnStatistics& stats, const StatContext& statContext);
     virtual ~DateColumnStatisticsImpl();
 
     bool hasMinimum() const override {
@@ -208,7 +221,7 @@ namespace orc {
     std::string sum;
 
   public:
-    DecimalColumnStatisticsImpl(const proto::ColumnStatistics& stats, bool correctStats);
+    DecimalColumnStatisticsImpl(const proto::ColumnStatistics& stats, const StatContext& statContext);
     virtual ~DecimalColumnStatisticsImpl();
 
     bool hasMinimum() const override {
@@ -446,7 +459,7 @@ namespace orc {
     uint64_t totalLength;
 
   public:
-    StringColumnStatisticsImpl(const proto::ColumnStatistics& stats, bool correctStats);
+    StringColumnStatisticsImpl(const proto::ColumnStatistics& stats, const StatContext& statContext);
     virtual ~StringColumnStatisticsImpl();
 
     bool hasMinimum() const override {
@@ -521,10 +534,14 @@ namespace orc {
     uint64_t valueCount;
     int64_t minimum;
     int64_t maximum;
+    bool _hasLowerBound;
+    bool _hasUpperBound;
+    int64_t lowerBound;
+    int64_t upperBound;
 
   public:
     TimestampColumnStatisticsImpl(const proto::ColumnStatistics& stats,
-                                  bool correctStats);
+                                  const StatContext& statContext);
     virtual ~TimestampColumnStatisticsImpl();
 
     bool hasMinimum() const override {
@@ -557,26 +574,79 @@ namespace orc {
 
     std::string toString() const override {
       std::ostringstream buffer;
+      struct tm tmValue;
+      char timeBuffer[20];
+      time_t secs = 0;
+
       buffer << "Data type: Timestamp" << std::endl
           << "Values: " << valueCount << std::endl;
       if(_hasMinimum){
-        buffer << "Minimum: " << minimum << std::endl;
+        secs = static_cast<time_t>(minimum/1000);
+        gmtime_r(&secs, &tmValue);
+        strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &tmValue);
+        buffer << "Minimum: " << timeBuffer << "." << (minimum % 1000) << std::endl;
       }else{
         buffer << "Minimum is not defined" << std::endl;
       }
 
+      if(_hasLowerBound){
+        secs = static_cast<time_t>(lowerBound/1000);
+        gmtime_r(&secs, &tmValue);
+        strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &tmValue);
+        buffer << "LowerBound: " << timeBuffer << "." << (lowerBound % 1000) << std::endl;
+      }else{
+        buffer << "LowerBound is not defined" << std::endl;
+      }
+
       if(_hasMaximum){
-        buffer << "Maximum: " << maximum << std::endl;
+        secs = static_cast<time_t>(maximum/1000);
+        gmtime_r(&secs, &tmValue);
+        strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &tmValue);
+        buffer << "Maximum: " << timeBuffer << "." << (maximum % 1000) << std::endl;
       }else{
         buffer << "Maximum is not defined" << std::endl;
       }
+
+      if(_hasUpperBound){
+        secs = static_cast<time_t>(upperBound/1000);
+        gmtime_r(&secs, &tmValue);
+        strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &tmValue);
+        buffer << "UpperBound: " << timeBuffer << "." << (upperBound % 1000) << std::endl;
+      }else{
+        buffer << "UpperBound is not defined" << std::endl;
+      }
+
       return buffer.str();
     }
+
+    bool hasLowerBound() const override {
+      return _hasLowerBound;
+    }
+
+    bool hasUpperBound() const override {
+      return _hasUpperBound;
+    }
+
+    int64_t getLowerBound() const override {
+      if(_hasLowerBound){
+        return lowerBound;
+      }else{
+        throw ParseError("LowerBound is not defined.");
+      }
+    }
+
+    int64_t getUpperBound() const override {
+      if(_hasUpperBound){
+        return upperBound;
+      }else{
+        throw ParseError("UpperBound is not defined.");
+      }
+    }
   };
 
 
   ColumnStatistics* convertColumnStatistics(const proto::ColumnStatistics& s,
-                                            bool correctStats);
+                                            const StatContext& statContext);
 
   class StatisticsImpl: public Statistics {
   private:
@@ -587,9 +657,9 @@ namespace orc {
     StatisticsImpl& operator=(const StatisticsImpl&);
 
   public:
-    StatisticsImpl(const proto::StripeStatistics& stripeStats, bool correctStats);
+    StatisticsImpl(const proto::StripeStatistics& stripeStats, const StatContext& statContext);
 
-    StatisticsImpl(const proto::Footer& footer, bool correctStats);
+    StatisticsImpl(const proto::Footer& footer, const StatContext& statContext);
 
     virtual const ColumnStatistics* getColumnStatistics(uint32_t columnId
                                                         ) const override {

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/src/Timezone.cc
----------------------------------------------------------------------
diff --git a/c++/src/Timezone.cc b/c++/src/Timezone.cc
index 5ffec3c..bc2ffb6 100644
--- a/c++/src/Timezone.cc
+++ b/c++/src/Timezone.cc
@@ -52,8 +52,6 @@ namespace orc {
   static const int64_t DAYS_PER_MONTH[2][MONTHS_PER_YEAR] =
      {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
       {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
-  static const int64_t SECONDS_PER_HOUR = 60 * 60;
-  static const int64_t SECONDS_PER_DAY = SECONDS_PER_HOUR * 24;
   static const int64_t DAYS_PER_WEEK = 7;
 
   // Leap years and day of the week repeat every 400 years, which makes it

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/src/Timezone.hh
----------------------------------------------------------------------
diff --git a/c++/src/Timezone.hh b/c++/src/Timezone.hh
index 1cf04cf..b1dbd6a 100644
--- a/c++/src/Timezone.hh
+++ b/c++/src/Timezone.hh
@@ -31,6 +31,9 @@
 
 namespace orc {
 
+  static const int64_t SECONDS_PER_HOUR = 60 * 60;
+  static const int64_t SECONDS_PER_DAY = SECONDS_PER_HOUR * 24;
+
   /**
    * A variant  (eg. PST or PDT) of a timezone (eg. America/Los_Angeles).
    */

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/test/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/c++/test/CMakeLists.txt b/c++/test/CMakeLists.txt
index 775e15f..83c48a3 100644
--- a/c++/test/CMakeLists.txt
+++ b/c++/test/CMakeLists.txt
@@ -32,6 +32,7 @@ add_executable (orc-test
   TestReader.cc
   TestRle.cc
   TestTimezone.cc
+  TestTimestampStatistics.cc
   TestType.cc
 )
 

http://git-wip-us.apache.org/repos/asf/orc/blob/dd464406/c++/test/TestTimestampStatistics.cc
----------------------------------------------------------------------
diff --git a/c++/test/TestTimestampStatistics.cc b/c++/test/TestTimestampStatistics.cc
new file mode 100644
index 0000000..8fe47f2
--- /dev/null
+++ b/c++/test/TestTimestampStatistics.cc
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "orc/OrcFile.hh"
+#include "orc/Reader.hh"
+
+#include "Adaptor.hh"
+
+#include "wrap/gmock.h"
+#include "wrap/gtest-wrapper.h"
+
+namespace orc {
+
+  TEST(TestTimestampStatistics, testOldFile) {
+
+    std::stringstream ss;
+    if(const char* example_dir = std::getenv("ORC_EXAMPLE_DIR")) {
+      ss << example_dir;
+    } else {
+      ss << "../../../examples";
+    }
+    ss << "/TestOrcFile.testTimestamp.orc";
+    std::unique_ptr<orc::Reader> reader =
+      createReader(readLocalFile(ss.str().c_str()), ReaderOptions());
+
+    std::unique_ptr<orc::ColumnStatistics> footerStats = reader->getColumnStatistics(0);
+    const orc::TimestampColumnStatistics* footerColStats =
+        reinterpret_cast<const orc::TimestampColumnStatistics*>(footerStats.get());
+
+    std::unique_ptr<orc::Statistics> stripeStats = reader->getStripeStatistics(0);
+    const orc::TimestampColumnStatistics* stripeColStats =
+        reinterpret_cast<const orc::TimestampColumnStatistics*>(stripeStats->getColumnStatistics(0));
+
+    EXPECT_FALSE(footerColStats->hasMinimum());
+    EXPECT_FALSE(footerColStats->hasMaximum());
+    EXPECT_TRUE(footerColStats->hasLowerBound());
+    EXPECT_TRUE(footerColStats->hasUpperBound());
+    EXPECT_EQ("Data type: Timestamp\nValues: 12\nMinimum is not defined\nLowerBound: 1994-12-31 07:00:00.688\nMaximum is not defined\nUpperBound: 2037-01-02 09:00:00.1\n", footerColStats->toString());
+
+    EXPECT_TRUE(stripeColStats->hasMinimum());
+    EXPECT_TRUE(stripeColStats->hasMaximum());
+    EXPECT_EQ("Data type: Timestamp\nValues: 12\nMinimum: 1995-01-01 00:00:00.688\nLowerBound: 1995-01-01 00:00:00.688\nMaximum: 2037-01-01 00:00:00.0\nUpperBound: 2037-01-01 00:00:00.1\n", stripeColStats->toString());
+  }
+
+}  // namespace