Skip to content

Commit fbcff00

Browse files
committed
Preserve legacy SQL write compatibility
1 parent 0bbe368 commit fbcff00

5 files changed

Lines changed: 70 additions & 6 deletions

File tree

‎AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorage.java‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.sql.ResultSetMetaData;
77
import java.sql.SQLException;
88
import java.sql.SQLDataException;
9+
import java.sql.Types;
910
import java.util.ArrayList;
1011
import java.util.HashMap;
1112
import java.util.LinkedHashMap;
@@ -244,7 +245,13 @@ private DataValue readValue(ResultSet result, int index, SqlUserSchema.ColumnDef
244245
private void bind(PreparedStatement statement, int index, DataValue value, SqlUserSchema.ColumnDefinition definition) throws SQLException {
245246
if (value == null) statement.setObject(index, null);
246247
else if (value.isString()) statement.setString(index, value.getString());
247-
else if (value.isInt()) statement.setInt(index, value.getInt());
248+
else if (value.isInt()) {
249+
// PostgreSQL's unspecified parameter type is inferred from the target
250+
// column. This preserves writes to both current integer columns and
251+
// legacy text columns retained by schema discovery.
252+
if (dialect == Dialect.POSTGRESQL) statement.setObject(index, Integer.toString(value.getInt()), Types.OTHER);
253+
else statement.setInt(index, value.getInt());
254+
}
248255
else if (value.isBoolean()) {
249256
BooleanStorage booleanStorage = booleanStorage(definition);
250257
if (booleanStorage == BooleanStorage.NATIVE) statement.setBoolean(index, value.getBoolean());

‎AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackend.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public final class MysqlUserBackend implements SqlUserBackend {
3030
private final SqlBackendLogger logger;
3131
private final HeadlessUserTable table;
3232
private final AtomicBoolean open = new AtomicBoolean(true);
33+
private final AtomicBoolean invalidUuidWarningLogged = new AtomicBoolean();
3334
private final ReentrantReadWriteLock operations = new ReentrantReadWriteLock(true);
3435
private volatile boolean tableClosed;
3536

@@ -103,7 +104,12 @@ private List<UserPageEntry> readUserPage(String cursor) {
103104
if (value == null) continue;
104105
UUID parsed = null;
105106
try { parsed = UUID.fromString(value); }
106-
catch (IllegalArgumentException invalid) { logger.warn("Skipping invalid UUID in " + table.getTableName() + ": " + value, invalid); }
107+
catch (IllegalArgumentException invalid) {
108+
if (invalidUuidWarningLogged.compareAndSet(false, true)) {
109+
logger.warn("Skipping malformed UUID entries while enumerating SQL users; further diagnostics suppressed",
110+
new IllegalArgumentException("Malformed SQL UUID value"));
111+
}
112+
}
107113
page.add(new UserPageEntry(value, parsed));
108114
}
109115
}

‎AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcPostgresBitBooleanTest.java‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.sql.PreparedStatement;
1313
import java.sql.ResultSet;
1414
import java.sql.ResultSetMetaData;
15+
import java.sql.Types;
1516
import java.util.ArrayList;
1617
import java.util.List;
1718
import java.util.UUID;
@@ -21,10 +22,31 @@
2122
import com.bencodez.advancedcore.api.user.UserStorage;
2223
import com.bencodez.simpleapi.sql.DataType;
2324
import com.bencodez.simpleapi.sql.data.DataValueBoolean;
25+
import com.bencodez.simpleapi.sql.data.DataValueInt;
2426

2527
class JdbcPostgresBitBooleanTest {
2628
private static final UUID USER = UUID.fromString("51829cd0-c37c-45bf-9910-57914800e0a1");
2729

30+
@Test
31+
void postgresIntegerBindingIsTargetTypedForLegacyTextColumns() throws Exception {
32+
Connection connection = mock(Connection.class);
33+
when(connection.getAutoCommit()).thenReturn(true);
34+
PreparedStatement exists = mock(PreparedStatement.class);
35+
PreparedStatement insert = mock(PreparedStatement.class);
36+
ResultSet missing = mock(ResultSet.class);
37+
when(exists.executeQuery()).thenReturn(missing);
38+
when(connection.prepareStatement(anyString())).thenReturn(exists, insert);
39+
when(insert.executeUpdate()).thenReturn(1);
40+
SqlUserSchema schema = SqlUserSchema.builder().column("Points", "INT DEFAULT '0'", DataType.INTEGER).build();
41+
JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema,
42+
() -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP);
43+
44+
storage.write(UserStorage.MYSQL, "Points", new DataValueInt(17));
45+
46+
verify(insert).setObject(2, "17", Types.OTHER);
47+
verify(insert, never()).setInt(2, 17);
48+
}
49+
2850
@Test
2951
void postgresBitUsesExplicitBitCastAndStringBindingOnInsert() throws Exception {
3052
Connection connection = mock(Connection.class);

‎AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.sql.PreparedStatement;
1818
import java.sql.ResultSet;
1919
import java.sql.SQLException;
20+
import java.sql.Types;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.HashMap;
@@ -132,7 +133,9 @@ class JdbcSqlUserStorageWriteOutcomeTest {
132133
assertEquals(dialect == JdbcSqlUserStorage.Dialect.SQLITE ? 1 : 2, jdbc.sql.size());
133134
assertTrue(jdbc.sql.stream().noneMatch(sql -> sql.startsWith("UPDATE") || sql.contains("SET " + dialect.quote("uuid"))));
134135
if (dialect == JdbcSqlUserStorage.Dialect.POSTGRESQL) verify(jdbc.insert).setObject(1, USER); else verify(jdbc.insert).setString(1, USER.toString());
135-
verify(jdbc.insert).setInt(2, 17); verify(jdbc.connection).commit();
136+
if (dialect == JdbcSqlUserStorage.Dialect.POSTGRESQL) verify(jdbc.insert).setObject(2, "17", Types.OTHER);
137+
else verify(jdbc.insert).setInt(2, 17);
138+
verify(jdbc.connection).commit();
136139
}
137140
}
138141

@@ -145,7 +148,7 @@ class JdbcSqlUserStorageWriteOutcomeTest {
145148
HashMap<String, DataValue> values = new LinkedHashMap<>(); values.put("A", new DataValueInt(2)); values.put("B", new DataValueInt(2));
146149
user.writeValues(UserStorage.MYSQL, values);
147150
assertEquals(List.of("SELECT 1 FROM \"Users\" WHERE \"uuid\"=? LIMIT 1 FOR UPDATE", "UPDATE \"Users\" SET \"A\"=?, \"B\"=? WHERE \"uuid\"=?"), sql);
148-
verify(statement).setInt(1, 2); verify(statement).setInt(2, 2); verify(statement).setObject(3, USER);
151+
verify(statement).setObject(1, "2", Types.OTHER); verify(statement).setObject(2, "2", Types.OTHER); verify(statement).setObject(3, USER);
149152
}
150153

151154
@Test void uuidOnlyBulkIsNoOpButExplicitIdentityMutationIsRejected() throws Exception {
@@ -160,7 +163,7 @@ class JdbcSqlUserStorageWriteOutcomeTest {
160163
@Test void postgresPartialUpdateDoesNotReinsertAnExistingRequiredColumnRow() throws Exception {
161164
Jdbc jdbc = new Jdbc(); ResultSet row = mock(ResultSet.class); when(row.next()).thenReturn(true); when(jdbc.update.executeQuery()).thenReturn(row);
162165
jdbc.user(UserStorage.MYSQL, JdbcSqlUserStorage.Dialect.POSTGRESQL).write(UserStorage.MYSQL, "Points", new DataValueInt(23));
163-
assertTrue(jdbc.sql.stream().noneMatch(sql -> sql.startsWith("INSERT"))); verify(jdbc.update).setInt(1, 23); verify(jdbc.connection).commit(); verify(row).close();
166+
assertTrue(jdbc.sql.stream().noneMatch(sql -> sql.startsWith("INSERT"))); verify(jdbc.update).setObject(1, "23", Types.OTHER); verify(jdbc.connection).commit(); verify(row).close();
164167
}
165168

166169
private static final class Jdbc {

‎AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBackendReviewRegressionTest.java‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.ArrayList;
1313
import java.util.List;
1414
import java.util.Locale;
15+
import java.util.concurrent.atomic.AtomicInteger;
1516

1617
import org.junit.jupiter.api.Test;
1718
import org.mockito.MockedConstruction;
@@ -23,6 +24,22 @@
2324

2425
/** Exercises real backend/SimpleAPI construction with mocked JDBC, not a live database. */
2526
class MysqlBackendReviewRegressionTest {
27+
@Test void malformedUuidDiagnosticsAreBoundedAndValueFree() throws Exception {
28+
Fixture fixture = new Fixture(DbType.MYSQL, "Points");
29+
fixture.enumerationRows = List.of("private-one", "private-two");
30+
List<String> warnings = new ArrayList<>();
31+
SqlBackendLogger logger = new SqlBackendLogger() {
32+
@Override public void info(String message) { }
33+
@Override public void warn(String message, Throwable error) {
34+
warnings.add(message + ":" + error.getMessage());
35+
}
36+
};
37+
try (var managers = fixture.managers(); var backend = fixture.open(logger)) {
38+
assertTrue(backend.enumerateUsers().isEmpty());
39+
}
40+
assertEquals(List.of("Skipping malformed UUID entries while enumerating SQL users; further diagnostics suppressed:Malformed SQL UUID value"), warnings);
41+
}
42+
2643
@Test void postgresRenamesCaseOnlyHistoricalColumnInsteadOfCreatingAParallelColumn() throws Exception {
2744
Fixture fixture = new Fixture(DbType.POSTGRESQL, "points");
2845
try (var managers = fixture.managers()) {
@@ -104,6 +121,7 @@ private static final class Fixture {
104121
int uuidInspections, migrations;
105122
boolean competitorConverts;
106123
SQLException migrationFailure, reinspectionFailure;
124+
List<String> enumerationRows = List.of();
107125

108126
Fixture(DbType type, String storedColumn) {
109127
this.type = type;
@@ -120,14 +138,18 @@ MockedConstruction<ConnectionManager> managers() {
120138
}
121139

122140
MysqlUserBackend open() {
141+
return open(SqlBackendLogger.NO_OP);
142+
}
143+
144+
MysqlUserBackend open(SqlBackendLogger logger) {
123145
MysqlConfig config = new MysqlConfig();
124146
config.setDbType(type);
125147
config.setDatabase("test_database");
126148
config.setTablePrefix("");
127149
config.setTableName("Users");
128150
config.setMaxThreads(1);
129151
return new MysqlUserBackend("Users", config, SqlUserSchema.builder()
130-
.column("Points", "INT DEFAULT '0'", DataType.INTEGER).build(), SqlBackendLogger.NO_OP);
152+
.column("Points", "INT DEFAULT '0'", DataType.INTEGER).build(), logger);
131153
}
132154

133155
Connection connection() throws SQLException {
@@ -166,6 +188,10 @@ Connection connection() throws SQLException {
166188
when(metadata.getColumnCount()).thenReturn(2);
167189
when(metadata.getColumnName(1)).thenReturn("uuid");
168190
when(metadata.getColumnName(2)).thenReturn(storedColumn);
191+
} else if (sql.startsWith("SELECT `uuid` FROM")) {
192+
AtomicInteger row = new AtomicInteger();
193+
when(result.next()).thenAnswer(invocation -> row.get() < enumerationRows.size());
194+
when(result.getString(1)).thenAnswer(invocation -> enumerationRows.get(row.getAndIncrement()));
169195
}
170196
return result;
171197
});

0 commit comments

Comments
 (0)