WL#17318: Support for 4-Part MySQL Version

Affects: Server-26.x   —   Status: Complete

Executive Summary

WL17318 adds a four-part release identity to MySQL Server. The new MYSQL_VERSION_UPDATE source component produces a full version in the form MAJOR.MINOR.PATCH.UPDATE${MYSQL_VERSION_EXTRA}, allowing support, diagnostics, and automation to distinguish separately delivered updates on the same patch line. An empty update component is represented internally and externally as 0 in the full version.

The existing three-part compatibility model is retained. MYSQL_VERSION_ID, LIBMYSQL_VERSION_ID, numeric comparison logic, VERSION(), @@version, and the existing C APIs continue to use MAJOR.MINOR.PATCH plus any existing suffixes. New SQL and C API surfaces expose the full four-part string. mysql_upgrade_history retains its three-part version and records exact release history in a nested update_history array.

Problem Statement

The historical MySQL version model assumes that a server release is fully described by a three-component version:

major.minor.patch

That model works well when each patch version maps to a single delivered binary. It becomes insufficient when multiple deliverables need to exist on the same patch line.

This need arises when:

  • security bug fixes are delivered on an existing patch version
  • support, diagnostics, and automation need to identify the exact binary in use
  • release engineering needs a version identity that is more precise than patch level

Without an explicit update component, two delivered updates can appear identical from a version-reporting perspective even when one contains additional security or corrective fixes.

At the same time, changing the semantics of existing version interfaces is risky because many consumers depend on them:

  • SQL applications parse VERSION() and @@version
  • internal server code uses MYSQL_VERSION_ID for compatibility checks
  • replication, DD, and upgrade logic interpret version values as compatibility identifiers
  • external automation and operational tooling often assume a three-component version string

The design problem is therefore:

  • how to expose exact release identity for updates on the same patch line
  • without changing the meaning of existing compatibility-version surfaces
  • and without introducing broad regression risk in version parsing and compatibility logic

Objective

The objective of WL17318 is to make update-qualified releases observable without changing MySQL Server compatibility semantics.

The work specifically aims to:

  • Add MYSQL_VERSION_UPDATE as the fourth source component.
  • Generate a full version string that always includes the update component.
  • Preserve MYSQL_VERSION_ID and LIBMYSQL_VERSION_ID as patch-level IDs.
  • Expose the full version through dedicated SQL, C API, and command-line interfaces.
  • Log the full four-part runtime version in a separate startup error-log message.
  • Keep VERSION(), @@version, mysqld --version, and existing C APIs at three-part output.
  • Avoid new compatibility rules, changes to persisted DD, table-metadata, or version-gate data, and protocol-layout changes.

User and Developer Stories

  • As a release engineer, I want a four-part server version string so that I can distinguish an update from the original patch release without redefining compatibility.
  • As a support engineer, I want to read the full server version through SQL so that I can verify whether a deployed server contains a specific delivered update.
  • As an automation author, I want a dedicated full-version SQL and C API surface so that I can identify the exact release without changing existing three-part version interfaces.
  • As a server developer, I want all compatibility-sensitive version checks to remain patch-level so that updates do not silently change upgrade, downgrade, replication, or metadata behavior.
  • As a component or plugin developer, I want numeric compatibility macros to remain unchanged so that existing ABI and version-gating logic does not need reinterpretation for update releases.

Scope

This work covers:

  • version source metadata in MYSQL_VERSION
  • CMake validation, compatibility-version generation, and full-version generation
  • generated version-header content
  • runtime composition of compatibility and full server version strings
  • SQL-visible full-version reporting
  • new C API reporting functions
  • Windows VERSIONINFO metadata
  • MySQL Enterprise Backup server-variable filtering
  • nested exact-release entries in the mysql_upgrade_history audit trail
  • focused regression tests for the new full-version interfaces

This work does not cover:

  • MSI-specific packaging policy

Functional Requirements

FR-1: Update Version Source Metadata

FR-1.1

The version source file MYSQL_VERSION MUST accept a new MYSQL_VERSION_UPDATE field. An empty value denotes update 0.

FR-1.2

MYSQL_VERSION_UPDATE MUST be validated as a non-negative integer without leading zeroes after an empty value has been normalized to 0.

FR-2: Generated Version Strings And Version IDs

FR-2.1

The generated base version strings with full 4 part version before runtime suffixes MUST be:

  • MYSQL_SERVER_VERSION_WITH_UPDATE: MAJOR.MINOR.PATCH.UPDATE${MYSQL_VERSION_EXTRA}

The full version string MUST include UPDATE even when it is 0.

The existing three-part compatibility version string MUST remain: - MYSQL_SERVER_VERSION: MAJOR.MINOR.PATCH${MYSQL_VERSION_EXTRA}

FR-2.2

MYSQL_SERVER_VERSION MUST continue to use the three part compatibilty version string.

FR-2.3

LIBMYSQL_VERSION MUST continue to use the three part compatibility version string.

FR-2.4

MYSQL_VERSION_ID MUST remain major * 10000 + minor * 100 + patch. The update component MUST NOT be included in this value.

FR-2.5

LIBMYSQL_VERSION_ID MUST remain equal to MYSQL_VERSION_ID.

FR-2.6

The generated my_config.h MUST expose:

  • MYSQL_VERSION_MAJOR
  • MYSQL_VERSION_MINOR
  • MYSQL_VERSION_PATCH
  • MYSQL_VERSION_UPDATE

MYSQL_VERSION_UPDATE MUST be generated from UPDATE_VERSION, normalized to 0 when the source value is empty, just like the other version components.

The generated mysql_version.h MUST retain MYSQL_SERVER_VERSION and expose MYSQL_SERVER_VERSION_WITH_UPDATE. The latter MUST contain the full version, including update 0. This work does not add individual version-component macros to mysql_version.h.

FR-3: Runtime Version Representation

FR-3.1

The server runtime MUST introduce a new mutable 'server_version_with_update' string buffer for full four part version.

The existing server_version string buffer must continue to represent the three-part compatibility version.

FR-3.2

The server runtime MUST initialize server_version from MYSQL_SERVER_VERSION and server_version_with_update from MYSQL_SERVER_VERSION_WITH_UPDATE.

FR-3.3

The server runtime MUST append runtime suffixes such as -debug, -valgrind, -asan, -lsan, -ubsan, and -tsan to both strings when applicable.

FR-4: SQL Version Reporting

FR-4.1

VERSION() MUST continue to return the three-part runtime server_version string.

FR-4.2

@@version MUST continue to return the three-part runtime server_version string.

FR-4.3

A new read-only global system variable version_with_update MUST expose the full runtime version string.

FR-4.4

@@version_with_update MUST be accepted as shorthand for @@GLOBAL.version_with_update.

FR-4.5

@@SESSION.version_with_update and @@LOCAL.version_with_update MUST be rejected because the variable is global only.

FR-4.6

version_with_update MUST NOT be writable. SET commands, including SET @@GLOBAL.version_with_update = ..., MUST be rejected.

FR-4.7

version_with_update MUST NOT be persistable.

FR-4.8

version_with_update MUST NOT be backed by a command-line option.

FR-4.9

A new native SQL function VERSION_WITH_UPDATE() MUST return the full runtime version string.

FR-4.10

VERSION_WITH_UPDATE() MUST be marked unsafe for statement-based binary logging, consistent with VERSION().

FR-5: C API Version Reporting

FR-5.1

mysql_get_server_info() and mysql_get_client_info() MUST continue to return compatibility strings, while mysql_get_server_version() and mysql_get_client_version() MUST continue to return three-part numeric IDs.

FR-5.2

mysql_get_client_info_with_version_update() MUST return the full version compiled into libmysql. mysql_get_server_info_with_version_update() MUST return the full version of the connected server when it is available.

FR-5.3

Callers MUST invoke mysql_get_server_info_with_version_update() only when the connection is command-ready. If it is not command-ready, the function MUST return NULL without issuing an internal query. If the connected server does not support VERSION_WITH_UPDATE(), the function MUST return NULL and MUST NOT make the connection unusable.

FR-6: Upgrade History

The examples below use representative timestamps. Every server-created history entry records its date; new entries also contain maturity and, during initialization, initialize.

Unless a recovery requirement applies, history-transition requirements operate only on a structurally valid document. The server validates the complete document before inspecting its latest entries.

FR-6.1: Version History

Records normal history creation, exact-version restarts, and exact-version transitions within the current compatibility version.

FR-6.1.1 — Create a history entry. If the server creates a new entry in upgrade_history, then it MUST store the running compatibility version in version and add an initial update_history entry containing the running version_with_update, including update 0. maturity and initialize MUST be stored on the upgrade_history entry only.

Before initializing 26.10.0.0, no mysql_upgrade_history file exists.

After initialization:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-25 09:15:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "initialize": true,
      "update_history": [
        {
          "date": "2026-08-25 09:15:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.1.2 — Do not duplicate an exact restart. If the latest upgrade_history entry has the running version, and its last update_history entry has the running version_with_update, then the server MUST leave the file unchanged.

Before restarting 26.10.0.0:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-12 14:30:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-12 14:30:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

After restarting 26.10.0.0, the state is unchanged:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-12 14:30:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-12 14:30:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.1.3 — Record an exact-version transition. If the latest upgrade_history entry has the running version, but its last update_history entry has a different version_with_update, then the server MUST append an entry containing the running version_with_update. This applies to both update upgrades and downgrades.

Before running 26.10.0.0 after 26.10.0.1:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-01 10:00:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {"date": "2026-08-01 10:00:00", "version_with_update": "26.10.0.0"},
        {"date": "2026-08-15 16:45:00", "version_with_update": "26.10.0.1"}
      ]
    }
  ]
}

After restart, .0 is appended again:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-01 10:00:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {"date": "2026-08-01 10:00:00", "version_with_update": "26.10.0.0"},
        {"date": "2026-08-15 16:45:00", "version_with_update": "26.10.0.1"},
        {"date": "2026-08-25 09:30:00", "version_with_update": "26.10.0.0"}
      ]
    }
  ]
}

FR-6.2: Compatibility Handling

Handles valid legacy update_history shapes while preserving existing upgrade_history data.

FR-6.2.1 — Extend a matching legacy entry. If the latest upgrade_history entry has the running version but has no update_history member, then the server MUST add one entry to update_history containing the running version_with_update.

Before restart, the legacy entry has no update_history member:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-06-07 11:20:00",
      "version": "26.10.0",
      "maturity": "LTS"
    }
  ]
}

After restart, that entry is extended in place:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-06-07 11:20:00",
      "version": "26.10.0",
      "maturity": "LTS",
      "update_history": [
        {
          "date": "2026-08-25 09:35:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.2.2 — Record a compatibility-version transition. If the latest upgrade_history entry has a different version from the running server, then the server MUST retain existing entries and append a new entry for the running compatibility version and exact version.

Before starting 26.10.0.0:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2025-10-14 08:50:00",
      "version": "9.7.0",
      "maturity": "LTS"
    }
  ]
}

After starting 26.10.0.0:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2025-10-14 08:50:00",
      "version": "9.7.0",
      "maturity": "LTS"
    },
    {
      "date": "2026-08-25 09:40:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 09:40:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.2.3 — Extend an empty update history. If the latest matching upgrade_history entry has an empty update_history array, then the server MUST retain that entry and append an entry containing the running version_with_update.

Before restart:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-06-19 13:10:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": []
    }
  ]
}

After restart:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-06-19 13:10:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 09:45:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.2.4 — Replace malformed update history entries. If any update_history entry is not an object, has no version_with_update member, or has a non-string version_with_update member, then the server MUST treat the history document as corrupt. It MUST log a warning that the file will be recreated and replace it with a new document containing only the running compatibility version and exact version.

Before restart, the last update_history entry has no version_with_update:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-07-30 09:00:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [{"date": "2026-08-03 11:22:33"}]
    }
  ]
}

After restart, the corrupt document is replaced:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-25 09:50:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 09:50:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.2.5 — Preserve existing entry members. If the server adds the optional update_history member to an existing upgrade_history entry, then it MUST preserve that entry's existing members and their values.

Before starting a matching server:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-01-01 00:00:00",
      "version": "26.10.0",
      "maturity": "INNOVATION"
    }
  ]
}

After start, the existing date, version, and maturity values are unchanged, and update_history is added:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-01-01 00:00:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 09:55:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.2.6 — Replace a non-array update history. If any parent entry has a present update_history member that is not a JSON array—for example, null, an object, a string, number, or boolean—then the server MUST treat the history document as corrupt. It MUST log a warning that the file will be recreated and replace it with a new document containing only the running compatibility version and exact version.

Before restart:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-12 17:05:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": "some_string"
    }
  ]
}

After restart, the corrupt document is replaced:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-25 09:55:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 09:55:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.3: Recovery Cases

Recovers from an empty history or a corrupt history document.

FR-6.3.1 — Start an empty history. If the upgrade_history array is empty, then the server MUST append an entry for the running compatibility version and exact version.

Before starting 26.10.0.0:

{
  "file_format": "1",
  "upgrade_history": []
}

After start:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-25 10:00:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 10:00:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.3.2 — Replace a document containing a malformed parent. If any upgrade_history entry is not an object, or has a missing or non-string version member, then the server MUST treat the history document as corrupt. It MUST log a warning that the file will be recreated and replace it with a new document containing only the running compatibility version and exact version.

Before restart:

{
  "file_format": "1",
  "upgrade_history": ["not an object"]
}

After restart, the corrupt document is replaced:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-25 10:05:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 10:05:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

FR-6.3.3 — Replace a corrupt document. The server MUST treat the history document as corrupt if any of the following is true:

  • JSON parsing fails.
  • The root is not an object.
  • file_format is missing or is not a string.
  • upgrade_history is missing or is not an array.
  • A parent is malformed as defined in FR-6.3.2.
  • A present update_history is not an array.
  • An update-history entry is malformed as defined in FR-6.2.4.

For every corruption case, the server MUST log a warning that the file will be recreated and replace it with a new history document whose upgrade_history array contains only an entry for the running compatibility version and exact version.

Before restart:

{"file_format": "1"}

After restart:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "2026-08-25 10:10:00",
      "version": "26.10.0",
      "maturity": "INNOVATION",
      "update_history": [
        {
          "date": "2026-08-25 10:10:00",
          "version_with_update": "26.10.0.0"
        }
      ]
    }
  ]
}

Non-Functional Requirements

NFR-1: Compatibility Preservation

NFR-1.1

The implementation MUST preserve all existing compatibility-sensitive numeric version semantics.

NFR-1.2

The implementation MUST NOT redefine MYSQL_VERSION_ID or any other compatibility-oriented numeric version encoding to include the update component.

NFR-1.3

The implementation MUST NOT introduce DD version changes solely for update-version reporting.

NFR-1.4

The implementation MUST NOT change persisted DD, table-metadata, or version-gate data solely for update-version reporting.

NFR-1.5

The implementation MUST NOT introduce a protocol version or handshake-layout change solely for update-version reporting.

NFR-1.6

mysql_upgrade_history is an audit file only. The server MUST NOT use its contents to permit, reject, or otherwise change upgrade, downgrade, or DD compatibility decisions.

NFR-2: Behavioral Stability

NFR-2.1

The implementation MUST preserve the existing VERSION() and @@version three-part string shape for every update value. Runtime suffixes remain appended to that compatibility string.

NFR-2.2

The implementation MUST preserve binary compatibility (ABI) for existing client applications. It MUST NOT remove or change existing exported symbols, public C API signatures, or public data-type layouts. Additive APIs are permitted.

NFR-2.3

After a successful retrieval, mysql_get_server_info_with_version_update() MUST return the cached full-version value without issuing another internal query on that connection. The remaining runtime overhead is limited to generated metadata, one additional runtime string, one read-only string variable, and one native SQL function.

NFR-2.4

If the server cannot read or write mysql_upgrade_history, then it MUST log a warning and continue startup. Failure to maintain this audit file MUST NOT abort initialization or server restart.

NFR-3: Validation

NFR-3.1

The implementation MUST provide regression coverage for the full-version SQL surface, C API surfaces, empty/zero/positive update values, invalid update values, and unchanged numeric compatibility behavior.

NFR-3.2

The implementation MUST provide regression coverage that malformed JSON and every defined structural corruption case in mysql_upgrade_history emit the recovery warning and replace the file with only the current version data.

Technical Summary

The implementation separates release identity, which identifies the exact delivered update, from compatibility identity, which continues to identify only the patch-level release line.

Release identity is carried by MYSQL_SERVER_VERSION_WITH_UPDATE, server_version_with_update, VERSION_WITH_UPDATE(), @@version_with_update, mysqld --version-with-update, and the new C APIs.

Compatibility identity is carried by MYSQL_SERVER_VERSION, LIBMYSQL_VERSION, server_version, VERSION(), @@version, mysqld --version, existing C APIs, MYSQL_VERSION_ID, LIBMYSQL_VERSION_ID, MYSQL_NO_DASH_VERSION, and all numeric compatibility checks.

MYSQL_VERSION_EXTRA is appended to both generated strings before runtime suffixes. Thus 9.7.5.12-enterprise-debug is full release identity, while 9.7.5-enterprise-debug is the compatibility display string.

mysql_upgrade_history retains its legacy parent version field and adds an update_history member to each new parent entry. The member is an array of exact releases that ran while that parent was current; each child has date and version_with_update, including update 0 when it ran. The parent retains maturity and, on initialization, initialize.

update_history is an additive JSON member: it does not change the root array, or the names, types, or meanings of existing parent fields in a valid document. Consumers that read those existing fields continue to work unchanged; consumers that enforce an exact JSON schema must allow this optional member. New tools can read update_history to reconstruct exact update-release transitions.

When runtime server_version differs from the latest parent version, the server appends a new parent and its first exact-release child. When it matches, the server compares server_version_with_update with the latest update_history child: an identical restart writes nothing, while an update upgrade or downgrade appends a new child. Because server_version includes applicable runtime suffixes, a suffix transition (for example, -debug to -asan) creates a new parent entry. A matching legacy parent without update_history receives an initial child for the current exact release. Before making any of these decisions, the server validates the entire JSON document: the root structure, every parent, every present update_history array, and every child entry. Any corruption causes a warning and replacement of the audit file with a document containing only the current version data. Existing DD and upgrade checks remain patch-level.

At server startup or initialization, the error log emits a separate System message containing server_version_with_update.

For example, a data directory initialized with 9.7.5.0, upgraded to 9.7.5.1, and then downgraded to 9.7.5.0 records one parent entry and three exact-release children:

{
  "upgrade_history": [
    {
      "date": "t1",
      "version": "9.7.5",
      "maturity": "LTS",
      "initialize": true,
      "update_history": [
        {"date": "t1", "version_with_update": "9.7.5.0"},
        {"date": "t2", "version_with_update": "9.7.5.1"},
        {"date": "t3", "version_with_update": "9.7.5.0"}
      ]
    }
  ]
}

User Interface

Command-Line Version Reporting

mysqld --version continues to print the three-part compatibility runtime version (server_version), including any applicable runtime suffixes.

mysqld --version-with-update prints the four-part runtime version (server_version_with_update), including the update component and the same runtime suffixes. The new option does not change the output or compatibility semantics of mysqld --version.

For example, for a debug build with update 0:

$ ./bin/mysqld --version
./bin/mysqld  Ver 26.10.0-debug for Linux on x86_64 (Source distribution)

$ ./bin/mysqld --version-with-update
./bin/mysqld  Ver 26.10.0.0-debug for Linux on x86_64 (Source distribution)

SQL Interfaces

The following new SQL surfaces are available after this work:

  • @@GLOBAL.version_with_update
  • @@version_with_update
  • VERSION_WITH_UPDATE()

The existingVERSION() and @@version report the three-part runtime compatibility string. VERSION_WITH_UPDATE() and @@version_with_update report the full four-part runtime string.

System Variable Definition

  • NAME: version_with_update
  • VALUES: full runtime version string
  • DEFAULT: server_version_with_update
  • SCOPE: GLOBAL
  • REPLICATED: No
  • DYNAMIC: No
  • PERSIST: NOT PERSISTABLE
  • COMMAND LINE: No
  • DESCRIPTION: Reports the complete server version string including the update component.
  • USAGE: @@GLOBAL.version_with_update, @@version_with_update, performance_schema.global_variables

Native Function Definition

  • NAME: VERSION_WITH_UPDATE()
  • ARGUMENTS: none
  • RETURN TYPE: string
  • EFFECT: read-only reporting
  • DESCRIPTION: Returns the complete version string of the running MySQL Server including the update component.
  • USAGE: SELECT VERSION_WITH_UPDATE();

The full-version C APIs are mysql_get_client_info_with_version_update(), which returns the full version compiled into the client library, and mysql_get_server_info_with_version_update(), which queries the full server version when command-ready and caches a successfully retrieved value.

If the connection is not command-ready or the server does not support VERSION_WITH_UPDATE(), this function returns NULL; existing C APIs continue to return their compatibility values.

Observability

Observable examples:

  • @@version: 9.7.2-enterprise-debug
  • @@version_with_update: 9.7.2.1-enterprise-debug
  • error log: [System] Server version with update: '9.7.2.1-enterprise-debug'

The work adds no status variables, Performance Schema instrumentation keys, mutexes, conditions, threads, or numeric compatibility values.

When mysql_upgrade_history is corrupt, the server logs the warning

Invalid format of file '%s'. The file will be recreated.` 

before replacing the audit file with current version data.

Deployment and Installation

This work updates generated and installed metadata:

  • generated compatibility and full version strings in mysql_version.h
  • generated numeric component macros, including MYSQL_VERSION_UPDATE, in my_config.h
  • Windows VERSIONINFO metadata
  • exported libmysql API symbols

No new installed file or packaging artifact is introduced. Package-facing version values remain compatibility-oriented unless packaging explicitly uses the full-version variable.

CMake reports both identities during configuration: the existing MySQL status line for the compatibility version and a MySQL full version status line for the four-part version.

Protocols

No protocol change is introduced.

The classic MySQL handshake continues to send server_version, preserving the existing three-part version string for old and new clients. The handshake structure and protocol version are unchanged. libmysql retains that value in MYSQL::server_version for mysql_get_server_info(), mysql_get_server_version(), and existing client code.

When command-ready, mysql_get_server_info_with_version_update() obtains VERSION_WITH_UPDATE() through an internal SQL query unless a successful result is already cached in private connection-extension state. This uses existing protocol behavior and does not change the public MYSQL structure layout. If the full version function is unavailable, libmysql leaves the connection usable and the new API returns NULL; a later call may retry the query. If the connection is not command-ready, the API returns NULL without issuing the internal query.

Binary log behavior:

  • human-readable fields derived from server_version remain three-part
  • numeric fields such as original_server_version and immediate_server_version remain patch-level because they use existing three-component conversion

No new event format, protocol version, or handshake-layout rule is introduced.

Upgrade, Downgrade, and Replication Semantics

The update component is descriptive only. It does not change DD versioning, persisted version gates, plugin interface version math, replication policy, or Group Replication compatibility policy.

Low-Level Design Specification

1. Version Source Metadata

MYSQL_VERSION gains the following field:

MYSQL_VERSION_UPDATE=<empty-or-integer>

An empty value identifies update 0. The field is the source of the fourth version component; it does not contribute to any numeric compatibility ID.

2. CMake Validation

cmake/mysql_version.cmake reads MYSQL_VERSION_UPDATE with MYSQL_GET_CONFIG_VALUE(), normalizes an empty value to 0, and validates it using the existing canonical integer rule. The same rule is applied to the major, minor, and patch components:

MYSQL_GET_CONFIG_VALUE("MYSQL_VERSION_UPDATE" UPDATE_VERSION)
IF("${UPDATE_VERSION}" STREQUAL "")
  SET(UPDATE_VERSION 0)
ENDIF()
MYSQL_VALIDATE_VERSION_COMPONENT(UPDATE_VERSION "MYSQL_VERSION_UPDATE")

This accepts 0 and positive integers, and rejects negative values, non-numeric values, and multi-digit leading-zero forms such as 01.

3. Version String Generation

SET(MYSQL_THREE_PART_VERSION
  "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}")
SET(VERSION "${MYSQL_THREE_PART_VERSION}${EXTRA_VERSION}")
SET(MYSQL_VERSION_WITH_UPDATE
  "${MYSQL_THREE_PART_VERSION}.${UPDATE_VERSION}${EXTRA_VERSION}")

VERSION feeds MYSQL_SERVER_VERSION and LIBMYSQL_VERSION; the full value feeds MYSQL_SERVER_VERSION_WITH_UPDATE. MYSQL_VERSION_ID, MYSQL_NO_DASH_VERSION, package-facing compatibility values, and VERSION_SRC remain three-part.

4. Generated Header Content

config.h.cmake generates the version-component macros in my_config.h:

#define MYSQL_VERSION_MAJOR 9
#define MYSQL_VERSION_MINOR 7
#define MYSQL_VERSION_PATCH 2
#define MYSQL_VERSION_UPDATE 1

MYSQL_VERSION_UPDATE is normalized to 0 before my_config.h is generated when the source value is empty.

include/mysql_version.h.in retains the compatibility string and adds the full release string:

#define MYSQL_SERVER_VERSION "9.7.2-enterprise"
#define MYSQL_SERVER_VERSION_WITH_UPDATE "9.7.2.1-enterprise"

LIBMYSQL_VERSION remains the three-part compatibility string; mysql_version.h does not define individual component macros.

5. Runtime Version Composition

sql/mysqld.cc defines both runtime buffers and uses one suffix-appending helper for each:

char server_version[SERVER_VERSION_LENGTH];
char server_version_with_update[SERVER_VERSION_LENGTH];

append_runtime_version_suffixes(server_version, MYSQL_SERVER_VERSION);
append_runtime_version_suffixes(server_version_with_update,
                                MYSQL_SERVER_VERSION_WITH_UPDATE);

Both buffers receive the same runtime suffixes. server_version remains the compatibility value used by existing server code and the classic handshake; server_version_with_update is used by the full-version SQL interfaces, C APIs, and the upgrade-history audit trail.

mysqld --version initializes and prints server_version, preserving its three-part compatibility output. mysqld --version-with-update initializes and prints server_version_with_update, which includes the fourth update component. Both commands print the applicable runtime suffixes.

During normal startup and initialization, init_common_variables() logs ER_SERVER_VERSION_INFO at System level immediately after the corresponding existing startup message. The new message is:

Server version with update: '%s'

It receives only server_version_with_update. Emitting it as a separate event leaves existing startup-event text unchanged for error-log parsers.

6. C API Full-Version Retrieval

The classic handshake continues to send server_version. Consequently, MYSQL::server_version continues to back mysql_get_server_info() and no handshake parsing or public MYSQL layout change is required.

mysql_get_client_info_with_version_update() returns the generated MYSQL_SERVER_VERSION_WITH_UPDATE value. On a command-ready call to mysql_get_server_info_with_version_update(), libmysql issues:

SELECT VERSION_WITH_UPDATE()

Only a successfully retrieved result is cached in private MYSQL_EXTENSION state. The cache contains a string pointer and is cleared when a new handshake is parsed. Subsequent calls return a cached value without issuing another query; failed retrievals are not cached and may be retried.

If the connection is not command-ready, the server lacks VERSION_WITH_UPDATE(), or the query cannot produce a value, the server-info function returns NULL. In the unsupported-server case, the connection remains usable and existing C APIs retain their compatibility behavior.

The public declarations are in include/mysql.h and include/mysql.h.pp; the symbols are exported by libmysql/CMakeLists.txt:

const char *mysql_get_server_info_with_version_update(MYSQL *mysql);
const char *mysql_get_client_info_with_version_update(void);

7. SQL System Variable

sql/sys_vars.cc defines version_with_update as a read-only global Sys_var_charptr backed by server_version_with_update. It is visible through @@GLOBAL.version_with_update, @@version_with_update, and performance_schema.global_variables. It has no command-line option and is not persistable.

8. Native SQL Function

Item_func_version_with_update is a static string function registered as:

{"VERSION_WITH_UPDATE", SQL_FN(Item_func_version_with_update, 0)},

It returns server_version_with_update and marks statements unsafe for statement-based binary logging during itemization, like VERSION().

9. Windows Version Metadata

cmake/versioninfo.rc.in uses UPDATE_VERSION as the fourth component of FILEVERSION, PRODUCTVERSION, and their corresponding string values. This metadata always includes update 0 for a normal patch release.

10. Compatibility-Sensitive Code Paths

All current version checks remain three-part:

  • MYSQL_VERSION_ID and LIBMYSQL_VERSION_ID
  • plugin ABI/interface macros
  • DD, upgrade, and table metadata checks
  • executable-comment gates
  • replication and Group Replication numeric compatibility values
  • MYSQL_NO_DASH_VERSION

No code path compares or persists an update-aware numeric version ID.

11. Upgrade History Audit Trail

dd::upgrade::update_upgrade_history_file() retains the existing mysql_upgrade_history parent entry fields. A new parent version creates a new parent entry with the legacy compatibility value in version; its update_history array records every exact runtime release that ran while that parent was current. initialize and maturity remain on the parent only:

"version": "26.10.0",
"update_history": [
  {"version_with_update": "26.10.0.0"},
  {"version_with_update": "26.10.0.1"}
]

update_history is an additive optional member. In a valid document, it preserves the root array and all existing parent fields, so consumers that read only the parent history continue to work. Update-aware consumers can read the nested array for exact release transitions; tools with strict JSON schemas must allow this optional member.

Update 0 is recorded like every other exact release. For the latest parent with a matching version, duplicate detection compares the last update_history value with server_version_with_update; a different full version, including an update downgrade, appends a new child entry. A base version or runtime-suffix change creates a new parent entry. Legacy parents without update_history receive an initial current exact-release child. Before using the latest entries, the server validates the complete document: the root, every parent and its string version, every present update_history array, and every child object with a string version_with_update. A malformed JSON document or any structural violation logs Invalid format of file '%s'. The file will be recreated. and is replaced with a document containing only the current parent and child entries. A present empty array remains valid and receives the current exact-release child. The existing 1000-entry cap applies to parent entries only; no separate cap is applied to update_history. The file is not used for upgrade or downgrade validation.

For example, an initialized 26.10.0.0 data directory that runs .1 and is then downgraded to .0 is recorded as:

{
  "file_format": "1",
  "upgrade_history": [
    {
      "date": "t1",
      "version": "26.10.0",
      "maturity": "LTS",
      "initialize": true,
      "update_history": [
        {"date": "t1", "version_with_update": "26.10.0.0"},
        {"date": "t2", "version_with_update": "26.10.0.1"},
        {"date": "t3", "version_with_update": "26.10.0.0"}
      ]
    }
  ]
}

The write path is equivalent to the following pseudocode:

read and parse mysql_upgrade_history
validate the complete document:
    root is an object with string file_format and array upgrade_history
    every parent is an object with string version
    every present update_history is an array
    every update-history entry is an object with string version_with_update
if parsing or validation fails:
    log "Invalid format of file '%s'. The file will be recreated."
    create an empty upgrade_history document
    append a parent containing {date, version, maturity, initialize if applicable}
    append its first {date, version_with_update} child, including update 0
    write document
    return

if upgrade_history is empty:
    append a parent containing {date, version, maturity, initialize if applicable}
    append its first {date, version_with_update} child, including update 0
    write document
    return

last_parent = last upgrade_history entry
if last_parent.version == server_version:
    if last_parent.update_history is an array:
        last_update = last update_history entry
        if last_update.version_with_update == server_version_with_update:
            return  # exact-version restart
        # An empty child array reaches this append path.
        append {date, version_with_update} to last_parent.update_history
        write document
        return

    if update_history is missing:
        add an array containing {date, version_with_update}
        write document
        return

cap parent entries when required
append a parent containing {date, version, maturity, initialize if applicable}
append its first {date, version_with_update} child, including update 0
write document