Skip to content

Commit 3849dec

Browse files
authored
Merge pull request #5984 from sysown/test/coverage-low-lib-files
test: add unit tests for low-coverage lib files
2 parents 90d4cc1 + 8b2f652 commit 3849dec

30 files changed

Lines changed: 1740 additions & 382 deletions

lib/MySQLFFTO.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ void MySQLFFTO::report_query_stats(const std::string& query, unsigned long long
252252
if (query.empty() || !GloMyQPro || !m_session) return;
253253
if (!m_session->client_myds || !m_session->client_myds->myconn || !m_session->client_myds->myconn->userinfo) return;
254254
auto* ui = m_session->client_myds->myconn->userinfo;
255-
if (!ui->username || !ui->schemaname) return;
255+
if (!ui->username) return;
256+
char empty_schema[] = "";
257+
char* schemaname = ui->schemaname ? ui->schemaname : empty_schema;
256258

257259
options opts;
258260
opts.lowercase = mysql_thread___query_digests_lowercase;
@@ -276,7 +278,7 @@ void MySQLFFTO::report_query_stats(const std::string& query, unsigned long long
276278
uint64_t hash2; SpookyHash myhash; myhash.Init(19, 3);
277279
myhash.Update(ui->username, strlen(ui->username));
278280
myhash.Update(&qp.digest, sizeof(qp.digest));
279-
myhash.Update(ui->schemaname, strlen(ui->schemaname));
281+
myhash.Update(schemaname, strlen(schemaname));
280282
myhash.Update(&m_session->current_hostgroup, sizeof(m_session->current_hostgroup));
281283
myhash.Update(ca, strlen(ca));
282284
myhash.Final(&qp.digest_total, &hash2);
@@ -297,7 +299,9 @@ void MySQLFFTO::report_error(const unsigned char* data, size_t len) {
297299
if (!mysql_parse_err_packet(data, len, &err_no, &errmsg, &errmsg_len)) return;
298300

299301
auto* ui = m_session->client_myds->myconn->userinfo;
300-
if (!ui->username || !ui->schemaname) return;
302+
if (!ui->username) return;
303+
/* schemaname may be unset before COM_INIT_DB; still record the error */
304+
char* schemaname = ui->schemaname ? ui->schemaname : (char*)"";
301305

302306
// Build a null-terminated copy of the error message
303307
std::string msg(errmsg ? errmsg : "", errmsg_len);
@@ -322,7 +326,7 @@ void MySQLFFTO::report_error(const unsigned char* data, size_t len) {
322326

323327
MyHGM->add_mysql_errors(
324328
hostgroup, hostname, port,
325-
ui->username, client_addr, ui->schemaname,
329+
ui->username, client_addr, schemaname,
326330
err_no, (char*)msg.c_str()
327331
);
328332
}

lib/PgSQLFFTO.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,9 @@ void PgSQLFFTO::report_query_stats(const std::string& query, unsigned long long
279279
if (query.empty() || !GloPgQPro || !m_session) return;
280280
if (!m_session->client_myds || !m_session->client_myds->myconn || !m_session->client_myds->myconn->userinfo) return;
281281
auto* ui = m_session->client_myds->myconn->userinfo;
282-
if (!ui->username || !ui->schemaname) return;
282+
if (!ui->username) return;
283+
char empty_schema[] = "";
284+
char* schemaname = ui->schemaname ? ui->schemaname : empty_schema;
283285

284286
options opts;
285287
opts.lowercase = pgsql_thread___query_digests_lowercase;
@@ -303,7 +305,7 @@ void PgSQLFFTO::report_query_stats(const std::string& query, unsigned long long
303305
uint64_t hash2; SpookyHash myhash; myhash.Init(19, 3);
304306
myhash.Update(ui->username, strlen(ui->username));
305307
myhash.Update(&qp.digest, sizeof(qp.digest));
306-
myhash.Update(ui->schemaname, strlen(ui->schemaname));
308+
myhash.Update(schemaname, strlen(schemaname));
307309
myhash.Update(&m_session->current_hostgroup, sizeof(m_session->current_hostgroup));
308310
myhash.Update(ca, strlen(ca));
309311
myhash.Final(&qp.digest_total, &hash2);
@@ -322,7 +324,9 @@ void PgSQLFFTO::report_error(const unsigned char* payload, size_t len) {
322324
if (!err.parsed) return;
323325

324326
auto* ui = m_session->client_myds->myconn->userinfo;
325-
if (!ui->username || !ui->schemaname) return;
327+
if (!ui->username) return;
328+
/* database/schemaname may be unset early in the session; still record */
329+
char* schemaname = ui->schemaname ? ui->schemaname : (char*)"";
326330

327331
// Build a null-terminated copy of the error message
328332
std::string msg(err.message ? err.message : "", err.message_len);
@@ -348,7 +352,7 @@ void PgSQLFFTO::report_error(const unsigned char* payload, size_t len) {
348352
// ui->schemaname and ui->dbname are the same field (union in PgSQL_Connection_userinfo)
349353
PgHGM->add_pgsql_errors(
350354
hostgroup, hostname, port,
351-
ui->username, client_addr, ui->schemaname,
355+
ui->username, client_addr, schemaname,
352356
err.sqlstate, msg.c_str()
353357
);
354358
}

test/infra/control/run-tests-isolated.bash

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ COVERAGE_REPORT_DIR="${WORKSPACE}/ci_infra_logs/${INFRA_ID}/coverage-report"
4949
if [ "${COVERAGE_MODE}" = "1" ]; then
5050
echo ">>> Code coverage enabled - reports will be saved to ${COVERAGE_REPORT_DIR}"
5151
mkdir -p "${COVERAGE_REPORT_DIR}"
52+
53+
# Coverage workflows use sparse checkout for source trees, so the tracked
54+
# root codecov.yml may not be materialized in the worktree even though it
55+
# exists at HEAD. codecov-action is explicitly pointed at this path; restore
56+
# just that file from the tested commit without broadening sparse checkout.
57+
if [ ! -f "${WORKSPACE}/codecov.yml" ]; then
58+
if git -C "${WORKSPACE}" cat-file -e HEAD:codecov.yml 2>/dev/null; then
59+
git -C "${WORKSPACE}" show HEAD:codecov.yml > "${WORKSPACE}/codecov.yml"
60+
echo ">>> Materialized codecov.yml from HEAD for coverage upload"
61+
else
62+
echo ">>> WARNING: codecov.yml is not tracked at HEAD"
63+
fi
64+
fi
5265
fi
5366

5467
# 1. Determine Required Infras
Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4+
# Portable pin check for CI runners without ripgrep (uses POSIX grep -F/-x/-q).
45
dockerfile_path="${1:-$(dirname "$0")/Dockerfile}"
56

7+
if [ ! -f "$dockerfile_path" ]; then
8+
echo "ERROR: Dockerfile not found: $dockerfile_path" >&2
9+
exit 1
10+
fi
11+
612
expected_arg='ARG MYSQL_CONNECTOR_PYTHON_VERSION=9.7.0'
7-
if ! rg -Fqx -- "$expected_arg" "$dockerfile_path"; then
8-
echo "ERROR: missing Dockerfile argument: $expected_arg" >&2
9-
exit 1
13+
if ! grep -Fqx -- "$expected_arg" "$dockerfile_path"; then
14+
echo "ERROR: missing Dockerfile argument: $expected_arg" >&2
15+
exit 1
1016
fi
1117

1218
for package in mysql-connector-python mysqlx-connector-python; do
13-
expected_pin="${package}==\${MYSQL_CONNECTOR_PYTHON_VERSION}"
14-
expected_dependency="\"${expected_pin}\" \\"
15-
if ! rg -Fq -- "$expected_dependency" "$dockerfile_path"; then
16-
echo "ERROR: missing package pin: $expected_pin" >&2
17-
exit 1
18-
fi
19+
expected_pin="${package}==\${MYSQL_CONNECTOR_PYTHON_VERSION}"
20+
expected_dependency="\"${expected_pin}\" \\"
21+
if ! grep -Fq -- "$expected_dependency" "$dockerfile_path"; then
22+
echo "ERROR: missing package pin: $expected_pin" >&2
23+
exit 1
24+
fi
1925
done
2026

2127
echo "PASS: MySQLX Connector/Python version pins are present"

test/tap/groups/groups.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"fast_forward_grace_close_libmysql-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
3535
"fast_forward_switch_replication_deprecate_eof_libmysql-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
3636
"ffto_protocol_unit-t" : [ "unit-tests-g1","@proxysql_min_version:3.1" ],
37+
"ffto_state_machine_unit-t" : [ "unit-tests-g1","@proxysql_min_version:3.1" ],
3738
"firewall_commands1-t" : [ "legacy-g1","mariadb10-galera-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql93-g1","mysql93-gr-g1","mysql95-g1","mysql95-gr-g1" ],
3839
"gen_utils_unit-t" : [ "unit-tests-g1" ],
3940
"genai_config_query_unit-t" : [ "ai-g1","@proxysql_min_version:4.0" ],
@@ -57,6 +58,7 @@
5758
"gtid_trxid_interval_unit-t" : [ "unit-tests-g1" ],
5859
"hostgroup_routing_unit-t" : [ "unit-tests-g1" ],
5960
"hostgroups_unit-t" : [ "unit-tests-g1" ],
61+
"http_server_unit-t" : [ "unit-tests-g1" ],
6062
"issue5384-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql84-g4","mysql90-g4","mysql95-g4" ],
6163
"kill_connection-t" : [ "legacy-g1","mariadb10-galera-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql93-g1","mysql93-gr-g1","mysql95-g1","mysql95-gr-g1" ],
6264
"kill_connection2-t" : [ "legacy-g1","mariadb10-galera-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql93-g1","mysql93-gr-g1","mysql95-g1","mysql95-gr-g1" ],
@@ -147,6 +149,7 @@
147149
"nl2sql_unit_base-t" : [ "ai-g1","@proxysql_min_version:4.0" ],
148150
"ok_packet_mixed_queries-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql84-g4","mysql90-g4","mysql95-g4" ],
149151
"parsersql_digest_test-t" : [ "unit-tests-g1" ],
152+
"parsersql_unit-t" : [ "unit-tests-g1" ],
150153
"pgsql-admin_metacmds-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
151154
"pgsql-admin_metacmds_describe_all_tables-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
152155
"pgsql-admin_metacmds_describe_queries-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
@@ -203,6 +206,7 @@
203206
"pgsql_monitor_unit-t" : [ "unit-tests-g1" ],
204207
"pgsql_query_logging_autodump-t" : [ "legacy-g6","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
205208
"pgsql_query_logging_memory-t" : [ "legacy-g6","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
209+
"pgsql_query_processor_unit-t" : [ "unit-tests-g1" ],
206210
"pgsql_servers_ssl_params_unit-t" : [ "unit-tests-g1" ],
207211
"pgsql_tokenizer_unit-t" : [ "unit-tests-g1" ],
208212
"pgsql_txn_state_unit-t" : [ "unit-tests-g1" ],
@@ -302,6 +306,7 @@
302306
"reg_test_stmt_resultset_err_no_rows_php-t" : [ "legacy-g7","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g7","mysql90-g2","mysql95-g2" ],
303307
"reg_test_stmt_send_long_data_short_packet-t" : [ "mysql84-g6","mysql95-g1" ],
304308
"reg_test_unexp_ping_pkt-t" : [ "legacy-g7","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g7","mysql90-g2","mysql95-g2" ],
309+
"restapi_server_unit-t" : [ "unit-tests-g1" ],
305310
"rule_matching_unit-t" : [ "unit-tests-g1" ],
306311
"savepoint-3749-t" : [ "legacy-g7","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g7","mysql90-g2","mysql95-g2" ],
307312
"savepoint-948-t" : [ "legacy-g7","mariadb10-galera-g7","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g7","mysql84-gr-g7","mysql90-g2","mysql95-g2" ],
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* @file ffto_mysql_helpers.h
3+
* @brief Shared setup for MySQL FFTO TAP tests.
4+
*
5+
* Ensures:
6+
* 1. mysql-ffto_enabled is on at runtime
7+
* 2. fast_forward=1 on the connecting user's frontend credential
8+
* 3. After connect, session actually has fast_forward (via processlist extended_info)
9+
*/
10+
#ifndef FFTO_MYSQL_HELPERS_H
11+
#define FFTO_MYSQL_HELPERS_H
12+
13+
#include <stdio.h>
14+
#include <string.h>
15+
#include <unistd.h>
16+
#include "mysql.h"
17+
#include "tap.h"
18+
#include "command_line.h"
19+
#include "utils.h"
20+
21+
/**
22+
* @brief Enable FFTO + fast_forward for @p username and LOAD to runtime.
23+
* @return 0 on success, non-zero on failure (diags already emitted).
24+
*/
25+
static inline int ffto_mysql_enable_ff(MYSQL* admin, const char* username) {
26+
if (!admin || !username || !username[0]) {
27+
diag("ffto_mysql_enable_ff: invalid args");
28+
return 1;
29+
}
30+
31+
/* Prefer SET (same path as mysql-fast_forward-t / session_track FF tests). */
32+
if (mysql_query(admin, "SET mysql-ffto_enabled='true'")) {
33+
diag("SET mysql-ffto_enabled failed: %s", mysql_error(admin));
34+
return 1;
35+
}
36+
if (mysql_query(admin, "SET mysql-ffto_max_buffer_size=1048576")) {
37+
diag("SET mysql-ffto_max_buffer_size failed: %s", mysql_error(admin));
38+
return 1;
39+
}
40+
if (mysql_query(admin, "SET mysql-show_processlist_extended=2")) {
41+
diag("SET mysql-show_processlist_extended failed: %s", mysql_error(admin));
42+
return 1;
43+
}
44+
if (mysql_query(admin, "LOAD MYSQL VARIABLES TO RUNTIME")) {
45+
diag("LOAD MYSQL VARIABLES TO RUNTIME failed: %s", mysql_error(admin));
46+
return 1;
47+
}
48+
49+
/* Enable fast_forward on every row for this user (frontend + backend). */
50+
char q[512];
51+
snprintf(q, sizeof(q),
52+
"UPDATE mysql_users SET fast_forward=1 WHERE username='%s'", username);
53+
if (mysql_query(admin, q)) {
54+
diag("UPDATE mysql_users fast_forward failed: %s", mysql_error(admin));
55+
return 1;
56+
}
57+
/* Belt-and-suspenders: also flip all users (matches historical suite setup). */
58+
if (mysql_query(admin, "UPDATE mysql_users SET fast_forward=1")) {
59+
diag("UPDATE mysql_users SET fast_forward=1 (all) failed: %s", mysql_error(admin));
60+
return 1;
61+
}
62+
if (mysql_query(admin, "LOAD MYSQL USERS TO RUNTIME")) {
63+
diag("LOAD MYSQL USERS TO RUNTIME failed: %s", mysql_error(admin));
64+
return 1;
65+
}
66+
67+
/* Hard-require the connecting user's FRONTEND credential has fast_forward=1. */
68+
snprintf(q, sizeof(q),
69+
"SELECT COUNT(*) FROM runtime_mysql_users "
70+
"WHERE username='%s' AND frontend=1 AND fast_forward=1", username);
71+
if (mysql_query(admin, q)) {
72+
diag("runtime_mysql_users check failed: %s", mysql_error(admin));
73+
return 1;
74+
}
75+
MYSQL_RES* res = mysql_store_result(admin);
76+
MYSQL_ROW row = res ? mysql_fetch_row(res) : NULL;
77+
int cnt = row && row[0] ? atoi(row[0]) : 0;
78+
if (res) mysql_free_result(res);
79+
if (cnt < 1) {
80+
diag("FATAL: runtime frontend user '%s' still has fast_forward=0 after LOAD", username);
81+
if (mysql_query(admin,
82+
"SELECT username, frontend, backend, fast_forward FROM runtime_mysql_users") == 0) {
83+
MYSQL_RES* dump = mysql_store_result(admin);
84+
MYSQL_ROW r;
85+
while (dump && (r = mysql_fetch_row(dump))) {
86+
diag(" runtime_mysql_users: user=%s fe=%s be=%s ff=%s",
87+
r[0] ? r[0] : "NULL",
88+
r[1] ? r[1] : "NULL",
89+
r[2] ? r[2] : "NULL",
90+
r[3] ? r[3] : "NULL");
91+
}
92+
if (dump) mysql_free_result(dump);
93+
}
94+
return 1;
95+
}
96+
diag("runtime frontend user '%s' has fast_forward=1", username);
97+
return 0;
98+
}
99+
100+
/**
101+
* @brief After a client connection is up, confirm the session is in fast_forward.
102+
* Polls stats_mysql_processlist.extended_info for up to 2s.
103+
* @return true if session reports fast_forward != 0.
104+
*/
105+
static inline bool ffto_mysql_session_is_ff(MYSQL* admin, const char* username) {
106+
if (!admin || !username) return false;
107+
char q[512];
108+
snprintf(q, sizeof(q),
109+
"SELECT extended_info FROM stats_mysql_processlist WHERE user='%s'", username);
110+
for (int i = 0; i < 20; i++) {
111+
if (mysql_query(admin, q) != 0) { usleep(100000); continue; }
112+
MYSQL_RES* res = mysql_store_result(admin);
113+
if (!res) { usleep(100000); continue; }
114+
MYSQL_ROW row;
115+
bool found = false;
116+
while ((row = mysql_fetch_row(res))) {
117+
const char* info = row[0] ? row[0] : "";
118+
/* extended_info JSON: "fast_forward":1 or "fast_forward": 1 */
119+
if (strstr(info, "\"fast_forward\":1") || strstr(info, "\"fast_forward\": 1")) {
120+
found = true;
121+
break;
122+
}
123+
/* Also accept non-zero enum values dumped as numbers > 0 */
124+
const char* p = strstr(info, "\"fast_forward\":");
125+
if (p) {
126+
p += strlen("\"fast_forward\":");
127+
while (*p == ' ') p++;
128+
if (*p && *p != '0' && *p != ',' && *p != '}') {
129+
found = true;
130+
break;
131+
}
132+
}
133+
}
134+
mysql_free_result(res);
135+
if (found) return true;
136+
usleep(100000);
137+
}
138+
return false;
139+
}
140+
141+
/**
142+
* @brief Clear in-memory query digests (DELETE on the stats mirror is NOT enough).
143+
*/
144+
static inline int ffto_mysql_reset_digests(MYSQL* admin) {
145+
if (mysql_query(admin, "SELECT * FROM stats_mysql_query_digest_reset")) {
146+
diag("stats_mysql_query_digest_reset failed: %s", mysql_error(admin));
147+
return 1;
148+
}
149+
MYSQL_RES* r = mysql_store_result(admin);
150+
if (r) mysql_free_result(r);
151+
return 0;
152+
}
153+
154+
/**
155+
* @brief Clear in-memory mysql error stats.
156+
*/
157+
static inline int ffto_mysql_reset_errors(MYSQL* admin) {
158+
if (mysql_query(admin, "SELECT * FROM stats_mysql_errors_reset")) {
159+
diag("stats_mysql_errors_reset failed: %s", mysql_error(admin));
160+
return 1;
161+
}
162+
MYSQL_RES* r = mysql_store_result(admin);
163+
if (r) mysql_free_result(r);
164+
return 0;
165+
}
166+
167+
#endif /* FFTO_MYSQL_HELPERS_H */

test/tap/tests/test_ffto_bypass-t.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ int main(int argc, char** argv) {
3030
ok(admin != NULL, "Connected to ProxySQL Admin");
3131

3232
// Set a very small threshold: 100 bytes
33-
ok(mysql_query(admin, "UPDATE global_variables SET variable_value='true' WHERE variable_name='mysql-ffto_enabled'") == 0, "Enable FFTO");
33+
ok(mysql_query(admin, "SET mysql-ffto_enabled='true'") == 0, "Enable FFTO");
3434
ok(mysql_query(admin, "UPDATE global_variables SET variable_value='100' WHERE variable_name='mysql-ffto_max_buffer_size'") == 0, "Set FFTO max buffer size to 100");
3535
ok(mysql_query(admin, "LOAD MYSQL VARIABLES TO RUNTIME") == 0, "Load variables to runtime");
3636

37-
// Ensure user exists
38-
char user_query[1024];
39-
snprintf(user_query, sizeof(user_query), "INSERT OR REPLACE INTO mysql_users (username, password, default_hostgroup, fast_forward, default_schema) VALUES ('%s', '%s', 0, 1, 'information_schema')", cl.username, cl.password);
40-
ok(mysql_query(admin, user_query) == 0, "Configure user with fast_forward=1 and default_schema");
37+
/* Enable fast_forward on ALL mysql_users rows (frontend + backend).
38+
* Partial INSERT OR REPLACE only updates the backend PK row and leaves
39+
* frontend credentials at fast_forward=0, so FFTO never runs. */
40+
ok(mysql_query(admin, "UPDATE mysql_users SET fast_forward=1, default_schema='information_schema'") == 0,
41+
"Enable fast_forward on all mysql_users");
4142
ok(mysql_query(admin, "LOAD MYSQL USERS TO RUNTIME") == 0, "Load users to runtime");
4243

4344
// Ensure backend server exists

0 commit comments

Comments
 (0)