Browse Source
* init commit * cleaned code and add test-properties * cleaned code * psql-update * timescale-update * code-refactoring * fix typo * renamed dao * revert indents * refactored code * fix typo * init-partitioning * code updated * cleaned code * fixed license * fix typo * fixed code after review * add annotation to repository * update psql version for docker * postgres-10 * postgres-10 * update docker compose config * fixed partition saving * change key_id to serial column definition * upgrade psql added * add separate upgrade service * added upgrade script * change image on k8s * change logs * resolve conflict after merge with master * revert datasource url in yml * fix typo * license header fix * remove old methods for the timeseries inserts * clean up code * fix saveOrUpdate for PostgreSQL * refactoring & revert Timescale to use latest table * added PsqlTsAnyDao * duplicated code method removed * remove unused invert dictionary map * change the upgrade directory from 2.4.1 to 2.4.3 * refactor JpaPsqlTimeseriesDaopull/2380/head
committed by
Andrew Shvayka
75 changed files with 2669 additions and 1417 deletions
@ -0,0 +1,179 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2020 The Thingsboard Authors |
||||
|
-- |
||||
|
-- Licensed 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. |
||||
|
-- |
||||
|
|
||||
|
-- load function check_version() |
||||
|
|
||||
|
CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$ |
||||
|
DECLARE |
||||
|
current_version integer; |
||||
|
valid_version boolean; |
||||
|
BEGIN |
||||
|
RAISE NOTICE 'Check the current installed PostgreSQL version...'; |
||||
|
SELECT current_setting('server_version_num') INTO current_version; |
||||
|
IF current_version < 100000 THEN |
||||
|
valid_version := FALSE; |
||||
|
ELSE |
||||
|
valid_version := TRUE; |
||||
|
END IF; |
||||
|
IF valid_version = FALSE THEN |
||||
|
RAISE NOTICE 'Postgres version should be at least more than 10!'; |
||||
|
ELSE |
||||
|
RAISE NOTICE 'PostgreSQL version is valid!'; |
||||
|
RAISE NOTICE 'Schema update started...'; |
||||
|
END IF; |
||||
|
RETURN valid_version; |
||||
|
END; |
||||
|
$$ LANGUAGE 'plpgsql'; |
||||
|
|
||||
|
-- load function create_partition_table() |
||||
|
|
||||
|
CREATE OR REPLACE FUNCTION create_partition_table() RETURNS VOID AS $$ |
||||
|
|
||||
|
BEGIN |
||||
|
ALTER TABLE ts_kv |
||||
|
RENAME TO ts_kv_old; |
||||
|
CREATE TABLE IF NOT EXISTS ts_kv |
||||
|
( |
||||
|
LIKE ts_kv_old |
||||
|
) |
||||
|
PARTITION BY RANGE (ts); |
||||
|
ALTER TABLE ts_kv |
||||
|
DROP COLUMN entity_type; |
||||
|
ALTER TABLE ts_kv |
||||
|
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; |
||||
|
ALTER TABLE ts_kv |
||||
|
ALTER COLUMN key TYPE integer USING key::integer; |
||||
|
END; |
||||
|
$$ LANGUAGE 'plpgsql'; |
||||
|
|
||||
|
|
||||
|
-- load function create_partitions() |
||||
|
|
||||
|
CREATE OR REPLACE FUNCTION create_partitions() RETURNS VOID AS |
||||
|
$$ |
||||
|
DECLARE |
||||
|
partition_date varchar; |
||||
|
from_ts bigint; |
||||
|
to_ts bigint; |
||||
|
key_cursor CURSOR FOR select SUBSTRING(month_date.first_date, 1, 7) AS partition_date, |
||||
|
extract(epoch from (month_date.first_date)::timestamp) * 1000 as from_ts, |
||||
|
extract(epoch from (month_date.first_date::date + INTERVAL '1 MONTH')::timestamp) * |
||||
|
1000 as to_ts |
||||
|
FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_MM_01') AS first_date |
||||
|
FROM ts_kv_old) AS month_date; |
||||
|
BEGIN |
||||
|
OPEN key_cursor; |
||||
|
LOOP |
||||
|
FETCH key_cursor INTO partition_date, from_ts, to_ts; |
||||
|
EXIT WHEN NOT FOUND; |
||||
|
EXECUTE 'CREATE TABLE IF NOT EXISTS ts_kv_' || partition_date || |
||||
|
' PARTITION OF ts_kv(PRIMARY KEY (entity_id, key, ts)) FOR VALUES FROM (' || from_ts || |
||||
|
') TO (' || to_ts || ');'; |
||||
|
RAISE NOTICE 'A partition % has been created!',CONCAT('ts_kv_', partition_date); |
||||
|
END LOOP; |
||||
|
|
||||
|
CLOSE key_cursor; |
||||
|
END; |
||||
|
$$ language 'plpgsql'; |
||||
|
|
||||
|
-- load function create_ts_kv_dictionary_table() |
||||
|
|
||||
|
CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$ |
||||
|
|
||||
|
BEGIN |
||||
|
CREATE TABLE IF NOT EXISTS ts_kv_dictionary |
||||
|
( |
||||
|
key varchar(255) NOT NULL, |
||||
|
key_id serial UNIQUE, |
||||
|
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) |
||||
|
); |
||||
|
END; |
||||
|
$$ LANGUAGE 'plpgsql'; |
||||
|
|
||||
|
-- load function insert_into_dictionary() |
||||
|
|
||||
|
CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS |
||||
|
$$ |
||||
|
DECLARE |
||||
|
insert_record RECORD; |
||||
|
key_cursor CURSOR FOR SELECT DISTINCT key |
||||
|
FROM ts_kv_old |
||||
|
ORDER BY key; |
||||
|
BEGIN |
||||
|
OPEN key_cursor; |
||||
|
LOOP |
||||
|
FETCH key_cursor INTO insert_record; |
||||
|
EXIT WHEN NOT FOUND; |
||||
|
IF NOT EXISTS(SELECT key FROM ts_kv_dictionary WHERE key = insert_record.key) THEN |
||||
|
INSERT INTO ts_kv_dictionary(key) VALUES (insert_record.key); |
||||
|
RAISE NOTICE 'Key: % has been inserted into the dictionary!',insert_record.key; |
||||
|
ELSE |
||||
|
RAISE NOTICE 'Key: % already exists in the dictionary!',insert_record.key; |
||||
|
END IF; |
||||
|
END LOOP; |
||||
|
CLOSE key_cursor; |
||||
|
END; |
||||
|
$$ language 'plpgsql'; |
||||
|
|
||||
|
-- load function insert_into_ts_kv() |
||||
|
|
||||
|
CREATE OR REPLACE FUNCTION insert_into_ts_kv() RETURNS void AS |
||||
|
$$ |
||||
|
DECLARE |
||||
|
insert_size CONSTANT integer := 10000; |
||||
|
insert_counter integer DEFAULT 0; |
||||
|
insert_record RECORD; |
||||
|
insert_cursor CURSOR FOR SELECT CONCAT(first, '-', second, '-1', third, '-', fourth, '-', fifth)::uuid AS entity_id, |
||||
|
substrings.key AS key, |
||||
|
substrings.ts AS ts, |
||||
|
substrings.bool_v AS bool_v, |
||||
|
substrings.str_v AS str_v, |
||||
|
substrings.long_v AS long_v, |
||||
|
substrings.dbl_v AS dbl_v |
||||
|
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first, |
||||
|
SUBSTRING(entity_id, 4, 4) AS second, |
||||
|
SUBSTRING(entity_id, 1, 3) AS third, |
||||
|
SUBSTRING(entity_id, 16, 4) AS fourth, |
||||
|
SUBSTRING(entity_id, 20) AS fifth, |
||||
|
key_id AS key, |
||||
|
ts, |
||||
|
bool_v, |
||||
|
str_v, |
||||
|
long_v, |
||||
|
dbl_v |
||||
|
FROM ts_kv_old |
||||
|
INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS substrings; |
||||
|
BEGIN |
||||
|
OPEN insert_cursor; |
||||
|
LOOP |
||||
|
insert_counter := insert_counter + 1; |
||||
|
FETCH insert_cursor INTO insert_record; |
||||
|
IF NOT FOUND THEN |
||||
|
RAISE NOTICE '% records have been inserted into the partitioned ts_kv!',insert_counter - 1; |
||||
|
EXIT; |
||||
|
END IF; |
||||
|
INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) |
||||
|
VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, |
||||
|
insert_record.long_v, insert_record.dbl_v); |
||||
|
IF MOD(insert_counter, insert_size) = 0 THEN |
||||
|
RAISE NOTICE '% records have been inserted into the partitioned ts_kv!',insert_counter; |
||||
|
END IF; |
||||
|
END LOOP; |
||||
|
CLOSE insert_cursor; |
||||
|
END; |
||||
|
$$ LANGUAGE 'plpgsql'; |
||||
|
|
||||
|
|
||||
@ -0,0 +1,22 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.install; |
||||
|
|
||||
|
public interface DatabaseEntitiesUpgradeService { |
||||
|
|
||||
|
void upgradeDatabase(String fromVersion) throws Exception; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.install; |
||||
|
|
||||
|
import org.springframework.context.annotation.Profile; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
@Service |
||||
|
@SqlTsDao |
||||
|
@PsqlDao |
||||
|
@Profile("install") |
||||
|
public class PsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService |
||||
|
implements TsDatabaseSchemaService { |
||||
|
public PsqlTsDatabaseSchemaService() { |
||||
|
super("schema-ts-psql.sql", null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.install; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.annotation.Profile; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.nio.file.Files; |
||||
|
import java.nio.file.Path; |
||||
|
import java.nio.file.Paths; |
||||
|
import java.sql.CallableStatement; |
||||
|
import java.sql.Connection; |
||||
|
import java.sql.DriverManager; |
||||
|
import java.sql.SQLException; |
||||
|
import java.sql.Types; |
||||
|
|
||||
|
@Service |
||||
|
@Profile("install") |
||||
|
@Slf4j |
||||
|
@SqlTsDao |
||||
|
@PsqlDao |
||||
|
public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { |
||||
|
|
||||
|
private static final String CALL_REGEX = "call "; |
||||
|
private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; |
||||
|
private static final String CHECK_VERSION = CALL_REGEX + "check_version()"; |
||||
|
private static final String CREATE_PARTITION_TABLE = CALL_REGEX + "create_partition_table()"; |
||||
|
private static final String CREATE_PARTITIONS = CALL_REGEX + "create_partitions()"; |
||||
|
private static final String CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + "create_ts_kv_dictionary_table()"; |
||||
|
private static final String INSERT_INTO_DICTIONARY = CALL_REGEX + "insert_into_dictionary()"; |
||||
|
private static final String INSERT_INTO_TS_KV = CALL_REGEX + "insert_into_ts_kv()"; |
||||
|
private static final String DROP_OLD_TABLE = "DROP TABLE ts_kv_old;"; |
||||
|
|
||||
|
@Value("${spring.datasource.url}") |
||||
|
private String dbUrl; |
||||
|
|
||||
|
@Value("${spring.datasource.username}") |
||||
|
private String dbUserName; |
||||
|
|
||||
|
@Value("${spring.datasource.password}") |
||||
|
private String dbPassword; |
||||
|
|
||||
|
@Autowired |
||||
|
private InstallScripts installScripts; |
||||
|
|
||||
|
@Override |
||||
|
public void upgradeDatabase(String fromVersion) throws Exception { |
||||
|
switch (fromVersion) { |
||||
|
case "2.4.3": |
||||
|
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
||||
|
log.info("Updating timeseries schema ..."); |
||||
|
log.info("Load upgrade functions ..."); |
||||
|
loadSql(conn); |
||||
|
log.info("Upgrade functions successfully loaded!"); |
||||
|
boolean versionValid = checkVersion(conn); |
||||
|
if (!versionValid) { |
||||
|
log.info("PostgreSQL version should be at least more than 10!"); |
||||
|
log.info("Please upgrade your PostgreSQL and restart the script!"); |
||||
|
} else { |
||||
|
log.info("PostgreSQL version is valid!"); |
||||
|
log.info("Updating schema ..."); |
||||
|
executeFunction(conn, CREATE_PARTITION_TABLE); |
||||
|
executeFunction(conn, CREATE_PARTITIONS); |
||||
|
executeFunction(conn, CREATE_TS_KV_DICTIONARY_TABLE); |
||||
|
executeFunction(conn, INSERT_INTO_DICTIONARY); |
||||
|
executeFunction(conn, INSERT_INTO_TS_KV); |
||||
|
dropOldTable(conn, DROP_OLD_TABLE); |
||||
|
log.info("schema timeseries updated!"); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void loadSql(Connection conn) { |
||||
|
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); |
||||
|
try { |
||||
|
loadFunctions(schemaUpdateFile, conn); |
||||
|
} catch (Exception e) { |
||||
|
log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void loadFunctions(Path sqlFile, Connection conn) throws Exception { |
||||
|
String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); |
||||
|
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
|
||||
|
} |
||||
|
|
||||
|
private boolean checkVersion(Connection conn) { |
||||
|
log.info("Check the current PostgreSQL version..."); |
||||
|
boolean versionValid = false; |
||||
|
try { |
||||
|
CallableStatement callableStatement = conn.prepareCall("{? = " + CHECK_VERSION + " }"); |
||||
|
callableStatement.registerOutParameter(1, Types.BOOLEAN); |
||||
|
callableStatement.execute(); |
||||
|
versionValid = callableStatement.getBoolean(1); |
||||
|
callableStatement.close(); |
||||
|
} catch (Exception e) { |
||||
|
log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); |
||||
|
} |
||||
|
return versionValid; |
||||
|
} |
||||
|
|
||||
|
private void executeFunction(Connection conn, String query) { |
||||
|
log.info("{} ... ", query); |
||||
|
try { |
||||
|
CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); |
||||
|
callableStatement.execute(); |
||||
|
callableStatement.close(); |
||||
|
log.info("Successfully executed: {}", query.replace(CALL_REGEX, "")); |
||||
|
} catch (Exception e) { |
||||
|
log.info("Failed to execute {} due to: {}", query, e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void dropOldTable(Connection conn, String query) { |
||||
|
try { |
||||
|
conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
|
||||
|
Thread.sleep(5000); |
||||
|
} catch (InterruptedException | SQLException e) { |
||||
|
log.info("Failed to drop table {} due to: {}", query.replace("DROP TABLE ", ""), e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.util; |
||||
|
|
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
|
||||
|
@ConditionalOnExpression("('${database.ts.type}'=='sql' || '${database.entities.type}'=='timescale') " + |
||||
|
"&& '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'") |
||||
|
public @interface PsqlTsAnyDao { |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao; |
||||
|
|
||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan; |
||||
|
import org.springframework.context.annotation.ComponentScan; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; |
||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
@Configuration |
||||
|
@EnableAutoConfiguration |
||||
|
@ComponentScan({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.latest"}) |
||||
|
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) |
||||
|
@EntityScan({"org.thingsboard.server.dao.model.sqlts.psql", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) |
||||
|
@EnableTransactionManagement |
||||
|
@SqlTsDao |
||||
|
@PsqlDao |
||||
|
public class PsqlTsDaoConfig { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sqlts.dictionary; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.hibernate.annotations.Generated; |
||||
|
import org.hibernate.annotations.GenerationTime; |
||||
|
|
||||
|
import javax.persistence.Column; |
||||
|
import javax.persistence.Entity; |
||||
|
import javax.persistence.Id; |
||||
|
import javax.persistence.IdClass; |
||||
|
import javax.persistence.Table; |
||||
|
|
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; |
||||
|
|
||||
|
@Data |
||||
|
@Entity |
||||
|
@Table(name = "ts_kv_dictionary") |
||||
|
@IdClass(TsKvDictionaryCompositeKey.class) |
||||
|
public final class TsKvDictionary { |
||||
|
|
||||
|
@Id |
||||
|
@Column(name = KEY_COLUMN) |
||||
|
private String key; |
||||
|
|
||||
|
@Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="serial") |
||||
|
@Generated(GenerationTime.INSERT) |
||||
|
private int keyId; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sqlts.dictionary; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import javax.persistence.Transient; |
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public class TsKvDictionaryCompositeKey implements Serializable{ |
||||
|
|
||||
|
@Transient |
||||
|
private static final long serialVersionUID = -4089175869616037523L; |
||||
|
|
||||
|
private String key; |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sqlts.psql; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import javax.persistence.Transient; |
||||
|
import java.io.Serializable; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class TsKvCompositeKey implements Serializable { |
||||
|
|
||||
|
@Transient |
||||
|
private static final long serialVersionUID = -4089175869616037523L; |
||||
|
|
||||
|
private UUID entityId; |
||||
|
private int key; |
||||
|
private long ts; |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sqlts.psql; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.KvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.LongDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.StringDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.dao.model.ToData; |
||||
|
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; |
||||
|
|
||||
|
import javax.persistence.Column; |
||||
|
import javax.persistence.Entity; |
||||
|
import javax.persistence.Id; |
||||
|
import javax.persistence.IdClass; |
||||
|
import javax.persistence.Table; |
||||
|
import javax.persistence.Transient; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; |
||||
|
|
||||
|
@Data |
||||
|
@Entity |
||||
|
@Table(name = "ts_kv") |
||||
|
@IdClass(TsKvCompositeKey.class) |
||||
|
public final class TsKvEntity extends AbstractTsKvEntity implements ToData<TsKvEntry> { |
||||
|
|
||||
|
@Id |
||||
|
@Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") |
||||
|
protected UUID entityId; |
||||
|
|
||||
|
@Id |
||||
|
@Column(name = KEY_COLUMN) |
||||
|
protected int key; |
||||
|
|
||||
|
@Transient |
||||
|
protected String strKey; |
||||
|
|
||||
|
public TsKvEntity() { |
||||
|
} |
||||
|
|
||||
|
public TsKvEntity(String strValue) { |
||||
|
this.strValue = strValue; |
||||
|
} |
||||
|
|
||||
|
public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) { |
||||
|
if (!isAllNull(longValue, doubleValue, longCountValue, doubleCountValue)) { |
||||
|
switch (aggType) { |
||||
|
case AVG: |
||||
|
double sum = 0.0; |
||||
|
if (longValue != null) { |
||||
|
sum += longValue; |
||||
|
} |
||||
|
if (doubleValue != null) { |
||||
|
sum += doubleValue; |
||||
|
} |
||||
|
long totalCount = longCountValue + doubleCountValue; |
||||
|
if (totalCount > 0) { |
||||
|
this.doubleValue = sum / (longCountValue + doubleCountValue); |
||||
|
} else { |
||||
|
this.doubleValue = 0.0; |
||||
|
} |
||||
|
break; |
||||
|
case SUM: |
||||
|
if (doubleCountValue > 0) { |
||||
|
this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0); |
||||
|
} else { |
||||
|
this.longValue = longValue; |
||||
|
} |
||||
|
break; |
||||
|
case MIN: |
||||
|
case MAX: |
||||
|
if (longCountValue > 0 && doubleCountValue > 0) { |
||||
|
this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); |
||||
|
} else if (doubleCountValue > 0) { |
||||
|
this.doubleValue = doubleValue; |
||||
|
} else if (longCountValue > 0) { |
||||
|
this.longValue = longValue; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { |
||||
|
if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { |
||||
|
if (booleanValueCount != 0) { |
||||
|
this.longValue = booleanValueCount; |
||||
|
} else if (strValueCount != 0) { |
||||
|
this.longValue = strValueCount; |
||||
|
} else { |
||||
|
this.longValue = longValueCount + doubleValueCount; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isNotEmpty() { |
||||
|
return strValue != null || longValue != null || doubleValue != null || booleanValue != null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TsKvEntry toData() { |
||||
|
KvEntry kvEntry = null; |
||||
|
if (strValue != null) { |
||||
|
kvEntry = new StringDataEntry(strKey, strValue); |
||||
|
} else if (longValue != null) { |
||||
|
kvEntry = new LongDataEntry(strKey, longValue); |
||||
|
} else if (doubleValue != null) { |
||||
|
kvEntry = new DoubleDataEntry(strKey, doubleValue); |
||||
|
} else if (booleanValue != null) { |
||||
|
kvEntry = new BooleanDataEntry(strKey, booleanValue); |
||||
|
} |
||||
|
return new BasicTsKvEntry(ts, kvEntry); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,58 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2020 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.dao.sqlts; |
|
||||
|
|
||||
import org.springframework.data.jpa.repository.Modifying; |
|
||||
import org.springframework.stereotype.Repository; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
@Repository |
|
||||
public abstract class AbstractLatestInsertRepository extends AbstractInsertRepository { |
|
||||
|
|
||||
public abstract void saveOrUpdate(TsKvLatestEntity entity); |
|
||||
|
|
||||
public abstract void saveOrUpdate(List<TsKvLatestEntity> entities); |
|
||||
|
|
||||
protected void processSaveOrUpdate(TsKvLatestEntity entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { |
|
||||
if (entity.getBooleanValue() != null) { |
|
||||
saveOrUpdateBoolean(entity, requestBoolValue); |
|
||||
} |
|
||||
if (entity.getStrValue() != null) { |
|
||||
saveOrUpdateString(entity, requestStrValue); |
|
||||
} |
|
||||
if (entity.getLongValue() != null) { |
|
||||
saveOrUpdateLong(entity, requestLongValue); |
|
||||
} |
|
||||
if (entity.getDoubleValue() != null) { |
|
||||
saveOrUpdateDouble(entity, requestDblValue); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateBoolean(TsKvLatestEntity entity, String query); |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateString(TsKvLatestEntity entity, String query); |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateLong(TsKvLatestEntity entity, String query); |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateDouble(TsKvLatestEntity entity, String query); |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,156 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import com.google.common.util.concurrent.SettableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.kv.Aggregation; |
||||
|
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; |
||||
|
import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; |
||||
|
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.concurrent.CompletableFuture; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class AbstractSimpleSqlTimeseriesDao<T extends AbstractTsKvEntity> extends AbstractSqlTimeseriesDao { |
||||
|
|
||||
|
@Autowired |
||||
|
private InsertTsRepository<T> insertRepository; |
||||
|
|
||||
|
@Value("${sql.ts.batch_size:1000}") |
||||
|
private int tsBatchSize; |
||||
|
|
||||
|
@Value("${sql.ts.batch_max_delay:100}") |
||||
|
private long tsMaxDelay; |
||||
|
|
||||
|
@Value("${sql.ts.stats_print_interval_ms:1000}") |
||||
|
private long tsStatsPrintIntervalMs; |
||||
|
|
||||
|
protected TbSqlBlockingQueue<EntityContainer<T>> tsQueue; |
||||
|
|
||||
|
@PostConstruct |
||||
|
protected void init() { |
||||
|
super.init(); |
||||
|
TbSqlBlockingQueueParams tsParams = TbSqlBlockingQueueParams.builder() |
||||
|
.logName("TS") |
||||
|
.batchSize(tsBatchSize) |
||||
|
.maxDelay(tsMaxDelay) |
||||
|
.statsPrintIntervalMs(tsStatsPrintIntervalMs) |
||||
|
.build(); |
||||
|
tsQueue = new TbSqlBlockingQueue<>(tsParams); |
||||
|
tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
protected void destroy() { |
||||
|
super.init(); |
||||
|
if (tsQueue != null) { |
||||
|
tsQueue.destroy(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { |
||||
|
if (query.getAggregation() == Aggregation.NONE) { |
||||
|
return findAllAsyncWithLimit(entityId, query); |
||||
|
} else { |
||||
|
long stepTs = query.getStartTs(); |
||||
|
List<ListenableFuture<Optional<TsKvEntry>>> futures = new ArrayList<>(); |
||||
|
while (stepTs < query.getEndTs()) { |
||||
|
long startTs = stepTs; |
||||
|
long endTs = stepTs + query.getInterval(); |
||||
|
long ts = startTs + (endTs - startTs) / 2; |
||||
|
futures.add(findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); |
||||
|
stepTs = endTs; |
||||
|
} |
||||
|
return getTskvEntriesFuture(Futures.allAsList(futures)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation); |
||||
|
|
||||
|
protected abstract ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query); |
||||
|
|
||||
|
protected SettableFuture<T> setFutures(List<CompletableFuture<T>> entitiesFutures) { |
||||
|
SettableFuture<T> listenableFuture = SettableFuture.create(); |
||||
|
CompletableFuture<List<T>> entities = |
||||
|
CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) |
||||
|
.thenApply(v -> entitiesFutures.stream() |
||||
|
.map(CompletableFuture::join) |
||||
|
.collect(Collectors.toList())); |
||||
|
|
||||
|
entities.whenComplete((tsKvEntities, throwable) -> { |
||||
|
if (throwable != null) { |
||||
|
listenableFuture.setException(throwable); |
||||
|
} else { |
||||
|
T result = null; |
||||
|
for (T entity : tsKvEntities) { |
||||
|
if (entity.isNotEmpty()) { |
||||
|
result = entity; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
listenableFuture.set(result); |
||||
|
} |
||||
|
}); |
||||
|
return listenableFuture; |
||||
|
} |
||||
|
|
||||
|
protected void switchAgregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List<CompletableFuture<T>> entitiesFutures) { |
||||
|
switch (aggregation) { |
||||
|
case AVG: |
||||
|
findAvg(entityId, key, startTs, endTs, entitiesFutures); |
||||
|
break; |
||||
|
case MAX: |
||||
|
findMax(entityId, key, startTs, endTs, entitiesFutures); |
||||
|
break; |
||||
|
case MIN: |
||||
|
findMin(entityId, key, startTs, endTs, entitiesFutures); |
||||
|
break; |
||||
|
case SUM: |
||||
|
findSum(entityId, key, startTs, endTs, entitiesFutures); |
||||
|
break; |
||||
|
case COUNT: |
||||
|
findCount(entityId, key, startTs, endTs, entitiesFutures); |
||||
|
break; |
||||
|
default: |
||||
|
throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract void findCount(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<T>> entitiesFutures); |
||||
|
|
||||
|
protected abstract void findSum(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<T>> entitiesFutures); |
||||
|
|
||||
|
protected abstract void findMin(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<T>> entitiesFutures); |
||||
|
|
||||
|
protected abstract void findMax(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<T>> entitiesFutures); |
||||
|
|
||||
|
protected abstract void findAvg(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<T>> entitiesFutures); |
||||
|
} |
||||
@ -1,58 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2020 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.dao.sqlts; |
|
||||
|
|
||||
import org.springframework.data.jpa.repository.Modifying; |
|
||||
import org.springframework.stereotype.Repository; |
|
||||
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
@Repository |
|
||||
public abstract class AbstractTimeseriesInsertRepository<T extends AbstractTsKvEntity> extends AbstractInsertRepository { |
|
||||
|
|
||||
public abstract void saveOrUpdate(T entity); |
|
||||
|
|
||||
public abstract void saveOrUpdate(List<T> entities); |
|
||||
|
|
||||
protected void processSaveOrUpdate(T entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { |
|
||||
if (entity.getBooleanValue() != null) { |
|
||||
saveOrUpdateBoolean(entity, requestBoolValue); |
|
||||
} |
|
||||
if (entity.getStrValue() != null) { |
|
||||
saveOrUpdateString(entity, requestStrValue); |
|
||||
} |
|
||||
if (entity.getLongValue() != null) { |
|
||||
saveOrUpdateLong(entity, requestLongValue); |
|
||||
} |
|
||||
if (entity.getDoubleValue() != null) { |
|
||||
saveOrUpdateDouble(entity, requestDblValue); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateBoolean(T entity, String query); |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateString(T entity, String query); |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateLong(T entity, String query); |
|
||||
|
|
||||
@Modifying |
|
||||
protected abstract void saveOrUpdateDouble(T entity, String query); |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
public class EntityContainer<T extends AbstractTsKvEntity> { |
||||
|
|
||||
|
private T entity; |
||||
|
private String partitionDate; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts; |
||||
|
|
||||
|
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public interface InsertLatestRepository { |
||||
|
|
||||
|
void saveOrUpdate(List<TsKvLatestEntity> entities); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts; |
||||
|
|
||||
|
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public interface InsertTsRepository<T extends AbstractTsKvEntity> { |
||||
|
|
||||
|
void saveOrUpdate(List<EntityContainer<T>> entities); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.dictionary; |
||||
|
|
||||
|
import org.springframework.data.repository.CrudRepository; |
||||
|
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; |
||||
|
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
@PsqlDao |
||||
|
public interface TsKvDictionaryRepository extends CrudRepository<TsKvDictionary, TsKvDictionaryCompositeKey> { |
||||
|
|
||||
|
Optional<TsKvDictionary> findByKeyId(int keyId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.hsql; |
||||
|
|
||||
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; |
||||
|
import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; |
||||
|
import org.thingsboard.server.dao.sqlts.EntityContainer; |
||||
|
import org.thingsboard.server.dao.sqlts.InsertTsRepository; |
||||
|
import org.thingsboard.server.dao.util.HsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import java.sql.PreparedStatement; |
||||
|
import java.sql.SQLException; |
||||
|
import java.sql.Types; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@SqlTsDao |
||||
|
@HsqlDao |
||||
|
@Repository |
||||
|
@Transactional |
||||
|
public class HsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> { |
||||
|
|
||||
|
private static final String INSERT_OR_UPDATE = |
||||
|
"MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + |
||||
|
"T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
||||
|
"ON (ts_kv.entity_type=T.entity_type " + |
||||
|
"AND ts_kv.entity_id=T.entity_id " + |
||||
|
"AND ts_kv.key=T.key " + |
||||
|
"AND ts_kv.ts=T.ts) " + |
||||
|
"WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v " + |
||||
|
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
||||
|
"VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; |
||||
|
|
||||
|
@Override |
||||
|
public void saveOrUpdate(List<EntityContainer<TsKvEntity>> entities) { |
||||
|
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { |
||||
|
@Override |
||||
|
public void setValues(PreparedStatement ps, int i) throws SQLException { |
||||
|
EntityContainer<TsKvEntity> tsKvEntityEntityContainer = entities.get(i); |
||||
|
TsKvEntity tsKvEntity = tsKvEntityEntityContainer.getEntity(); |
||||
|
ps.setString(1, tsKvEntity.getEntityType().name()); |
||||
|
ps.setString(2, tsKvEntity.getEntityId()); |
||||
|
ps.setString(3, tsKvEntity.getKey()); |
||||
|
ps.setLong(4, tsKvEntity.getTs()); |
||||
|
|
||||
|
if (tsKvEntity.getBooleanValue() != null) { |
||||
|
ps.setBoolean(5, tsKvEntity.getBooleanValue()); |
||||
|
} else { |
||||
|
ps.setNull(5, Types.BOOLEAN); |
||||
|
} |
||||
|
|
||||
|
ps.setString(6, tsKvEntity.getStrValue()); |
||||
|
|
||||
|
if (tsKvEntity.getLongValue() != null) { |
||||
|
ps.setLong(7, tsKvEntity.getLongValue()); |
||||
|
} else { |
||||
|
ps.setNull(7, Types.BIGINT); |
||||
|
} |
||||
|
|
||||
|
if (tsKvEntity.getDoubleValue() != null) { |
||||
|
ps.setDouble(8, tsKvEntity.getDoubleValue()); |
||||
|
} else { |
||||
|
ps.setNull(8, Types.DOUBLE); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int getBatchSize() { |
||||
|
return entities.size(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,206 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.hsql; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.domain.PageRequest; |
||||
|
import org.springframework.data.domain.Sort; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.kv.Aggregation; |
||||
|
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; |
||||
|
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.dao.DaoUtil; |
||||
|
import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; |
||||
|
import org.thingsboard.server.dao.sqlts.AbstractSimpleSqlTimeseriesDao; |
||||
|
import org.thingsboard.server.dao.sqlts.EntityContainer; |
||||
|
import org.thingsboard.server.dao.timeseries.TimeseriesDao; |
||||
|
import org.thingsboard.server.dao.util.HsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.concurrent.CompletableFuture; |
||||
|
|
||||
|
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
||||
|
|
||||
|
|
||||
|
@Component |
||||
|
@Slf4j |
||||
|
@SqlTsDao |
||||
|
@HsqlDao |
||||
|
public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao<TsKvEntity> implements TimeseriesDao { |
||||
|
|
||||
|
@Autowired |
||||
|
private TsKvHsqlRepository tsKvRepository; |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) { |
||||
|
return processFindAllAsync(tenantId, entityId, queries); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { |
||||
|
TsKvEntity entity = new TsKvEntity(); |
||||
|
entity.setEntityType(entityId.getEntityType()); |
||||
|
entity.setEntityId(fromTimeUUID(entityId.getId())); |
||||
|
entity.setTs(tsKvEntry.getTs()); |
||||
|
entity.setKey(tsKvEntry.getKey()); |
||||
|
entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); |
||||
|
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
||||
|
entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
||||
|
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
||||
|
log.trace("Saving entity: {}", entity); |
||||
|
return tsQueue.add(new EntityContainer(entity, null)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
||||
|
return service.submit(() -> { |
||||
|
tsKvRepository.delete( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
query.getKey(), |
||||
|
query.getStartTs(), |
||||
|
query.getEndTs()); |
||||
|
return null; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { |
||||
|
return getSaveLatestFuture(entityId, tsKvEntry); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
||||
|
return getRemoveLatestFuture(tenantId, entityId, query); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<TsKvEntry> findLatest(TenantId tenantId, EntityId entityId, String key) { |
||||
|
return getFindLatestFuture(entityId, key); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<List<TsKvEntry>> findAllLatest(TenantId tenantId, EntityId entityId) { |
||||
|
return getFindAllLatestFuture(entityId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { |
||||
|
return Futures.immediateFuture(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
||||
|
return Futures.immediateFuture(null); |
||||
|
} |
||||
|
|
||||
|
protected ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { |
||||
|
List<CompletableFuture<TsKvEntity>> entitiesFutures = new ArrayList<>(); |
||||
|
switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); |
||||
|
return Futures.transform(setFutures(entitiesFutures), entity -> { |
||||
|
if (entity != null && entity.isNotEmpty()) { |
||||
|
entity.setEntityId(fromTimeUUID(entityId.getId())); |
||||
|
entity.setEntityType(entityId.getEntityType()); |
||||
|
entity.setKey(key); |
||||
|
entity.setTs(ts); |
||||
|
return Optional.of(DaoUtil.getData(entity)); |
||||
|
} else { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { |
||||
|
return Futures.immediateFuture( |
||||
|
DaoUtil.convertDataList( |
||||
|
tsKvRepository.findAllWithLimit( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
query.getKey(), |
||||
|
query.getStartTs(), |
||||
|
query.getEndTs(), |
||||
|
new PageRequest(0, query.getLimit(), |
||||
|
new Sort(Sort.Direction.fromString( |
||||
|
query.getOrderBy()), "ts"))))); |
||||
|
} |
||||
|
|
||||
|
protected void findCount(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
entitiesFutures.add(tsKvRepository.findCount( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findSum(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
entitiesFutures.add(tsKvRepository.findSum( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findMin(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
entitiesFutures.add(tsKvRepository.findStringMin( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
entitiesFutures.add(tsKvRepository.findNumericMin( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findMax(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
entitiesFutures.add(tsKvRepository.findStringMax( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
entitiesFutures.add(tsKvRepository.findNumericMax( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
entitiesFutures.add(tsKvRepository.findAvg( |
||||
|
fromTimeUUID(entityId.getId()), |
||||
|
entityId.getEntityType(), |
||||
|
key, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.latest; |
||||
|
|
||||
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; |
||||
|
import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; |
||||
|
import org.thingsboard.server.dao.sqlts.InsertLatestRepository; |
||||
|
import org.thingsboard.server.dao.util.HsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import java.sql.PreparedStatement; |
||||
|
import java.sql.SQLException; |
||||
|
import java.sql.Types; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@SqlTsDao |
||||
|
@HsqlDao |
||||
|
@Repository |
||||
|
@Transactional |
||||
|
public class HsqlLatestInsertRepository extends AbstractInsertRepository implements InsertLatestRepository { |
||||
|
|
||||
|
private static final String INSERT_OR_UPDATE = |
||||
|
"MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + |
||||
|
"T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
||||
|
"ON (ts_kv_latest.entity_type=T.entity_type " + |
||||
|
"AND ts_kv_latest.entity_id=T.entity_id " + |
||||
|
"AND ts_kv_latest.key=T.key) " + |
||||
|
"WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v " + |
||||
|
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
||||
|
"VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; |
||||
|
|
||||
|
@Override |
||||
|
public void saveOrUpdate(List<TsKvLatestEntity> entities) { |
||||
|
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { |
||||
|
@Override |
||||
|
public void setValues(PreparedStatement ps, int i) throws SQLException { |
||||
|
ps.setString(1, entities.get(i).getEntityType().name()); |
||||
|
ps.setString(2, entities.get(i).getEntityId()); |
||||
|
ps.setString(3, entities.get(i).getKey()); |
||||
|
ps.setLong(4, entities.get(i).getTs()); |
||||
|
|
||||
|
if (entities.get(i).getBooleanValue() != null) { |
||||
|
ps.setBoolean(5, entities.get(i).getBooleanValue()); |
||||
|
} else { |
||||
|
ps.setNull(5, Types.BOOLEAN); |
||||
|
} |
||||
|
|
||||
|
ps.setString(6, entities.get(i).getStrValue()); |
||||
|
|
||||
|
if (entities.get(i).getLongValue() != null) { |
||||
|
ps.setLong(7, entities.get(i).getLongValue()); |
||||
|
} else { |
||||
|
ps.setNull(7, Types.BIGINT); |
||||
|
} |
||||
|
|
||||
|
if (entities.get(i).getDoubleValue() != null) { |
||||
|
ps.setDouble(8, entities.get(i).getDoubleValue()); |
||||
|
} else { |
||||
|
ps.setNull(8, Types.DOUBLE); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int getBatchSize() { |
||||
|
return entities.size(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,312 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.psql; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.hibernate.exception.ConstraintViolationException; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.data.domain.PageRequest; |
||||
|
import org.springframework.data.domain.Sort; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.kv.Aggregation; |
||||
|
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; |
||||
|
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.dao.DaoUtil; |
||||
|
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; |
||||
|
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; |
||||
|
import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; |
||||
|
import org.thingsboard.server.dao.sqlts.AbstractSimpleSqlTimeseriesDao; |
||||
|
import org.thingsboard.server.dao.sqlts.EntityContainer; |
||||
|
import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; |
||||
|
import org.thingsboard.server.dao.timeseries.PsqlPartition; |
||||
|
import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; |
||||
|
import org.thingsboard.server.dao.timeseries.TimeseriesDao; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import java.time.Instant; |
||||
|
import java.time.LocalDateTime; |
||||
|
import java.time.ZoneOffset; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.CompletableFuture; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.locks.ReentrantLock; |
||||
|
|
||||
|
import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_START; |
||||
|
|
||||
|
|
||||
|
@Component |
||||
|
@Slf4j |
||||
|
@SqlTsDao |
||||
|
@PsqlDao |
||||
|
public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao<TsKvEntity> implements TimeseriesDao { |
||||
|
|
||||
|
private final ConcurrentMap<String, Integer> tsKvDictionaryMap = new ConcurrentHashMap<>(); |
||||
|
private final Set<PsqlPartition> partitions = ConcurrentHashMap.newKeySet(); |
||||
|
|
||||
|
private static final ReentrantLock tsCreationLock = new ReentrantLock(); |
||||
|
private static final ReentrantLock partitionCreationLock = new ReentrantLock(); |
||||
|
|
||||
|
@Autowired |
||||
|
private TsKvDictionaryRepository dictionaryRepository; |
||||
|
|
||||
|
@Autowired |
||||
|
private TsKvPsqlRepository tsKvRepository; |
||||
|
|
||||
|
@Autowired |
||||
|
private PsqlPartitioningRepository partitioningRepository; |
||||
|
|
||||
|
private SqlTsPartitionDate tsFormat; |
||||
|
|
||||
|
@Value("${sql.ts_key_value_partitioning}") |
||||
|
private String partitioning; |
||||
|
|
||||
|
@Override |
||||
|
protected void init() { |
||||
|
super.init(); |
||||
|
Optional<SqlTsPartitionDate> partition = SqlTsPartitionDate.parse(partitioning); |
||||
|
if (partition.isPresent()) { |
||||
|
tsFormat = partition.get(); |
||||
|
} else { |
||||
|
log.warn("Incorrect configuration of partitioning {}", partitioning); |
||||
|
throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) { |
||||
|
return processFindAllAsync(tenantId, entityId, queries); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { |
||||
|
String strKey = tsKvEntry.getKey(); |
||||
|
Integer keyId = getOrSaveKeyId(strKey); |
||||
|
TsKvEntity entity = new TsKvEntity(); |
||||
|
entity.setEntityId(entityId.getId()); |
||||
|
entity.setTs(tsKvEntry.getTs()); |
||||
|
entity.setKey(keyId); |
||||
|
entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); |
||||
|
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
||||
|
entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
||||
|
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
||||
|
PsqlPartition psqlPartition = toPartition(tsKvEntry.getTs()); |
||||
|
savePartition(psqlPartition); |
||||
|
log.trace("Saving entity: {}", entity); |
||||
|
return tsQueue.add(new EntityContainer(entity, psqlPartition.getPartitionDate())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
||||
|
return service.submit(() -> { |
||||
|
String strKey = query.getKey(); |
||||
|
Integer keyId = getOrSaveKeyId(strKey); |
||||
|
tsKvRepository.delete( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
query.getStartTs(), |
||||
|
query.getEndTs()); |
||||
|
return null; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
||||
|
return getRemoveLatestFuture(tenantId, entityId, query); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { |
||||
|
return getSaveLatestFuture(entityId, tsKvEntry); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<TsKvEntry> findLatest(TenantId tenantId, EntityId entityId, String key) { |
||||
|
return getFindLatestFuture(entityId, key); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<List<TsKvEntry>> findAllLatest(TenantId tenantId, EntityId entityId) { |
||||
|
return getFindAllLatestFuture(entityId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { |
||||
|
return Futures.immediateFuture(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
||||
|
return Futures.immediateFuture(null); |
||||
|
} |
||||
|
|
||||
|
protected ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { |
||||
|
List<CompletableFuture<TsKvEntity>> entitiesFutures = new ArrayList<>(); |
||||
|
switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); |
||||
|
return Futures.transform(setFutures(entitiesFutures), entity -> { |
||||
|
if (entity != null && entity.isNotEmpty()) { |
||||
|
entity.setEntityId(entityId.getId()); |
||||
|
entity.setStrKey(key); |
||||
|
entity.setTs(ts); |
||||
|
return Optional.of(DaoUtil.getData(entity)); |
||||
|
} else { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { |
||||
|
Integer keyId = getOrSaveKeyId(query.getKey()); |
||||
|
List<TsKvEntity> tsKvEntities = tsKvRepository.findAllWithLimit( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
query.getStartTs(), |
||||
|
query.getEndTs(), |
||||
|
new PageRequest(0, query.getLimit(), |
||||
|
new Sort(Sort.Direction.fromString( |
||||
|
query.getOrderBy()), "ts"))); |
||||
|
tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); |
||||
|
return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); |
||||
|
} |
||||
|
|
||||
|
protected void findCount(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
Integer keyId = getOrSaveKeyId(key); |
||||
|
entitiesFutures.add(tsKvRepository.findCount( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findSum(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
Integer keyId = getOrSaveKeyId(key); |
||||
|
entitiesFutures.add(tsKvRepository.findSum( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findMin(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
Integer keyId = getOrSaveKeyId(key); |
||||
|
entitiesFutures.add(tsKvRepository.findStringMin( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
entitiesFutures.add(tsKvRepository.findNumericMin( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findMax(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
Integer keyId = getOrSaveKeyId(key); |
||||
|
entitiesFutures.add(tsKvRepository.findStringMax( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
entitiesFutures.add(tsKvRepository.findNumericMax( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) { |
||||
|
Integer keyId = getOrSaveKeyId(key); |
||||
|
entitiesFutures.add(tsKvRepository.findAvg( |
||||
|
entityId.getId(), |
||||
|
keyId, |
||||
|
startTs, |
||||
|
endTs)); |
||||
|
} |
||||
|
|
||||
|
private Integer getOrSaveKeyId(String strKey) { |
||||
|
Integer keyId = tsKvDictionaryMap.get(strKey); |
||||
|
if (keyId == null) { |
||||
|
Optional<TsKvDictionary> tsKvDictionaryOptional; |
||||
|
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
||||
|
if (!tsKvDictionaryOptional.isPresent()) { |
||||
|
tsCreationLock.lock(); |
||||
|
try { |
||||
|
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
||||
|
if (!tsKvDictionaryOptional.isPresent()) { |
||||
|
TsKvDictionary tsKvDictionary = new TsKvDictionary(); |
||||
|
tsKvDictionary.setKey(strKey); |
||||
|
try { |
||||
|
TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); |
||||
|
tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); |
||||
|
keyId = saved.getKeyId(); |
||||
|
} catch (ConstraintViolationException e) { |
||||
|
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
||||
|
TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); |
||||
|
tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); |
||||
|
keyId = dictionary.getKeyId(); |
||||
|
} |
||||
|
} else { |
||||
|
keyId = tsKvDictionaryOptional.get().getKeyId(); |
||||
|
} |
||||
|
} finally { |
||||
|
tsCreationLock.unlock(); |
||||
|
} |
||||
|
} else { |
||||
|
keyId = tsKvDictionaryOptional.get().getKeyId(); |
||||
|
tsKvDictionaryMap.put(strKey, keyId); |
||||
|
} |
||||
|
} |
||||
|
return keyId; |
||||
|
} |
||||
|
|
||||
|
private void savePartition(PsqlPartition psqlPartition) { |
||||
|
if (!partitions.contains(psqlPartition)) { |
||||
|
partitionCreationLock.lock(); |
||||
|
try { |
||||
|
log.trace("Saving partition: {}", psqlPartition); |
||||
|
partitioningRepository.save(psqlPartition); |
||||
|
log.trace("Adding partition to Set: {}", psqlPartition); |
||||
|
partitions.add(psqlPartition); |
||||
|
} finally { |
||||
|
partitionCreationLock.unlock(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private PsqlPartition toPartition(long ts) { |
||||
|
LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); |
||||
|
LocalDateTime localDateTimeStart = tsFormat.trancateTo(time); |
||||
|
if (localDateTimeStart == SqlTsPartitionDate.EPOCH_START) { |
||||
|
return new PsqlPartition(toMills(EPOCH_START), Long.MAX_VALUE, tsFormat.getPattern()); |
||||
|
} else { |
||||
|
LocalDateTime localDateTimeEnd = tsFormat.plusTo(localDateTimeStart); |
||||
|
return new PsqlPartition(toMills(localDateTimeStart), toMills(localDateTimeEnd), tsFormat.getPattern()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private long toMills(LocalDateTime time) { return time.toInstant(ZoneOffset.UTC).toEpochMilli(); } |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.psql; |
||||
|
|
||||
|
import org.springframework.stereotype.Repository; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.server.dao.timeseries.PsqlPartition; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import javax.persistence.EntityManager; |
||||
|
import javax.persistence.PersistenceContext; |
||||
|
|
||||
|
@SqlTsDao |
||||
|
@PsqlDao |
||||
|
@Repository |
||||
|
@Transactional |
||||
|
public class PsqlPartitioningRepository { |
||||
|
|
||||
|
@PersistenceContext |
||||
|
private EntityManager entityManager; |
||||
|
|
||||
|
public void save(PsqlPartition partition) { |
||||
|
entityManager.createNativeQuery(partition.getQuery()) |
||||
|
.executeUpdate(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.psql; |
||||
|
|
||||
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; |
||||
|
import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; |
||||
|
import org.thingsboard.server.dao.sqlts.EntityContainer; |
||||
|
import org.thingsboard.server.dao.sqlts.InsertTsRepository; |
||||
|
import org.thingsboard.server.dao.util.PsqlDao; |
||||
|
import org.thingsboard.server.dao.util.SqlTsDao; |
||||
|
|
||||
|
import java.sql.PreparedStatement; |
||||
|
import java.sql.SQLException; |
||||
|
import java.sql.Types; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@SqlTsDao |
||||
|
@PsqlDao |
||||
|
@Repository |
||||
|
@Transactional |
||||
|
public class PsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> { |
||||
|
|
||||
|
private static final String INSERT_INTO_TS_KV = "INSERT INTO ts_kv_"; |
||||
|
|
||||
|
private static final String VALUES_ON_CONFLICT_DO_UPDATE = " (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES (?, ?, ?, ?, ?, ?, ?) " + |
||||
|
"ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; |
||||
|
|
||||
|
@Override |
||||
|
public void saveOrUpdate(List<EntityContainer<TsKvEntity>> entities) { |
||||
|
Map<String, List<TsKvEntity>> partitionMap = new HashMap<>(); |
||||
|
for (EntityContainer<TsKvEntity> entityContainer : entities) { |
||||
|
List<TsKvEntity> tsKvEntities = partitionMap.computeIfAbsent(entityContainer.getPartitionDate(), k -> new ArrayList<>()); |
||||
|
tsKvEntities.add(entityContainer.getEntity()); |
||||
|
} |
||||
|
partitionMap.forEach((partition, entries) -> jdbcTemplate.batchUpdate(getInsertOrUpdateQuery(partition), new BatchPreparedStatementSetter() { |
||||
|
@Override |
||||
|
public void setValues(PreparedStatement ps, int i) throws SQLException { |
||||
|
TsKvEntity tsKvEntity = entries.get(i); |
||||
|
ps.setObject(1, tsKvEntity.getEntityId()); |
||||
|
ps.setInt(2, tsKvEntity.getKey()); |
||||
|
ps.setLong(3, tsKvEntity.getTs()); |
||||
|
|
||||
|
if (tsKvEntity.getBooleanValue() != null) { |
||||
|
ps.setBoolean(4, tsKvEntity.getBooleanValue()); |
||||
|
ps.setBoolean(8, tsKvEntity.getBooleanValue()); |
||||
|
} else { |
||||
|
ps.setNull(4, Types.BOOLEAN); |
||||
|
ps.setNull(8, Types.BOOLEAN); |
||||
|
} |
||||
|
|
||||
|
ps.setString(5, replaceNullChars(tsKvEntity.getStrValue())); |
||||
|
ps.setString(9, replaceNullChars(tsKvEntity.getStrValue())); |
||||
|
|
||||
|
|
||||
|
if (tsKvEntity.getLongValue() != null) { |
||||
|
ps.setLong(6, tsKvEntity.getLongValue()); |
||||
|
ps.setLong(10, tsKvEntity.getLongValue()); |
||||
|
} else { |
||||
|
ps.setNull(6, Types.BIGINT); |
||||
|
ps.setNull(10, Types.BIGINT); |
||||
|
} |
||||
|
|
||||
|
if (tsKvEntity.getDoubleValue() != null) { |
||||
|
ps.setDouble(7, tsKvEntity.getDoubleValue()); |
||||
|
ps.setDouble(11, tsKvEntity.getDoubleValue()); |
||||
|
} else { |
||||
|
ps.setNull(7, Types.DOUBLE); |
||||
|
ps.setNull(11, Types.DOUBLE); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int getBatchSize() { |
||||
|
return entries.size(); |
||||
|
} |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
private String getInsertOrUpdateQuery(String partitionDate) { |
||||
|
return INSERT_INTO_TS_KV + partitionDate + VALUES_ON_CONFLICT_DO_UPDATE; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sqlts.psql; |
||||
|
|
||||
|
import org.springframework.data.domain.Pageable; |
||||
|
import org.springframework.data.jpa.repository.Modifying; |
||||
|
import org.springframework.data.jpa.repository.Query; |
||||
|
import org.springframework.data.repository.CrudRepository; |
||||
|
import org.springframework.data.repository.query.Param; |
||||
|
import org.springframework.scheduling.annotation.Async; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.server.dao.model.sqlts.psql.TsKvCompositeKey; |
||||
|
import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; |
||||
|
import org.thingsboard.server.dao.util.SqlDao; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.CompletableFuture; |
||||
|
|
||||
|
@SqlDao |
||||
|
public interface TsKvPsqlRepository extends CrudRepository<TsKvEntity, TsKvCompositeKey> { |
||||
|
|
||||
|
@Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + |
||||
|
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
List<TsKvEntity> findAllWithLimit(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int key, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs, |
||||
|
Pageable pageable); |
||||
|
|
||||
|
@Transactional |
||||
|
@Modifying |
||||
|
@Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + |
||||
|
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
void delete(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int key, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.strValue IS NOT NULL " + |
||||
|
"AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " + |
||||
|
"MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " + |
||||
|
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"'MAX') FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.strValue IS NOT NULL " + |
||||
|
"AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " + |
||||
|
"MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " + |
||||
|
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"'MIN') FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findNumericMin( |
||||
|
@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + |
||||
|
"SUM(COALESCE(tskv.doubleValue, 0.0)), " + |
||||
|
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"'AVG') FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findAvg(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
@Async |
||||
|
@Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + |
||||
|
"SUM(COALESCE(tskv.doubleValue, 0.0)), " + |
||||
|
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
||||
|
"'SUM') FROM TsKvEntity tskv " + |
||||
|
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
||||
|
CompletableFuture<TsKvEntity> findSum(@Param("entityId") UUID entityId, |
||||
|
@Param("entityKey") int entityKey, |
||||
|
@Param("startTs") long startTs, |
||||
|
@Param("endTs") long endTs); |
||||
|
|
||||
|
} |
||||
@ -1,140 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2020 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.dao.sqlts.ts; |
|
||||
|
|
||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
|
||||
import org.springframework.stereotype.Repository; |
|
||||
import org.springframework.transaction.annotation.Transactional; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; |
|
||||
import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; |
|
||||
import org.thingsboard.server.dao.util.HsqlDao; |
|
||||
import org.thingsboard.server.dao.util.SqlTsDao; |
|
||||
|
|
||||
import java.sql.PreparedStatement; |
|
||||
import java.sql.SQLException; |
|
||||
import java.sql.Types; |
|
||||
import java.util.List; |
|
||||
|
|
||||
@SqlTsDao |
|
||||
@HsqlDao |
|
||||
@Repository |
|
||||
@Transactional |
|
||||
public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { |
|
||||
|
|
||||
private static final String TS_KV_LATEST_CONSTRAINT = "(ts_kv_latest.entity_type=A.entity_type AND ts_kv_latest.entity_id=A.entity_id AND ts_kv_latest.key=A.key)"; |
|
||||
|
|
||||
private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, BOOL_V, HSQL_LATEST_ON_BOOL_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, STR_V, HSQL_LATEST_ON_STR_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, HSQL_LATEST_ON_LONG_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, HSQL_LATEST_ON_DBL_VALUE_UPDATE_SET_NULLS); |
|
||||
|
|
||||
private static final String INSERT_OR_UPDATE = |
|
||||
"MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + |
|
||||
"T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
|
||||
"ON (ts_kv_latest.entity_type=T.entity_type " + |
|
||||
"AND ts_kv_latest.entity_id=T.entity_id " + |
|
||||
"AND ts_kv_latest.key=T.key) " + |
|
||||
"WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v " + |
|
||||
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
|
||||
"VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; |
|
||||
|
|
||||
@Override |
|
||||
public void saveOrUpdate(TsKvLatestEntity entity) { |
|
||||
processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveOrUpdate(List<TsKvLatestEntity> entities) { |
|
||||
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { |
|
||||
@Override |
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException { |
|
||||
ps.setString(1, entities.get(i).getEntityType().name()); |
|
||||
ps.setString(2, entities.get(i).getEntityId()); |
|
||||
ps.setString(3, entities.get(i).getKey()); |
|
||||
ps.setLong(4, entities.get(i).getTs()); |
|
||||
|
|
||||
if (entities.get(i).getBooleanValue() != null) { |
|
||||
ps.setBoolean(5, entities.get(i).getBooleanValue()); |
|
||||
} else { |
|
||||
ps.setNull(5, Types.BOOLEAN); |
|
||||
} |
|
||||
|
|
||||
ps.setString(6, entities.get(i).getStrValue()); |
|
||||
|
|
||||
if (entities.get(i).getLongValue() != null) { |
|
||||
ps.setLong(7, entities.get(i).getLongValue()); |
|
||||
} else { |
|
||||
ps.setNull(7, Types.BIGINT); |
|
||||
} |
|
||||
|
|
||||
if (entities.get(i).getDoubleValue() != null) { |
|
||||
ps.setDouble(8, entities.get(i).getDoubleValue()); |
|
||||
} else { |
|
||||
ps.setNull(8, Types.DOUBLE); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int getBatchSize() { |
|
||||
return entities.size(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("bool_v", entity.getBooleanValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateString(TsKvLatestEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("str_v", entity.getStrValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateLong(TsKvLatestEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("long_v", entity.getLongValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateDouble(TsKvLatestEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("dbl_v", entity.getDoubleValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,141 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2020 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.dao.sqlts.ts; |
|
||||
|
|
||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
|
||||
import org.springframework.stereotype.Repository; |
|
||||
import org.springframework.transaction.annotation.Transactional; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; |
|
||||
import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; |
|
||||
import org.thingsboard.server.dao.util.HsqlDao; |
|
||||
import org.thingsboard.server.dao.util.SqlTsDao; |
|
||||
|
|
||||
import java.sql.PreparedStatement; |
|
||||
import java.sql.SQLException; |
|
||||
import java.sql.Types; |
|
||||
import java.util.List; |
|
||||
|
|
||||
@SqlTsDao |
|
||||
@HsqlDao |
|
||||
@Repository |
|
||||
@Transactional |
|
||||
public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepository<TsKvEntity> { |
|
||||
|
|
||||
private static final String TS_KV_CONSTRAINT = "(ts_kv.entity_type=A.entity_type AND ts_kv.entity_id=A.entity_id AND ts_kv.key=A.key AND ts_kv.ts=A.ts)"; |
|
||||
|
|
||||
private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, BOOL_V, HSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, STR_V, HSQL_ON_STR_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, HSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); |
|
||||
|
|
||||
private static final String INSERT_OR_UPDATE = |
|
||||
"MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + |
|
||||
"T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
|
||||
"ON (ts_kv.entity_type=T.entity_type " + |
|
||||
"AND ts_kv.entity_id=T.entity_id " + |
|
||||
"AND ts_kv.key=T.key " + |
|
||||
"AND ts_kv.ts=T.ts) " + |
|
||||
"WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v " + |
|
||||
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + |
|
||||
"VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; |
|
||||
|
|
||||
@Override |
|
||||
public void saveOrUpdate(TsKvEntity entity) { |
|
||||
processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveOrUpdate(List<TsKvEntity> entities) { |
|
||||
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { |
|
||||
@Override |
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException { |
|
||||
ps.setString(1, entities.get(i).getEntityType().name()); |
|
||||
ps.setString(2, entities.get(i).getEntityId()); |
|
||||
ps.setString(3, entities.get(i).getKey()); |
|
||||
ps.setLong(4, entities.get(i).getTs()); |
|
||||
|
|
||||
if (entities.get(i).getBooleanValue() != null) { |
|
||||
ps.setBoolean(5, entities.get(i).getBooleanValue()); |
|
||||
} else { |
|
||||
ps.setNull(5, Types.BOOLEAN); |
|
||||
} |
|
||||
|
|
||||
ps.setString(6, entities.get(i).getStrValue()); |
|
||||
|
|
||||
if (entities.get(i).getLongValue() != null) { |
|
||||
ps.setLong(7, entities.get(i).getLongValue()); |
|
||||
} else { |
|
||||
ps.setNull(7, Types.BIGINT); |
|
||||
} |
|
||||
|
|
||||
if (entities.get(i).getDoubleValue() != null) { |
|
||||
ps.setDouble(8, entities.get(i).getDoubleValue()); |
|
||||
} else { |
|
||||
ps.setNull(8, Types.DOUBLE); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int getBatchSize() { |
|
||||
return entities.size(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateBoolean(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("bool_v", entity.getBooleanValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateString(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("str_v", entity.getStrValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateLong(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("long_v", entity.getLongValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateDouble(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("dbl_v", entity.getDoubleValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,436 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2020 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.dao.sqlts.ts; |
|
||||
|
|
||||
import com.google.common.collect.Lists; |
|
||||
import com.google.common.util.concurrent.FutureCallback; |
|
||||
import com.google.common.util.concurrent.Futures; |
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import com.google.common.util.concurrent.SettableFuture; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||
import org.springframework.beans.factory.annotation.Value; |
|
||||
import org.springframework.data.domain.PageRequest; |
|
||||
import org.springframework.data.domain.Sort; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
import org.thingsboard.server.common.data.UUIDConverter; |
|
||||
import org.thingsboard.server.common.data.id.EntityId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.kv.Aggregation; |
|
||||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
|
||||
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; |
|
||||
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
|
||||
import org.thingsboard.server.common.data.kv.StringDataEntry; |
|
||||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|
||||
import org.thingsboard.server.dao.DaoUtil; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestCompositeKey; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; |
|
||||
import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; |
|
||||
import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; |
|
||||
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; |
|
||||
import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; |
|
||||
import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; |
|
||||
import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; |
|
||||
import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; |
|
||||
import org.thingsboard.server.dao.timeseries.TimeseriesDao; |
|
||||
import org.thingsboard.server.dao.util.SqlTsDao; |
|
||||
|
|
||||
import javax.annotation.Nullable; |
|
||||
import javax.annotation.PostConstruct; |
|
||||
import javax.annotation.PreDestroy; |
|
||||
import java.util.ArrayList; |
|
||||
import java.util.List; |
|
||||
import java.util.Optional; |
|
||||
import java.util.concurrent.CompletableFuture; |
|
||||
import java.util.concurrent.ExecutionException; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
|
||||
|
|
||||
|
|
||||
@Component |
|
||||
@Slf4j |
|
||||
@SqlTsDao |
|
||||
public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements TimeseriesDao { |
|
||||
|
|
||||
@Autowired |
|
||||
private TsKvRepository tsKvRepository; |
|
||||
|
|
||||
@Autowired |
|
||||
private TsKvLatestRepository tsKvLatestRepository; |
|
||||
|
|
||||
@Autowired |
|
||||
private AbstractTimeseriesInsertRepository insertRepository; |
|
||||
|
|
||||
@Autowired |
|
||||
private AbstractLatestInsertRepository insertLatestRepository; |
|
||||
|
|
||||
@Autowired |
|
||||
ScheduledLogExecutorComponent logExecutor; |
|
||||
|
|
||||
@Value("${sql.ts.batch_size:1000}") |
|
||||
private int tsBatchSize; |
|
||||
|
|
||||
@Value("${sql.ts.batch_max_delay:100}") |
|
||||
private long tsMaxDelay; |
|
||||
|
|
||||
@Value("${sql.ts.stats_print_interval_ms:1000}") |
|
||||
private long tsStatsPrintIntervalMs; |
|
||||
|
|
||||
@Value("${sql.ts_latest.batch_size:1000}") |
|
||||
private int tsLatestBatchSize; |
|
||||
|
|
||||
@Value("${sql.ts_latest.batch_max_delay:100}") |
|
||||
private long tsLatestMaxDelay; |
|
||||
|
|
||||
@Value("${sql.ts_latest.stats_print_interval_ms:1000}") |
|
||||
private long tsLatestStatsPrintIntervalMs; |
|
||||
|
|
||||
private TbSqlBlockingQueue<TsKvEntity> tsQueue; |
|
||||
private TbSqlBlockingQueue<TsKvLatestEntity> tsLatestQueue; |
|
||||
|
|
||||
|
|
||||
@PostConstruct |
|
||||
private void init() { |
|
||||
TbSqlBlockingQueueParams tsParams = TbSqlBlockingQueueParams.builder() |
|
||||
.logName("TS") |
|
||||
.batchSize(tsBatchSize) |
|
||||
.maxDelay(tsMaxDelay) |
|
||||
.statsPrintIntervalMs(tsStatsPrintIntervalMs) |
|
||||
.build(); |
|
||||
tsQueue = new TbSqlBlockingQueue<>(tsParams); |
|
||||
tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); |
|
||||
|
|
||||
TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder() |
|
||||
.logName("TS Latest") |
|
||||
.batchSize(tsLatestBatchSize) |
|
||||
.maxDelay(tsLatestMaxDelay) |
|
||||
.statsPrintIntervalMs(tsLatestStatsPrintIntervalMs) |
|
||||
.build(); |
|
||||
tsLatestQueue = new TbSqlBlockingQueue<>(tsLatestParams); |
|
||||
tsLatestQueue.init(logExecutor, v -> insertLatestRepository.saveOrUpdate(v)); |
|
||||
} |
|
||||
|
|
||||
@PreDestroy |
|
||||
private void destroy() { |
|
||||
if (tsQueue != null) { |
|
||||
tsQueue.destroy(); |
|
||||
} |
|
||||
|
|
||||
if (tsLatestQueue != null) { |
|
||||
tsLatestQueue.destroy(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) { |
|
||||
return processFindAllAsync(tenantId, entityId, queries); |
|
||||
} |
|
||||
|
|
||||
protected ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { |
|
||||
if (query.getAggregation() == Aggregation.NONE) { |
|
||||
return findAllAsyncWithLimit(entityId, query); |
|
||||
} else { |
|
||||
long stepTs = query.getStartTs(); |
|
||||
List<ListenableFuture<Optional<TsKvEntry>>> futures = new ArrayList<>(); |
|
||||
while (stepTs < query.getEndTs()) { |
|
||||
long startTs = stepTs; |
|
||||
long endTs = stepTs + query.getInterval(); |
|
||||
long ts = startTs + (endTs - startTs) / 2; |
|
||||
futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); |
|
||||
stepTs = endTs; |
|
||||
} |
|
||||
return getTskvEntriesFuture(Futures.allAsList(futures)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { |
|
||||
List<CompletableFuture<TsKvEntity>> entitiesFutures = new ArrayList<>(); |
|
||||
String entityIdStr = fromTimeUUID(entityId.getId()); |
|
||||
switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures, entityIdStr); |
|
||||
|
|
||||
SettableFuture<TsKvEntity> listenableFuture = SettableFuture.create(); |
|
||||
|
|
||||
CompletableFuture<List<TsKvEntity>> entities = |
|
||||
CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) |
|
||||
.thenApply(v -> entitiesFutures.stream() |
|
||||
.map(CompletableFuture::join) |
|
||||
.collect(Collectors.toList())); |
|
||||
|
|
||||
entities.whenComplete((tsKvEntities, throwable) -> { |
|
||||
if (throwable != null) { |
|
||||
listenableFuture.setException(throwable); |
|
||||
} else { |
|
||||
TsKvEntity result = null; |
|
||||
for (TsKvEntity entity : tsKvEntities) { |
|
||||
if (entity.isNotEmpty()) { |
|
||||
result = entity; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
listenableFuture.set(result); |
|
||||
} |
|
||||
}); |
|
||||
return Futures.transform(listenableFuture, entity -> { |
|
||||
if (entity != null && entity.isNotEmpty()) { |
|
||||
entity.setEntityId(entityIdStr); |
|
||||
entity.setEntityType(entityId.getEntityType()); |
|
||||
entity.setKey(key); |
|
||||
entity.setTs(ts); |
|
||||
return Optional.of(DaoUtil.getData(entity)); |
|
||||
} else { |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void switchAgregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List<CompletableFuture<TsKvEntity>> entitiesFutures, String entityIdStr) { |
|
||||
switch (aggregation) { |
|
||||
case AVG: |
|
||||
findAvg(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); |
|
||||
break; |
|
||||
case MAX: |
|
||||
findMax(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); |
|
||||
break; |
|
||||
case MIN: |
|
||||
findMin(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); |
|
||||
break; |
|
||||
case SUM: |
|
||||
findSum(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); |
|
||||
break; |
|
||||
case COUNT: |
|
||||
findCount(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); |
|
||||
break; |
|
||||
default: |
|
||||
throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void findCount(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures, String entityIdStr) { |
|
||||
entitiesFutures.add(tsKvRepository.findCount( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
} |
|
||||
|
|
||||
private void findSum(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures, String entityIdStr) { |
|
||||
entitiesFutures.add(tsKvRepository.findSum( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
} |
|
||||
|
|
||||
private void findMin(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures, String entityIdStr) { |
|
||||
entitiesFutures.add(tsKvRepository.findStringMin( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
entitiesFutures.add(tsKvRepository.findNumericMin( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
} |
|
||||
|
|
||||
private void findMax(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures, String entityIdStr) { |
|
||||
entitiesFutures.add(tsKvRepository.findStringMax( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
entitiesFutures.add(tsKvRepository.findNumericMax( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
} |
|
||||
|
|
||||
private void findAvg(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures, String entityIdStr) { |
|
||||
entitiesFutures.add(tsKvRepository.findAvg( |
|
||||
entityIdStr, |
|
||||
entityId.getEntityType(), |
|
||||
key, |
|
||||
startTs, |
|
||||
endTs)); |
|
||||
} |
|
||||
|
|
||||
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { |
|
||||
return Futures.immediateFuture( |
|
||||
DaoUtil.convertDataList( |
|
||||
tsKvRepository.findAllWithLimit( |
|
||||
fromTimeUUID(entityId.getId()), |
|
||||
entityId.getEntityType(), |
|
||||
query.getKey(), |
|
||||
query.getStartTs(), |
|
||||
query.getEndTs(), |
|
||||
new PageRequest(0, query.getLimit(), |
|
||||
new Sort(Sort.Direction.fromString( |
|
||||
query.getOrderBy()), "ts"))))); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<TsKvEntry> findLatest(TenantId tenantId, EntityId entityId, String key) { |
|
||||
TsKvLatestCompositeKey compositeKey = |
|
||||
new TsKvLatestCompositeKey( |
|
||||
entityId.getEntityType(), |
|
||||
fromTimeUUID(entityId.getId()), |
|
||||
key); |
|
||||
Optional<TsKvLatestEntity> entry = tsKvLatestRepository.findById(compositeKey); |
|
||||
TsKvEntry result; |
|
||||
if (entry.isPresent()) { |
|
||||
result = DaoUtil.getData(entry.get()); |
|
||||
} else { |
|
||||
result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); |
|
||||
} |
|
||||
return Futures.immediateFuture(result); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<List<TsKvEntry>> findAllLatest(TenantId tenantId, EntityId entityId) { |
|
||||
return Futures.immediateFuture( |
|
||||
DaoUtil.convertDataList(Lists.newArrayList( |
|
||||
tsKvLatestRepository.findAllByEntityTypeAndEntityId( |
|
||||
entityId.getEntityType(), |
|
||||
UUIDConverter.fromTimeUUID(entityId.getId()))))); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { |
|
||||
TsKvEntity entity = new TsKvEntity(); |
|
||||
entity.setEntityType(entityId.getEntityType()); |
|
||||
entity.setEntityId(fromTimeUUID(entityId.getId())); |
|
||||
entity.setTs(tsKvEntry.getTs()); |
|
||||
entity.setKey(tsKvEntry.getKey()); |
|
||||
entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); |
|
||||
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
|
||||
entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
|
||||
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
|
||||
log.trace("Saving entity: {}", entity); |
|
||||
return tsQueue.add(entity); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { |
|
||||
return Futures.immediateFuture(null); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { |
|
||||
TsKvLatestEntity latestEntity = new TsKvLatestEntity(); |
|
||||
latestEntity.setEntityType(entityId.getEntityType()); |
|
||||
latestEntity.setEntityId(fromTimeUUID(entityId.getId())); |
|
||||
latestEntity.setTs(tsKvEntry.getTs()); |
|
||||
latestEntity.setKey(tsKvEntry.getKey()); |
|
||||
latestEntity.setStrValue(tsKvEntry.getStrValue().orElse(null)); |
|
||||
latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
|
||||
latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
|
||||
latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
|
||||
return tsLatestQueue.add(latestEntity); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
|
||||
return service.submit(() -> { |
|
||||
tsKvRepository.delete( |
|
||||
fromTimeUUID(entityId.getId()), |
|
||||
entityId.getEntityType(), |
|
||||
query.getKey(), |
|
||||
query.getStartTs(), |
|
||||
query.getEndTs()); |
|
||||
return null; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
|
||||
ListenableFuture<TsKvEntry> latestFuture = findLatest(tenantId, entityId, query.getKey()); |
|
||||
|
|
||||
ListenableFuture<Boolean> booleanFuture = Futures.transform(latestFuture, tsKvEntry -> { |
|
||||
long ts = tsKvEntry.getTs(); |
|
||||
return ts > query.getStartTs() && ts <= query.getEndTs(); |
|
||||
}, service); |
|
||||
|
|
||||
ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { |
|
||||
if (isRemove) { |
|
||||
TsKvLatestEntity latestEntity = new TsKvLatestEntity(); |
|
||||
latestEntity.setEntityType(entityId.getEntityType()); |
|
||||
latestEntity.setEntityId(fromTimeUUID(entityId.getId())); |
|
||||
latestEntity.setKey(query.getKey()); |
|
||||
return service.submit(() -> { |
|
||||
tsKvLatestRepository.delete(latestEntity); |
|
||||
return null; |
|
||||
}); |
|
||||
} |
|
||||
return Futures.immediateFuture(null); |
|
||||
}, service); |
|
||||
|
|
||||
final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>(); |
|
||||
Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() { |
|
||||
@Override |
|
||||
public void onSuccess(@Nullable Void result) { |
|
||||
if (query.getRewriteLatestIfDeleted()) { |
|
||||
ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { |
|
||||
if (isRemove) { |
|
||||
return getNewLatestEntryFuture(tenantId, entityId, query); |
|
||||
} |
|
||||
return Futures.immediateFuture(null); |
|
||||
}, service); |
|
||||
|
|
||||
try { |
|
||||
resultFuture.set(savedLatestFuture.get()); |
|
||||
} catch (InterruptedException | ExecutionException e) { |
|
||||
log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e); |
|
||||
} |
|
||||
} else { |
|
||||
resultFuture.set(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onFailure(Throwable t) { |
|
||||
log.warn("[{}] Failed to process remove of the latest value", entityId, t); |
|
||||
} |
|
||||
}); |
|
||||
return resultFuture; |
|
||||
} |
|
||||
|
|
||||
private ListenableFuture<Void> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
|
||||
ListenableFuture<List<TsKvEntry>> future = findNewLatestEntryFuture(tenantId, entityId, query); |
|
||||
return Futures.transformAsync(future, entryList -> { |
|
||||
if (entryList.size() == 1) { |
|
||||
return saveLatest(tenantId, entityId, entryList.get(0)); |
|
||||
} else { |
|
||||
log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey()); |
|
||||
} |
|
||||
return Futures.immediateFuture(null); |
|
||||
}, service); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { |
|
||||
return service.submit(() -> null); |
|
||||
} |
|
||||
} |
|
||||
@ -1,143 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2020 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.dao.sqlts.ts; |
|
||||
|
|
||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
|
||||
import org.springframework.stereotype.Repository; |
|
||||
import org.springframework.transaction.annotation.Transactional; |
|
||||
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; |
|
||||
import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; |
|
||||
import org.thingsboard.server.dao.util.PsqlDao; |
|
||||
import org.thingsboard.server.dao.util.SqlTsDao; |
|
||||
|
|
||||
import java.sql.PreparedStatement; |
|
||||
import java.sql.SQLException; |
|
||||
import java.sql.Types; |
|
||||
import java.util.List; |
|
||||
|
|
||||
@SqlTsDao |
|
||||
@PsqlDao |
|
||||
@Repository |
|
||||
@Transactional |
|
||||
public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepository<TsKvEntity> { |
|
||||
|
|
||||
private static final String TS_KV_CONSTRAINT = "(entity_type, entity_id, key, ts)"; |
|
||||
|
|
||||
private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, BOOL_V, PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, STR_V, PSQL_ON_STR_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); |
|
||||
private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); |
|
||||
|
|
||||
private static final String INSERT_OR_UPDATE = |
|
||||
"INSERT INTO ts_kv (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + |
|
||||
"ON CONFLICT (entity_type, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; |
|
||||
|
|
||||
@Override |
|
||||
public void saveOrUpdate(TsKvEntity entity) { |
|
||||
processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateBoolean(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("bool_v", entity.getBooleanValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateString(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("str_v", replaceNullChars(entity.getStrValue())) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateLong(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("long_v", entity.getLongValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void saveOrUpdateDouble(TsKvEntity entity, String query) { |
|
||||
entityManager.createNativeQuery(query) |
|
||||
.setParameter("entity_type", entity.getEntityType().name()) |
|
||||
.setParameter("entity_id", entity.getEntityId()) |
|
||||
.setParameter("key", entity.getKey()) |
|
||||
.setParameter("ts", entity.getTs()) |
|
||||
.setParameter("dbl_v", entity.getDoubleValue()) |
|
||||
.executeUpdate(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveOrUpdate(List<TsKvEntity> entities) { |
|
||||
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { |
|
||||
@Override |
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException { |
|
||||
TsKvEntity tsKvEntity = entities.get(i); |
|
||||
ps.setString(1, tsKvEntity.getEntityType().name()); |
|
||||
ps.setString(2, tsKvEntity.getEntityId()); |
|
||||
ps.setString(3, tsKvEntity.getKey()); |
|
||||
ps.setLong(4, tsKvEntity.getTs()); |
|
||||
|
|
||||
if (tsKvEntity.getBooleanValue() != null) { |
|
||||
ps.setBoolean(5, tsKvEntity.getBooleanValue()); |
|
||||
ps.setBoolean(9, tsKvEntity.getBooleanValue()); |
|
||||
} else { |
|
||||
ps.setNull(5, Types.BOOLEAN); |
|
||||
ps.setNull(9, Types.BOOLEAN); |
|
||||
} |
|
||||
|
|
||||
ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); |
|
||||
ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); |
|
||||
|
|
||||
|
|
||||
if (tsKvEntity.getLongValue() != null) { |
|
||||
ps.setLong(7, tsKvEntity.getLongValue()); |
|
||||
ps.setLong(11, tsKvEntity.getLongValue()); |
|
||||
} else { |
|
||||
ps.setNull(7, Types.BIGINT); |
|
||||
ps.setNull(11, Types.BIGINT); |
|
||||
} |
|
||||
|
|
||||
if (tsKvEntity.getDoubleValue() != null) { |
|
||||
ps.setDouble(8, tsKvEntity.getDoubleValue()); |
|
||||
ps.setDouble(12, tsKvEntity.getDoubleValue()); |
|
||||
} else { |
|
||||
ps.setNull(8, Types.DOUBLE); |
|
||||
ps.setNull(12, Types.DOUBLE); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int getBatchSize() { |
|
||||
return entities.size(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.timeseries; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
@Data |
||||
|
public class PsqlPartition { |
||||
|
|
||||
|
private static final String TABLE_REGEX = "ts_kv_"; |
||||
|
|
||||
|
private long start; |
||||
|
private long end; |
||||
|
private String partitionDate; |
||||
|
private String query; |
||||
|
|
||||
|
public PsqlPartition(long start, long end, String pattern) { |
||||
|
this.start = start; |
||||
|
this.end = end; |
||||
|
this.partitionDate = new SimpleDateFormat(pattern).format(new Date(start)); |
||||
|
this.query = createStatement(start, end, partitionDate); |
||||
|
} |
||||
|
|
||||
|
private String createStatement(long start, long end, String partitionDate) { |
||||
|
return "CREATE TABLE IF NOT EXISTS " + TABLE_REGEX + partitionDate + " PARTITION OF ts_kv(PRIMARY KEY (entity_id, key, ts)) FOR VALUES FROM (" + start + ") TO (" + end + ")"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.timeseries; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
import java.time.ZoneOffset; |
||||
|
import java.time.temporal.ChronoUnit; |
||||
|
import java.time.temporal.TemporalUnit; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
public enum SqlTsPartitionDate { |
||||
|
|
||||
|
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), INDEFINITE("indefinite", ChronoUnit.FOREVER); |
||||
|
|
||||
|
private final String pattern; |
||||
|
private final transient TemporalUnit truncateUnit; |
||||
|
public final static LocalDateTime EPOCH_START = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC); |
||||
|
|
||||
|
SqlTsPartitionDate(String pattern, TemporalUnit truncateUnit) { |
||||
|
this.pattern = pattern; |
||||
|
this.truncateUnit = truncateUnit; |
||||
|
} |
||||
|
|
||||
|
public String getPattern() { |
||||
|
return pattern; |
||||
|
} |
||||
|
|
||||
|
public TemporalUnit getTruncateUnit() { |
||||
|
return truncateUnit; |
||||
|
} |
||||
|
|
||||
|
public LocalDateTime trancateTo(LocalDateTime time) { |
||||
|
switch (this) { |
||||
|
case MINUTES: |
||||
|
return time.truncatedTo(ChronoUnit.MINUTES); |
||||
|
case HOURS: |
||||
|
return time.truncatedTo(ChronoUnit.HOURS); |
||||
|
case DAYS: |
||||
|
return time.truncatedTo(ChronoUnit.DAYS); |
||||
|
case MONTHS: |
||||
|
return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1); |
||||
|
case YEARS: |
||||
|
return time.truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); |
||||
|
case INDEFINITE: |
||||
|
return EPOCH_START; |
||||
|
default: |
||||
|
throw new RuntimeException("Failed to parse partitioning property!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public LocalDateTime plusTo(LocalDateTime time) { |
||||
|
switch (this) { |
||||
|
case MINUTES: |
||||
|
return time.plusMinutes(1); |
||||
|
case HOURS: |
||||
|
return time.plusHours(1); |
||||
|
case DAYS: |
||||
|
return time.plusDays(1); |
||||
|
case MONTHS: |
||||
|
return time.plusMonths(1); |
||||
|
case YEARS: |
||||
|
return time.plusYears(1); |
||||
|
default: |
||||
|
throw new RuntimeException("Failed to parse partitioning property!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static Optional<SqlTsPartitionDate> parse(String name) { |
||||
|
SqlTsPartitionDate partition = null; |
||||
|
if (name != null) { |
||||
|
for (SqlTsPartitionDate partitionDate : SqlTsPartitionDate.values()) { |
||||
|
if (partitionDate.name().equalsIgnoreCase(name)) { |
||||
|
partition = partitionDate; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return Optional.of(partition); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2020 The Thingsboard Authors |
||||
|
-- |
||||
|
-- Licensed 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. |
||||
|
-- |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS ts_kv ( |
||||
|
entity_id uuid NOT NULL, |
||||
|
key int NOT NULL, |
||||
|
ts bigint NOT NULL, |
||||
|
bool_v boolean, |
||||
|
str_v varchar(10000000), |
||||
|
long_v bigint, |
||||
|
dbl_v double precision |
||||
|
) PARTITION BY RANGE (ts); |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( |
||||
|
key varchar(255) NOT NULL, |
||||
|
key_id serial UNIQUE, |
||||
|
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) |
||||
|
); |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS ts_kv_latest ( |
||||
|
entity_type varchar(255) NOT NULL, |
||||
|
entity_id varchar(31) NOT NULL, |
||||
|
key varchar(255) NOT NULL, |
||||
|
ts bigint NOT NULL, |
||||
|
bool_v boolean, |
||||
|
str_v varchar(10000000), |
||||
|
long_v bigint, |
||||
|
dbl_v double precision, |
||||
|
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key) |
||||
|
); |
||||
Loading…
Reference in new issue