v0.8.14
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
pub const INSERT_READING: &str = r#"
|
||||
INSERT INTO readings(
|
||||
device_id,
|
||||
timestamp,
|
||||
indoor_temperature,
|
||||
outdoor_temperature,
|
||||
target_temperature,
|
||||
power,
|
||||
source
|
||||
)
|
||||
VALUES(?1,?2,?3,?4,?5,?6,?7)
|
||||
"#;
|
||||
|
||||
pub const LIST_READINGS_BY_DEVICE: &str = r#"
|
||||
SELECT id,device_id,timestamp,indoor_temperature,outdoor_temperature,target_temperature,power,source
|
||||
FROM readings
|
||||
WHERE device_id=?1 AND timestamp>=?2
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT ?3
|
||||
"#;
|
||||
|
||||
pub const LIST_READINGS_ALL: &str = r#"
|
||||
SELECT id,device_id,timestamp,indoor_temperature,outdoor_temperature,target_temperature,power,source
|
||||
FROM readings
|
||||
WHERE timestamp>=?1
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT ?2
|
||||
"#;
|
||||
|
||||
pub const LIST_DEVICE_HISTORY_BY_DEVICE_BUCKETED: &str = r#"
|
||||
SELECT MIN(id),device_id,MIN(timestamp),AVG(indoor_temperature),AVG(outdoor_temperature),
|
||||
AVG(target_temperature),MAX(power),MAX(source)
|
||||
FROM readings
|
||||
WHERE device_id=?1 AND timestamp>=?2
|
||||
GROUP BY device_id, CAST(unixepoch(timestamp)/?3 AS INTEGER)
|
||||
ORDER BY MIN(timestamp) ASC
|
||||
LIMIT ?4
|
||||
"#;
|
||||
|
||||
pub const LIST_DEVICE_HISTORY_ALL_BUCKETED: &str = r#"
|
||||
SELECT MIN(id),device_id,MIN(timestamp),AVG(indoor_temperature),AVG(outdoor_temperature),
|
||||
AVG(target_temperature),MAX(power),MAX(source)
|
||||
FROM readings
|
||||
WHERE timestamp>=?1
|
||||
GROUP BY device_id, CAST(unixepoch(timestamp)/?2 AS INTEGER)
|
||||
ORDER BY MIN(timestamp) ASC
|
||||
LIMIT ?3
|
||||
"#;
|
||||
|
||||
pub const LIST_DEVICE_HISTORY_BEFORE: &str = r#"
|
||||
SELECT id,device_id,timestamp,indoor_temperature,outdoor_temperature,target_temperature,power,source
|
||||
FROM readings WHERE timestamp < ?1 ORDER BY timestamp ASC LIMIT ?2
|
||||
"#;
|
||||
pub const DELETE_READING_BY_ID: &str = "DELETE FROM readings WHERE id=?1";
|
||||
|
||||
pub const PRUNE_READINGS: &str = "DELETE FROM readings WHERE timestamp < ?1";
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
pub const COUNT_DEVICES: &str = "SELECT COUNT(*) FROM devices";
|
||||
|
||||
pub const UPSERT_DEVICE: &str = r#"
|
||||
INSERT INTO devices(id, mac, name, ip, simulated, payload, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
mac=excluded.mac,
|
||||
name=excluded.name,
|
||||
ip=excluded.ip,
|
||||
simulated=excluded.simulated,
|
||||
payload=excluded.payload,
|
||||
updated_at=excluded.updated_at
|
||||
"#;
|
||||
|
||||
pub const LIST_DEVICES: &str = "SELECT payload FROM devices ORDER BY name COLLATE NOCASE";
|
||||
pub const GET_DEVICE_BY_ID: &str = "SELECT payload FROM devices WHERE id=?1";
|
||||
pub const GET_DEVICE_BY_MAC: &str = "SELECT payload FROM devices WHERE lower(mac)=lower(?1)";
|
||||
pub const DELETE_DEVICE_READINGS: &str = "DELETE FROM readings WHERE device_id=?1";
|
||||
pub const DELETE_SCHEDULES_BY_DEVICE_ID: &str =
|
||||
"DELETE FROM schedules WHERE zone_id IN (SELECT id FROM zones WHERE json_extract(payload, '$.device_id')=?1)";
|
||||
pub const DELETE_ZONES_BY_DEVICE_ID: &str =
|
||||
"DELETE FROM zones WHERE json_extract(payload, '$.device_id')=?1";
|
||||
pub const DELETE_DEVICE: &str = "DELETE FROM devices WHERE id=?1";
|
||||
|
||||
pub const UPSERT_ZONE: &str = r#"
|
||||
INSERT INTO zones(id,payload,updated_at) VALUES(?1,?2,?3)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
payload=excluded.payload,
|
||||
updated_at=excluded.updated_at
|
||||
"#;
|
||||
pub const LIST_ZONES: &str =
|
||||
"SELECT payload FROM zones ORDER BY json_extract(payload, '$.name') COLLATE NOCASE";
|
||||
pub const GET_ZONE: &str = "SELECT payload FROM zones WHERE id=?1";
|
||||
pub const DELETE_SCHEDULES_BY_ZONE_ID: &str = "DELETE FROM schedules WHERE zone_id=?1";
|
||||
pub const DELETE_ZONE: &str = "DELETE FROM zones WHERE id=?1";
|
||||
|
||||
pub const UPSERT_GROUP: &str = r#"
|
||||
INSERT INTO climate_groups(id,payload,updated_at) VALUES(?1,?2,?3)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
payload=excluded.payload,
|
||||
updated_at=excluded.updated_at
|
||||
"#;
|
||||
pub const LIST_GROUPS: &str =
|
||||
"SELECT payload FROM climate_groups ORDER BY json_extract(payload, '$.name') COLLATE NOCASE";
|
||||
pub const GET_GROUP: &str = "SELECT payload FROM climate_groups WHERE id=?1";
|
||||
pub const DELETE_GROUP: &str = "DELETE FROM climate_groups WHERE id=?1";
|
||||
|
||||
pub const UPSERT_SCHEDULE: &str = r#"
|
||||
INSERT INTO schedules(id,zone_id,payload,updated_at) VALUES(?1,?2,?3,?4)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
zone_id=excluded.zone_id,
|
||||
payload=excluded.payload,
|
||||
updated_at=excluded.updated_at
|
||||
"#;
|
||||
pub const LIST_SCHEDULES: &str =
|
||||
"SELECT payload FROM schedules ORDER BY json_extract(payload, '$.name') COLLATE NOCASE";
|
||||
pub const GET_SCHEDULE: &str = "SELECT payload FROM schedules WHERE id=?1";
|
||||
pub const DELETE_SCHEDULE: &str = "DELETE FROM schedules WHERE id=?1";
|
||||
|
||||
pub const UPSERT_AUTOMATION: &str = r#"
|
||||
INSERT INTO automations(id,payload,updated_at) VALUES(?1,?2,?3)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
payload=excluded.payload,
|
||||
updated_at=excluded.updated_at
|
||||
"#;
|
||||
pub const LIST_AUTOMATIONS: &str =
|
||||
"SELECT payload FROM automations ORDER BY json_extract(payload, '$.name') COLLATE NOCASE";
|
||||
pub const GET_AUTOMATION: &str = "SELECT payload FROM automations WHERE id=?1";
|
||||
pub const DELETE_AUTOMATION: &str = "DELETE FROM automations WHERE id=?1";
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
pub const INSERT_HA_READING_IF_DUE: &str = r#"
|
||||
INSERT INTO ha_readings(entity_id,zone_id,kind,timestamp,temperature)
|
||||
SELECT ?1,?2,?3,?4,?5
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM ha_readings
|
||||
WHERE entity_id=?1 AND COALESCE(zone_id,'')=COALESCE(?2,'') AND kind=?3 AND timestamp>=?6
|
||||
LIMIT 1
|
||||
)
|
||||
"#;
|
||||
|
||||
pub const LIST_HA_HISTORY_BY_ENTITY_BUCKETED: &str = r#"
|
||||
SELECT MIN(id),entity_id,MAX(zone_id),MAX(kind),MIN(timestamp),AVG(temperature)
|
||||
FROM ha_readings
|
||||
WHERE entity_id=?1 AND timestamp>=?2
|
||||
GROUP BY entity_id,COALESCE(zone_id,''),kind,CAST(unixepoch(timestamp)/?3 AS INTEGER)
|
||||
ORDER BY MIN(timestamp) ASC
|
||||
LIMIT ?4
|
||||
"#;
|
||||
|
||||
pub const LIST_HA_HISTORY_ALL_BUCKETED: &str = r#"
|
||||
SELECT MIN(id),entity_id,MAX(zone_id),MAX(kind),MIN(timestamp),AVG(temperature)
|
||||
FROM ha_readings
|
||||
WHERE timestamp>=?1
|
||||
GROUP BY entity_id,COALESCE(zone_id,''),kind,CAST(unixepoch(timestamp)/?2 AS INTEGER)
|
||||
ORDER BY MIN(timestamp) ASC
|
||||
LIMIT ?3
|
||||
"#;
|
||||
|
||||
pub const LIST_HA_HISTORY_BEFORE: &str = r#"
|
||||
SELECT id,entity_id,zone_id,kind,timestamp,temperature
|
||||
FROM ha_readings WHERE timestamp < ?1 ORDER BY timestamp ASC LIMIT ?2
|
||||
"#;
|
||||
pub const DELETE_HA_READING_BY_ID: &str = "DELETE FROM ha_readings WHERE id=?1";
|
||||
|
||||
pub const PRUNE_HA_READINGS: &str = "DELETE FROM ha_readings WHERE timestamp < ?1";
|
||||
|
||||
|
||||
// Tiered history compaction keeps only the resolution the charts can actually display.
|
||||
@@ -0,0 +1,70 @@
|
||||
pub const COMPACT_DEVICE_HISTORY: &str = r#"
|
||||
DELETE FROM readings WHERE id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (
|
||||
PARTITION BY device_id, CAST(unixepoch(timestamp)/?1 AS INTEGER)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
) AS rn
|
||||
FROM readings WHERE timestamp < ?2 AND timestamp >= ?3
|
||||
) WHERE rn > 1
|
||||
)
|
||||
"#;
|
||||
|
||||
pub const COMPACT_ZONE_HISTORY: &str = r#"
|
||||
DELETE FROM zone_readings WHERE id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (
|
||||
PARTITION BY zone_id, CAST(unixepoch(timestamp)/?1 AS INTEGER)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
) AS rn
|
||||
FROM zone_readings WHERE timestamp < ?2 AND timestamp >= ?3
|
||||
) WHERE rn > 1
|
||||
)
|
||||
"#;
|
||||
|
||||
pub const COMPACT_HA_HISTORY: &str = r#"
|
||||
DELETE FROM ha_readings WHERE id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (
|
||||
PARTITION BY entity_id, COALESCE(zone_id,''), kind, CAST(unixepoch(timestamp)/?1 AS INTEGER)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
) AS rn
|
||||
FROM ha_readings WHERE timestamp < ?2 AND timestamp >= ?3
|
||||
) WHERE rn > 1
|
||||
)
|
||||
"#;
|
||||
|
||||
pub const CLEAR_CONFIGURATION: &str = r#"
|
||||
DELETE FROM schedules;
|
||||
DELETE FROM automations;
|
||||
DELETE FROM climate_groups;
|
||||
DELETE FROM zones;
|
||||
DELETE FROM devices;
|
||||
"#;
|
||||
pub const HISTORY_COUNTS: &str = r#"
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM readings),
|
||||
(SELECT COUNT(*) FROM zone_readings),
|
||||
(SELECT COUNT(*) FROM ha_readings)
|
||||
"#;
|
||||
|
||||
pub const INSERT_EVENT: &str =
|
||||
"INSERT INTO event_log(timestamp,level,kind,message,metadata) VALUES(?1,?2,?3,?4,?5)";
|
||||
pub const LIST_EVENTS: &str =
|
||||
"SELECT id,timestamp,level,kind,message,metadata FROM event_log ORDER BY id DESC LIMIT ?1";
|
||||
pub const PRUNE_EVENTS: &str = "DELETE FROM event_log WHERE timestamp < ?1";
|
||||
|
||||
pub const LIST_API_TOKENS: &str =
|
||||
"SELECT id,name,token_prefix,created_at FROM api_tokens ORDER BY created_at DESC";
|
||||
pub const INSERT_API_TOKEN: &str =
|
||||
"INSERT INTO api_tokens(id,name,token_hash,token_prefix,created_at) VALUES(?1,?2,?3,?4,?5)";
|
||||
pub const API_TOKEN_EXISTS: &str = "SELECT 1 FROM api_tokens WHERE token_hash=?1 LIMIT 1";
|
||||
pub const DELETE_API_TOKEN: &str = "DELETE FROM api_tokens WHERE id=?1";
|
||||
|
||||
pub const LOAD_RUNTIME_SETTINGS: &str = "SELECT value FROM settings WHERE key='runtime'";
|
||||
pub const UPSERT_RUNTIME_SETTINGS: &str = r#"
|
||||
INSERT INTO settings(key,value,updated_at) VALUES('runtime',?1,?2)
|
||||
ON CONFLICT(key) DO UPDATE SET
|
||||
value=excluded.value,
|
||||
updated_at=excluded.updated_at
|
||||
"#;
|
||||
@@ -0,0 +1,131 @@
|
||||
pub const INIT_SCHEMA: &str = r#"
|
||||
PRAGMA journal_mode=WAL;
|
||||
PRAGMA synchronous=NORMAL;
|
||||
PRAGMA foreign_keys=ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
applied_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id TEXT PRIMARY KEY,
|
||||
mac TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
ip TEXT NOT NULL,
|
||||
simulated INTEGER NOT NULL DEFAULT 0,
|
||||
payload TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS zones (
|
||||
id TEXT PRIMARY KEY,
|
||||
payload TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS climate_groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
payload TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schedules (
|
||||
id TEXT PRIMARY KEY,
|
||||
zone_id TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS schedules_zone_idx ON schedules(zone_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS automations (
|
||||
id TEXT PRIMARY KEY,
|
||||
payload TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS readings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
indoor_temperature REAL,
|
||||
outdoor_temperature REAL,
|
||||
target_temperature REAL NOT NULL,
|
||||
power INTEGER NOT NULL,
|
||||
source TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS readings_device_time_idx
|
||||
ON readings(device_id, timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS zone_readings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
zone_id TEXT NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
gree_temperature REAL,
|
||||
external_temperature REAL,
|
||||
control_temperature REAL,
|
||||
target_temperature REAL,
|
||||
device_setpoint REAL,
|
||||
outdoor_temperature REAL,
|
||||
power INTEGER NOT NULL,
|
||||
mode TEXT NOT NULL,
|
||||
fan_speed INTEGER NOT NULL,
|
||||
demand INTEGER NOT NULL,
|
||||
control_source TEXT NOT NULL,
|
||||
active_preset TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS zone_readings_zone_time_idx
|
||||
ON zone_readings(zone_id, timestamp DESC);
|
||||
CREATE INDEX IF NOT EXISTS zone_readings_time_idx
|
||||
ON zone_readings(timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ha_readings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entity_id TEXT NOT NULL,
|
||||
zone_id TEXT,
|
||||
kind TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
temperature REAL NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ha_readings_entity_time_idx
|
||||
ON ha_readings(entity_id, timestamp DESC);
|
||||
CREATE INDEX IF NOT EXISTS ha_readings_zone_time_idx
|
||||
ON ha_readings(zone_id, timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS event_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp TEXT NOT NULL,
|
||||
level TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
metadata TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS event_log_time_idx ON event_log(timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS api_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
token_prefix TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
||||
VALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|
||||
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
||||
VALUES (2, strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|
||||
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
||||
VALUES (3, strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|
||||
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
||||
VALUES (4, strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|
||||
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
||||
VALUES (5, strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|
||||
"#;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
pub const INSERT_ZONE_READING_IF_DUE: &str = r#"
|
||||
INSERT INTO zone_readings(
|
||||
zone_id, device_id, timestamp, gree_temperature, external_temperature,
|
||||
control_temperature, target_temperature, device_setpoint, outdoor_temperature,
|
||||
power, mode, fan_speed, demand, control_source, active_preset
|
||||
)
|
||||
SELECT ?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM zone_readings WHERE zone_id=?1 AND timestamp>=?16 LIMIT 1
|
||||
)
|
||||
"#;
|
||||
|
||||
|
||||
pub const LIST_ZONE_HISTORY_BY_ZONE_BUCKETED: &str = r#"
|
||||
SELECT MIN(id),zone_id,MAX(device_id),MIN(timestamp),
|
||||
AVG(gree_temperature),AVG(external_temperature),AVG(control_temperature),
|
||||
AVG(target_temperature),AVG(device_setpoint),AVG(outdoor_temperature),
|
||||
MAX(power),MAX(mode),CAST(ROUND(AVG(fan_speed)) AS INTEGER),MAX(demand),
|
||||
MAX(control_source),MAX(active_preset)
|
||||
FROM zone_readings
|
||||
WHERE zone_id=?1 AND timestamp>=?2
|
||||
GROUP BY zone_id, CAST(unixepoch(timestamp)/?3 AS INTEGER)
|
||||
ORDER BY MIN(timestamp) ASC
|
||||
LIMIT ?4
|
||||
"#;
|
||||
|
||||
pub const LIST_ZONE_HISTORY_ALL_BUCKETED: &str = r#"
|
||||
SELECT MIN(id),zone_id,MAX(device_id),MIN(timestamp),
|
||||
AVG(gree_temperature),AVG(external_temperature),AVG(control_temperature),
|
||||
AVG(target_temperature),AVG(device_setpoint),AVG(outdoor_temperature),
|
||||
MAX(power),MAX(mode),CAST(ROUND(AVG(fan_speed)) AS INTEGER),MAX(demand),
|
||||
MAX(control_source),MAX(active_preset)
|
||||
FROM zone_readings
|
||||
WHERE timestamp>=?1
|
||||
GROUP BY zone_id, CAST(unixepoch(timestamp)/?2 AS INTEGER)
|
||||
ORDER BY MIN(timestamp) ASC
|
||||
LIMIT ?3
|
||||
"#;
|
||||
|
||||
pub const DELETE_ZONE_READINGS_BY_ZONE_ID: &str = "DELETE FROM zone_readings WHERE zone_id=?1";
|
||||
pub const DELETE_ZONE_READINGS_BY_DEVICE_ID: &str = "DELETE FROM zone_readings WHERE device_id=?1";
|
||||
pub const LIST_ZONE_HISTORY_BEFORE: &str = r#"
|
||||
SELECT id,zone_id,device_id,timestamp,gree_temperature,external_temperature,control_temperature,
|
||||
target_temperature,device_setpoint,outdoor_temperature,power,mode,fan_speed,demand,control_source,active_preset
|
||||
FROM zone_readings WHERE timestamp < ?1 ORDER BY timestamp ASC LIMIT ?2
|
||||
"#;
|
||||
pub const DELETE_ZONE_READING_BY_ID: &str = "DELETE FROM zone_readings WHERE id=?1";
|
||||
|
||||
pub const PRUNE_ZONE_READINGS: &str = "DELETE FROM zone_readings WHERE timestamp < ?1";
|
||||
|
||||
Reference in New Issue
Block a user