| <!-- | |
| 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. | |
| --> | |
| ## TsFile API | |
| TsFile is a file format of Time Series used in IoTDB. This session introduces the usage of this file format. | |
| ### TsFile library Installation | |
| There are two ways to use TsFile in your own project. | |
| * Use as jars: Compile the source codes and build to jars | |
| ```shell | |
| git clone https://github.com/apache/iotdb.git | |
| cd tsfile/ | |
| mvn clean package -Dmaven.test.skip=true | |
| ``` | |
| Then, all the jars are in folder named `target/`. Import `target/tsfile-0.12.0-jar-with-dependencies.jar` to your project. | |
| * Use as a maven dependency: | |
| Compile source codes and deploy to your local repository in three steps: | |
| * Get the source codes | |
| ```shell | |
| git clone https://github.com/apache/iotdb.git | |
| ``` | |
| * Compile the source codes and deploy | |
| ```shell | |
| cd tsfile/ | |
| mvn clean install -Dmaven.test.skip=true | |
| ``` | |
| * add dependencies into your project: | |
| ```xml | |
| <dependency> | |
| <groupId>org.apache.iotdb</groupId> | |
| <artifactId>tsfile</artifactId> | |
| <version>1.0.0</version> | |
| </dependency> | |
| ``` | |
| Or, you can download the dependencies from official Maven repository: | |
| * First, find your maven `settings.xml` on path: `${username}\.m2\settings.xml` | |
| , add this `<profile>` to `<profiles>`: | |
| ```xml | |
| <profile> | |
| <id>allow-snapshots</id> | |
| <activation><activeByDefault>true</activeByDefault></activation> | |
| <repositories> | |
| <repository> | |
| <id>apache.snapshots</id> | |
| <name>Apache Development Snapshot Repository</name> | |
| <url>https://repository.apache.org/content/repositories/snapshots/</url> | |
| <releases> | |
| <enabled>false</enabled> | |
| </releases> | |
| <snapshots> | |
| <enabled>true</enabled> | |
| </snapshots> | |
| </repository> | |
| </repositories> | |
| </profile> | |
| ``` | |
| * Then add dependencies into your project: | |
| ```xml | |
| <dependency> | |
| <groupId>org.apache.iotdb</groupId> | |
| <artifactId>tsfile</artifactId> | |
| <version>1.0.0</version> | |
| </dependency> | |
| ``` | |
| ### TsFile Usage | |
| This section demonstrates the detailed usages of TsFile. | |
| Time-series Data | |
| Time-series data is considered as a sequence of quadruples. A quadruple is defined as (device, measurement, time, value). | |
| * **measurement**: A physical or formal measurement that a time-series data takes, e.g., the temperature of a city, the | |
| sales number of some goods or the speed of a train at different times. As a traditional sensor (like a thermometer) also | |
| takes a single measurement and produce a time-series, we will use measurement and sensor interchangeably below. | |
| * **device**: A device refers to an entity that takes several measurements (producing multiple time-series), e.g., | |
| a running train monitors its speed, oil meter, miles it has run, current passengers each is conveyed to a time-series dataset. | |
| **One Line of Data**: In many industrial applications, a device normally contains more than one sensor and these sensors | |
| may have values at the same timestamp, which is called one line of data. | |
| Formally, one line of data consists of a `device_id`, a timestamp which indicates the milliseconds since January 1, | |
| 1970, 00:00:00, and several data pairs composed of `measurement_id` and corresponding `value`. All data pairs in one | |
| line belong to this `device_id` and have the same timestamp. If one of the `measurements` does not have a `value` | |
| in the `timestamp`, use a space instead(Actually, TsFile does not store null values). Its format is shown as follow: | |
| ``` | |
| device_id, timestamp, <measurement_id, value>... | |
| ``` | |
| An example is illustrated as follow. In this example, the data type of two measurements are `INT32`, `FLOAT` respectively. | |
| ``` | |
| device_1, 1490860659000, m1, 10, m2, 12.12 | |
| ``` | |
| #### Write TsFile | |
| A TsFile is generated by the following three steps and the complete code is given in the section "Example for writing TsFile". | |
| 1. construct a `TsFileWriter` instance. | |
| Here are the available constructors: | |
| * Without pre-defined schema | |
| ```java | |
| public TsFileWriter(File file) throws IOException | |
| ``` | |
| * With pre-defined schema | |
| ```java | |
| public TsFileWriter(File file, Schema schema) throws IOException | |
| ``` | |
| This one is for using the HDFS file system. `TsFileOutput` can be an instance of class `HDFSOutput`. | |
| ```java | |
| public TsFileWriter(TsFileOutput output, Schema schema) throws IOException | |
| ``` | |
| If you want to set some TSFile configuration on your own, you could use param `config`. For example: | |
| ```java | |
| TSFileConfig conf = new TSFileConfig(); | |
| conf.setTSFileStorageFs("HDFS"); | |
| TsFileWriter tsFileWriter = new TsFileWriter(file, schema, conf); | |
| ``` | |
| In this example, data files will be stored in HDFS, instead of local file system. If you'd like to store data files in local file system, you can use `conf.setTSFileStorageFs("LOCAL")`, which is also the default config. | |
| You can also config the ip and rpc port of your HDFS by `config.setHdfsIp(...)` and `config.setHdfsPort(...)`. The default ip is `localhost` and default rpc port is `9000`. | |
| **Parameters:** | |
| * file : The TsFile to write | |
| * schema : The file schemas, will be introduced in next part. | |
| * config : The config of TsFile. | |
| 2. add measurements | |
| Or you can make an instance of class `Schema` first and pass this to the constructor of class `TsFileWriter` | |
| The class `Schema` contains a map whose key is the name of one measurement schema, and the value is the schema itself. | |
| Here are the interfaces: | |
| ```java | |
| // Create an empty Schema or from an existing map | |
| public Schema() | |
| public Schema(Map<String, MeasurementSchema> measurements) | |
| // Use this two interfaces to add measurements | |
| public void registerMeasurement(MeasurementSchema descriptor) | |
| public void registerMeasurements(Map<String, MeasurementSchema> measurements) | |
| // Some useful getter and checker | |
| public TSDataType getMeasurementDataType(String measurementId) | |
| public MeasurementSchema getMeasurementSchema(String measurementId) | |
| public Map<String, MeasurementSchema> getAllMeasurementSchema() | |
| public boolean hasMeasurement(String measurementId) | |
| ``` | |
| You can always use the following interface in `TsFileWriter` class to add additional measurements: | |
| ```java | |
| public void addMeasurement(MeasurementSchema measurementSchema) throws WriteProcessException | |
| ``` | |
| The class `MeasurementSchema` contains the information of one measurement, there are several constructors: | |
| ```java | |
| public MeasurementSchema(String measurementId, TSDataType type, TSEncoding encoding) | |
| public MeasurementSchema(String measurementId, TSDataType type, TSEncoding encoding, CompressionType compressionType) | |
| public MeasurementSchema(String measurementId, TSDataType type, TSEncoding encoding, CompressionType compressionType, | |
| Map<String, String> props) | |
| ``` | |
| **Parameters:** | |
| ​ | |
| * measurementID: The name of this measurement, typically the name of the sensor. | |
| * type: The data type, now support six types: `BOOLEAN`, `INT32`, `INT64`, `FLOAT`, `DOUBLE`, `TEXT`; | |
| * encoding: The data encoding. | |
| * compression: The data compression. | |
| * props: Properties for special data types.Such as `max_point_number` for `FLOAT` and `DOUBLE`, `max_string_length` for | |
| `TEXT`. Use as string pairs into a map such as ("max_point_number", "3"). | |
| > **Notice:** Although one measurement name can be used in multiple deltaObjects, the properties cannot be changed. I.e. | |
| it's not allowed to add one measurement name for multiple times with different type or encoding. | |
| Here is a bad example: | |
| ```java | |
| // The measurement "sensor_1" is float type | |
| addMeasurement(new MeasurementSchema("sensor_1", TSDataType.FLOAT, TSEncoding.RLE)); | |
| // This call will throw a WriteProcessException exception | |
| addMeasurement(new MeasurementSchema("sensor_1", TSDataType.INT32, TSEncoding.RLE)); | |
| ``` | |
| ``` | |
| ``` | |
| 3. insert and write data continually. | |
| Use this interface to create a new `TSRecord`(a timestamp and device pair). | |
| ```java | |
| public TSRecord(long timestamp, String deviceId) | |
| ``` | |
| ``` | |
| Then create a `DataPoint`(a measurement and value pair), and use the addTuple method to add the DataPoint to the correct | |
| TsRecord. | |
| Use this method to write | |
| ```java | |
| public void write(TSRecord record) throws IOException, WriteProcessException | |
| ``` | |
| 4. call `close` to finish this writing process. | |
| ```java | |
| public void close() throws IOException | |
| ``` | |
| We are also able to write data into a closed TsFile. | |
| 1. Use `ForceAppendTsFileWriter` to open a closed file. | |
| ```java | |
| public ForceAppendTsFileWriter(File file) throws IOException | |
| ``` | |
| 2. call `doTruncate` truncate the part of Metadata | |
| 3. Then use `ForceAppendTsFileWriter` to construct a new `TsFileWriter` | |
| ```java | |
| public TsFileWriter(TsFileIOWriter fileWriter) throws IOException | |
| ``` | |
| Please note, we should redo the step of adding measurements before writing new data to the TsFile. | |
| #### Example for writing a TsFile | |
| You should install TsFile to your local maven repository. | |
| ```shell | |
| mvn clean install -pl tsfile -am -DskipTests | |
| ``` | |
| You could write a TsFile by constructing **TSRecord** if you have the **non-aligned** (e.g. not all sensors contain values) time series data. | |
| A more thorough example can be found at `/example/tsfile/src/main/java/org/apache/iotdb/tsfile/TsFileWriteWithTSRecord.java` | |
| You could write a TsFile by constructing **Tablet** if you have the **aligned** time series data. | |
| A more thorough example can be found at `/example/tsfile/src/main/java/org/apache/iotdb/tsfile/TsFileWriteWithTablet.java` | |
| You could write data into a closed TsFile by using **ForceAppendTsFileWriter**. | |
| A more thorough example can be found at `/example/tsfile/src/main/java/org/apache/iotdb/tsfile/TsFileForceAppendWrite.java` | |
| #### Interface for Reading TsFile | |
| * Definition of Path | |
| A path is a dot-separated string which uniquely identifies a time-series in TsFile, e.g., "root.area_1.device_1.sensor_1". | |
| The last section "sensor_1" is called "measurementId" while the remaining parts "root.area_1.device_1" is called deviceId. | |
| As mentioned above, the same measurement in different devices has the same data type and encoding, and devices are also unique. | |
| In read interfaces, The parameter `paths` indicates the measurements to be selected. | |
| Path instance can be easily constructed through the class `Path`. For example: | |
| ```java | |
| Path p = new Path("device_1.sensor_1"); | |
| ``` | |
| We will pass an ArrayList of paths for final query call to support multiple paths. | |
| ```java | |
| List<Path> paths = new ArrayList<Path>(); | |
| paths.add(new Path("device_1.sensor_1")); | |
| paths.add(new Path("device_1.sensor_3")); | |
| ``` | |
| > **Notice:** When constructing a Path, the format of the parameter should be a dot-separated string, the last part will | |
| be recognized as measurementId while the remaining parts will be recognized as deviceId. | |
| * Definition of Filter | |
| * Usage Scenario | |
| Filter is used in TsFile reading process to select data satisfying one or more given condition(s). | |
| * IExpression | |
| The `IExpression` is a filter expression interface and it will be passed to our final query call. | |
| We create one or more filter expressions and may use binary filter operators to link them to our final expression. | |
| * **Create a Filter Expression** | |
| There are two types of filters. | |
| * TimeFilter: A filter for `time` in time-series data. | |
| ``` | |
| IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter); | |
| ``` | |
| Use the following relationships to get a `TimeFilter` object (value is a long int variable). | |
| |Relationship|Description| | |
| |---|---| | |
| |TimeFilter.eq(value)|Choose the time equal to the value| | |
| |TimeFilter.lt(value)|Choose the time less than the value| | |
| |TimeFilter.gt(value)|Choose the time greater than the value| | |
| |TimeFilter.ltEq(value)|Choose the time less than or equal to the value| | |
| |TimeFilter.gtEq(value)|Choose the time greater than or equal to the value| | |
| |TimeFilter.notEq(value)|Choose the time not equal to the value| | |
| |TimeFilter.not(TimeFilter)|Choose the time not satisfy another TimeFilter| | |
| * ValueFilter: A filter for `value` in time-series data. | |
| ``` | |
| IExpression valueFilterExpr = new SingleSeriesExpression(Path, ValueFilter); | |
| ``` | |
| The usage of `ValueFilter` is the same as using `TimeFilter`, just to make sure that the type of the value | |
| equal to the measurement's(defined in the path). | |
| * **Binary Filter Operators** | |
| Binary filter operators can be used to link two single expressions. | |
| * BinaryExpression.and(Expression, Expression): Choose the value satisfy for both expressions. | |
| * BinaryExpression.or(Expression, Expression): Choose the value satisfy for at least one expression. | |
| Filter Expression Examples | |
| * **TimeFilterExpression Examples** | |
| ```java | |
| IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.eq(15)); // series time = 15 | |
| ``` | |
| ``` | |
| ```java | |
| IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.ltEq(15)); // series time <= 15 | |
| ``` | |
| ```java | |
| IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.lt(15)); // series time < 15 | |
| ``` | |
| ```java | |
| IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.gtEq(15)); // series time >= 15 | |
| ``` | |
| ```java | |
| IExpression timeFilterExpr = new GlobalTimeExpression(TimeFilter.notEq(15)); // series time != 15 | |
| ``` | |
| ```java | |
| IExpression timeFilterExpr = BinaryExpression.and( | |
| new GlobalTimeExpression(TimeFilter.gtEq(15L)), | |
| new GlobalTimeExpression(TimeFilter.lt(25L))); // 15 <= series time < 25 | |
| ``` | |
| ```java | |
| IExpression timeFilterExpr = BinaryExpression.or( | |
| new GlobalTimeExpression(TimeFilter.gtEq(15L)), | |
| new GlobalTimeExpression(TimeFilter.lt(25L))); // series time >= 15 or series time < 25 | |
| ``` | |
| * Read Interface | |
| First, we open the TsFile and get a `ReadOnlyTsFile` instance from a file path string `path`. | |
| ```java | |
| TsFileSequenceReader reader = new TsFileSequenceReader(path); | |
| ReadOnlyTsFile readTsFile = new ReadOnlyTsFile(reader); | |
| ``` | |
| Next, we prepare the path array and query expression, then get final `QueryExpression` object by this interface: | |
| ```java | |
| QueryExpression queryExpression = QueryExpression.create(paths, statement); | |
| ``` | |
| The ReadOnlyTsFile class has two `query` method to perform a query. | |
| * **Method 1** | |
| ```java | |
| public QueryDataSet query(QueryExpression queryExpression) throws IOException | |
| ``` | |
| * **Method 2** | |
| ```java | |
| public QueryDataSet query(QueryExpression queryExpression, long partitionStartOffset, long partitionEndOffset) throws IOException | |
| ``` | |
| This method is designed for advanced applications such as the TsFile-Spark Connector. | |
| * **params** : For method 2, two additional parameters are added to support partial query: | |
| * ```partitionStartOffset```: start offset for a TsFile | |
| * ```partitionEndOffset```: end offset for a TsFile | |
| > **What is Partial Query ?** | |
| > | |
| > In some distributed file systems(e.g. HDFS), a file is split into severval parts which are called "Blocks" and stored in different nodes. Executing a query paralleled in each nodes involved makes better efficiency. Thus Partial Query is needed. Paritial Query only selects the results stored in the part split by ```QueryConstant.PARTITION_START_OFFSET``` and ```QueryConstant.PARTITION_END_OFFSET``` for a TsFile. | |
| * QueryDataset Interface | |
| The query performed above will return a `QueryDataset` object. | |
| Here's the useful interfaces for user. | |
| * `bool hasNext();` | |
| Return true if this dataset still has elements. | |
| * `List<Path> getPaths()` | |
| Get the paths in this data set. | |
| * `List<TSDataType> getDataTypes();` | |
| Get the data types. The class TSDataType is an enum class, the value will be one of the following: | |
| BOOLEAN, | |
| INT32, | |
| INT64, | |
| FLOAT, | |
| DOUBLE, | |
| TEXT; | |
| * `RowRecord next() throws IOException;` | |
| Get the next record. | |
| The class `RowRecord` consists of a `long` timestamp and a `List<Field>` for data in different sensors, | |
| we can use two getter methods to get them. | |
| ```java | |
| long getTimestamp(); | |
| List<Field> getFields(); | |
| ``` | |
| To get data from one Field, use these methods: | |
| ```java | |
| TSDataType getDataType(); | |
| Object getObjectValue(); | |
| ``` | |
| #### Example for reading an existing TsFile | |
| You should install TsFile to your local maven repository. | |
| A more thorough example with query statement can be found at | |
| `/tsfile/example/src/main/java/org/apache/iotdb/tsfile/TsFileRead.java` | |
| ```java | |
| package org.apache.iotdb.tsfile; | |
| import java.io.IOException; | |
| import java.util.ArrayList; | |
| import org.apache.iotdb.tsfile.read.ReadOnlyTsFile; | |
| import org.apache.iotdb.tsfile.read.TsFileSequenceReader; | |
| import org.apache.iotdb.tsfile.read.common.Path; | |
| import org.apache.iotdb.tsfile.read.expression.IExpression; | |
| import org.apache.iotdb.tsfile.read.expression.QueryExpression; | |
| import org.apache.iotdb.tsfile.read.expression.impl.BinaryExpression; | |
| import org.apache.iotdb.tsfile.read.expression.impl.GlobalTimeExpression; | |
| import org.apache.iotdb.tsfile.read.expression.impl.SingleSeriesExpression; | |
| import org.apache.iotdb.tsfile.read.filter.TimeFilter; | |
| import org.apache.iotdb.tsfile.read.filter.ValueFilter; | |
| import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet; | |
| /** | |
| * The class is to show how to read TsFile file named "test.tsfile". | |
| * The TsFile file "test.tsfile" is generated from class TsFileWrite. | |
| * Run TsFileWrite to generate the test.tsfile first | |
| */ | |
| public class TsFileRead { | |
| private static void queryAndPrint(ArrayList<Path> paths, ReadOnlyTsFile readTsFile, IExpression statement) | |
| throws IOException { | |
| QueryExpression queryExpression = QueryExpression.create(paths, statement); | |
| QueryDataSet queryDataSet = readTsFile.query(queryExpression); | |
| while (queryDataSet.hasNext()) { | |
| System.out.println(queryDataSet.next()); | |
| } | |
| System.out.println("------------"); | |
| } | |
| public static void main(String[] args) throws IOException { | |
| // file path | |
| String path = "test.tsfile"; | |
| // create reader and get the readTsFile interface | |
| TsFileSequenceReader reader = new TsFileSequenceReader(path); | |
| ReadOnlyTsFile readTsFile = new ReadOnlyTsFile(reader); | |
| // use these paths(all sensors) for all the queries | |
| ArrayList<Path> paths = new ArrayList<>(); | |
| paths.add(new Path("device_1.sensor_1")); | |
| paths.add(new Path("device_1.sensor_2")); | |
| paths.add(new Path("device_1.sensor_3")); | |
| // no query statement | |
| queryAndPrint(paths, readTsFile, null); | |
| //close the reader when you left | |
| reader.close(); | |
| } | |
| } | |
| ``` | |
| ### Change TsFile Configuration | |
| ```java | |
| TSFileConfig config = TSFileDescriptor.getInstance().getConfig(); | |
| config.setXXX(); | |
| ``` | |