Showing
9 changed files
with
963 additions
and
0 deletions
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.attributes; | ||
17 | + | ||
18 | +import com.datastax.driver.core.ResultSetFuture; | ||
19 | +import org.thingsboard.server.common.data.id.EntityId; | ||
20 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
21 | + | ||
22 | +import java.util.List; | ||
23 | +import java.util.UUID; | ||
24 | + | ||
25 | +/** | ||
26 | + * @author Andrew Shvayka | ||
27 | + */ | ||
28 | +public interface AttributesDao { | ||
29 | + | ||
30 | + AttributeKvEntry find(EntityId entityId, String attributeType, String attributeKey); | ||
31 | + | ||
32 | + List<AttributeKvEntry> findAll(EntityId entityId, String attributeType); | ||
33 | + | ||
34 | + ResultSetFuture save(EntityId entityId, String attributeType, AttributeKvEntry attribute); | ||
35 | + | ||
36 | + void removeAll(EntityId entityId, String scope, List<String> keys); | ||
37 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.attributes; | ||
17 | + | ||
18 | +import com.datastax.driver.core.ResultSet; | ||
19 | +import com.datastax.driver.core.ResultSetFuture; | ||
20 | +import com.google.common.util.concurrent.ListenableFuture; | ||
21 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
22 | +import org.thingsboard.server.common.data.id.EntityId; | ||
23 | +import org.thingsboard.server.common.data.id.UUIDBased; | ||
24 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
25 | + | ||
26 | +import java.util.List; | ||
27 | + | ||
28 | +/** | ||
29 | + * @author Andrew Shvayka | ||
30 | + */ | ||
31 | +public interface AttributesService { | ||
32 | + | ||
33 | + AttributeKvEntry find(EntityId entityId, String scope, String attributeKey); | ||
34 | + | ||
35 | + List<AttributeKvEntry> findAll(EntityId entityId, String scope); | ||
36 | + | ||
37 | + ListenableFuture<List<ResultSet>> save(EntityId entityId, String scope, List<AttributeKvEntry> attributes); | ||
38 | + | ||
39 | + void removeAll(EntityId entityId, String scope, List<String> attributeKeys); | ||
40 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.attributes; | ||
17 | + | ||
18 | +import com.datastax.driver.core.*; | ||
19 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | ||
20 | +import com.datastax.driver.core.querybuilder.Select; | ||
21 | +import lombok.extern.slf4j.Slf4j; | ||
22 | +import org.springframework.stereotype.Component; | ||
23 | +import org.thingsboard.server.common.data.id.EntityId; | ||
24 | +import org.thingsboard.server.common.data.kv.DataType; | ||
25 | +import org.thingsboard.server.dao.AbstractDao; | ||
26 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
27 | +import org.slf4j.Logger; | ||
28 | +import org.slf4j.LoggerFactory; | ||
29 | +import org.thingsboard.server.common.data.kv.*; | ||
30 | +import org.thingsboard.server.dao.timeseries.BaseTimeseriesDao; | ||
31 | + | ||
32 | +import java.util.ArrayList; | ||
33 | +import java.util.List; | ||
34 | + | ||
35 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
36 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.*; | ||
37 | + | ||
38 | +/** | ||
39 | + * @author Andrew Shvayka | ||
40 | + */ | ||
41 | +@Component | ||
42 | +@Slf4j | ||
43 | +public class BaseAttributesDao extends AbstractDao implements AttributesDao { | ||
44 | + | ||
45 | + private PreparedStatement saveStmt; | ||
46 | + | ||
47 | + @Override | ||
48 | + public AttributeKvEntry find(EntityId entityId, String attributeType, String attributeKey) { | ||
49 | + Select.Where select = select().from(ATTRIBUTES_KV_CF) | ||
50 | + .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) | ||
51 | + .and(eq(ENTITY_ID_COLUMN, entityId.getId())) | ||
52 | + .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) | ||
53 | + .and(eq(ATTRIBUTE_KEY_COLUMN, attributeKey)); | ||
54 | + log.trace("Generated query [{}] for entityId {} and key {}", select, entityId, attributeKey); | ||
55 | + return convertResultToAttributesKvEntry(attributeKey, executeRead(select).one()); | ||
56 | + } | ||
57 | + | ||
58 | + @Override | ||
59 | + public List<AttributeKvEntry> findAll(EntityId entityId, String attributeType) { | ||
60 | + Select.Where select = select().from(ATTRIBUTES_KV_CF) | ||
61 | + .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) | ||
62 | + .and(eq(ENTITY_ID_COLUMN, entityId.getId())) | ||
63 | + .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)); | ||
64 | + log.trace("Generated query [{}] for entityId {} and attributeType {}", select, entityId, attributeType); | ||
65 | + return convertResultToAttributesKvEntryList(executeRead(select)); | ||
66 | + } | ||
67 | + | ||
68 | + @Override | ||
69 | + public ResultSetFuture save(EntityId entityId, String attributeType, AttributeKvEntry attribute) { | ||
70 | + BoundStatement stmt = getSaveStmt().bind(); | ||
71 | + stmt.setString(0, entityId.getEntityType().name()); | ||
72 | + stmt.setUUID(1, entityId.getId()); | ||
73 | + stmt.setString(2, attributeType); | ||
74 | + stmt.setString(3, attribute.getKey()); | ||
75 | + stmt.setLong(4, attribute.getLastUpdateTs()); | ||
76 | + stmt.setString(5, attribute.getStrValue().orElse(null)); | ||
77 | + if (attribute.getBooleanValue().isPresent()) { | ||
78 | + stmt.setBool(6, attribute.getBooleanValue().get()); | ||
79 | + } else { | ||
80 | + stmt.setToNull(6); | ||
81 | + } | ||
82 | + if (attribute.getLongValue().isPresent()) { | ||
83 | + stmt.setLong(7, attribute.getLongValue().get()); | ||
84 | + } else { | ||
85 | + stmt.setToNull(7); | ||
86 | + } | ||
87 | + if (attribute.getDoubleValue().isPresent()) { | ||
88 | + stmt.setDouble(8, attribute.getDoubleValue().get()); | ||
89 | + } else { | ||
90 | + stmt.setToNull(8); | ||
91 | + } | ||
92 | + return executeAsyncWrite(stmt); | ||
93 | + } | ||
94 | + | ||
95 | + @Override | ||
96 | + public void removeAll(EntityId entityId, String attributeType, List<String> keys) { | ||
97 | + for (String key : keys) { | ||
98 | + delete(entityId, attributeType, key); | ||
99 | + } | ||
100 | + } | ||
101 | + | ||
102 | + private void delete(EntityId entityId, String attributeType, String key) { | ||
103 | + Statement delete = QueryBuilder.delete().all().from(ModelConstants.ATTRIBUTES_KV_CF) | ||
104 | + .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) | ||
105 | + .and(eq(ENTITY_ID_COLUMN, entityId.getId())) | ||
106 | + .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) | ||
107 | + .and(eq(ATTRIBUTE_KEY_COLUMN, key)); | ||
108 | + log.debug("Remove request: {}", delete.toString()); | ||
109 | + getSession().execute(delete); | ||
110 | + } | ||
111 | + | ||
112 | + private PreparedStatement getSaveStmt() { | ||
113 | + if (saveStmt == null) { | ||
114 | + saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.ATTRIBUTES_KV_CF + | ||
115 | + "(" + ENTITY_TYPE_COLUMN + | ||
116 | + "," + ENTITY_ID_COLUMN + | ||
117 | + "," + ATTRIBUTE_TYPE_COLUMN + | ||
118 | + "," + ATTRIBUTE_KEY_COLUMN + | ||
119 | + "," + LAST_UPDATE_TS_COLUMN + | ||
120 | + "," + ModelConstants.STRING_VALUE_COLUMN + | ||
121 | + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + | ||
122 | + "," + ModelConstants.LONG_VALUE_COLUMN + | ||
123 | + "," + ModelConstants.DOUBLE_VALUE_COLUMN + | ||
124 | + ")" + | ||
125 | + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); | ||
126 | + } | ||
127 | + return saveStmt; | ||
128 | + } | ||
129 | + | ||
130 | + private AttributeKvEntry convertResultToAttributesKvEntry(String key, Row row) { | ||
131 | + AttributeKvEntry attributeEntry = null; | ||
132 | + if (row != null) { | ||
133 | + long lastUpdateTs = row.get(LAST_UPDATE_TS_COLUMN, Long.class); | ||
134 | + attributeEntry = new BaseAttributeKvEntry(BaseTimeseriesDao.toKvEntry(row, key), lastUpdateTs); | ||
135 | + } | ||
136 | + return attributeEntry; | ||
137 | + } | ||
138 | + | ||
139 | + private List<AttributeKvEntry> convertResultToAttributesKvEntryList(ResultSet resultSet) { | ||
140 | + List<Row> rows = resultSet.all(); | ||
141 | + List<AttributeKvEntry> entries = new ArrayList<>(rows.size()); | ||
142 | + if (!rows.isEmpty()) { | ||
143 | + rows.stream().forEach(row -> { | ||
144 | + String key = row.getString(ModelConstants.ATTRIBUTE_KEY_COLUMN); | ||
145 | + AttributeKvEntry kvEntry = convertResultToAttributesKvEntry(key, row); | ||
146 | + if (kvEntry != null) { | ||
147 | + entries.add(kvEntry); | ||
148 | + } | ||
149 | + }); | ||
150 | + } | ||
151 | + return entries; | ||
152 | + } | ||
153 | + | ||
154 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.attributes; | ||
17 | + | ||
18 | +import com.datastax.driver.core.ResultSet; | ||
19 | +import com.datastax.driver.core.ResultSetFuture; | ||
20 | +import com.google.common.collect.Lists; | ||
21 | +import com.google.common.util.concurrent.Futures; | ||
22 | +import com.google.common.util.concurrent.ListenableFuture; | ||
23 | +import org.thingsboard.server.common.data.id.EntityId; | ||
24 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
25 | +import org.thingsboard.server.dao.exception.IncorrectParameterException; | ||
26 | +import org.springframework.beans.factory.annotation.Autowired; | ||
27 | +import org.springframework.stereotype.Service; | ||
28 | +import org.thingsboard.server.dao.service.Validator; | ||
29 | + | ||
30 | +import java.util.List; | ||
31 | + | ||
32 | +/** | ||
33 | + * @author Andrew Shvayka | ||
34 | + */ | ||
35 | +@Service | ||
36 | +public class BaseAttributesService implements AttributesService { | ||
37 | + | ||
38 | + @Autowired | ||
39 | + private AttributesDao attributesDao; | ||
40 | + | ||
41 | + @Override | ||
42 | + public AttributeKvEntry find(EntityId entityId, String scope, String attributeKey) { | ||
43 | + validate(entityId, scope); | ||
44 | + Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); | ||
45 | + return attributesDao.find(entityId, scope, attributeKey); | ||
46 | + } | ||
47 | + | ||
48 | + @Override | ||
49 | + public List<AttributeKvEntry> findAll(EntityId entityId, String scope) { | ||
50 | + validate(entityId, scope); | ||
51 | + return attributesDao.findAll(entityId, scope); | ||
52 | + } | ||
53 | + | ||
54 | + @Override | ||
55 | + public ListenableFuture<List<ResultSet>> save(EntityId entityId, String scope, List<AttributeKvEntry> attributes) { | ||
56 | + validate(entityId, scope); | ||
57 | + attributes.forEach(attribute -> validate(attribute)); | ||
58 | + List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(attributes.size()); | ||
59 | + for(AttributeKvEntry attribute : attributes) { | ||
60 | + futures.add(attributesDao.save(entityId, scope, attribute)); | ||
61 | + } | ||
62 | + return Futures.allAsList(futures); | ||
63 | + } | ||
64 | + | ||
65 | + @Override | ||
66 | + public void removeAll(EntityId entityId, String scope, List<String> keys) { | ||
67 | + validate(entityId, scope); | ||
68 | + attributesDao.removeAll(entityId, scope, keys); | ||
69 | + } | ||
70 | + | ||
71 | + private static void validate(EntityId id, String scope) { | ||
72 | + Validator.validateId(id.getId(), "Incorrect id " + id); | ||
73 | + Validator.validateString(scope, "Incorrect scope " + scope); | ||
74 | + } | ||
75 | + | ||
76 | + private static void validate(AttributeKvEntry kvEntry) { | ||
77 | + if (kvEntry == null) { | ||
78 | + throw new IncorrectParameterException("Key value entry can't be null"); | ||
79 | + } else if (kvEntry.getDataType() == null) { | ||
80 | + throw new IncorrectParameterException("Incorrect kvEntry. Data type can't be null"); | ||
81 | + } else { | ||
82 | + Validator.validateString(kvEntry.getKey(), "Incorrect kvEntry. Key can't be empty"); | ||
83 | + Validator.validatePositiveNumber(kvEntry.getLastUpdateTs(), "Incorrect last update ts. Ts should be positive"); | ||
84 | + } | ||
85 | + } | ||
86 | + | ||
87 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.timeseries; | ||
17 | + | ||
18 | +import com.datastax.driver.core.*; | ||
19 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | ||
20 | +import com.datastax.driver.core.querybuilder.Select; | ||
21 | +import lombok.extern.slf4j.Slf4j; | ||
22 | +import org.springframework.beans.factory.annotation.Value; | ||
23 | +import org.springframework.stereotype.Component; | ||
24 | +import org.thingsboard.server.common.data.kv.*; | ||
25 | +import org.thingsboard.server.common.data.kv.DataType; | ||
26 | +import org.thingsboard.server.dao.AbstractDao; | ||
27 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
28 | + | ||
29 | +import java.util.*; | ||
30 | + | ||
31 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | ||
32 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; | ||
33 | + | ||
34 | +/** | ||
35 | + * @author Andrew Shvayka | ||
36 | + */ | ||
37 | +@Component | ||
38 | +@Slf4j | ||
39 | +public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao { | ||
40 | + | ||
41 | + @Value("${cassandra.query.max_limit_per_request}") | ||
42 | + protected Integer maxLimitPerRequest; | ||
43 | + | ||
44 | + private PreparedStatement partitionInsertStmt; | ||
45 | + private PreparedStatement[] latestInsertStmts; | ||
46 | + private PreparedStatement[] saveStmts; | ||
47 | + private PreparedStatement findLatestStmt; | ||
48 | + private PreparedStatement findAllLatestStmt; | ||
49 | + | ||
50 | + @Override | ||
51 | + public List<TsKvEntry> find(String entityType, UUID entityId, TsKvQuery query, Optional<Long> minPartition, Optional<Long> maxPartition) { | ||
52 | + List<Row> rows = Collections.emptyList(); | ||
53 | + Long[] parts = fetchPartitions(entityType, entityId, query.getKey(), minPartition, maxPartition); | ||
54 | + int partsLength = parts.length; | ||
55 | + if (parts != null && partsLength > 0) { | ||
56 | + int limit = maxLimitPerRequest; | ||
57 | + Optional<Integer> lim = query.getLimit(); | ||
58 | + if (lim.isPresent() && lim.get() < maxLimitPerRequest) { | ||
59 | + limit = lim.get(); | ||
60 | + } | ||
61 | + | ||
62 | + rows = new ArrayList<>(limit); | ||
63 | + int lastIdx = partsLength - 1; | ||
64 | + for (int i = 0; i < partsLength; i++) { | ||
65 | + int currentLimit; | ||
66 | + if (rows.size() >= limit) { | ||
67 | + break; | ||
68 | + } else { | ||
69 | + currentLimit = limit - rows.size(); | ||
70 | + } | ||
71 | + Long partition = parts[i]; | ||
72 | + Select.Where where = select().from(ModelConstants.TS_KV_CF).where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityType)) | ||
73 | + .and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId)) | ||
74 | + .and(eq(ModelConstants.KEY_COLUMN, query.getKey())) | ||
75 | + .and(eq(ModelConstants.PARTITION_COLUMN, partition)); | ||
76 | + if (i == 0 && query.getStartTs().isPresent()) { | ||
77 | + where.and(QueryBuilder.gt(ModelConstants.TS_COLUMN, query.getStartTs().get())); | ||
78 | + } else if (i == lastIdx && query.getEndTs().isPresent()) { | ||
79 | + where.and(QueryBuilder.lte(ModelConstants.TS_COLUMN, query.getEndTs().get())); | ||
80 | + } | ||
81 | + where.limit(currentLimit); | ||
82 | + rows.addAll(executeRead(where).all()); | ||
83 | + } | ||
84 | + } | ||
85 | + return convertResultToTsKvEntryList(rows); | ||
86 | + } | ||
87 | + | ||
88 | + @Override | ||
89 | + public ResultSetFuture findLatest(String entityType, UUID entityId, String key) { | ||
90 | + BoundStatement stmt = getFindLatestStmt().bind(); | ||
91 | + stmt.setString(0, entityType); | ||
92 | + stmt.setUUID(1, entityId); | ||
93 | + stmt.setString(2, key); | ||
94 | + log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityType, entityId); | ||
95 | + return executeAsyncRead(stmt); | ||
96 | + } | ||
97 | + | ||
98 | + @Override | ||
99 | + public ResultSetFuture findAllLatest(String entityType, UUID entityId) { | ||
100 | + BoundStatement stmt = getFindAllLatestStmt().bind(); | ||
101 | + stmt.setString(0, entityType); | ||
102 | + stmt.setUUID(1, entityId); | ||
103 | + log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityType, entityId); | ||
104 | + return executeAsyncRead(stmt); | ||
105 | + } | ||
106 | + | ||
107 | + @Override | ||
108 | + public ResultSetFuture save(String entityType, UUID entityId, long partition, TsKvEntry tsKvEntry) { | ||
109 | + DataType type = tsKvEntry.getDataType(); | ||
110 | + BoundStatement stmt = getSaveStmt(type).bind() | ||
111 | + .setString(0, entityType) | ||
112 | + .setUUID(1, entityId) | ||
113 | + .setString(2, tsKvEntry.getKey()) | ||
114 | + .setLong(3, partition) | ||
115 | + .setLong(4, tsKvEntry.getTs()); | ||
116 | + addValue(tsKvEntry, stmt, 5); | ||
117 | + return executeAsyncWrite(stmt); | ||
118 | + } | ||
119 | + | ||
120 | + @Override | ||
121 | + public ResultSetFuture saveLatest(String entityType, UUID entityId, TsKvEntry tsKvEntry) { | ||
122 | + DataType type = tsKvEntry.getDataType(); | ||
123 | + BoundStatement stmt = getLatestStmt(type).bind() | ||
124 | + .setString(0, entityType) | ||
125 | + .setUUID(1, entityId) | ||
126 | + .setString(2, tsKvEntry.getKey()) | ||
127 | + .setLong(3, tsKvEntry.getTs()); | ||
128 | + addValue(tsKvEntry, stmt, 4); | ||
129 | + return executeAsyncWrite(stmt); | ||
130 | + } | ||
131 | + | ||
132 | + @Override | ||
133 | + public ResultSetFuture savePartition(String entityType, UUID entityId, long partition, String key) { | ||
134 | + log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityType, entityId, key); | ||
135 | + return executeAsyncWrite(getPartitionInsertStmt().bind() | ||
136 | + .setString(0, entityType) | ||
137 | + .setUUID(1, entityId) | ||
138 | + .setLong(2, partition) | ||
139 | + .setString(3, key)); | ||
140 | + } | ||
141 | + | ||
142 | + @Override | ||
143 | + public List<TsKvEntry> convertResultToTsKvEntryList(List<Row> rows) { | ||
144 | + List<TsKvEntry> entries = new ArrayList<>(rows.size()); | ||
145 | + if (!rows.isEmpty()) { | ||
146 | + rows.stream().forEach(row -> { | ||
147 | + TsKvEntry kvEntry = convertResultToTsKvEntry(row); | ||
148 | + if (kvEntry != null) { | ||
149 | + entries.add(kvEntry); | ||
150 | + } | ||
151 | + }); | ||
152 | + } | ||
153 | + return entries; | ||
154 | + } | ||
155 | + | ||
156 | + @Override | ||
157 | + public TsKvEntry convertResultToTsKvEntry(Row row) { | ||
158 | + String key = row.getString(ModelConstants.KEY_COLUMN); | ||
159 | + long ts = row.getLong(ModelConstants.TS_COLUMN); | ||
160 | + return new BasicTsKvEntry(ts, toKvEntry(row, key)); | ||
161 | + } | ||
162 | + | ||
163 | + public static KvEntry toKvEntry(Row row, String key) { | ||
164 | + KvEntry kvEntry = null; | ||
165 | + String strV = row.get(ModelConstants.STRING_VALUE_COLUMN, String.class); | ||
166 | + if (strV != null) { | ||
167 | + kvEntry = new StringDataEntry(key, strV); | ||
168 | + } else { | ||
169 | + Long longV = row.get(ModelConstants.LONG_VALUE_COLUMN, Long.class); | ||
170 | + if (longV != null) { | ||
171 | + kvEntry = new LongDataEntry(key, longV); | ||
172 | + } else { | ||
173 | + Double doubleV = row.get(ModelConstants.DOUBLE_VALUE_COLUMN, Double.class); | ||
174 | + if (doubleV != null) { | ||
175 | + kvEntry = new DoubleDataEntry(key, doubleV); | ||
176 | + } else { | ||
177 | + Boolean boolV = row.get(ModelConstants.BOOLEAN_VALUE_COLUMN, Boolean.class); | ||
178 | + if (boolV != null) { | ||
179 | + kvEntry = new BooleanDataEntry(key, boolV); | ||
180 | + } else { | ||
181 | + log.warn("All values in key-value row are nullable "); | ||
182 | + } | ||
183 | + } | ||
184 | + } | ||
185 | + } | ||
186 | + return kvEntry; | ||
187 | + } | ||
188 | + | ||
189 | + /** | ||
190 | + * Select existing partitions from the table | ||
191 | + * <code>{@link ModelConstants#TS_KV_PARTITIONS_CF}</code> for the given entity | ||
192 | + */ | ||
193 | + private Long[] fetchPartitions(String entityType, UUID entityId, String key, Optional<Long> minPartition, Optional<Long> maxPartition) { | ||
194 | + Select.Where select = QueryBuilder.select(ModelConstants.PARTITION_COLUMN).from(ModelConstants.TS_KV_PARTITIONS_CF).where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityType)) | ||
195 | + .and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId)).and(eq(ModelConstants.KEY_COLUMN, key)); | ||
196 | + minPartition.ifPresent(startTs -> select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition.get()))); | ||
197 | + maxPartition.ifPresent(endTs -> select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition.get()))); | ||
198 | + ResultSet resultSet = executeRead(select); | ||
199 | + return resultSet.all().stream().map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)).toArray(Long[]::new); | ||
200 | + } | ||
201 | + | ||
202 | + private PreparedStatement getSaveStmt(DataType dataType) { | ||
203 | + if (saveStmts == null) { | ||
204 | + saveStmts = new PreparedStatement[DataType.values().length]; | ||
205 | + for (DataType type : DataType.values()) { | ||
206 | + saveStmts[type.ordinal()] = getSession().prepare("INSERT INTO " + ModelConstants.TS_KV_CF + | ||
207 | + "(" + ModelConstants.ENTITY_TYPE_COLUMN + | ||
208 | + "," + ModelConstants.ENTITY_ID_COLUMN + | ||
209 | + "," + ModelConstants.KEY_COLUMN + | ||
210 | + "," + ModelConstants.PARTITION_COLUMN + | ||
211 | + "," + ModelConstants.TS_COLUMN + | ||
212 | + "," + getColumnName(type) + ")" + | ||
213 | + " VALUES(?, ?, ?, ?, ?, ?)"); | ||
214 | + } | ||
215 | + } | ||
216 | + return saveStmts[dataType.ordinal()]; | ||
217 | + } | ||
218 | + | ||
219 | + private PreparedStatement getLatestStmt(DataType dataType) { | ||
220 | + if (latestInsertStmts == null) { | ||
221 | + latestInsertStmts = new PreparedStatement[DataType.values().length]; | ||
222 | + for (DataType type : DataType.values()) { | ||
223 | + latestInsertStmts[type.ordinal()] = getSession().prepare("INSERT INTO " + ModelConstants.TS_KV_LATEST_CF + | ||
224 | + "(" + ModelConstants.ENTITY_TYPE_COLUMN + | ||
225 | + "," + ModelConstants.ENTITY_ID_COLUMN + | ||
226 | + "," + ModelConstants.KEY_COLUMN + | ||
227 | + "," + ModelConstants.TS_COLUMN + | ||
228 | + "," + getColumnName(type) + ")" + | ||
229 | + " VALUES(?, ?, ?, ?, ?)"); | ||
230 | + } | ||
231 | + } | ||
232 | + return latestInsertStmts[dataType.ordinal()]; | ||
233 | + } | ||
234 | + | ||
235 | + | ||
236 | + private PreparedStatement getPartitionInsertStmt() { | ||
237 | + if (partitionInsertStmt == null) { | ||
238 | + partitionInsertStmt = getSession().prepare("INSERT INTO " + ModelConstants.TS_KV_PARTITIONS_CF + | ||
239 | + "(" + ModelConstants.ENTITY_TYPE_COLUMN + | ||
240 | + "," + ModelConstants.ENTITY_ID_COLUMN + | ||
241 | + "," + ModelConstants.PARTITION_COLUMN + | ||
242 | + "," + ModelConstants.KEY_COLUMN + ")" + | ||
243 | + " VALUES(?, ?, ?, ?)"); | ||
244 | + } | ||
245 | + return partitionInsertStmt; | ||
246 | + } | ||
247 | + | ||
248 | + private PreparedStatement getFindLatestStmt() { | ||
249 | + if (findLatestStmt == null) { | ||
250 | + findLatestStmt = getSession().prepare("SELECT " + | ||
251 | + ModelConstants.KEY_COLUMN + "," + | ||
252 | + ModelConstants.TS_COLUMN + "," + | ||
253 | + ModelConstants.STRING_VALUE_COLUMN + "," + | ||
254 | + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + | ||
255 | + ModelConstants.LONG_VALUE_COLUMN + "," + | ||
256 | + ModelConstants.DOUBLE_VALUE_COLUMN + " " + | ||
257 | + "FROM " + ModelConstants.TS_KV_LATEST_CF + " " + | ||
258 | + "WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + " = ? " + | ||
259 | + "AND " + ModelConstants.ENTITY_ID_COLUMN + " = ? " + | ||
260 | + "AND " + ModelConstants.KEY_COLUMN + " = ? "); | ||
261 | + } | ||
262 | + return findLatestStmt; | ||
263 | + } | ||
264 | + | ||
265 | + private PreparedStatement getFindAllLatestStmt() { | ||
266 | + if (findAllLatestStmt == null) { | ||
267 | + findAllLatestStmt = getSession().prepare("SELECT " + | ||
268 | + ModelConstants.KEY_COLUMN + "," + | ||
269 | + ModelConstants.TS_COLUMN + "," + | ||
270 | + ModelConstants.STRING_VALUE_COLUMN + "," + | ||
271 | + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + | ||
272 | + ModelConstants.LONG_VALUE_COLUMN + "," + | ||
273 | + ModelConstants.DOUBLE_VALUE_COLUMN + " " + | ||
274 | + "FROM " + ModelConstants.TS_KV_LATEST_CF + " " + | ||
275 | + "WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + " = ? " + | ||
276 | + "AND " + ModelConstants.ENTITY_ID_COLUMN + " = ? "); | ||
277 | + } | ||
278 | + return findAllLatestStmt; | ||
279 | + } | ||
280 | + | ||
281 | + public static String getColumnName(DataType type) { | ||
282 | + switch (type) { | ||
283 | + case BOOLEAN: | ||
284 | + return ModelConstants.BOOLEAN_VALUE_COLUMN; | ||
285 | + case STRING: | ||
286 | + return ModelConstants.STRING_VALUE_COLUMN; | ||
287 | + case LONG: | ||
288 | + return ModelConstants.LONG_VALUE_COLUMN; | ||
289 | + case DOUBLE: | ||
290 | + return ModelConstants.DOUBLE_VALUE_COLUMN; | ||
291 | + default: | ||
292 | + throw new RuntimeException("Not implemented!"); | ||
293 | + } | ||
294 | + } | ||
295 | + | ||
296 | + public static void addValue(KvEntry kvEntry, BoundStatement stmt, int column) { | ||
297 | + switch (kvEntry.getDataType()) { | ||
298 | + case BOOLEAN: | ||
299 | + stmt.setBool(column, kvEntry.getBooleanValue().get().booleanValue()); | ||
300 | + break; | ||
301 | + case STRING: | ||
302 | + stmt.setString(column, kvEntry.getStrValue().get()); | ||
303 | + break; | ||
304 | + case LONG: | ||
305 | + stmt.setLong(column, kvEntry.getLongValue().get().longValue()); | ||
306 | + break; | ||
307 | + case DOUBLE: | ||
308 | + stmt.setDouble(column, kvEntry.getDoubleValue().get().doubleValue()); | ||
309 | + break; | ||
310 | + } | ||
311 | + } | ||
312 | + | ||
313 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.timeseries; | ||
17 | + | ||
18 | +import com.datastax.driver.core.ResultSet; | ||
19 | +import com.datastax.driver.core.ResultSetFuture; | ||
20 | +import com.datastax.driver.core.Row; | ||
21 | +import com.google.common.collect.Lists; | ||
22 | +import com.google.common.util.concurrent.Futures; | ||
23 | +import com.google.common.util.concurrent.ListenableFuture; | ||
24 | +import lombok.extern.slf4j.Slf4j; | ||
25 | +import org.thingsboard.server.common.data.id.UUIDBased; | ||
26 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
27 | +import org.thingsboard.server.common.data.kv.TsKvQuery; | ||
28 | +import org.thingsboard.server.dao.exception.IncorrectParameterException; | ||
29 | +import org.slf4j.Logger; | ||
30 | +import org.slf4j.LoggerFactory; | ||
31 | +import org.springframework.beans.factory.annotation.Autowired; | ||
32 | +import org.springframework.beans.factory.annotation.Value; | ||
33 | +import org.springframework.stereotype.Service; | ||
34 | +import org.thingsboard.server.dao.service.Validator; | ||
35 | + | ||
36 | +import javax.annotation.PostConstruct; | ||
37 | +import java.time.Instant; | ||
38 | +import java.time.LocalDateTime; | ||
39 | +import java.time.ZoneOffset; | ||
40 | +import java.util.*; | ||
41 | + | ||
42 | +import static org.apache.commons.lang3.StringUtils.isBlank; | ||
43 | + | ||
44 | +/** | ||
45 | + * @author Andrew Shvayka | ||
46 | + */ | ||
47 | +@Service | ||
48 | +@Slf4j | ||
49 | +public class BaseTimeseriesService implements TimeseriesService { | ||
50 | + | ||
51 | + public static final int INSERTS_PER_ENTRY = 3; | ||
52 | + | ||
53 | + @Value("${cassandra.query.ts_key_value_partitioning}") | ||
54 | + private String partitioning; | ||
55 | + | ||
56 | + @Autowired | ||
57 | + private TimeseriesDao timeseriesDao; | ||
58 | + | ||
59 | + private TsPartitionDate tsFormat; | ||
60 | + | ||
61 | + @PostConstruct | ||
62 | + public void init() { | ||
63 | + Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); | ||
64 | + if (partition.isPresent()) { | ||
65 | + tsFormat = partition.get(); | ||
66 | + } else { | ||
67 | + log.warn("Incorrect configuration of partitioning {}", partitioning); | ||
68 | + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); | ||
69 | + } | ||
70 | + } | ||
71 | + | ||
72 | + @Override | ||
73 | + public List<TsKvEntry> find(String entityType, UUIDBased entityId, TsKvQuery query) { | ||
74 | + validate(entityType, entityId); | ||
75 | + validate(query); | ||
76 | + return timeseriesDao.find(entityType, entityId.getId(), query, toPartitionTs(query.getStartTs()), toPartitionTs(query.getEndTs())); | ||
77 | + } | ||
78 | + | ||
79 | + private Optional<Long> toPartitionTs(Optional<Long> ts) { | ||
80 | + if (ts.isPresent()) { | ||
81 | + return Optional.of(toPartitionTs(ts.get())); | ||
82 | + } else { | ||
83 | + return Optional.empty(); | ||
84 | + } | ||
85 | + } | ||
86 | + | ||
87 | + @Override | ||
88 | + public ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys) { | ||
89 | + validate(entityType, entityId); | ||
90 | + List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(keys.size()); | ||
91 | + keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key)); | ||
92 | + keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityType, entityId.getId(), key))); | ||
93 | + return Futures.allAsList(futures); | ||
94 | + } | ||
95 | + | ||
96 | + @Override | ||
97 | + public ResultSetFuture findAllLatest(String entityType, UUIDBased entityId) { | ||
98 | + validate(entityType, entityId); | ||
99 | + return timeseriesDao.findAllLatest(entityType, entityId.getId()); | ||
100 | + } | ||
101 | + | ||
102 | + @Override | ||
103 | + public ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, TsKvEntry tsKvEntry) { | ||
104 | + validate(entityType, entityId); | ||
105 | + if (tsKvEntry == null) { | ||
106 | + throw new IncorrectParameterException("Key value entry can't be null"); | ||
107 | + } | ||
108 | + UUID uid = entityId.getId(); | ||
109 | + long partitionTs = toPartitionTs(tsKvEntry.getTs()); | ||
110 | + | ||
111 | + List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); | ||
112 | + saveAndRegisterFutures(futures, entityType, tsKvEntry, uid, partitionTs); | ||
113 | + return Futures.allAsList(futures); | ||
114 | + } | ||
115 | + | ||
116 | + @Override | ||
117 | + public ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, List<TsKvEntry> tsKvEntries) { | ||
118 | + validate(entityType, entityId); | ||
119 | + List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); | ||
120 | + for (TsKvEntry tsKvEntry : tsKvEntries) { | ||
121 | + if (tsKvEntry == null) { | ||
122 | + throw new IncorrectParameterException("Key value entry can't be null"); | ||
123 | + } | ||
124 | + UUID uid = entityId.getId(); | ||
125 | + long partitionTs = toPartitionTs(tsKvEntry.getTs()); | ||
126 | + saveAndRegisterFutures(futures, entityType, tsKvEntry, uid, partitionTs); | ||
127 | + } | ||
128 | + return Futures.allAsList(futures); | ||
129 | + } | ||
130 | + | ||
131 | + @Override | ||
132 | + public TsKvEntry convertResultToTsKvEntry(Row row) { | ||
133 | + return timeseriesDao.convertResultToTsKvEntry(row); | ||
134 | + } | ||
135 | + | ||
136 | + @Override | ||
137 | + public List<TsKvEntry> convertResultSetToTsKvEntryList(ResultSet rs) { | ||
138 | + return timeseriesDao.convertResultToTsKvEntryList(rs.all()); | ||
139 | + } | ||
140 | + | ||
141 | + private void saveAndRegisterFutures(List<ResultSetFuture> futures, String entityType, TsKvEntry tsKvEntry, UUID uid, long partitionTs) { | ||
142 | + futures.add(timeseriesDao.savePartition(entityType, uid, partitionTs, tsKvEntry.getKey())); | ||
143 | + futures.add(timeseriesDao.saveLatest(entityType, uid, tsKvEntry)); | ||
144 | + futures.add(timeseriesDao.save(entityType, uid, partitionTs, tsKvEntry)); | ||
145 | + } | ||
146 | + | ||
147 | + private long toPartitionTs(long ts) { | ||
148 | + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | ||
149 | + | ||
150 | + LocalDateTime parititonTime = tsFormat.truncatedTo(time); | ||
151 | + | ||
152 | + return parititonTime.toInstant(ZoneOffset.UTC).toEpochMilli(); | ||
153 | + } | ||
154 | + | ||
155 | + private static void validate(String entityType, UUIDBased entityId) { | ||
156 | + Validator.validateString(entityType, "Incorrect entityType " + entityType); | ||
157 | + Validator.validateId(entityId, "Incorrect entityId " + entityId); | ||
158 | + } | ||
159 | + | ||
160 | + private static void validate(TsKvQuery query) { | ||
161 | + if (query == null) { | ||
162 | + throw new IncorrectParameterException("TsKvQuery can't be null"); | ||
163 | + } else if (isBlank(query.getKey())) { | ||
164 | + throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty"); | ||
165 | + } | ||
166 | + } | ||
167 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.timeseries; | ||
17 | + | ||
18 | +import com.datastax.driver.core.ResultSetFuture; | ||
19 | +import com.datastax.driver.core.Row; | ||
20 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
21 | +import org.thingsboard.server.common.data.kv.TsKvQuery; | ||
22 | + | ||
23 | +import java.util.List; | ||
24 | +import java.util.Optional; | ||
25 | +import java.util.Set; | ||
26 | +import java.util.UUID; | ||
27 | + | ||
28 | +/** | ||
29 | + * @author Andrew Shvayka | ||
30 | + */ | ||
31 | +public interface TimeseriesDao { | ||
32 | + | ||
33 | + List<TsKvEntry> find(String entityType, UUID entityId, TsKvQuery query, Optional<Long> minPartition, Optional<Long> maxPartition); | ||
34 | + | ||
35 | + ResultSetFuture findLatest(String entityType, UUID entityId, String key); | ||
36 | + | ||
37 | + ResultSetFuture findAllLatest(String entityType, UUID entityId); | ||
38 | + | ||
39 | + ResultSetFuture save(String entityType, UUID entityId, long partition, TsKvEntry tsKvEntry); | ||
40 | + | ||
41 | + ResultSetFuture savePartition(String entityType, UUID entityId, long partition, String key); | ||
42 | + | ||
43 | + ResultSetFuture saveLatest(String entityType, UUID entityId, TsKvEntry tsKvEntry); | ||
44 | + | ||
45 | + TsKvEntry convertResultToTsKvEntry(Row row); | ||
46 | + | ||
47 | + List<TsKvEntry> convertResultToTsKvEntryList(List<Row> rows); | ||
48 | + | ||
49 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.timeseries; | ||
17 | + | ||
18 | +import com.datastax.driver.core.ResultSet; | ||
19 | +import com.datastax.driver.core.ResultSetFuture; | ||
20 | +import com.datastax.driver.core.Row; | ||
21 | +import com.google.common.util.concurrent.ListenableFuture; | ||
22 | +import org.thingsboard.server.common.data.id.UUIDBased; | ||
23 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
24 | +import org.thingsboard.server.common.data.kv.TsKvQuery; | ||
25 | + | ||
26 | +import java.util.Collection; | ||
27 | +import java.util.List; | ||
28 | +import java.util.Set; | ||
29 | + | ||
30 | +/** | ||
31 | + * @author Andrew Shvayka | ||
32 | + */ | ||
33 | +public interface TimeseriesService { | ||
34 | + | ||
35 | + //TODO: Replace this with async operation | ||
36 | + List<TsKvEntry> find(String entityType, UUIDBased entityId, TsKvQuery query); | ||
37 | + | ||
38 | + ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys); | ||
39 | + | ||
40 | + ResultSetFuture findAllLatest(String entityType, UUIDBased entityId); | ||
41 | + | ||
42 | + ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, TsKvEntry tsKvEntry); | ||
43 | + | ||
44 | + ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, List<TsKvEntry> tsKvEntry); | ||
45 | + | ||
46 | + TsKvEntry convertResultToTsKvEntry(Row row); | ||
47 | + | ||
48 | + List<TsKvEntry> convertResultSetToTsKvEntryList(ResultSet rs); | ||
49 | + | ||
50 | +} |
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.timeseries; | ||
17 | + | ||
18 | +import java.time.LocalDateTime; | ||
19 | +import java.time.temporal.ChronoUnit; | ||
20 | +import java.time.temporal.TemporalUnit; | ||
21 | +import java.util.Optional; | ||
22 | + | ||
23 | +public enum TsPartitionDate { | ||
24 | + | ||
25 | + MINUTES("yyyy-MM-dd-HH-mm", ChronoUnit.MINUTES), HOURS("yyyy-MM-dd-HH", ChronoUnit.HOURS), DAYS("yyyy-MM-dd", ChronoUnit.DAYS), MONTHS("yyyy-MM", ChronoUnit.MONTHS), YEARS("yyyy", ChronoUnit.YEARS); | ||
26 | + | ||
27 | + private final String pattern; | ||
28 | + private final TemporalUnit truncateUnit; | ||
29 | + | ||
30 | + TsPartitionDate(String pattern, TemporalUnit truncateUnit) { | ||
31 | + this.pattern = pattern; | ||
32 | + this.truncateUnit = truncateUnit; | ||
33 | + } | ||
34 | + | ||
35 | + public String getPattern() { | ||
36 | + return pattern; | ||
37 | + } | ||
38 | + | ||
39 | + public TemporalUnit getTruncateUnit() { | ||
40 | + return truncateUnit; | ||
41 | + } | ||
42 | + | ||
43 | + public LocalDateTime truncatedTo(LocalDateTime time) { | ||
44 | + switch (this){ | ||
45 | + case MONTHS: | ||
46 | + return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1); | ||
47 | + case YEARS: | ||
48 | + return time.truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); | ||
49 | + default: | ||
50 | + return time.truncatedTo(truncateUnit); | ||
51 | + } | ||
52 | + } | ||
53 | + | ||
54 | + public static Optional<TsPartitionDate> parse(String name) { | ||
55 | + TsPartitionDate partition = null; | ||
56 | + if (name != null) { | ||
57 | + for (TsPartitionDate partitionDate : TsPartitionDate.values()) { | ||
58 | + if (partitionDate.name().equalsIgnoreCase(name)) { | ||
59 | + partition = partitionDate; | ||
60 | + break; | ||
61 | + } | ||
62 | + } | ||
63 | + } | ||
64 | + return Optional.of(partition); | ||
65 | + } | ||
66 | +} |