fix(agent): preserve JDBC decimal precision
This commit is contained in:
parent
710eb3d008
commit
b6274858fe
|
|
@ -1,15 +1,20 @@
|
|||
package com.dbx.agent;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Connection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
@ -18,7 +23,17 @@ public final class JsonRpcServer {
|
|||
private static final long CONNECTION_VALIDATION_INTERVAL_MILLIS = 5_000L;
|
||||
|
||||
private final DatabaseAgent agent;
|
||||
private final Gson gson = new Gson();
|
||||
private final Gson gson = new GsonBuilder()
|
||||
// JDBC DECIMAL/NUMERIC values can exceed JavaScript Number precision after JSON-RPC parsing.
|
||||
.registerTypeAdapter(
|
||||
BigDecimal.class,
|
||||
(JsonSerializer<BigDecimal>) (value, type, context) -> new JsonPrimitive(value.toPlainString())
|
||||
)
|
||||
.registerTypeAdapter(
|
||||
BigInteger.class,
|
||||
(JsonSerializer<BigInteger>) (value, type, context) -> new JsonPrimitive(value.toString())
|
||||
)
|
||||
.create();
|
||||
private ConnectParams lastConnectParams;
|
||||
private long lastConnectionValidationTimeMillis;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import java.io.InputStreamReader;
|
|||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -70,6 +72,28 @@ class CommonJavaCompatibilityTest {
|
|||
assertTrue(containsCapability(result.getAsJsonArray("capabilities"), "metadata"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonRpcServerSerializesArbitraryPrecisionNumbersAsStrings() {
|
||||
JsonRpcServer server = new JsonRpcServer(new PreciseNumberAgent());
|
||||
|
||||
String response = server.handleRequest(
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":8,\"method\":\"" + AgentProtocol.METHOD_EXECUTE_QUERY + "\",\"params\":{\"sql\":\"select n from t\"}}"
|
||||
);
|
||||
|
||||
JsonArray row = JsonParser.parseString(response)
|
||||
.getAsJsonObject()
|
||||
.getAsJsonObject("result")
|
||||
.getAsJsonArray("rows")
|
||||
.get(0)
|
||||
.getAsJsonArray();
|
||||
assertEquals("12345678901234567890.1234", row.get(0).getAsString());
|
||||
assertTrue(row.get(0).getAsJsonPrimitive().isString());
|
||||
assertEquals("12345678901234567890", row.get(1).getAsString());
|
||||
assertTrue(row.get(1).getAsJsonPrimitive().isString());
|
||||
assertEquals(42, row.get(2).getAsInt());
|
||||
assertTrue(row.get(2).getAsJsonPrimitive().isNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonRpcServerReconnectsWhenStoredJdbcConnectionIsStale() {
|
||||
ReconnectingAgent agent = new ReconnectingAgent();
|
||||
|
|
@ -370,6 +394,22 @@ class CommonJavaCompatibilityTest {
|
|||
}
|
||||
}
|
||||
|
||||
private static final class PreciseNumberAgent extends MinimalAgent {
|
||||
@Override
|
||||
public QueryResult executeQuery(String sql, String schema, ExecuteQueryOptions options) {
|
||||
return new QueryResult(
|
||||
Arrays.asList("decimal_value", "integer_value", "safe_int"),
|
||||
Collections.singletonList(Arrays.asList(
|
||||
new BigDecimal("12345678901234567890.1234"),
|
||||
new BigInteger("12345678901234567890"),
|
||||
42
|
||||
)),
|
||||
0L,
|
||||
0L
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ReconnectingAgent extends MinimalAgent {
|
||||
private int connectCount;
|
||||
private int disconnectCount;
|
||||
|
|
|
|||
|
|
@ -1029,6 +1029,10 @@ pub fn format_grid_sql_literal(
|
|||
return format_pg_array_sql_literal(arr);
|
||||
}
|
||||
let text = value.as_str().map_or_else(|| value.to_string(), ToString::to_string);
|
||||
if column_info.map(|column| is_numeric_type(&column.data_type)).unwrap_or(false) && is_numeric_literal(&text) {
|
||||
// BigDecimal/BigInteger cells cross JSON-RPC as strings so browsers cannot round them.
|
||||
return text;
|
||||
}
|
||||
if database_type == Some(DatabaseType::ManticoreSearch) {
|
||||
if let Some(typed_value) = manticore_typed_attribute_value(&text, column_info) {
|
||||
return format_grid_sql_literal(&typed_value, database_type, column_info);
|
||||
|
|
@ -2312,6 +2316,25 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_numeric_string_literals_for_numeric_columns_without_quotes() {
|
||||
let number = column("amount", "NUMBER(20,0)", true, None);
|
||||
let text = column("code", "VARCHAR2(32)", true, None);
|
||||
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("12345678901234567890"), Some(DatabaseType::Oracle), Some(&number)),
|
||||
"12345678901234567890"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("12345678901234567890"), Some(DatabaseType::Oracle), Some(&text)),
|
||||
"'12345678901234567890'"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("123-not-a-number"), Some(DatabaseType::Oracle), Some(&number)),
|
||||
"'123-not-a-number'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepares_oracle_timestamp_insert_from_iso_grid_value() {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
|
|
|
|||
Loading…
Reference in New Issue