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 final static 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
) {
45 ).formatted(TABLE_KEY_VALUE
);
46 try (final var connection
= database
.getConnection()) {
47 try (final var statement
= connection
.prepareStatement(sql
)) {
48 statement
.setString(1, key
.key());
50 final var result
= Utils
.executeQueryForOptional(statement
,
51 resultSet
-> readValueFromResultSet(key
, resultSet
)).orElse(null);
54 return key
.defaultValue();
58 } catch (SQLException e
) {
59 throw new RuntimeException("Failed read from pre_key store", e
);
63 public <T
> void storeEntry(KeyValueEntry
<T
> key
, T value
) {
66 INSERT INTO %s (key, value)
68 ON CONFLICT (key) DO UPDATE SET value=excluded.value
70 ).formatted(TABLE_KEY_VALUE
);
71 try (final var connection
= database
.getConnection()) {
72 try (final var statement
= connection
.prepareStatement(sql
)) {
73 statement
.setString(1, key
.key());
74 setParameterValue(statement
, 2, key
.clazz(), value
);
75 statement
.executeUpdate();
77 } catch (SQLException e
) {
78 throw new RuntimeException("Failed update key_value store", e
);
82 @SuppressWarnings("unchecked")
83 private static <T
> T
readValueFromResultSet(
84 final KeyValueEntry
<T
> key
, final ResultSet resultSet
85 ) throws SQLException
{
87 final var clazz
= key
.clazz();
88 if (clazz
== int.class || clazz
== Integer
.class) {
89 value
= resultSet
.getInt("value");
90 } else if (clazz
== long.class || clazz
== Long
.class) {
91 value
= resultSet
.getLong("value");
92 } else if (clazz
== boolean.class || clazz
== Boolean
.class) {
93 value
= resultSet
.getBoolean("value");
94 } else if (clazz
== byte[].class || clazz
== Byte
[].class) {
95 value
= resultSet
.getBytes("value");
96 } else if (clazz
== String
.class) {
97 value
= resultSet
.getString("value");
98 } else if (Enum
.class.isAssignableFrom(clazz
)) {
99 final var name
= resultSet
.getString("value");
104 value
= Enum
.valueOf((Class
<Enum
>) key
.clazz(), name
);
105 } catch (IllegalArgumentException e
) {
106 logger
.debug("Read invalid enum value from store, ignoring: {} for {}", name
, key
.clazz());
111 throw new AssertionError("Invalid key type " + clazz
.getSimpleName());
113 if (resultSet
.wasNull()) {
119 private static <T
> void setParameterValue(
120 final PreparedStatement statement
, final int parameterIndex
, final Class
<T
> clazz
, final T value
121 ) throws SQLException
{
122 if (clazz
== int.class || clazz
== Integer
.class) {
124 statement
.setNull(parameterIndex
, Types
.INTEGER
);
126 statement
.setInt(parameterIndex
, (int) value
);
128 } else if (clazz
== long.class || clazz
== Long
.class) {
130 statement
.setNull(parameterIndex
, Types
.INTEGER
);
132 statement
.setLong(parameterIndex
, (long) value
);
134 } else if (clazz
== boolean.class || clazz
== Boolean
.class) {
136 statement
.setNull(parameterIndex
, Types
.BOOLEAN
);
138 statement
.setBoolean(parameterIndex
, (boolean) value
);
140 } else if (clazz
== byte[].class || clazz
== Byte
[].class) {
142 statement
.setNull(parameterIndex
, Types
.BLOB
);
144 statement
.setBytes(parameterIndex
, (byte[]) value
);
146 } else if (clazz
== String
.class) {
148 statement
.setNull(parameterIndex
, Types
.VARCHAR
);
150 statement
.setString(parameterIndex
, (String
) value
);
152 } else if (Enum
.class.isAssignableFrom(clazz
)) {
154 statement
.setNull(parameterIndex
, Types
.VARCHAR
);
156 statement
.setString(parameterIndex
, ((Enum
<?
>) value
).name());
159 throw new AssertionError("Invalid key type " + clazz
.getSimpleName());