diff --git a/mysql-dual-conn/init.sql b/mysql-dual-conn/init.sql index d1c2a791..9e79bb18 100644 --- a/mysql-dual-conn/init.sql +++ b/mysql-dual-conn/init.sql @@ -8,3 +8,33 @@ CREATE USER IF NOT EXISTS 'stagebuster'@'%' IDENTIFIED BY 'camundaPassword'; GRANT ALL PRIVILEGES ON camunda.* TO 'stagebuster'@'%'; FLUSH PRIVILEGES; + +-- Column-type fidelity fixture (keploy/keploy#4426). +-- +-- The OMS datasource runs with useServerPrepStmts=true, so a SELECT over +-- this table comes back as a binary-protocol result set — the wire format +-- whose FLOAT/DOUBLE columns keploy decoded as their raw IEEE-754 bit +-- pattern rather than their value, corrupting mocks.yaml at record time. +-- +-- The BIGINT UNSIGNED column covers the other half: values above MaxInt64 +-- have no lossless float64 form, so a mock format that routes them through +-- one collapses distinct rows onto the same number. +USE myntra_oms; + +CREATE TABLE IF NOT EXISTS numeric_fidelity ( + id INT PRIMARY KEY, + label VARCHAR(32) NOT NULL, + price_f FLOAT NOT NULL, + ratio_d DOUBLE NOT NULL, + big_u BIGINT UNSIGNED NOT NULL +); + +INSERT INTO numeric_fidelity (id, label, price_f, ratio_d, big_u) VALUES + -- 9.99 is the value from the bug report: read as a numeric cast it + -- surfaces as 1.0926057e+09 (FLOAT) / 4.621813488089437e+18 (DOUBLE). + (1, 'nine-ninety-nine', 9.99, 9.99, 18446744073709551615), + (2, 'negative', -0.5, -1234.5678, 9223372036854775808), + (3, 'zero', 0, 0, 0), + (4, 'whole', 10, 10, 4294967296), + (5, 'small', 1.5, 2.2250738585072014e-308, 1) +ON DUPLICATE KEY UPDATE label = VALUES(label); diff --git a/mysql-dual-conn/src/main/java/com/example/mysqlreplicate/QueryController.java b/mysql-dual-conn/src/main/java/com/example/mysqlreplicate/QueryController.java index 8802d0b2..d57cfdb6 100644 --- a/mysql-dual-conn/src/main/java/com/example/mysqlreplicate/QueryController.java +++ b/mysql-dual-conn/src/main/java/com/example/mysqlreplicate/QueryController.java @@ -72,6 +72,51 @@ public List> queryCamunda() { * cascade into "Connection closing due to no matching mock found" and * tear down the TCP connection. */ + /** + * Selects FLOAT / DOUBLE / BIGINT UNSIGNED columns over the OMS + * datasource, which runs with useServerPrepStmts=true. + * + * The {@code id >= ?} predicate is load-bearing, not filler: without + * a bound parameter JdbcTemplate issues a plain Statement, which + * Connector/J sends as COM_QUERY and MySQL answers with a *text* + * result set — every value a length-encoded string, which is not the + * code path this fixture exists to cover. The parameter forces a + * server-side prepared statement, so the rows come back as a + * binary-protocol result set carrying raw IEEE-754 bytes. + * + * This is the read path for keploy/keploy#4426: keploy decoded the + * FLOAT and DOUBLE wire bytes as a numeric cast instead of an + * IEEE-754 reinterpret, so a column holding 9.99 was recorded as + * 1.0926057e+09 / 4.621813488089437e+18. The corruption happened at + * record time, so it survived re-recording and replay asserted + * against a value the database never returned. + * + * big_u covers the neighbouring defect: a BIGINT UNSIGNED above + * MaxInt64 has no lossless float64 form, so any mock format that + * routes it through one collapses distinct rows onto one number. + */ + @GetMapping("/api/oms/numerics") + public List> numerics() { + return omsJdbc.queryForList( + "SELECT id, label, price_f, ratio_d, big_u FROM numeric_fidelity " + + "WHERE id >= ? ORDER BY id", + 0); + } + + /** + * Binds a FLOAT parameter, exercising the COM_STMT_EXECUTE decode + * path rather than the result-set one. The bound value is a float32 + * on the wire and comes back out of the mock file as a float64, so + * keploy's parameter matcher has to compare the two at float32 + * precision — widening instead means a correctly recorded FLOAT + * parameter never matches itself and replay finds no mock. + */ + @GetMapping("/api/oms/float-param/{v}") + public List> floatParam(@PathVariable("v") float v) { + return omsJdbc.queryForList( + "SELECT id, label, price_f FROM numeric_fidelity WHERE price_f = ? ORDER BY id", v); + } + @GetMapping("/api/oms/stmt-reset/{n}") public List stmtReset(@PathVariable("n") int n) { return omsJdbc.execute((java.sql.Connection conn) -> {