You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tsfile.apache.org by ha...@apache.org on 2026/04/29 02:45:04 UTC

(tsfile-viewer) branch main updated: fix: graceful error handling for incompatible TsFile data decoding (#15)

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

HTHou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tsfile-viewer.git


The following commit(s) were added to refs/heads/main by this push:
     new 5cb12ea  fix: graceful error handling for incompatible TsFile data decoding (#15)
5cb12ea is described below

commit 5cb12ead65c06dcdfe40aa2120135eaa4aac3a73
Author: CritasWang <cr...@outlook.com>
AuthorDate: Wed Apr 29 10:44:58 2026 +0800

    fix: graceful error handling for incompatible TsFile data decoding (#15)
    
    When reading TsFile data created by incompatible SDK versions (e.g.,
    Python SDK), the DeltaBinaryDecoder throws BufferUnderflowException.
    Previously this caused a 500 error with no user-facing explanation.
    
    Changes:
    - Catch BufferUnderflowException and other runtime exceptions in all
      TsFileDataReader query methods, allowing partial data to be returned
    - Add warnings field to DataReadResult and DataPreviewResponse to
      propagate decode error details to the frontend
    - Display warnings as an Alert in the data preview view when data
      cannot be fully decoded
    - Add i18n translations for warning message (en-US and zh-CN)
---
 .../tsfile/viewer/dto/DataPreviewResponse.java     | 32 ++++++++++-
 .../apache/tsfile/viewer/service/DataService.java  |  8 +--
 .../tsfile/viewer/tsfile/TsFileDataReader.java     | 66 +++++++++++++++++++++-
 frontend/src/api/tsfile/types.ts                   |  1 +
 frontend/src/i18n/locales/en-US.json               |  1 +
 frontend/src/i18n/locales/zh-CN.json               |  1 +
 frontend/src/views/tsfile/data-preview/index.vue   | 15 +++++
 7 files changed, 118 insertions(+), 6 deletions(-)

diff --git a/backend/src/main/java/org/apache/tsfile/viewer/dto/DataPreviewResponse.java b/backend/src/main/java/org/apache/tsfile/viewer/dto/DataPreviewResponse.java
index c69528b..d959aa7 100644
--- a/backend/src/main/java/org/apache/tsfile/viewer/dto/DataPreviewResponse.java
+++ b/backend/src/main/java/org/apache/tsfile/viewer/dto/DataPreviewResponse.java
@@ -33,6 +33,7 @@ public class DataPreviewResponse {
   private int limit;
   private int offset;
   private boolean hasMore;
+  private List<String> warnings;
 
   /** Default constructor for JSON deserialization. */
   public DataPreviewResponse() {}
@@ -55,6 +56,21 @@ public class DataPreviewResponse {
     this.hasMore = hasMore;
   }
 
+  public DataPreviewResponse(
+      List<DataRow> data,
+      int total,
+      int limit,
+      int offset,
+      boolean hasMore,
+      List<String> warnings) {
+    this.data = data;
+    this.total = total;
+    this.limit = limit;
+    this.offset = offset;
+    this.hasMore = hasMore;
+    this.warnings = warnings;
+  }
+
   public List<DataRow> getData() {
     return data;
   }
@@ -95,6 +111,14 @@ public class DataPreviewResponse {
     this.hasMore = hasMore;
   }
 
+  public List<String> getWarnings() {
+    return warnings;
+  }
+
+  public void setWarnings(List<String> warnings) {
+    this.warnings = warnings;
+  }
+
   /** Builder class for creating DataPreviewResponse instances. */
   public static class Builder {
     private List<DataRow> data;
@@ -102,6 +126,7 @@ public class DataPreviewResponse {
     private int limit;
     private int offset;
     private boolean hasMore;
+    private List<String> warnings;
 
     public Builder data(List<DataRow> data) {
       this.data = data;
@@ -128,8 +153,13 @@ public class DataPreviewResponse {
       return this;
     }
 
+    public Builder warnings(List<String> warnings) {
+      this.warnings = warnings;
+      return this;
+    }
+
     public DataPreviewResponse build() {
-      return new DataPreviewResponse(data, total, limit, offset, hasMore);
+      return new DataPreviewResponse(data, total, limit, offset, hasMore, warnings);
     }
   }
 
diff --git a/backend/src/main/java/org/apache/tsfile/viewer/service/DataService.java b/backend/src/main/java/org/apache/tsfile/viewer/service/DataService.java
index b55fe16..13687e1 100644
--- a/backend/src/main/java/org/apache/tsfile/viewer/service/DataService.java
+++ b/backend/src/main/java/org/apache/tsfile/viewer/service/DataService.java
@@ -26,10 +26,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
-
 import org.apache.tsfile.viewer.config.TsFileProperties;
 import org.apache.tsfile.viewer.dto.AdvancedCondition;
 import org.apache.tsfile.viewer.dto.AggregationType;
@@ -46,6 +42,9 @@ import org.apache.tsfile.viewer.exception.AccessDeniedException;
 import org.apache.tsfile.viewer.exception.QueryTimeoutException;
 import org.apache.tsfile.viewer.exception.TsFileNotFoundException;
 import org.apache.tsfile.viewer.tsfile.TsFileDataReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
 
 /**
  * Service for data query operations.
@@ -155,6 +154,7 @@ public class DataService {
           .limit(limit)
           .offset(offset)
           .hasMore(hasMore)
+          .warnings(readResult.getWarnings())
           .build();
 
     } catch (java.util.concurrent.TimeoutException e) {
diff --git a/backend/src/main/java/org/apache/tsfile/viewer/tsfile/TsFileDataReader.java b/backend/src/main/java/org/apache/tsfile/viewer/tsfile/TsFileDataReader.java
index aae5fe8..22e1d20 100644
--- a/backend/src/main/java/org/apache/tsfile/viewer/tsfile/TsFileDataReader.java
+++ b/backend/src/main/java/org/apache/tsfile/viewer/tsfile/TsFileDataReader.java
@@ -97,6 +97,7 @@ public class TsFileDataReader {
     private final boolean hasMore;
     private final List<String> columnNames;
     private final List<String> columnTypes;
+    private final List<String> warnings;
 
     public DataReadResult(
         List<DataRow> data,
@@ -104,11 +105,22 @@ public class TsFileDataReader {
         boolean hasMore,
         List<String> columnNames,
         List<String> columnTypes) {
+      this(data, totalRowsRead, hasMore, columnNames, columnTypes, new ArrayList<>());
+    }
+
+    public DataReadResult(
+        List<DataRow> data,
+        int totalRowsRead,
+        boolean hasMore,
+        List<String> columnNames,
+        List<String> columnTypes,
+        List<String> warnings) {
       this.data = data;
       this.totalRowsRead = totalRowsRead;
       this.hasMore = hasMore;
       this.columnNames = columnNames;
       this.columnTypes = columnTypes;
+      this.warnings = warnings != null ? warnings : new ArrayList<>();
     }
 
     public List<DataRow> getData() {
@@ -130,6 +142,10 @@ public class TsFileDataReader {
     public List<String> getColumnTypes() {
       return columnTypes;
     }
+
+    public List<String> getWarnings() {
+      return warnings;
+    }
   }
 
   /** Filter conditions for data reading. */
@@ -206,6 +222,7 @@ public class TsFileDataReader {
     List<DataRow> results = new ArrayList<>();
     List<String> colNames = new ArrayList<>();
     List<String> colTypes = new ArrayList<>();
+    List<String> warnings = new ArrayList<>();
     int skipped = 0, collected = 0;
     boolean hasMore = false;
 
@@ -301,10 +318,23 @@ public class TsFileDataReader {
           }
         } catch (NoTableException | NoMeasurementException | ReadProcessException e) {
           logger.warn("Error querying {}: {}", tblName, e.getMessage());
+          warnings.add("Error querying table '" + tblName + "': " + e.getMessage());
+        } catch (java.nio.BufferUnderflowException | java.nio.BufferOverflowException e) {
+          logger.warn(
+              "Buffer error reading table {}, possibly incompatible file version: {}",
+              tblName,
+              e.getMessage());
+          warnings.add(
+              "Failed to decode data in table '"
+                  + tblName
+                  + "': file may have been created with an incompatible TsFile SDK version");
+        } catch (RuntimeException e) {
+          logger.warn("Unexpected error reading table {}: {}", tblName, e.getMessage());
+          warnings.add("Unexpected error reading table '" + tblName + "': " + e.getMessage());
         }
       }
     }
-    return new DataReadResult(results, collected, hasMore, colNames, colTypes);
+    return new DataReadResult(results, collected, hasMore, colNames, colTypes, warnings);
   }
 
   /** Reads data using an existing reader with time range filtering and pagination. */
@@ -390,6 +420,13 @@ public class TsFileDataReader {
         }
       } catch (NoTableException | NoMeasurementException | ReadProcessException e) {
         logger.warn("Error querying {}: {}", tblName, e.getMessage());
+      } catch (java.nio.BufferUnderflowException | java.nio.BufferOverflowException e) {
+        logger.warn(
+            "Buffer error reading table {}, possibly incompatible file version: {}",
+            tblName,
+            e.getMessage());
+      } catch (RuntimeException e) {
+        logger.warn("Unexpected error reading table {}: {}", tblName, e.getMessage());
       }
     }
     return new DataReadResult(results, collected, hasMore, colNames, colTypes);
@@ -507,6 +544,13 @@ public class TsFileDataReader {
           }
         } catch (NoTableException | NoMeasurementException | ReadProcessException e) {
           logger.warn("Error querying {}: {}", tblName, e.getMessage());
+        } catch (java.nio.BufferUnderflowException | java.nio.BufferOverflowException e) {
+          logger.warn(
+              "Buffer error reading table {}, possibly incompatible file version: {}",
+              tblName,
+              e.getMessage());
+        } catch (RuntimeException e) {
+          logger.warn("Unexpected error reading table {}: {}", tblName, e.getMessage());
         }
       }
     }
@@ -590,6 +634,13 @@ public class TsFileDataReader {
           }
         } catch (NoTableException | NoMeasurementException | ReadProcessException e) {
           logger.warn("Error querying {}: {}", tblName, e.getMessage());
+        } catch (java.nio.BufferUnderflowException | java.nio.BufferOverflowException e) {
+          logger.warn(
+              "Buffer error reading table {}, possibly incompatible file version: {}",
+              tblName,
+              e.getMessage());
+        } catch (RuntimeException e) {
+          logger.warn("Unexpected error reading table {}: {}", tblName, e.getMessage());
         }
       }
     }
@@ -669,6 +720,13 @@ public class TsFileDataReader {
           }
         } catch (NoTableException | NoMeasurementException | ReadProcessException e) {
           logger.warn("Error querying {}: {}", tblName, e.getMessage());
+        } catch (java.nio.BufferUnderflowException | java.nio.BufferOverflowException e) {
+          logger.warn(
+              "Buffer error reading table {}, possibly incompatible file version: {}",
+              tblName,
+              e.getMessage());
+        } catch (RuntimeException e) {
+          logger.warn("Unexpected error reading table {}: {}", tblName, e.getMessage());
         }
       }
     }
@@ -716,6 +774,12 @@ public class TsFileDataReader {
           while (rs.next()) total++;
         } catch (NoTableException | NoMeasurementException | ReadProcessException e) {
           logger.warn("Error counting: {}", e.getMessage());
+        } catch (java.nio.BufferUnderflowException | java.nio.BufferOverflowException e) {
+          logger.warn(
+              "Buffer error counting rows in table {}: {}", ts.getTableName(), e.getMessage());
+        } catch (RuntimeException e) {
+          logger.warn(
+              "Unexpected error counting rows in table {}: {}", ts.getTableName(), e.getMessage());
         }
       }
     }
diff --git a/frontend/src/api/tsfile/types.ts b/frontend/src/api/tsfile/types.ts
index 862e609..171c13b 100644
--- a/frontend/src/api/tsfile/types.ts
+++ b/frontend/src/api/tsfile/types.ts
@@ -182,6 +182,7 @@ export interface DataPreviewResponse {
   limit: number;
   offset: number;
   hasMore: boolean;
+  warnings?: string[];
 }
 
 // Table Model Query Types
diff --git a/frontend/src/i18n/locales/en-US.json b/frontend/src/i18n/locales/en-US.json
index 0dc2113..8b993fe 100644
--- a/frontend/src/i18n/locales/en-US.json
+++ b/frontend/src/i18n/locales/en-US.json
@@ -178,6 +178,7 @@
       "pages": "pages",
       "moreAvailable": "More data available",
       "noDataFound": "No data found",
+      "dataReadWarning": "Some data could not be read",
       "enterDevices": "Enter device names (comma-separated)",
       "enterMeasurements": "Enter measurement names (comma-separated)",
       "advancedFilter": "Advanced Filter",
diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json
index 6784589..c449a6a 100644
--- a/frontend/src/i18n/locales/zh-CN.json
+++ b/frontend/src/i18n/locales/zh-CN.json
@@ -178,6 +178,7 @@
       "pages": "页",
       "moreAvailable": "还有更多数据",
       "noDataFound": "未找到数据",
+      "dataReadWarning": "部分数据无法读取",
       "enterDevices": "输入设备名称(逗号分隔)",
       "enterMeasurements": "输入测点名称(逗号分隔)",
       "advancedFilter": "高级筛选",
diff --git a/frontend/src/views/tsfile/data-preview/index.vue b/frontend/src/views/tsfile/data-preview/index.vue
index 4f7dc7f..c258bc2 100644
--- a/frontend/src/views/tsfile/data-preview/index.vue
+++ b/frontend/src/views/tsfile/data-preview/index.vue
@@ -51,6 +51,7 @@ const currentLimit = ref(100);
 const hasMore = ref(false);
 const loading = ref(false);
 const error = ref<string | null>(null);
+const warnings = ref<string[]>([]);
 const currentFilters = ref<Record<string, unknown>>({});
 const metadata = ref<TSFileMetadata | null>(null);
 const metaError = ref<string | null>(null);
@@ -115,6 +116,7 @@ async function loadData(filters: Record<string, unknown>) {
     currentOffset.value = response.offset;
     currentLimit.value = response.limit;
     hasMore.value = response.hasMore;
+    warnings.value = response.warnings ?? [];
   } catch (e: unknown) {
     error.value = e instanceof Error ? e.message : "Failed to load data";
   } finally {
@@ -217,6 +219,19 @@ function goToQuickScan() {
         @change="handleFilterChange"
       />
     </div>
+    <Alert
+      v-if="warnings.length > 0"
+      type="warning"
+      show-icon
+      :message="t('tsfile.data.dataReadWarning')"
+      class="mt-3 flex-shrink-0"
+    >
+      <template #description>
+        <ul class="m-0 pl-4">
+          <li v-for="(w, i) in warnings" :key="i">{{ w }}</li>
+        </ul>
+      </template>
+    </Alert>
     <div class="flex-1 mt-3 min-h-0">
       <DataTable
         :data="dataRows"