Documentation
¶
Overview ¶
Package utils contains some common utilities used by all other packages.
Index ¶
- Constants
- func Abs[T Signed](n T) T
- func AuxTableName(tableName, suffix string) string
- func CanonicalInteger(s string) (string, bool)
- func CheckpointTableName(tableName string) string
- func CloseAndLog(closer Closer)
- func CloseAndLogWithContext(ctx context.Context, closer ContextCloser)
- func CompareMySQLVersions(a, b string) int
- func DBLevelGrantCoversSchema(grant, schemaName string) bool
- func DBLevelGrantHasAny(grant, schemaName string, privs ...string) bool
- func DBLevelGrantName(grant, schemaName string) (string, bool)
- func DBLevelGrantNamesLackingSet(grants []string, schemaName string) []string
- func DBLevelGrantsAll(grants []string, schemaName string, ok func(grant string) bool) bool
- func DBLevelGrantsCoverSchema(grants []string, schemaName string) bool
- func EstimateRenderedChunkSize(rows [][]any) uint64
- func EstimateRenderedRowSize(values []any) int
- func ForeignKeyProbeName(tableName string, i int) string
- func ForeignKeyProbeParentName(tableName string, i int) string
- func ForeignKeyProbeTableName(tableName string) string
- func FormatMySQLDouble(f float64) string
- func FormatMySQLFloat(f float32) string
- func FormatScaledDecimal(value *big.Int, scale int) string
- func GlobalGrantHasAny(grant string, privs ...string) bool
- func GlobalGrantNamesAny(grant string, privs ...string) bool
- func HashKey(key []any) string
- func IsDecimalNumber(text string) bool
- func IsEnumOrSetType(mysqlType string) bool
- func IsEnumType(mysqlType string) bool
- func IsSetType(mysqlType string) bool
- func JSONEqual(a, b string) (equal, valid bool)
- func MySQLLikeMatch(pattern, name string) bool
- func NewForeignKeyName(tableName, name string) string
- func NewTableName(tableName string) string
- func OldTableName(tableName string) string
- func OldTableNameWithTimestamp(tableName, timestamp string) string
- func ParseEnumSetElements(mysqlType string) ([]string, error)
- func ParseSizeNumber(s string) (uint64, error)
- func Percentile[T cmp.Ordered](values []T, p int) T
- func ProcedureGrantHasAny(grant, schemaName, procName string, privs ...string) bool
- func QuoteEnumSetMember(member string) string
- func RenamedForeignKeyName(name, from, to string) string
- func RoundDecimal(text string, scale int) (value *big.Int, exact, ok bool)
- func ShadowingGrantsNote(grants []string, schemaName string) string
- func StringContainsAll(s string, substrings ...string) bool
- func TopologicalOrder[T comparable](nodes []T, dependsOn map[T][]T) []T
- func TruncateTableName(tableName string, suffixLength int) string
- func UnsupportedIdentifierError(kind, name string) error
- func ValidUTF8MB3(s string) bool
- func WithCancelGrace(parent context.Context, grace time.Duration) (context.Context, context.CancelFunc)
- type Closer
- type ContextCloser
- type Signed
- type Temporal
Constants ¶
const ( PrimaryKeySeparator = "-#-" // used to hash a composite primary key // MaxTableNameLength is the maximum table name length in MySQL. MaxTableNameLength = 64 )
const ( // NameFormatTimestamp is the time.Format layout used in the timestamped // _<table>_old_<timestamp> name when SkipDropAfterCutover is set. NameFormatTimestamp = "20060102_150405" )
Variables ¶
This section is empty.
Functions ¶
func Abs ¶ added in v0.18.0
func Abs[T Signed](n T) T
Abs returns the absolute value of n. The standard library has no integer abs (math.Abs is float64-only), so this avoids a float round trip.
Like any two's-complement abs, it overflows for the most negative value of T: Abs(math.MinInt64) is math.MinInt64.
func AuxTableName ¶ added in v0.13.0
AuxTableName builds a deterministic auxiliary table name for the given original table name and suffix (e.g. "_chkpnt", "_new", "_old"). The returned name is `_<table><suffix>`, with the table-name portion deterministically truncated when needed and the result hard-capped at MySQL's 64-character identifier limit (a defensive guard against an abusively long suffix; in practice callers pass small suffixes).
Truncation is deterministic: the same (tableName, suffix) input always produces the same output. Two distinct table names that share a long common prefix can collide; callers must record the original table name out-of-band (e.g. in the checkpoint) so collisions can be detected.
func CanonicalInteger ¶ added in v0.18.0
CanonicalInteger returns the decimal integer literal s in the form MySQL renders it as text: no '+' sign, no leading zeros, and "0" for negative zero. So "+007" is "7", "-0" is "0", and "-00123" is "-123". It accepts an optional sign followed by one or more ASCII digits, of any length, and returns false for anything else (a decimal point, an exponent, spaces).
func CheckpointTableName ¶ added in v0.13.0
CheckpointTableName returns the auxiliary checkpoint table name for the given original table.
func CloseAndLog ¶ added in v0.10.2
func CloseAndLog(closer Closer)
CloseAndLog closes a resource and logs any error. This is useful for defer statements where the error cannot be meaningfully handled except by logging. Example: defer utils.CloseAndLog(db)
func CloseAndLogWithContext ¶ added in v0.10.2
func CloseAndLogWithContext(ctx context.Context, closer ContextCloser)
CloseAndLogWithContext closes a resource that requires context and logs any error. This is useful for defer statements on resources like table locks. Example: defer utils.CloseAndLogWithContext(ctx, lock)
func CompareMySQLVersions ¶ added in v0.19.0
CompareMySQLVersions compares two dotted MySQL versions numerically, returning -1, 0 or 1. Anything after the numeric part (8.0.28-log) is ignored, and a missing component counts as 0.
func DBLevelGrantCoversSchema ¶ added in v0.15.0
DBLevelGrantCoversSchema reports whether a single SHOW GRANTS line is a database-level grant that confers the privileges spirit needs on schemaName. Unlike a literal substring match, it expands MySQL wildcard patterns in the granted database name (see MySQLLikeMatch), so a grant on `strata_%`.* is recognized as covering strata_boardgames_sharded_n80. The previous literal match handled only exact and escaped-underscore database names.
func DBLevelGrantHasAny ¶ added in v0.18.0
DBLevelGrantHasAny reports whether a single SHOW GRANTS line is a database-level grant whose database name pattern matches schemaName (see MySQLLikeMatch) and that confers ALL PRIVILEGES or any privilege in privs.
func DBLevelGrantName ¶ added in v0.18.0
DBLevelGrantName returns the database name of a single SHOW GRANTS line if it is a database-level grant whose name pattern matches schemaName (see MySQLLikeMatch), with the doubled backquotes SHOW GRANTS writes undone. The name is returned as granted, so a pattern keeps its wildcards and escapes.
SHOW GRANTS prints one line per mysql.db row, and MySQL applies only one row to a schema, not the union of every row whose name matches it: an exact-name row can shadow a pattern row, depending on the order the grants were created. Callers that need a privilege on the schema can group the matching lines by this name and require the privilege on every name.
func DBLevelGrantNamesLackingSet ¶ added in v0.19.0
DBLevelGrantNamesLackingSet returns, sorted, the database-level granted names that match schemaName but do not carry the full set spirit needs (see DBLevelGrantCoversSchema) on any of their lines.
func DBLevelGrantsAll ¶ added in v0.19.0
DBLevelGrantsAll reports whether ok holds on every database-level grant name that matches schemaName, where a name holds if ok is true for any of its SHOW GRANTS lines. It returns false if no database-level grant matches.
MySQL applies one database-level grant (mysql.db row) to a schema, not the union of every row whose name matches it, and SHOW GRANTS does not show which one. An exact-name row can shadow a pattern row, so the matching lines are grouped by granted name (see DBLevelGrantName) and every name must satisfy ok.
func DBLevelGrantsCoverSchema ¶ added in v0.19.0
DBLevelGrantsCoverSchema reports whether the database-level grants in grants (SHOW GRANTS lines) confer the privileges spirit needs on schemaName (see DBLevelGrantCoversSchema) on every matching granted name (see DBLevelGrantsAll). It returns false if no database-level grant matches.
func EstimateRenderedChunkSize ¶ added in v0.18.0
EstimateRenderedChunkSize estimates the size of a chunk's rows, for the memory-based dynamic chunker: the sum of EstimateRenderedRowSize over every row. It is not exact Go heap accounting (it is the payload plus quotes and separators), but it is good enough to servo chunk row-count toward a byte budget: the servo cares about the relative size of chunks, and any consistent measure converges. It is cheap because the rows are already in hand.
Every row counts as at least 2 bytes (its parentheses), even one whose values are all empty. This keeps the sum non-zero for any chunk that has rows, so a zero-byte total unambiguously means an empty (gap) chunk (see dynamicChunkSizer.feedbackBytes).
func EstimateRenderedRowSize ¶ added in v0.18.0
EstimateRenderedRowSize estimates the size in bytes of a row's values as they will be rendered into a VALUES clause. It does not need to be precise: the statement budget it feeds (applier.MaxStatementSizeBytes, 1 MiB) sits ~64x below a typical max_allowed_packet, so the estimate only has to be the right order of magnitude to keep a statement well clear of the wire limit.
The applier uses it to cut chunklets, the copier (via EstimateRenderedChunkSize) to size chunks, and callers that batch rows before handing them to Apply to bound a batch by the same measure — the checksum's chunk repair does this.
It does need to be cheap. It runs on every value of every copied row, once per row on top of the rendering writeChunklet does anyway, so it is pure overhead on the hottest client-side path. The previous implementation measured len(fmt.Sprintf("%v", value)), which was neither cheap nor accurate: a text-protocol Scan into *any hands back []byte for string, temporal and DECIMAL columns, and %v renders a []byte as "[49 50 51 …]" — roughly four characters per byte. That cost ~2.2us and ~12 allocations per row and over-estimated by ~2.7x, so chunklets were being cut well short of the budget they were supposed to fill. A type switch is ~290x cheaper, allocates nothing, and lands much closer to what datum.String() actually emits.
func ForeignKeyProbeName ¶ added in v0.19.0
ForeignKeyProbeName returns the name of the i-th foreign key of the probe table of the given original table. Foreign key names are unique per schema, so the name is derived from the table's.
func ForeignKeyProbeParentName ¶ added in v0.19.0
ForeignKeyProbeParentName returns the name of the copy of the i-th table the foreign keys of the given original table reference, for the probe.
func ForeignKeyProbeTableName ¶ added in v0.19.0
ForeignKeyProbeTableName returns the name of the table the cutover probes the foreign keys of the given original table on.
func FormatMySQLDouble ¶ added in v0.18.0
FormatMySQLDouble renders a double the way MySQL does when it converts one to text with no fixed number of decimals (my_gcvt in strings/dtoa.cc), which is the form SHOW CREATE TABLE reports a float or double column's DEFAULT in.
The digits are the shortest decimal that round-trips to the same double, at most 17 of them. They are laid out in fixed notation unless the exponent makes that longer than MySQL allows: with decpt the position of the decimal point (value = 0.d1d2… × 10^decpt), fixed notation is used while -14 <= decpt <= 15, or when decpt > 15 but the digits already run past the decimal point. Otherwise it is exponent notation in MySQL's own spelling: no '+' on a positive exponent and no padding, with the '.' omitted when there is a single digit. Negative zero is "0". Verified against MySQL 8.0.43:
1e2 -> 100 1.50 -> 1.5 0.0000001 (1e-7) -> 0.0000001 1e-15 -> 0.000000000000001 1e-16 -> 1e-16 0.0000000000000001234 -> 1.234e-16 100000000000000 (1e14)-> 100000000000000 1e15 -> 1e15 1234567890123456 -> 1.234567890123456e15 1234567890123456.7 -> 1234567890123456.8 123456789012345678 -> 1.2345678901234568e17 1.5e300 -> 1.5e300 -0.0 -> 0
func FormatMySQLFloat ¶ added in v0.18.0
FormatMySQLFloat renders a single-precision float the way MySQL does: like FormatMySQLDouble, but with at most 6 significant digits (FLT_DIG), correctly rounded from the float's exact value, trailing zeros dropped. Verified against MySQL 8.0.43:
0.1 -> 0.1 1.23456789 -> 1.23457 1234567 -> 1234570 16777217 -> 16777200 1e-45 -> 1.4013e-45 3.4e38 -> 3.4e38
func FormatScaledDecimal ¶ added in v0.18.0
FormatScaledDecimal renders value × 10^-scale with exactly scale fractional digits, the way MySQL reports a DECIMAL(M,D) value: 124 at scale 2 is "1.24", 5 at scale 2 is "0.05", -124 at scale 2 is "-1.24", and any value at scale 0 is its plain digits.
func GlobalGrantHasAny ¶ added in v0.18.0
GlobalGrantHasAny reports whether a single SHOW GRANTS line is a global (*.*) grant of ALL PRIVILEGES or of any privilege in privs. ALL PRIVILEGES counts because it includes every static privilege, so privs should contain at least one static privilege for it to be meaningful.
func GlobalGrantNamesAny ¶ added in v0.18.0
GlobalGrantNamesAny reports whether a single SHOW GRANTS line is a global (*.*) grant that names any privilege in privs explicitly. Unlike GlobalGrantHasAny, ALL PRIVILEGES does not count. Use it for dynamic privileges such as SHOW_ROUTINE: GRANT ALL includes a dynamic privilege only if it was registered when the grant was issued, so a global ALL grant made before an upgrade can lack it, and SHOW GRANTS still prints ALL PRIVILEGES.
func HashKey ¶
HashKey converts a composite key into a string so that it can be used as a map key. The result is an opaque map key only — it is never reversed back into SQL (the applier builds DELETE literals from the original typed values, not from this string). Each component is escaped before joining so that values containing the separator cannot collide with key boundaries: without escaping, ("a-#-b", "c") and ("a", "b-#-c") would hash to the same string, causing one row's buffered change to silently overwrite another's.
func IsDecimalNumber ¶ added in v0.18.0
IsDecimalNumber reports whether text is a decimal number in the form MySQL reads from a literal or a string: an optional sign, digits with an optional fraction, and an optional exponent. At least one digit is required, so "-" and "." are not numbers. No surrounding whitespace is accepted; callers that want MySQL's string conversion trim it first.
func IsEnumOrSetType ¶ added in v0.18.0
IsEnumOrSetType reports whether a MySQL column type string is an ENUM or SET.
func IsEnumType ¶ added in v0.18.0
IsEnumType reports whether a MySQL column type string (as reported by information_schema.COLUMNS.column_type) is an ENUM, e.g. "enum('a','b')".
func IsSetType ¶ added in v0.18.0
IsSetType reports whether a MySQL column type string is a SET, e.g. "set('a','b')".
func JSONEqual ¶ added in v0.18.0
JSONEqual reports whether a and b are the same JSON document, and whether both are valid JSON at all. Object keys compare in any order, whitespace is ignored, and a number compares by the exact value MySQL stores for it in a JSON value: an integer literal that fits in an int64 or a uint64 is that integer, every other number is the nearest double (an integer literal too large for a uint64 included: 12345678901234567890123 is stored as 1.2345678901234568e22). So 9007199254740992 and 9007199254740993 are different documents, 1e2 and 100.0 are the same, as are 1 and 1.0, and the integer 9007199254740993 differs from the double 9007199254740992.0 — and from 9007199254740993.0, which is that same double. Decoding everything to float64 (encoding/json's default), or comparing an integer against a double as float64, folds integers past 2^53 together, which hides a change MySQL stores and reports.
func MySQLLikeMatch ¶ added in v0.15.0
MySQLLikeMatch reports whether name matches the given MySQL LIKE-style pattern. As in MySQL pattern matching, '%' matches any sequence of characters (including the empty string), '_' matches any single character, and a backslash escapes the following character so that it is treated as a literal (so `\%` and `\_` match a literal '%' and '_').
This mirrors how MySQL evaluates the database-name portion of a database-level GRANT. A grant on `strata_%`.* applies to a database named strata_boardgames, even though SHOW GRANTS reports the pattern verbatim. Privilege checks therefore cannot compare the granted database name to the target schema with a plain string match; they must expand the pattern.
func NewForeignKeyName ¶ added in v0.19.0
NewForeignKeyName returns the name a migration gives the copy of foreign key name, of table tableName, on the table's _new table.
A foreign key name is unique per schema, not per table, so the copy cannot have the name of the foreign key it copies while the original table exists. A name MySQL renames along with its table (see RenamedForeignKeyName) is given the _new table's prefix instead, which the cutover's RENAME TABLE turns back into the original name. Any other name becomes _<name>_new, which the cutover renames back under its table lock, once it has dropped the foreign keys of the old table.
func NewTableName ¶ added in v0.13.0
NewTableName returns the auxiliary _new table name for the given original table.
func OldTableName ¶ added in v0.13.0
OldTableName returns the auxiliary _old table name for the given original table.
func OldTableNameWithTimestamp ¶ added in v0.13.0
OldTableNameWithTimestamp returns the auxiliary _old_<timestamp> table name for the given original table and timestamp string. Used when SkipDropAfterCutover is set so the renamed-away table is preserved with a unique name across multiple migrations.
func ParseEnumSetElements ¶ added in v0.18.0
ParseEnumSetElements extracts the element values from a MySQL type string like "enum('a','b','c')" or "set('x','y','z')", as information_schema reports it in COLUMNS.COLUMN_TYPE.
It uses a small state machine to correctly handle values containing commas, the characters MySQL escapes in that text, and the hex literals it writes for members that are not valid utf8mb3 (see QuoteEnumSetMember).
It is fail-closed: any unexpected characters or malformed structure returns an error so that callers (safety checks, the binlog ENUM/SET decoder) are never silently bypassed.
func ParseSizeNumber ¶ added in v0.18.0
ParseSizeNumber parses a MySQL size_number: an unsigned integer with an optional K, M or G suffix (case-insensitive), the form AUTOEXTEND_SIZE and the tablespace sizes accept. It returns the value in bytes, so "4M" is 4194304. Any other suffix is an error, as it is in MySQL.
func Percentile ¶ added in v0.18.0
Percentile returns the p-th percentile (0 < p <= 100) of values, which may be in any order. It uses the nearest-rank method: the smallest value with at least p% of the samples at or below it, i.e. sorted[ceil(n*p/100) - 1]. It returns the zero value for an empty slice. A p outside (0, 100] is clamped: p <= 0 returns the smallest value and p > 100 the largest, so a bad p cannot panic.
values is not modified: Percentile sorts a copy, so callers can pass a live history (such as a ring buffer) without it being reordered. The rank is computed in integer arithmetic so float rounding of n*p can never move it by one.
func ProcedureGrantHasAny ¶ added in v0.18.0
ProcedureGrantHasAny reports whether a single SHOW GRANTS line is a procedure-level grant on schemaName.procName that confers any privilege in privs. Stored routine names are not case sensitive, so neither name is compared case sensitively.
func QuoteEnumSetMember ¶ added in v0.18.0
QuoteEnumSetMember renders an ENUM or SET member the way MySQL writes it in information_schema.COLUMNS.COLUMN_TYPE (and SHOW CREATE TABLE).
A member that is valid utf8mb3 is written in single quotes, with a single quote doubled and a backslash, newline, carriage return and NUL backslash-escaped. No other character is escaped: a tab, Ctrl-Z, backspace or double quote is written as itself. This is MySQL's append_unescaped().
Any other member is written as a hex literal of its bytes, e.g. x'815c': invalid UTF-8, and a character outside utf8mb3 (a 4-byte UTF-8 character). Only a binary-charset column can hold such bytes, and MySQL writes its members this way. (A column of any other charset holds a 4-byte character only in utf8mb4, and MySQL reports that character as '?'. Spirit writes the member instead, so that it reads back unchanged.)
parseSQLQuotedList decodes exactly these forms.
func RenamedForeignKeyName ¶ added in v0.19.0
RenamedForeignKeyName returns the name MySQL gives a foreign key of table from when RENAME TABLE moves the table to to.
MySQL renames a foreign key whose name starts with the table's name followed by _ibfk_ along with its table, so a generated name keeps following the table it belongs to. The match is case-sensitive and the rest of the name can be anything: verified against MySQL 9.7, a table child renamed to kid takes child_ibfk_7 and child_ibfk_x to kid_ibfk_7 and kid_ibfk_x, and leaves CHILD_IBFK_8 alone. Every other name is kept.
func RoundDecimal ¶ added in v0.18.0
RoundDecimal reads a decimal number (see IsDecimalNumber) exactly and rounds it to scale fractional digits, half away from zero, as MySQL rounds a decimal literal or a string into a DECIMAL or integer column. It returns the rounded value scaled by 10^scale as an integer (so 1.235 at scale 2 is 124), whether the rounding was exact, and false for a text that is not a decimal number or whose exponent is beyond any MySQL type's range. A result of zero is never negative, which is also how MySQL stores -0.001 at scale 2.
func ShadowingGrantsNote ¶ added in v0.19.0
ShadowingGrantsNote explains a refusal for missing base privileges on schemaName when a matching database-level grant lacks the full set: MySQL may apply that grant to the schema instead of one that has it. It returns "" if no matching database-level grant lacks the set.
func StringContainsAll ¶ added in v0.18.0
StringContainsAll returns true if `s` contains all non empty given `substrings` The function returns `false` if no non-empty arguments are given.
func TopologicalOrder ¶ added in v0.18.0
func TopologicalOrder[T comparable](nodes []T, dependsOn map[T][]T) []T
TopologicalOrder returns nodes reordered so that every node comes after the nodes it depends on. dependsOn maps a node to the nodes that must precede it; a dependency that is not itself in nodes, or is the node itself, is ignored.
The order is deterministic: among the nodes whose dependencies are all placed, the earliest in nodes goes next, so nodes with no dependency between them keep their input order. A dependency cycle cannot be satisfied; when every remaining node still waits on another, the earliest remaining node that another remaining node waits on is placed anyway, so the broken edge is one of the cycle's and no node is placed ahead of a dependency it could have followed. nodes must not contain duplicates.
func TruncateTableName ¶ added in v0.13.0
TruncateTableName truncates a table name to fit within MySQL's 64-character limit, reserving space for a suffix of the given length. If the table name already fits, it is returned unchanged.
func UnsupportedIdentifierError ¶ added in v0.18.0
UnsupportedIdentifierError returns an error when name, a schema or table name, contains a character Spirit refuses in those identifiers, and nil otherwise. kind describes the identifier in the error message, e.g. "table name" or "schema name".
Two characters are refused:
- '.': the replication client keys each table's subscription by joining its schema and table name with a '.', so `a`.`b.c` and `a.b`.`c` map to the same key and a row change on one table can be applied to the other.
- '`': every statement Spirit builds that names the table has to escape the backtick, and a single missed escape produces broken or wrong SQL. Refusing the name removes that class of bug rather than chasing each statement.
func ValidUTF8MB3 ¶ added in v0.18.0
ValidUTF8MB3 reports whether s is valid in MySQL's utf8mb3 charset: valid UTF-8 with no character outside the Basic Multilingual Plane, since utf8mb3 stores at most 3 bytes per character. A 4-byte character such as an emoji is valid utf8mb4 but not utf8mb3.
It is the test MySQL 8.0.33+ applies when SHOW CREATE TABLE converts a binary, varbinary, or utf8mb4 char or varchar column's DEFAULT to the system charset: a default that fails it is reported as a hex literal instead of a string.
func WithCancelGrace ¶ added in v0.18.0
func WithCancelGrace(parent context.Context, grace time.Duration) (context.Context, context.CancelFunc)
WithCancelGrace returns a context that keeps parent's values and deadline but outlives parent's cancellation by grace: it is canceled grace after parent is canceled, at parent's deadline, or when the returned cancel is called, whichever comes first.
Use it for a statement that must not be abandoned mid-flight on cancel. The MySQL driver's cancellation only closes the socket, so a statement the server already received can still commit after the caller has moved on.
Types ¶
type Closer ¶ added in v0.10.2
type Closer interface {
Close() error
}
Closer is an interface for types that have a Close() method. This is compatible with io.Closer and many other types in the codebase.
type ContextCloser ¶ added in v0.10.2
ContextCloser is an interface for types that have a Close(context.Context) method. This is used for resources like table locks that need context for cleanup.
type Temporal ¶ added in v0.18.0
type Temporal struct {
Negative bool // TIME only
Year, Month, Day int // DATETIME and DATE; zero for TIME
Hour, Minute, Second, Micro int // Hour runs to 838 for TIME
// contains filtered or unexported fields
}
Temporal is a MySQL DATETIME, DATE or TIME value as MySQL reads it from a literal: its fields, its microseconds, and the digit MySQL keeps past the microseconds to round them with. Build one with ParseDateTimeString, ParseDateTimeNumber, ParseTimeString or ParseTimeNumber; round it to the column's fractional-seconds precision with Temporal.RoundDateTime or Temporal.RoundTime, or truncate it with Temporal.Truncate; render it with Temporal.DateTimeString, Temporal.DateString or Temporal.TimeString.
The parsers accept the spellings MySQL 8.0 accepts and reads unambiguously, and reject (return false for) everything else, including the spellings MySQL rejects. A caller canonicalizing a literal can therefore leave any rejected text as written: it is either invalid, which MySQL reports the same way in any spelling, or a form rare enough not to be worth modelling.
func ParseDateTimeNumber ¶ added in v0.18.0
ParseDateTimeNumber reads a DATETIME, TIMESTAMP or DATE numeric literal the way MySQL does (number_to_datetime): the integer part is YYMMDD, YYYYMMDD, YYMMDDHHMMSS or YYYYMMDDHHMMSS by its magnitude, so 10101 is 2001-01-01 and 9991231 is 0999-12-31; a two-digit year resolves to 1970-2069. A fraction is read only after the fourteen-digit form. Zero, which MySQL rejects as a default, is false, and so is a thirteen-digit number (a year 100-999 datetime), which is left to the caller.
func ParseDateTimeString ¶ added in v0.18.0
ParseDateTimeString reads a DATETIME, TIMESTAMP or DATE string literal: 'YYYY-MM-DD[ HH:MM[:SS[.fraction]]]' with any punctuation between the date fields and between the time fields, spaces, a 'T' or punctuation between the date and the time, fields of any width, a trailing separator, and a two-digit year resolved to 1970-2069; or the compact 'YYMMDD', 'YYYYMMDD', 'YYMMDDHHMMSS', 'YYYYMMDDHHMMSS' and 'YYMMDDTHHMMSS' / 'YYYYMMDDTHHMMSS', the last four with an optional fraction. Whitespace around the value is skipped. The fields are range-checked (month, day for the month and year, hour, minute, second), so a date MySQL rejects is false. The time fields are zero when absent; the caller decides whether to keep them.
A time-zone suffix ('2020-01-01 10:00:00+00:00') is false: MySQL converts the value to the session time zone, so no fixed text reproduces it.
func ParseTimeNumber ¶ added in v0.18.0
ParseTimeNumber reads a TIME numeric literal the way MySQL does (number_to_time): the integer part is HHMMSS read right to left, up to 838:59:59, and the fraction is seconds.
func ParseTimeString ¶ added in v0.18.0
ParseTimeString reads a TIME string literal: '[-][D ]HH:MM[:SS][.fraction]' with fields of any width and the day part separated by spaces or tabs, or '[-]HHMMSS[.fraction]' read right to left as seconds, minutes and hours. Whitespace around the value is skipped. Hours (days × 24 + hours) run to 838 and minutes and seconds to 59; the maximum 838:59:59 admits no fraction. A fraction past six digits is rounded from its last digit, which is how MySQL's TIME reader (unlike its DATETIME reader) carries the extra digits.
MySQL first tries a string of twelve or more characters as a DATETIME and keeps its time part ('2020-01-01 10:00:00' is '10:00:00'); such strings are false here unless they cannot be a date, so the caller leaves them alone.
func (Temporal) DateString ¶ added in v0.18.0
DateString renders 'YYYY-MM-DD'.
func (Temporal) DateTimeString ¶ added in v0.18.0
DateTimeString renders 'YYYY-MM-DD HH:MM:SS' with fsp fraction digits.
func (Temporal) RoundDateTime ¶ added in v0.18.0
RoundDateTime rounds the value to fsp fractional digits the way MySQL stores it under its default sql_mode: the digit past the microseconds rounds them first, then the microseconds round half up to fsp, each carrying into the seconds and on through the date. It returns false when the carry runs past 9999-12-31, and when it happens in year 0000, where MySQL does not carry the date but stores the zero date ('0000-12-09 23:59:59.5' is '0000-00-00 00:00:00', '0000-06-15 10:00:00.5' is '0000-00-00 10:00:01'), a value this reader does not produce.
func (Temporal) RoundTime ¶ added in v0.18.0
RoundTime is Temporal.RoundDateTime for a TIME value: the carry runs into the hours, and the result is false past 838:59:59.
func (Temporal) TimeString ¶ added in v0.18.0
TimeString renders '[-]HH:MM:SS' with fsp fraction digits; hours take as many digits as they need, and a zero value carries no sign.
func (Temporal) Truncate ¶ added in v0.18.0
Truncate drops the fraction past fsp digits the way MySQL stores the value with TIME_TRUNCATE_FRACTIONAL in its sql_mode, for a DATETIME, DATE or TIME alike: nothing rounds and nothing carries (a negative TIME truncates toward zero), so the result is always valid.