blob: 531f401cb720095ea36f1cb9f2e01dea6cc7401d [file] [view]
# TSFile Viewer API Documentation
This document describes the REST API endpoints provided by the TSFile Viewer backend.
## TSFile Model Compatibility
TSFile Viewer fully supports both TSFile V4 **Table Model** and V3/legacy **Tree Model** formats:
### Table Model (V4)
- **Structure**: Uses `TableSchema` with TAG and FIELD columns
- **TAG Columns**: Metadata like device_id, location (similar to primary keys)
- **FIELD Columns**: Actual measurement data (temperature, humidity, etc.)
- **Device Identifier**: Constructed as `tablename.tagvalue1.tagvalue2...` from table name and TAG column values
- **Use Case**: Better for relational data, multi-dimensional queries
- **Example**: A sensor_data table with device_id (TAG), location (TAG), temperature (FIELD), humidity (FIELD)
- Device ID format: `sensor_data.device_001.room_A`
### Tree Model (V3/Legacy)
- **Structure**: Traditional device-measurement hierarchy
- **Tables**: Each table represents a device (e.g., root.device1)
- **Columns**: Only measurement columns (no TAG columns)
- **Device Identifier**: Same as table name (e.g., `root.sensor1`)
- **Use Case**: Time-series data organized by device paths
- **Example**: root.sensor1 table with temperature, humidity, pressure columns
- **Implementation**: Uses `TsFileSequenceReader` with `QueryExpression` API for data reading
### API Compatibility
All query endpoints work transparently with both models:
- Use `devices` parameter to filter by table names (for table model queries)
- Use `measurements` parameter to filter by column names
- Pagination, time filtering, and value filtering work identically
- Response format is unified: `device` field contains the constructed device identifier
- Table Model: Device identifier includes TAG values (e.g., `tablename.tag1.tag2`)
- Tree Model: Device identifier is the device path (e.g., `root.device1`)
### Auto-Detection
The backend automatically detects the TSFile model type:
1. **Table Model Detection**: If `TableSchema` entries exist, uses Table Model API (`ITsFileReader.query()`)
2. **Tree Model Fallback**: If no `TableSchema` found, automatically switches to Tree Model API (`TsFileReader` with `QueryExpression`)
This detection is transparent to API consumers - the same endpoints work for both models without any client-side changes.
## Base URL
- Development: `http://localhost:8080/api`
- Production: Configure via `VITE_API_BASE_URL` environment variable
## Authentication
Currently, the API does not require authentication. Future versions may add OAuth2 or JWT-based authentication.
## Common Response Formats
### Success Response
```json
{
"data": { ... },
"status": 200
}
```
### Error Response
```json
{
"status": 400,
"error": "Bad Request",
"message": "Detailed error message",
"timestamp": "2024-01-17T10:30:00Z",
"path": "/api/files/upload",
"validationErrors": [
{
"field": "file",
"message": "File must be a .tsfile",
"rejectedValue": "test.txt"
}
]
}
```
## HTTP Status Codes
- `200 OK` - Request successful
- `400 Bad Request` - Invalid request parameters or validation errors
- `403 Forbidden` - Directory path outside whitelist
- `404 Not Found` - Resource not found
- `413 Payload Too Large` - Upload exceeds size limit
- `500 Internal Server Error` - Server-side error
- `504 Gateway Timeout` - Query timeout exceeded
---
## File Management Endpoints
### GET /api/files/tree
Browse server directory tree with lazy loading.
**Query Parameters:**
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------- |
| root | string | Yes | Root directory path (must be whitelisted) |
| path | string | No | Subdirectory path for lazy loading |
**Example Request:**
```http
GET /api/files/tree?root=/data/tsfiles&path=2024/01
```
**Example Response:**
```json
{
"name": "01",
"path": "/data/tsfiles/2024/01",
"isDirectory": true,
"isLoaded": true,
"children": [
{
"name": "sensor1.tsfile",
"path": "/data/tsfiles/2024/01/sensor1.tsfile",
"isDirectory": false,
"isLoaded": false,
"children": null
},
{
"name": "sensor2.tsfile",
"path": "/data/tsfiles/2024/01/sensor2.tsfile",
"isDirectory": false,
"isLoaded": false,
"children": null
}
]
}
```
**Error Responses:**
- `403 Forbidden` - Path outside whitelist
- `404 Not Found` - Directory not found
---
### POST /api/files/upload
Upload a TSFile to the server.
**Request:**
- Content-Type: `multipart/form-data`
- Body: Form data with `file` field
**Example Request:**
```http
POST /api/files/upload
Content-Type: multipart/form-data
file: [binary data]
```
**Example Response:**
```json
{
"fileId": "f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o",
"fileName": "sensor1.tsfile",
"fileSize": 1048576,
"uploadTime": "2024-01-17T10:30:00Z"
}
```
**Error Responses:**
- `400 Bad Request` - Invalid file type or corrupted file
- `413 Payload Too Large` - File exceeds 100MB limit
---
## Metadata Endpoints
### GET /api/meta/{fileId}
Get complete metadata for a TSFile.
**Path Parameters:**
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------- |
| fileId | string | Yes | Unique file identifier |
**Example Request:**
```http
GET /api/meta/f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o
```
**Example Response:**
```json
{
"fileId": "f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o",
"version": "1.0",
"timeRange": {
"startTime": 1705478400000,
"endTime": 1705564800000
},
"deviceCount": 5,
"measurementCount": 10,
"rowGroupCount": 3,
"chunkCount": 15,
"measurements": [
{
"name": "temperature",
"dataType": "FLOAT",
"encoding": "GORILLA",
"compression": "SNAPPY"
},
{
"name": "humidity",
"dataType": "FLOAT",
"encoding": "GORILLA",
"compression": "SNAPPY"
}
],
"rowGroups": [
{
"index": 0,
"device": "root.sensor1",
"startTime": 1705478400000,
"endTime": 1705492800000,
"chunkCount": 5
}
],
"chunks": [
{
"measurement": "temperature",
"offset": 1024,
"size": 4096,
"compressionRatio": 0.65
}
]
}
```
**Error Responses:**
- `404 Not Found` - File not found
- `500 Internal Server Error` - Failed to parse TSFile
---
## Data Query Endpoints
TSFile Viewer supports both **Table Model** (V4) and **Tree Model** (V3/legacy) TSFile formats:
- **Table Model**: Uses TableSchema with TAG and FIELD columns. Tables organize data with TAG columns for metadata (device_id, location) and FIELD columns for measurements (temperature, humidity).
- **Tree Model**: Traditional device-measurement hierarchy where each table represents a device with measurement columns only.
The API transparently handles both models using the same endpoints.
### POST /api/data/preview
Preview TSFile data with filtering and pagination.
**Request Body:**
```json
{
"fileId": "f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o",
"startTime": 1705478400000,
"endTime": 1705564800000,
"devices": ["root.sensor1", "root.sensor2"],
"measurements": ["temperature", "humidity"],
"valueRange": {
"min": 20.0,
"max": 30.0
},
"limit": 100,
"offset": 0
}
```
**Request Parameters:**
| Parameter | Type | Required | Description |
| ------------ | -------- | -------- | ----------------------------------------------------------------------------------- |
| fileId | string | Yes | Unique file identifier |
| startTime | long | No | Start timestamp (milliseconds) |
| endTime | long | No | End timestamp (milliseconds) |
| devices | string[] | No | Filter by device names (table names in V4) |
| measurements | string[] | No | Filter by measurement/column names |
| valueRange | object | No | Filter by value range (min/max) - requires ALL numeric values in row to be in range |
| limit | int | No | Page size (1-1000, default: 100) |
| offset | int | No | Page offset (default: 0) |
**Table Model Example Response:**
```json
{
"data": [
{
"timestamp": 1705478400000,
"device": "sensor_table.sensor_001.room_A",
"measurements": {
"device_id": "sensor_001",
"location": "room_A",
"temperature": 25.5,
"humidity": 60.0
}
}
],
"total": 1500,
"limit": 100,
"offset": 0,
"hasMore": true
}
```
**Device Identifier Format (Table Model):**
In Table Model, the device identifier is constructed as: `tablename.tagvalue1.tagvalue2...`
- TAG column values are concatenated with dots after the table name
- TAG columns (device_id, location, etc.) are also included in measurements for querying
- Example: `sensor_table.device_001.room_A` where `sensor_table` is the table name, `device_001` is the device_id TAG value, and `room_A` is the location TAG value
**Tree Model Example Response:**
```json
{
"data": [
{
"timestamp": 1705478400000,
"device": "root.sensor1",
"measurements": {
"temperature": 25.5,
"humidity": 60.0,
"pressure": 1013.2
}
}
],
"total": 1500,
"limit": 100,
"offset": 0,
"hasMore": true
}
```
**Multi-Table Query Example (V4 Table Model):**
To query data from a specific table in a multi-table TSFile, use the `devices` parameter with the table name:
```json
{
"fileId": "abc123",
"devices": ["sensor_table"],
"measurements": ["temperature", "humidity"],
"limit": 100,
"offset": 0
}
```
**Pagination Best Practices:**
- Use reasonable page sizes (100-1000) to balance performance and memory usage
- For large datasets, consider time-range filtering before pagination
- The `hasMore` field indicates if more data is available beyond current page
- Total count is computed for the filtered dataset
**Value Range Filter Notes:**
- The value filter requires **ALL** numeric values in a row to fall within the specified range
- Rows with any numeric value outside the range will be excluded
- This is useful for filtering rows where all measurements are within normal operating ranges
- For more flexible filtering, use time range + post-processing in client
**Error Responses:**
- `400 Bad Request` - Invalid filter parameters
- `404 Not Found` - File not found
- `504 Gateway Timeout` - Query exceeded timeout (30s)
---
### POST /api/data/query
Query TSFile data for chart visualization with aggregation and downsampling.
**Supports both Table and Tree models** with automatic downsampling for large datasets and optional time-window aggregation.
**Request Body:**
```json
{
"fileId": "f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o",
"startTime": 1705478400000,
"endTime": 1705564800000,
"measurements": ["temperature", "humidity"],
"devices": ["root.sensor1"],
"aggregation": "AVG",
"windowSize": 60000,
"maxPoints": 1000
}
```
**Request Parameters:**
| Parameter | Type | Required | Description |
| ------------ | -------- | -------- | ---------------------------------------------------------- |
| fileId | string | Yes | Unique file identifier |
| measurements | string[] | Yes | Measurements/columns to query |
| startTime | long | No | Start timestamp (milliseconds) |
| endTime | long | No | End timestamp (milliseconds) |
| devices | string[] | No | Filter by device/table names |
| aggregation | enum | No | Aggregation type (MIN, MAX, AVG, COUNT) |
| windowSize | int | No | Aggregation window size (milliseconds) |
| maxPoints | int | No | Maximum data points (default: 1000, triggers downsampling) |
**Table Model Example Request:**
```json
{
"fileId": "table-model-file",
"measurements": ["temperature", "humidity", "pressure"],
"devices": ["sensor_table"],
"startTime": 1705478400000,
"endTime": 1705564800000,
"aggregation": "AVG",
"windowSize": 300000,
"maxPoints": 500
}
```
**Tree Model Example Request:**
```json
{
"fileId": "tree-model-file",
"measurements": ["s1", "s2", "s3"],
"devices": ["root.device1", "root.device2"],
"startTime": 1705478400000,
"endTime": 1705564800000,
"maxPoints": 1000
}
```
**Example Response:**
```json
{
"series": [
{
"name": "temperature",
"data": [
[1705478400000, 25.5],
[1705478460000, 26.0],
[1705478520000, 25.8]
]
},
{
"name": "humidity",
"data": [
[1705478400000, 60.0],
[1705478460000, 62.0],
[1705478520000, 61.5]
]
}
],
"timeRange": {
"startTime": 1705478400000,
"endTime": 1705564800000
},
"totalPoints": 3,
"downsampled": false
}
```
**Aggregation Types:**
- `MIN` - Minimum value in time window
- `MAX` - Maximum value in time window
- `AVG` - Average value in time window
- `COUNT` - Count of data points in time window
**Time-Window Aggregation:**
When both `aggregation` and `windowSize` are specified, data is aggregated into time windows:
- Window size is in milliseconds (e.g., 60000 = 1 minute)
- Each window produces one aggregated data point
- Useful for reducing data volume and showing trends
- Example: 1 hour of per-second data (3600 points) → 60 one-minute averages
**Downsampling:**
When `totalPoints > maxPoints`, the LTTB (Largest Triangle Three Buckets) algorithm is applied to reduce data points while preserving visual trends.
- Automatically triggered when data exceeds `maxPoints` (default: 1000)
- Preserves peaks, valleys, and overall shape of the time series
- Response includes `downsampled: true` flag
- Recommended for rendering charts with large datasets (>10,000 points)
**Visualization Best Practices:**
1. **Initial Load**: Query with `maxPoints: 1000` to get overview
2. **Zoom In**: Query specific time range without aggregation for detail
3. **Dashboard View**: Use aggregation (AVG/MAX) with appropriate window size
4. **Real-time Updates**: Query latest time range with short intervals
5. **Multi-device Comparison**: Filter by specific devices/tables for clearer visualization
**Error Responses:**
- `400 Bad Request` - Invalid parameters or empty measurements
- `404 Not Found` - File not found
- `504 Gateway Timeout` - Query exceeded timeout (30s)
---
## Table Operations Endpoints
These endpoints provide enhanced support for multi-table TSFile scenarios, allowing you to list tables, discover devices, and query data from specific tables with advanced pagination.
### GET /api/tables/{fileId}
Get a list of all tables in the specified TSFile with column information and row counts.
**Path Parameters:**
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------- |
| fileId | string | Yes | Unique file identifier |
**Example Request:**
```http
GET /api/tables/f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o
```
**Example Response:**
```json
{
"tables": [
{
"tableName": "sensor_data",
"columns": ["device_id", "location", "temperature", "humidity"],
"tagColumns": ["device_id", "location"],
"fieldColumns": ["temperature", "humidity"],
"rowCount": 10000
},
{
"tableName": "device_status",
"columns": ["device_id", "status", "last_update"],
"tagColumns": ["device_id"],
"fieldColumns": ["status", "last_update"],
"rowCount": 500
}
],
"totalCount": 2
}
```
**Error Responses:**
- `404 Not Found` - File not found
- `403 Forbidden` - Access denied
---
### GET /api/tables/{fileId}/devices
Get a list of unique device identifiers in the specified TSFile.
**Path Parameters:**
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------- |
| fileId | string | Yes | Unique file identifier |
**Query Parameters:**
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------- |
| tableName | string | No | Filter devices by specific table name |
**Example Request:**
```http
GET /api/tables/f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o/devices?tableName=sensor_data
```
**Example Response:**
```json
{
"devices": [
{
"deviceId": "sensor_data.device_001.room_A",
"tableName": "sensor_data",
"tagValues": ["device_001", "room_A"],
"dataPointCount": 5000
},
{
"deviceId": "sensor_data.device_002.room_B",
"tableName": "sensor_data",
"tagValues": ["device_002", "room_B"],
"dataPointCount": 4500
}
],
"totalCount": 2
}
```
**Device Identifier Format:**
- For Table Model (V4) with TAG columns: `tablename.tagvalue1.tagvalue2...`
- For Tree Model (V3) without TAG columns: `tablename` (same as table name)
**Error Responses:**
- `404 Not Found` - File not found
- `403 Forbidden` - Access denied
---
### POST /api/tables/query
Query data from a specific table with advanced filtering and pagination.
**Request Body:**
```json
{
"fileId": "f7a3b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o",
"tableName": "sensor_data",
"startTime": 1705478400000,
"endTime": 1705564800000,
"columns": ["temperature", "humidity"],
"valueRange": {
"min": 20.0,
"max": 30.0
},
"limit": 100,
"offset": 0
}
```
**Request Parameters:**
| Parameter | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------------------- |
| fileId | string | Yes | Unique file identifier |
| tableName | string | Yes | Table name to query |
| startTime | long | No | Start timestamp (milliseconds) |
| endTime | long | No | End timestamp (milliseconds) |
| columns | string[] | No | Specific columns to retrieve |
| valueRange | object | No | Filter by value range (min/max) |
| limit | int | No | Page size (1-10000, default: 100) |
| offset | int | No | Page offset (default: 0) |
**Example Response:**
```json
{
"tableName": "sensor_data",
"columns": ["time", "temperature", "humidity"],
"columnTypes": ["TIMESTAMP", "DOUBLE", "INT32"],
"rows": [
{
"time": 1705478400000,
"temperature": 25.5,
"humidity": 60
},
{
"time": 1705478460000,
"temperature": 26.0,
"humidity": 62
}
],
"total": 10000,
"limit": 100,
"offset": 0,
"hasMore": true
}
```
**Pagination Notes:**
- `total`: Total number of rows matching the filter criteria
- `hasMore`: Indicates if more pages are available
- Use `offset + limit` for next page offset
- Maximum `limit` is 10000 for this endpoint (higher than preview API)
**Error Responses:**
- `400 Bad Request` - Invalid parameters or missing required fields
- `404 Not Found` - File or table not found
- `403 Forbidden` - Access denied
---
## Rate Limiting
Currently, no rate limiting is enforced. Future versions may implement rate limiting per IP or user.
## Caching
The backend implements two levels of caching:
1. **Metadata Cache**: Caches parsed metadata for 1 hour (configurable)
2. **Reader Cache**: Caches TSFile readers for 30 minutes (configurable)
Cache headers are not currently exposed in responses.
## Pagination
All paginated endpoints use limit/offset pagination:
- `limit`: Number of items per page (1-1000)
- `offset`: Number of items to skip
- `hasMore`: Boolean indicating if more data is available
## Error Handling
All errors follow a consistent format with:
- `status`: HTTP status code
- `error`: Error type (e.g., "Bad Request")
- `message`: Human-readable error message
- `timestamp`: ISO 8601 timestamp
- `path`: Request path that caused the error
- `validationErrors`: Array of field-level validation errors (for 400 responses)
## Examples
### Upload and Query Workflow
```bash
# 1. Upload a TSFile
curl -X POST http://localhost:8080/api/files/upload \
-F "file=@sensor1.tsfile"
# Response: { "fileId": "abc123", ... }
# 2. Get metadata
curl http://localhost:8080/api/meta/abc123
# 3. Preview data
curl -X POST http://localhost:8080/api/data/preview \
-H "Content-Type: application/json" \
-d '{
"fileId": "abc123",
"limit": 10,
"offset": 0
}'
# 4. Query chart data
curl -X POST http://localhost:8080/api/data/query \
-H "Content-Type: application/json" \
-d '{
"fileId": "abc123",
"measurements": ["temperature"],
"aggregation": "AVG",
"windowSize": 60000,
"maxPoints": 1000
}'
```
## Support
For API issues or questions:
- Check this documentation
- Review error messages in responses
- Check server logs for detailed error information
- Open a GitHub issue