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 private static <T
> T
readValueFromResultSet(
83 final KeyValueEntry
<T
> key
, final ResultSet resultSet
84 ) throws SQLException
{
86 final var clazz
= key
.clazz();
87 if (clazz
== int.class || clazz
== Integer
.class) {
88 value
= resultSet
.getInt("value");
89 } else if (clazz
== long.class || clazz
== Long
.class) {
90 value
= resultSet
.getLong("value");
91 } else if (clazz
== boolean.class || clazz
== Boolean
.class) {
92 value
= resultSet
.getBoolean("value");
93 } else if (clazz
== String
.class) {
94 value
= resultSet
.getString("value");
95 } else if (Enum
.class.isAssignableFrom(clazz
)) {
96 final var name
= resultSet
.getString("value");
101 value
= Enum
.valueOf((Class
<Enum
>) key
.clazz(), name
);
102 } catch (IllegalArgumentException e
) {
103 logger
.debug("Read invalid enum value from store, ignoring: {} for {}", name
, key
.clazz());
108 throw new AssertionError("Invalid key type " + clazz
.getSimpleName());
110 if (resultSet
.wasNull()) {
116 private static <T
> void setParameterValue(
117 final PreparedStatement statement
, final int parameterIndex
, final Class
<T
> clazz
, final T value
118 ) throws SQLException
{
119 if (clazz
== int.class || clazz
== Integer
.class) {
121 statement
.setNull(parameterIndex
, Types
.INTEGER
);
123 statement
.setInt(parameterIndex
, (int) value
);
125 } else if (clazz
== long.class || clazz
== Long
.class) {
127 statement
.setNull(parameterIndex
, Types
.INTEGER
);
129 statement
.setLong(parameterIndex
, (long) value
);
131 } else if (clazz
== boolean.class || clazz
== Boolean
.class) {
133 statement
.setNull(parameterIndex
, Types
.BOOLEAN
);
135 statement
.setBoolean(parameterIndex
, (boolean) value
);
137 } else if (clazz
== String
.class) {
139 statement
.setNull(parameterIndex
, Types
.VARCHAR
);
141 statement
.setString(parameterIndex
, (String
) value
);
143 } else if (Enum
.class.isAssignableFrom(clazz
)) {
145 statement
.setNull(parameterIndex
, Types
.VARCHAR
);
147 statement
.setString(parameterIndex
, ((Enum
<?
>) value
).name());
150 throw new AssertionError("Invalid key type " + clazz
.getSimpleName());