1 package org
.asamk
.signal
.manager
.storage
.keyValue
;
3 import org
.asamk
.signal
.manager
.storage
.Database
;
4 import org
.asamk
.signal
.manager
.storage
.Utils
;
5 import org
.slf4j
.Logger
;
6 import org
.slf4j
.LoggerFactory
;
8 import java
.sql
.Connection
;
9 import java
.sql
.PreparedStatement
;
10 import java
.sql
.ResultSet
;
11 import java
.sql
.SQLException
;
12 import java
.sql
.Types
;
14 public class KeyValueStore
{
16 private static final String TABLE_KEY_VALUE
= "key_value";
17 private static final Logger logger
= LoggerFactory
.getLogger(KeyValueStore
.class);
19 private final Database database
;
21 public static void createSql(Connection connection
) throws SQLException
{
22 // When modifying the CREATE statement here, also add a migration in AccountDatabase.java
23 try (final var statement
= connection
.createStatement()) {
24 statement
.executeUpdate("""
25 CREATE TABLE key_value (
26 _id INTEGER PRIMARY KEY,
27 key TEXT UNIQUE NOT NULL,
34 public KeyValueStore(final Database database
) {
35 this.database
= database
;
38 public <T
> T
getEntry(KeyValueEntry
<T
> key
) {
39 try (final var connection
= database
.getConnection()) {
40 return getEntry(connection
, key
);
41 } catch (SQLException e
) {
42 throw new RuntimeException("Failed read from pre_key store", e
);
46 public <T
> void storeEntry(KeyValueEntry
<T
> key
, T value
) {
47 try (final var connection
= database
.getConnection()) {
48 storeEntry(connection
, key
, value
);
49 } catch (SQLException e
) {
50 throw new RuntimeException("Failed update key_value store", e
);
54 private <T
> T
getEntry(final Connection connection
, final KeyValueEntry
<T
> key
) throws SQLException
{
61 ).formatted(TABLE_KEY_VALUE
);
62 try (final var statement
= connection
.prepareStatement(sql
)) {
63 statement
.setString(1, key
.key());
65 final var result
= Utils
.executeQueryForOptional(statement
,
66 resultSet
-> readValueFromResultSet(key
, resultSet
)).orElse(null);
69 return key
.defaultValue();
75 private <T
> void storeEntry(
76 final Connection connection
, final KeyValueEntry
<T
> key
, final T value
77 ) throws SQLException
{
80 INSERT INTO %s (key, value)
82 ON CONFLICT (key) DO UPDATE SET value=excluded.value
84 ).formatted(TABLE_KEY_VALUE
);
85 try (final var statement
= connection
.prepareStatement(sql
)) {
86 statement
.setString(1, key
.key());
87 setParameterValue(statement
, 2, key
.clazz(), value
);
88 statement
.executeUpdate();
92 @SuppressWarnings("unchecked")
93 private static <T
> T
readValueFromResultSet(
94 final KeyValueEntry
<T
> key
, final ResultSet resultSet
95 ) throws SQLException
{
97 final var clazz
= key
.clazz();
98 if (clazz
== int.class || clazz
== Integer
.class) {
99 value
= resultSet
.getInt("value");
100 } else if (clazz
== long.class || clazz
== Long
.class) {
101 value
= resultSet
.getLong("value");
102 } else if (clazz
== boolean.class || clazz
== Boolean
.class) {
103 value
= resultSet
.getBoolean("value");
104 } else if (clazz
== byte[].class || clazz
== Byte
[].class) {
105 value
= resultSet
.getBytes("value");
106 } else if (clazz
== String
.class) {
107 value
= resultSet
.getString("value");
108 } else if (Enum
.class.isAssignableFrom(clazz
)) {
109 final var name
= resultSet
.getString("value");
114 value
= Enum
.valueOf((Class
<Enum
>) key
.clazz(), name
);
115 } catch (IllegalArgumentException e
) {
116 logger
.debug("Read invalid enum value from store, ignoring: {} for {}", name
, key
.clazz());
121 throw new AssertionError("Invalid key type " + clazz
.getSimpleName());
123 if (resultSet
.wasNull()) {
129 private static <T
> void setParameterValue(
130 final PreparedStatement statement
, final int parameterIndex
, final Class
<T
> clazz
, final T value
131 ) throws SQLException
{
132 if (clazz
== int.class || clazz
== Integer
.class) {
134 statement
.setNull(parameterIndex
, Types
.INTEGER
);
136 statement
.setInt(parameterIndex
, (int) value
);
138 } else if (clazz
== long.class || clazz
== Long
.class) {
140 statement
.setNull(parameterIndex
, Types
.INTEGER
);
142 statement
.setLong(parameterIndex
, (long) value
);
144 } else if (clazz
== boolean.class || clazz
== Boolean
.class) {
146 statement
.setNull(parameterIndex
, Types
.BOOLEAN
);
148 statement
.setBoolean(parameterIndex
, (boolean) value
);
150 } else if (clazz
== byte[].class || clazz
== Byte
[].class) {
152 statement
.setNull(parameterIndex
, Types
.BLOB
);
154 statement
.setBytes(parameterIndex
, (byte[]) value
);
156 } else if (clazz
== String
.class) {
158 statement
.setNull(parameterIndex
, Types
.VARCHAR
);
160 statement
.setString(parameterIndex
, (String
) value
);
162 } else if (Enum
.class.isAssignableFrom(clazz
)) {
164 statement
.setNull(parameterIndex
, Types
.VARCHAR
);
166 statement
.setString(parameterIndex
, ((Enum
<?
>) value
).name());
169 throw new AssertionError("Invalid key type " + clazz
.getSimpleName());