Skip to content

Protocol encoding/decoding and utility unit tests (Phase 2.5)#5488

Merged
renecannao merged 3 commits intov3.0-5473from
v3.0-5477
Mar 22, 2026
Merged

Protocol encoding/decoding and utility unit tests (Phase 2.5)#5488
renecannao merged 3 commits intov3.0-5473from
v3.0-5477

Conversation

@renecannao
Copy link
Contributor

@renecannao renecannao commented Mar 21, 2026

Summary

Implements Phase 2.5 (#5477) of the Unit Testing Framework: unit tests for protocol encoding/decoding functions, query digest normalization, and string utility functions.

  • 43 TAP test cases across 11 test functions
  • Runs in <0.01 seconds with zero infrastructure dependencies
  • Tests pure functions with no global state dependencies

Test Coverage

Area Tests What's Verified
Length-encoded int decode (1-byte) 3 Values 0, 1, 250
Length-encoded int decode (2-byte) 2 0xFC prefix, max 16-bit
Length-encoded int decode (3-byte) 1 0xFD prefix
Length-encoded int decode (8-byte) 2 0xFE prefix, large values
Length-encoded int encode 6 All tier boundaries
Encode/decode roundtrip 1 10 values across all ranges
mysql_hdr structure 3 Size, field packing, max length
CPY3/CPY8 helpers 4 Little-endian semantics, boundaries
MySQL query digest 4 Normalization, whitespace, comments, empty
PgSQL query digest 2 Normalization, whitespace
String escaping 3 No-op, single quote doubling, multiple
Wildcard matching 12 %, _, exact, non-match, empty edge cases

Depends On

Test plan

  • All 43 tests pass on macOS (arm64)
  • Verify on Linux (CI)

Summary by CodeRabbit

  • Tests

    • Expanded deterministic unit test coverage for protocol and query-digest behavior, adding many edge-case checks for length encoding/decoding, header/field sizing, byte-order correctness, comment handling, string escaping, and wildcard matching; includes grouped assertions and explicit cleanup for reliability.
  • Chores

    • Test build updated to include the new protocol unit test in standard test targets.

Copilot AI review requested due to automatic review settings March 21, 2026 20:49
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the project's unit testing coverage by introducing a new suite of tests for critical protocol and utility functions. It ensures the robust and correct operation of MySQL length-encoded integer handling, verifies the mysql_hdr packet structure, and validates the normalization logic for both MySQL and PostgreSQL query digests. Additionally, it confirms the reliability of string manipulation utilities, including escaping and wildcard matching, thereby improving the overall stability and correctness of these core components.

Highlights

  • Protocol Encoding/Decoding Tests: Introduced comprehensive unit tests for MySQL length-encoded integer encoding and decoding, covering 1-byte, 2-byte, 3-byte, and 8-byte values, including roundtrip verification.
  • Packet Header and Byte Copy Helpers: Added tests for the mysql_hdr packet header structure and byte copy helpers CPY3 and CPY8, verifying their little-endian semantics and correct operation.
  • Query Digest Functions: Implemented unit tests for MySQL and PostgreSQL query digest normalization, ensuring proper handling of whitespace, literals, and comments.
  • String Utility Functions: Included tests for string escaping (single quotes) and wildcard matching (mywildcmp) with % and _ characters, covering various edge cases.
  • Test Framework Expansion: Expanded the unit testing framework with 43 new TAP test cases, contributing to Phase 2.5 of the Unit Testing Framework.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3428060e-4649-4e36-826f-8edfd80148ed

📥 Commits

Reviewing files that changed from the base of the PR and between f976324 and 045ff26.

📒 Files selected for processing (2)
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/protocol_unit-t.cpp

📝 Walkthrough

Walkthrough

Adds a new TAP unit-test binary and source validating protocol utilities: MySQL length-encoded integer encode/decode/roundtrip, packet/header helpers, byte-copy helpers, query digests, string escaping, and wildcard matching.

Changes

Cohort / File(s) Summary
Build Configuration
test/tap/tests/unit/Makefile
Appended protocol_unit-t to UNIT_TESTS and added an explicit make target to compile/link protocol_unit-t.cpp with existing test helper objects and library link flags.
Protocol Unit Tests
test/tap/tests/unit/protocol_unit-t.cpp
New ~425-line TAP test executable (main) with 43 assertions covering mysql_encode_length/mysql_decode_length variants, write/read roundtrip, mysql_hdr layout, CPY3/CPY8 byte semantics, MySQL/Postgres query digests, escape_string_single_quotes, and mywildcmp behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through packets, bytes, and strings,

I counted lengths and fixed tiny things,
Digests neat and wildcards tried,
Quotes escaped, roundtrips verified,
A rabbit cheers for tests that sing.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Phase 2.5 unit tests for protocol encoding/decoding and utility functions. It is specific, concise, and clearly indicates the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3.0-5477

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive suite of unit tests for protocol encoding/decoding and various utility functions. The tests are well-structured and cover a wide range of cases, including boundaries and edge conditions. My review identified a few potential issues in the underlying functions being tested, including a portability problem due to unaligned memory access and a confusing memory management contract in another function. I've provided specific feedback and suggestions to address these points for improved correctness and maintainability.

Comment on lines +226 to +228
unsigned int val = CPY3(buf);
ok(val == 0x030201,
"CPY3: little-endian 3-byte copy correct");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This test calls CPY3, which has an implementation in lib/MySQL_encode.cpp that can lead to undefined behavior. The use of *(uint32_t *)ptr on an unsigned char * can cause unaligned memory access, which may result in crashes or performance issues on certain architectures (e.g., ARM).

To ensure portability and correctness, it's highly recommended to refactor CPY3 to use a safe method like memcpy or manual byte-wise construction. For example:

// A safe implementation for CPY3
unsigned int CPY3(unsigned char *ptr) {
    return (unsigned int)(ptr[0]) |
           ((unsigned int)(ptr[1]) << 8) |
           ((unsigned int)(ptr[2]) << 16);
}

Since the implementation is not in this PR, this comment serves as a note that the test may be unreliable across different platforms.

Comment on lines +105 to +106
memset(buf + 1, 0, 8);
buf[1] = 0x01;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The buffer initialization here is slightly confusing. memset zeroes out 8 bytes starting from buf[1], and the next line immediately overwrites buf[1]. While the logic is correct, it could be made clearer to improve readability.

Suggested change
memset(buf + 1, 0, 8);
buf[1] = 0x01;
buf[1] = 0x01;
memset(buf + 2, 0, 7);

* @brief Test escape_string_single_quotes().
*/
static void test_escape_single_quotes() {
// No quotes — should return copy

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment // No quotes — should return copy is inaccurate. The underlying function escape_string_single_quotes returns the original input pointer, not a copy, when no quotes are present. This can be misleading.

Additionally, the function's memory management contract is inconsistent. When no quotes are found, it ignores the free_it flag, but when quotes are present, it honors it. This can lead to memory leaks or double-frees if not used carefully. While your test code handles this correctly, the function API is fragile. Consider updating the comment for accuracy and reviewing the function's contract for consistency.

Suggested change
// No quotes — should return copy
// No quotes — should return original string

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new fast, infra-free TAP unit test binary covering protocol-related pure functions (encoding/decoding helpers, query digest normalization, and string utilities) as part of the unit testing framework milestone (Phase 2.5).

Changes:

  • Introduces protocol_unit-t.cpp with 43 TAP assertions across MySQL/PgSQL digest utilities, length-encoded integer helpers, mysql_hdr, CPY helpers, and string utilities.
  • Extends the unit-test Makefile to build and clean the new protocol_unit-t binary alongside the existing smoke test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
test/tap/tests/unit/protocol_unit-t.cpp Adds new TAP unit tests for protocol utilities, digest normalization, and string helpers.
test/tap/tests/unit/Makefile Adds protocol_unit-t target to the unit-test build/clean pipeline.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


int pass_count = 0;
for (int i = 0; i < num_values; i++) {
memset(buf, 0, sizeof(buf));
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefix[0] can be uninitialized when enc_len == 1 (because mysql_encode_length() doesn't write hd for values < 251). Passing an uninitialized scalar as an argument is UB even if write_encoded_length() won't read it in the len==1 branch. Initialize prefix[0] each iteration or pass a constant (e.g. 0) when enc_len==1.

Suggested change
memset(buf, 0, sizeof(buf));
memset(buf, 0, sizeof(buf));
prefix[0] = 0;

Copilot uses AI. Check for mistakes.
Comment on lines +276 to +280
// Query with comment — first_comment should capture it
first_comment = nullptr;
digest = mysql_query_digest_and_first_comment_2(
"/* my_comment */ SELECT 1", 25,
&first_comment, buf);
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a comment is present, mysql_query_digest_and_first_comment_2() allocates first_comment with malloc(). This test overwrites first_comment with nullptr before the next call without freeing the previous allocation, which will show up as a leak under ASAN. Free first_comment after each call (or before resetting it) when it is non-null.

Copilot uses AI. Check for mistakes.
Comment on lines +299 to +300
char *digest = pgsql_query_digest_and_first_comment_2(
"SELECT * FROM orders WHERE total > 0", 42,
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The q_len passed to pgsql_query_digest_and_first_comment_2() appears off by one (the literal string length is 41, but 42 is passed). This makes the test diverge from real call sites that pass query.length() and can subtly change parsing by including the terminating \0. Prefer strlen()/std::strlen() (or sizeof(str)-1 for literals) to compute lengths for these tests.

Suggested change
char *digest = pgsql_query_digest_and_first_comment_2(
"SELECT * FROM orders WHERE total > 0", 42,
const char *query = "SELECT * FROM orders WHERE total > 0";
char *digest = pgsql_query_digest_and_first_comment_2(
query, strlen(query),

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +91
unsigned char buf[4];
uint32_t len = 0;

buf[0] = 0xFD;
buf[1] = 0x00;
buf[2] = 0x00;
buf[3] = 0x01;
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mysql_decode_length() uses CPY3(ptr+1), and CPY3() reads a full 4 bytes via a uint32_t* cast. With unsigned char buf[4], this becomes a 1-byte out-of-bounds read (starting at buf+1). Pad the test buffer by at least one extra byte (e.g., buf[5] with the extra byte zeroed) to keep the test sanitizer-safe.

Suggested change
unsigned char buf[4];
uint32_t len = 0;
buf[0] = 0xFD;
buf[1] = 0x00;
buf[2] = 0x00;
buf[3] = 0x01;
unsigned char buf[5];
uint32_t len = 0;
buf[0] = 0xFD;
buf[1] = 0x00;
buf[2] = 0x00;
buf[3] = 0x01;
buf[4] = 0x00; // padding to keep potential 4-byte reads from buf+1 in-bounds

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +230
unsigned char buf[3] = {0x01, 0x02, 0x03};
unsigned int val = CPY3(buf);
ok(val == 0x030201,
"CPY3: little-endian 3-byte copy correct");

unsigned char zero[3] = {0, 0, 0};
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPY3() reads 4 bytes via a uint32_t* cast; calling it with a 3-byte array (unsigned char buf[3]) triggers an out-of-bounds read. Use a 4-byte buffer with an extra padding byte (e.g., {0x01,0x02,0x03,0x00}) when testing CPY3() to avoid UB and sanitizer failures.

Suggested change
unsigned char buf[3] = {0x01, 0x02, 0x03};
unsigned int val = CPY3(buf);
ok(val == 0x030201,
"CPY3: little-endian 3-byte copy correct");
unsigned char zero[3] = {0, 0, 0};
unsigned char buf[4] = {0x01, 0x02, 0x03, 0x00};
unsigned int val = CPY3(buf);
ok(val == 0x030201,
"CPY3: little-endian 3-byte copy correct");
unsigned char zero[4] = {0, 0, 0, 0};

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/tap/tests/unit/protocol_unit-t.cpp`:
- Around line 276-283: The test only asserts digest != nullptr for MySQL and
never checks that the comment was captured, and PostgreSQL tests lack a
comment-input case; update the test(s) to assert that first_comment is non-null
and equals the expected comment after calling
mysql_query_digest_and_first_comment_2 (check the first_comment pointer/string
and contents via buf), and add a parallel test that calls the equivalent
pgsql_query_digest_and_first_comment_2 (or the PostgreSQL helper used in this
file) with a comment-prefixed query and asserts its first_comment is captured
and matches the expected text; apply the same fixes to the other block
referenced (the region around the other MySQL/PgSQL cases noted as also applying
to lines 295-311).
- Around line 225-231: The CPY3 tests allocate 3-byte arrays but CPY3 reads a
4-byte uint32_t, causing undefined over-read; update the test arrays used with
CPY3 (the variables named buf and zero in protocol_unit-t.cpp) to be 4 bytes
instead of 3 so they match CPY3's 4-byte read (i.e., change unsigned char buf[3]
and unsigned char zero[3] to unsigned char buf[4] and unsigned char zero[4]).
- Around line 85-93: The test triggers an out-of-bounds read because CPY3(ptr+1)
(used by mysql_decode_length) dereferences a 4-byte read starting at buf+1 while
buf is only 4 bytes; fix by enlarging the test buffer so CPY3 cannot read past
it (e.g. make buf larger than 4 bytes and initialize the extra byte(s)) or
adjust the pointer so CPY3 reads only within bounds; reference the buf variable,
mysql_decode_length call and CPY3 usage when applying the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 25155312-db5f-40d9-930e-254de11f14c0

📥 Commits

Reviewing files that changed from the base of the PR and between b187f9a and 14f9d48.

📒 Files selected for processing (2)
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/protocol_unit-t.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: CI-builds / builds (debian12,-dbg)
  • GitHub Check: CI-builds / builds (ubuntu22,-tap)
  • GitHub Check: Agent
  • GitHub Check: run / trigger
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-20T09:34:19.124Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:39-48
Timestamp: 2026-01-20T09:34:19.124Z
Learning: In ProxySQL's TAP test suite, resource leaks (e.g., not calling mysql_close() on early return paths) are commonly tolerated because test processes are short-lived and OS frees resources on exit. This pattern applies to all C++ test files under test/tap/tests. When reviewing, recognize this as a project-wide test convention and focus on test correctness and isolation rather than insisting on fixing such leaks in these test files.

Applied to files:

  • test/tap/tests/unit/protocol_unit-t.cpp
📚 Learning: 2026-01-20T07:40:34.938Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:24-28
Timestamp: 2026-01-20T07:40:34.938Z
Learning: In ProxySQL test files, calling `mysql_error(NULL)` after `mysql_init()` failure is safe because the MariaDB client library implementation returns an empty string for NULL handles (not undefined behavior).

Applied to files:

  • test/tap/tests/unit/protocol_unit-t.cpp
🪛 Clang (14.0.6)
test/tap/tests/unit/protocol_unit-t.cpp

[error] 18-18: 'tap.h' file not found

(clang-diagnostic-error)

🪛 Cppcheck (2.20.0)
test/tap/tests/unit/protocol_unit-t.cpp

[error] 149-149: Common realloc mistake

(memleakOnRealloc)


[warning] 86-86: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)


[warning] 31-31: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)


[warning] 138-138: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)

🔇 Additional comments (2)
test/tap/tests/unit/Makefile (1)

234-256: Good integration of the new unit-test target.

protocol_unit-t is correctly wired into UNIT_TESTS and reuses the same build/link contract as smoke_test-t, keeping behavior consistent across all and debug.

test/tap/tests/unit/protocol_unit-t.cpp (1)

174-183: No issue: prefix[0] is never read when enc_len == 1.

The write_encoded_length() function explicitly branches on the enc_len value. When enc_len == 1, it writes the value directly (*p = (char)val) and returns without using the prefix parameter. The prefix is only read when enc_len > 1, and in those cases, mysql_encode_length() has already written it. The code is safe as written.

Comment on lines +276 to +283
// Query with comment — first_comment should capture it
first_comment = nullptr;
digest = mysql_query_digest_and_first_comment_2(
"/* my_comment */ SELECT 1", 25,
&first_comment, buf);
ok(digest != nullptr,
"mysql digest: query with comment produces non-null digest");

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Comment-handling coverage is weaker than the PR objective.

The MySQL comment case only checks digest != nullptr and never asserts first_comment was captured. PgSQL has no comment-input case at all, so comment handling can regress undetected.

🛠️ Proposed test-strengthening diff
 	// Query with comment — first_comment should capture it
 	first_comment = nullptr;
 	digest = mysql_query_digest_and_first_comment_2(
 		"/* my_comment */ SELECT 1", 25,
 		&first_comment, buf);
-	ok(digest != nullptr,
-		"mysql digest: query with comment produces non-null digest");
+	ok(digest != nullptr && first_comment != nullptr && first_comment[0] != '\0',
+		"mysql digest: query with comment captures first_comment");
@@
 static void test_pgsql_query_digest() {
 	char buf[QUERY_DIGEST_BUF];
 	char *first_comment = nullptr;
@@
 	} else {
 		ok(0, "pgsql digest: whitespace normalized (skipped)");
 	}
+
+	first_comment = nullptr;
+	digest = pgsql_query_digest_and_first_comment_2(
+		"/* pg_comment */ SELECT 1", 25,
+		&first_comment, buf);
+	ok(digest != nullptr && first_comment != nullptr && first_comment[0] != '\0',
+		"pgsql digest: query with comment captures first_comment");
 }

Also applies to: 295-311

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/protocol_unit-t.cpp` around lines 276 - 283, The test
only asserts digest != nullptr for MySQL and never checks that the comment was
captured, and PostgreSQL tests lack a comment-input case; update the test(s) to
assert that first_comment is non-null and equals the expected comment after
calling mysql_query_digest_and_first_comment_2 (check the first_comment
pointer/string and contents via buf), and add a parallel test that calls the
equivalent pgsql_query_digest_and_first_comment_2 (or the PostgreSQL helper used
in this file) with a comment-prefixed query and asserts its first_comment is
captured and matches the expected text; apply the same fixes to the other block
referenced (the region around the other MySQL/PgSQL cases noted as also applying
to lines 295-311).

renecannao added a commit that referenced this pull request Mar 21, 2026
- test_decode_length_3byte: pad buffer to 5 bytes (CPY3 reads 4 bytes
  via uint32_t* cast from buf+1, causing OOB read with 4-byte buffer)
- test_cpy3: use 4-byte buffers with padding to avoid OOB read
- test_encode_decode_roundtrip: initialize prefix[0]=0 each iteration
  to avoid UB when mysql_encode_length doesn't write for 1-byte values
- test_mysql_query_digest: free first_comment between calls to prevent
  memory leak (malloc'd by digest function when comment present)
- test_pgsql_query_digest: use strlen() instead of hardcoded q_len
  to avoid off-by-one, free first_comment after use
- test_escape_single_quotes: fix misleading comment — function returns
  original pointer (not copy) when no quotes are present
renecannao added a commit that referenced this pull request Mar 21, 2026
- test_decode_length_3byte: pad buffer to 5 bytes (CPY3 reads 4 bytes
  via uint32_t* cast from buf+1, causing OOB read with 4-byte buffer)
- test_cpy3: use 4-byte buffers with padding to avoid OOB read
- test_encode_decode_roundtrip: initialize prefix[0]=0 each iteration
  to avoid UB when mysql_encode_length doesn't write for 1-byte values
- test_mysql_query_digest: free first_comment between calls to prevent
  memory leak (malloc'd by digest function when comment present)
- test_pgsql_query_digest: use strlen() instead of hardcoded q_len
  to avoid off-by-one, free first_comment after use
- test_escape_single_quotes: fix misleading comment — function returns
  original pointer (not copy) when no quotes are present
Unit tests for standalone protocol functions and utility routines
covering 43 test cases across 11 test functions. Runs in <0.01s with
no infrastructure dependencies.

Test coverage:
- MySQL length-encoded integer decoding: 1-byte (0-250), 2-byte
  (0xFC prefix), 3-byte (0xFD prefix), 8-byte (0xFE prefix)
- MySQL length-encoded integer encoding: boundary values at each
  encoding tier (251, 65535, 65536, 16777216)
- Encode/decode roundtrip: 10 values across all encoding ranges
  survive write_encoded_length() → mysql_decode_length_ll()
- mysql_hdr packet header: structure size (4 bytes), field packing,
  24-bit max length
- CPY3/CPY8 byte copy helpers: little-endian semantics, boundary values
- MySQL query digest: normalization via mysql_query_digest_and_first_
  comment_2(), whitespace collapsing, comment handling, empty query
- PgSQL query digest: normalization via pgsql_query_digest_and_first_
  comment_2(), whitespace collapsing
- escape_string_single_quotes: no-op on clean strings, single quote
  doubling, multiple quotes
- mywildcmp wildcard matching: exact match, % prefix/suffix/both,
  _ single char, non-match cases, empty string edge cases
- test_decode_length_3byte: pad buffer to 5 bytes (CPY3 reads 4 bytes
  via uint32_t* cast from buf+1, causing OOB read with 4-byte buffer)
- test_cpy3: use 4-byte buffers with padding to avoid OOB read
- test_encode_decode_roundtrip: initialize prefix[0]=0 each iteration
  to avoid UB when mysql_encode_length doesn't write for 1-byte values
- test_mysql_query_digest: free first_comment between calls to prevent
  memory leak (malloc'd by digest function when comment present)
- test_pgsql_query_digest: use strlen() instead of hardcoded q_len
  to avoid off-by-one, free first_comment after use
- test_escape_single_quotes: fix misleading comment — function returns
  original pointer (not copy) when no quotes are present
@renecannao renecannao merged commit 87e3280 into v3.0-5473 Mar 22, 2026
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
E Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants