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"