mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
remove one str->int implementation (we still have 10+ more), replace with a convenience template that calls my_strntoll_8bit()
376 lines
13 KiB
C++
376 lines
13 KiB
C++
/*
|
|
Copyright (c) 2025 MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA.
|
|
*/
|
|
|
|
#ifndef RPL_INFO_FILE_H
|
|
#define RPL_INFO_FILE_H
|
|
|
|
#include <cstdint> // uintN_t
|
|
#include <functional> // superclass of Info_file::Mem_fn
|
|
#include <my_sys.h> // IO_CACHE, FN_REFLEN, ...
|
|
|
|
|
|
/** Helpers for reading and writing integers to and from @ref IO_CACHE
|
|
TODO: Other components, if you find these useful,
|
|
feel free to move these out of this Replication module.
|
|
*/
|
|
namespace Int_IO_CACHE
|
|
{
|
|
/** Number of fully-utilized decimal digits plus
|
|
* the partially-utilized digit (e.g., the 2's place in "2147483647")
|
|
* The sign, if signed (:
|
|
*/
|
|
template<typename I> static constexpr size_t BUF_SIZE=
|
|
std::numeric_limits<I>::digits10 + 1 + std::numeric_limits<I>::is_signed;
|
|
|
|
/**
|
|
@ref IO_CACHE (reading one line with the `\n`) version of std::from_chars()
|
|
@tparam I integer type
|
|
@return `false` if the line has parsed successfully or `true` if error
|
|
*/
|
|
template<typename I> static bool from_chars(IO_CACHE *file, I &value)
|
|
{
|
|
int error;
|
|
/**
|
|
+2 for the terminating `\n\0`
|
|
(They are ignored, but my_b_gets() includes them.)
|
|
*/
|
|
char buf[BUF_SIZE<I> + 2];
|
|
/// includes the `\n` but excludes the `\0`
|
|
size_t length= my_b_gets(file, buf, sizeof(buf));
|
|
if (!length) // EOF
|
|
return true;
|
|
char *end= &(buf[length]);
|
|
longlong val= my_strtoll10(buf, &end, &error);
|
|
switch (error) {
|
|
case -1:
|
|
if (!std::numeric_limits<I>::is_signed)
|
|
return true;
|
|
[[fallthrough]];
|
|
case 0:
|
|
/*TODO
|
|
This upper range check is not needed when using type-
|
|
specific variants of a safe string-to-integer converter
|
|
(e.g., std::from_chars() when all platforms support it).
|
|
*/
|
|
if (*end == '\n' && value <= std::numeric_limits<I>::max())
|
|
{
|
|
value= static_cast<I>(val);
|
|
return false;
|
|
}
|
|
[[fallthrough]];
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
/**
|
|
Convenience overload of from_chars(IO_CACHE *, I &) for `operator=` types
|
|
@tparam I inner integer type
|
|
@tparam T wrapper type
|
|
*/
|
|
template<typename I, class T> static bool from_chars(IO_CACHE *file, T *self)
|
|
{
|
|
I value;
|
|
if (from_chars(file, value))
|
|
return true;
|
|
(*self)= value;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@ref IO_CACHE (writing *without* a `\n`) version of std::to_chars()
|
|
@tparam I (inner) integer type
|
|
*/
|
|
template<typename I> static void to_chars(IO_CACHE *file, I value)
|
|
{
|
|
char buf[BUF_SIZE<I>];
|
|
/*TODO:
|
|
* my_b_printf() needs updates and so doesn't
|
|
support `long long`s at the moment.
|
|
* We can avoid format parsing by expanding
|
|
int10_to_str() if not supporting std::to_chars().
|
|
*/
|
|
int len= std::numeric_limits<I>::is_signed ?
|
|
snprintf(buf, BUF_SIZE<I>, "%lld", static_cast<long long>(value)) :
|
|
snprintf(buf, BUF_SIZE<I>, "%llu", static_cast<unsigned long long>(value))
|
|
;
|
|
DBUG_ASSERT(len > 0);
|
|
my_b_write(file, reinterpret_cast<const uchar *>(buf), len);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
This common superclass of @ref Master_info_file and
|
|
@ref Relay_log_info_file provides them common code for saving
|
|
and loading values in their MySQL line-based sections.
|
|
As only the @ref Master_info_file has a MariaDB `key=value`
|
|
section with a mix of explicit and `DEFAULT`-able values,
|
|
code for those are in @ref Master_info_file instead.
|
|
|
|
Each value is an instance of an implementation of the
|
|
@ref Info_file::Persistent interface. For convenience, they also have
|
|
assignment and implicit conversion operators for their underlying types.
|
|
|
|
C++ templates enables code reuse for those implementation structs, but
|
|
templates are not suitable for the conventional header/implementation split.
|
|
Thus, this and derived files are header-only units (methods are `inline`).
|
|
Other files may include these files directly.
|
|
[C++20 modules](https://en.cppreference.com/w/cpp/language/modules.html)
|
|
can supercede the header-only design as well as headers' `#include` guards.
|
|
|
|
@note
|
|
It's an old inconsistency that @ref Master_info_file's line count includes
|
|
the line-count "value" while @ref Relay_log_info_file's excludes it.
|
|
This is reason that Info_file::load_from_file() and
|
|
Info_file::save_to_file() defines the line-count lines differently -
|
|
It is so the two subclasses can implement either interpretation
|
|
on top of the indifferent common code in this superclass.
|
|
*/
|
|
struct Info_file
|
|
{
|
|
IO_CACHE file;
|
|
|
|
|
|
/// Persistence interface for an unspecified item
|
|
struct Persistent
|
|
{
|
|
virtual ~Persistent()= default;
|
|
// for save_to_file()
|
|
virtual bool is_default() { return false; }
|
|
/// @return `true` if the item is mandatory and couldn't provide a default
|
|
virtual bool set_default() { return true; }
|
|
/** set the value by reading a line from the IO and consume the `\n`
|
|
@return `false` if the line has parsed successfully or `true` if error
|
|
@post is_default() is `false`
|
|
*/
|
|
virtual bool load_from(IO_CACHE *file)= 0;
|
|
/** write the *effective* value to the IO **without** a `\n`
|
|
(The caller will separately determine how
|
|
to represent using the default value.)
|
|
*/
|
|
virtual void save_to(IO_CACHE *file)= 0;
|
|
};
|
|
|
|
/** Integer Value
|
|
@tparam I signed or unsigned integer type
|
|
@see Master_info_file::Optional_int_value
|
|
version with `DEFAULT` (not a subclass)
|
|
*/
|
|
template<typename I> struct Int_value: Persistent
|
|
{
|
|
I value;
|
|
operator I() { return value; }
|
|
auto &operator=(I value)
|
|
{
|
|
this->value= value;
|
|
return *this;
|
|
}
|
|
virtual bool load_from(IO_CACHE *file) override
|
|
{ return Int_IO_CACHE::from_chars(file, value); }
|
|
virtual void save_to(IO_CACHE *file) override
|
|
{ return Int_IO_CACHE::to_chars(file, value); }
|
|
};
|
|
|
|
/// Null-Terminated String (usually file name) Value
|
|
template<size_t size= FN_REFLEN> struct String_value: Persistent
|
|
{
|
|
char buf[size];
|
|
/**
|
|
Reads should consider this an immutable '\0'-terminated string (especially
|
|
with @ref Optional_path_value where a `DEFAULT` may substitute the value).
|
|
Writes may prefers to directly address the underlying @ref buf.
|
|
*/
|
|
virtual operator const char *() { return buf; }
|
|
/// @param other non-`nullptr` `\0`-terminated string
|
|
auto &operator=(const char *other)
|
|
{
|
|
strmake(buf, other, size-1);
|
|
return *this;
|
|
}
|
|
virtual bool load_from(IO_CACHE *file) override
|
|
{
|
|
size_t length= my_b_gets(file, buf, size);
|
|
if (!length) // EOF
|
|
return true;
|
|
/// If we stopped on a newline, kill it.
|
|
char &last_char= buf[length-1];
|
|
if (last_char == '\n')
|
|
{
|
|
last_char= '\0';
|
|
return false;
|
|
}
|
|
/*
|
|
Consume the lost line break,
|
|
or error if the line overflows the @ref buf.
|
|
*/
|
|
return my_b_get(file) != '\n';
|
|
}
|
|
virtual void save_to(IO_CACHE *file) override
|
|
{
|
|
const char *buf= *this;
|
|
my_b_write(file, reinterpret_cast<const uchar *>(buf), strlen(buf));
|
|
}
|
|
};
|
|
|
|
|
|
virtual ~Info_file()= default;
|
|
virtual bool load_from_file()= 0;
|
|
virtual void save_to_file()= 0;
|
|
|
|
protected:
|
|
|
|
/**
|
|
std::Mem_fn()-like nullable replacement for
|
|
[member pointer upcasting](https://wg21.link/P0149R3)
|
|
*/
|
|
struct Mem_fn: std::function<Persistent &(Info_file *self)>
|
|
{
|
|
/// Null Constructor
|
|
Mem_fn(std::nullptr_t null= nullptr):
|
|
std::function<Persistent &(Info_file *)>(null) {}
|
|
/** Non-Null Constructor
|
|
@tparam T CRTP subclass of Info_file
|
|
@tparam M @ref Persistent subclass of the member
|
|
@param pm member pointer
|
|
*/
|
|
template<class T, typename M> Mem_fn(M T::* pm):
|
|
std::function<Persistent &(Info_file *)>(
|
|
[pm](Info_file *self) -> Persistent &
|
|
{ return self->*static_cast<M Info_file::*>(pm); }
|
|
) {}
|
|
};
|
|
|
|
/**
|
|
(Re)load the MySQL line-based section from the @ref file
|
|
@param value_list
|
|
List of wrapped member pointers to values. The first element must be a
|
|
file name @ref String_value to be unambiguous with the line-count line.
|
|
@param default_line_count
|
|
We cannot simply read lines until EOF as all versions
|
|
of MySQL/MariaDB may generate more lines than needed.
|
|
Therefore, starting with MySQL/MariaDB 4.1.x for @ref Master_info_file and
|
|
5.6.x/10.0.x for @ref Relay_log_info_file, the first line of the file is
|
|
the number of one-line-per-value lines (excluding itself) in the file.
|
|
This parameter specifies the number of effective lines before those
|
|
versions (i.e., not counting the line-count line if it was to have one),
|
|
where the first line is a filename with extension
|
|
(either contains a `.` or is entirely empty) rather than an integer.
|
|
@return `false` if the file has parsed successfully or `true` if error
|
|
*/
|
|
template<size_t size> bool load_from_file(
|
|
const Mem_fn (&value_list)[size],
|
|
size_t default_line_count= 0
|
|
) { return load_from_file(value_list, size, default_line_count); }
|
|
/**
|
|
Flush the MySQL line-based section to the @ref file
|
|
@param value_list List of wrapped member pointers to values.
|
|
@param total_line_count
|
|
The number of lines to describe the file as on the first line of the file.
|
|
If this is larger than `size`, suffix the file with empty
|
|
lines until the line count (*including* the line-count line) is this many.
|
|
(No correction is done if it is less than `size` + 1 for the line-count.)
|
|
This reservation provides compatibility
|
|
with older versions of MySQL and MariaDB.
|
|
*/
|
|
template<size_t size> void save_to_file(
|
|
const Mem_fn (&value_list)[size],
|
|
size_t total_line_count= size
|
|
) { return save_to_file(value_list, size, total_line_count); }
|
|
|
|
private:
|
|
bool
|
|
load_from_file(const Mem_fn *values, size_t size, size_t default_line_count)
|
|
{
|
|
int val;
|
|
/**
|
|
The first row is temporarily stored in the first value. If it is a line
|
|
count and not a log name (new format), the second row will overwrite it.
|
|
*/
|
|
auto &line1= dynamic_cast<String_value<> &>(values[0](this));
|
|
if (line1.load_from(&file))
|
|
return true;
|
|
char *end= str2int(line1.buf, sizeof(line1.buf), 10, &val);
|
|
/**
|
|
If this first line was not a number - the line count,
|
|
then it was the first value for real,
|
|
so the for loop should then skip over it, the index 0 of the list.
|
|
*/
|
|
size_t i= !end || *end != '\0';
|
|
/*
|
|
Set the default after parsing: While std::from_chars() does not replace
|
|
the output if it failed, it does replace if the line is not fully spent.
|
|
*/
|
|
size_t line_count= i ? default_line_count: static_cast<size_t>(val);
|
|
for (; i < line_count; ++i)
|
|
{
|
|
int c;
|
|
if (i < size) // line known in the `value_list`
|
|
{
|
|
const Mem_fn &pm= values[i];
|
|
if (pm)
|
|
{
|
|
if (pm(this).load_from(&file))
|
|
return true;
|
|
continue;
|
|
}
|
|
}
|
|
/*
|
|
Count and discard unrecognized lines.
|
|
This is especially to prepare for @ref Master_info_file for MariaDB 10.0+,
|
|
which reserves a bunch of lines before its unique `key=value` section
|
|
to accomodate any future line-based (old-style) additions in MySQL.
|
|
(This will make moving from MariaDB to MySQL easier by not
|
|
requiring MySQL to recognize MariaDB `key=value` lines.)
|
|
*/
|
|
while ((c= my_b_get(&file)) != '\n')
|
|
if (c == my_b_EOF)
|
|
return true; // EOF already?
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void save_to_file(const Mem_fn *values, size_t size, size_t total_line_count)
|
|
{
|
|
my_b_seek(&file, 0);
|
|
/*
|
|
If the new contents take less space than the previous file contents,
|
|
then this code would write the file with unerased trailing garbage lines.
|
|
But these garbage don't matter thanks to the number
|
|
of effective lines in the first line of the file.
|
|
*/
|
|
Int_IO_CACHE::to_chars(&file, total_line_count);
|
|
my_b_write_byte(&file, '\n');
|
|
for (size_t i= 0; i < size; ++i)
|
|
{
|
|
const Mem_fn &pm= values[i];
|
|
if (pm)
|
|
pm(this).save_to(&file);
|
|
my_b_write_byte(&file, '\n');
|
|
}
|
|
/*
|
|
Pad additional reserved lines:
|
|
(1 for the line count line + line count) exclusive -> max line inclusive
|
|
= line count exclusive <- max line exclusive
|
|
*/
|
|
while (--total_line_count > size)
|
|
my_b_write_byte(&file, '\n');
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|