Repository navigation
Release 2.0: MySQL 8.4, the checker as the product - #2
Merged
Merged
Conversation
…rce, as a CST parser — sql_yacc.yy with its actions stripped passes bison at %expect 59; sql_lex.cc's lexer compiles behind a five-point THD shim, natively and under emcc; 98.6% of the 96,580 mysql-test statements that expect no error are accepted, the misses being the charset registry the shim lacks and the splitter, not the grammar Throwaway spike for the 2.0 MySQL work. build.sh regenerates everything from the MySQL 8.4.6 source (grammar.y / parser.cc / lex_hash.h / lexer.cc are not committed); split.ts turns mysql-test/t into single statements; main.cc is the accept/reject driver. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o-name shim, and split mysql-test the way mysqltest reads it — 125,053 of 125,187 statements that expect no error are accepted (99.89%), every remaining miss is the splitter or a test that switches the client charset; the cost is a 4.6MB wasm against 0.9MB The two han weight tables come out of the server's own uca9dump, the collation library's objects are cached under gen/, and the registry's initialize() builds the lexer state maps so sql_chars needs no hand start-up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ts, the stripped grammar keeps %expect 59, 99.89% of mysql-test parses; the charset registry is linked whole (4.6MB wasm) because the type layer will read PAD SPACE, mbmaxlen and default collations from it, and the weight tables are the lever if size ever matters Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… one Go generator — strip the grammar, cut the lexer on signature anchors that fail loudly, split mysql-test the way mysqltest reads it, and drive bison, gen_lex_hash, uca9dump, g++ and em++ with the library objects cached; mysql/ starts as the nested module with a go.work at the root Output was compared byte for byte with the spike's before the spike was removed. The one difference is a fix: the TypeScript stripper turned the final action of a rule that had no ';' into a mid-rule action (13 places, %expect 59 held by luck); 80 mid-rule actions become the real 67. Parse results over the 137,062-statement corpus are identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sql/ likewise; the root stays Apache 2.0 in the 1.x line — releases are tagged cmd/sqlshape/vX.Y.Z, release.yml strips the prefix, the install line reads /v2@latest, and the workspace lists all three modules Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… module exports a parse API whose result is the CST in a 6-byte-per-node preorder wire format, node kinds are the grammar's own rule and token names numbered once in parsegen and shared by the C++ actions and kinds.go, and wazero runs it the way pgparse does; the mysql-test corpus parses at 99.89% through wasm in under four seconds Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… $1, $3)` is the map from the CST to the server's parse tree — and generates shapes.go for package mysqlparse; every rule node now records which alternative it reduced, so node.Shape() answers what the server would have built from it Of the 3,125 alternatives, 78% read as pass / const / new / list / flags / number / struct. From the statements sqlshape checks (DML, CREATE / ALTER / DROP TABLE, INDEX, VIEW, CALL) 428 rules and 1,778 alternatives are reachable, 223 of them unread: the hand-written scope, most of it trivial (struct init, deprecation warnings, number conversion). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ee classes by replaying the semantic actions parsegen read — PT_select_stmt, PTI_where, Item_func_eq over their constructor arguments — with hand-written hooks for the alternatives it could not read and an explicit Unsupported error for the rest; 97.7% of the mysql-test statements sqlshape reads build parsegen's action reader learned the server's idioms (charset conversion, LEX field assignment, brace and helper builders, the push_back family, error guards, mid-rule action renumbering), bringing the unread alternatives from 697 to 315; the leaves now carry the lexer's value (identifiers unquoted, literals unescaped) beside their span. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eaders (typedef'd templates and brace-built structs included) and struct field names from parser_yystype.h through the %type tags, so mysqlast nodes read PT_query_specification(opt_hints=…, item_list=…, opt_where_clause=…) and hooks address fields by name Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… side and fail at init when the grammar moves, comparison creators become their operators, the start-rule wrapper and END_OF_INPUT vanish, cross joins attach where add_cross_join puts them, CHECK folds its ENFORCED, unknown ALGORITHM / LOCK words are refused, hex and atol numbers read; parsegen lists the error guards it drops (-actions -guards) for the checks layer to come Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…typed views of the server's parse-tree classes, and vet gets a dialect seam (internal/dialect: Load, Analyze, the type table) chosen by schema.sql's declaration so that a third dialect only adds an implementation Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ss — AsPTQuerySpecification(v).OptWhereClause() — from the constructor parameter names, so the analyzer reads the server's classes by name rather than by position; generated Go passes through go/format Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… statements that hand up nullptr keep their child, bare keywords drop out of default nodes, field paths with get_or_default resolve, hooks take precedence over read shapes, and every hand-built node carries names Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d modifier lists, generated columns, TABLE t, sizes with K/M/G) and two more list idioms; mysqlparse.Split cuts a script into statements the way the mysql client does Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…olumns, keys, foreign keys, checks and options, CREATE INDEX / VIEW, ALTER TABLE's column and key actions, RENAME and DROP, folded from the server's parse-tree classes into a MySQL-shaped model; what the loader cannot apply becomes a positioned Problem Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… never a CI dependency Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rce — the native registry (347 functions, argument counts through the SQL_FN macros, the instantiator classes and the using-aliased spatial ones) and, for every Item class involved, its base, the family that fixes its result kind and the declarative facts of its resolve_type() Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…MySQL's first pass through it — schema.sql names its dialect (`-- sqlshape: mysql 8.4`), internal/dialect reads the line and hands the statement to the registered Analyzer, vet judges the dialect-neutral Result (the database's own errors, columns against R, parameters against P) on a thin path beside the untouched PostgreSQL one; mysql/internal/analyze types single-block SELECT / INSERT / UPDATE / DELETE over base tables (column resolution with MySQL's messages and numbers, join nullability, placeholder contexts) and leaves the rest untyped; mysql/dialect registers it with the Go type table, cmd/sqlshape imports it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd the next release is 1.3.0; scripts/release.sh sets the inter-module requires, commits and tags vX.Y.Z, mysql/vX.Y.Z and cmd/sqlshape/vX.Y.Z on one commit; release.yml fires on the root tag Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… requires, commits, tags, fills go.sum from the local clone (GOPRIVATE with git's insteadOf) so `go install …@vX.Y.Z` resolves, builds and smoke-tests the binary outside the workspace, then pushes; mysql/go.mod lists its dependencies Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ithmetic through Item_num_op's promotion and unsigned propagation, one-argument numeric functions through Item_func_num1, CASE / IF / COALESCE / GREATEST through Item::aggregate_type over field_type_merge (the 30×30 table and the result kinds now read out of sql/field.cc by parsegen into the catalog), casts by their target, aggregates by their class, and every registry function by its Item class family and resolve_type facts (type, nullability, unsigned, the placeholder types its argument positions declare); parsegen also reads the 109 resolve_type bodies written inline in the headers, mysqlast folds a CASE with several WHENs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… rules — the mysqld on PATH is initialized once per version into a cached template, copied and started per run on a unix socket, and Describe reads the result-set metadata of a SELECT run with NULL parameters (a write is prepared only); analyze's TestOracle puts every statement of its case tables to the server and gates on column names, types, nullability and error numbers, and the first run corrected the rules: string functions, JSON functions and casts are nullable (strict mode), FLOOR / CEILING of a decimal that fits is a bigint, a string literal names its column by its value, IN (subquery) is nullable, an unknown table in x.* is 1051; the $n → ? rewrite moves to its own package for both to share Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… calls the 347 functions over representative argument types (5,267 statements) through the mysqld oracle and records the outcome per statement in testdata/probe.golden (agree 4,994, untyped 29, disagree 10); what the probe corrected: the result collation of string functions (Item's default is binary, agg_arg_charsets takes the arguments', an explicit charset wins), Item_geometry_func's nullability, set_nullable / unsigned_flag written in constructors and fix_fields (parsegen reads class bodies), the fix_fields chain that decides whether Item_str_func's strict-mode nullability applies, `return Base::resolve_type()` inheritance, the last set_data_type_* winning, NULLIF, UNIX_TIMESTAMP and the FROM_UNIXTIME / ST_X / ST_SRID instantiators, and the schema loader's BINARY(n) / VARBINARY(n) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…'s facts, contracts are written on facts, a contract whose facts a dialect cannot produce is an error there; the user-facing docs become per-dialect tables of promises, to be reworked when the contracts reach MySQL Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ts/release.sh requires it) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o.mod (a dependency became direct), cmd/sqlshape's go.sum had recorded the hash of the go.mod at the tag before the amend, and the published v1.3.0-rc.1 failed its checksum in the release workflow; the tags now move and cmd/sqlshape is tidied again against them until the tree settles, and the header warns against asking the public proxy about the version before the push (its miss is cached); cmd/sqlshape/go.sum repaired to the published hashes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ore the next tidy (tidy verifies what go.sum already has) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…operations and INSERT ... SELECT — a scope now nests (a correlated reference resolves outward, the innermost level that knows the name wins), a relation is a base table or a derived one whose columns are its query's, and a query expression returns its columns so that a derived table, a view, a CTE, a set operation's arm and an INSERT's source all go through the same path; mysqlast gains hooks for the set operations (flatten_equal_set_ops, mirrored), ANY / ALL (PTI_comp_op_all) and with_list. The server's rules, checked against mysqld: a scalar subquery is NULL unless a query block is guaranteed a row (no tables, WHERE, HAVING, LIMIT), IN / ANY / ALL over a subquery is always nullable, a recursive CTE's columns are all nullable, a set operation's columns merge through field_type_merge, a derived table or view the server does not merge (grouped, DISTINCT, LIMIT, no tables, window functions, a subquery in the select list, TEMPTABLE) is materialized and a single SELECT's small integer expressions become int (create_tmp_field_from_item), a merged view's plain columns are updatable (1288 otherwise); errors 1241, 1248, 1222, 1353 as the server spells them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ble (SQLSHAPE_MYSQLTEST_DIR overrides; TMPDIR alone does not opt out, since nix-shell points it at a disk path): copying the 95 MB template and InnoDB's own writes stay off the disk, a start measured 2.05 s on ext4 and 1.42 s there, the package's own suite 9.2 s to 4.3 s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…), measured first with `go test -json` per test. mysqltest.Main(m), from a package's TestMain, boots one mysqld per set of `-- sqlshape: server` settings and hands it from test to test: Close drops the database instead of stopping the server, a Start while the server is in use or with other settings boots another, every server stops when m.Run returns (Pdeathsig on Linux against a test binary that dies early). dump.Local rides on it, so a pinned migrate test that canonicalized four schemas went from 6 s to a fraction of one; the nine packages using mysqltest get a main_test.go (cli's existing TestMain switches over). The migrate probes judge eight pairs at once on both dialects, the PRNG's draws kept sequential so the report is byte-identical (PG 172 s to 42 s, MySQL 32 s to 19 s; MySQL's directed pass shares the PRNG with the judging and stays serial). The PostgreSQL oracle's runtime directory moves to /dev/shm like mysqltest's (SQLSHAPE_ORACLE_DIR overrides; pgtest 7.3 s to 5.3 s). check/mysql/migrate 328 s to 24 s, check/mysql/internal/analyze 94 s to 13 s, check/postgres/migrate 219 s to 59 s. CHANGELOG [2.0.0] notes Main. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ure. mysqlast folds LOAD DATA's and INTO OUTFILE's FIELDS / LINES clauses (opt_field_term, field_term, line_term, the by-value separator structs merged the way merge_field_separators does), LOAD DATA's column list and SET clause (fields_or_vars, load_data_set_list into the set_var_list / set_expr_list pair PT_load_table reads), LOCK TABLES (table_lock, lock_option, the `lock` alternative to a lock_tables node) and ALTER VIEW (Sql_cmd_alter_view, the CREATE's own shape under the alter mode). A top-level SELECT with a trailing FOR UPDATE / FOR SHARE / LOCK IN SHARE MODE was "query expression not understood" (the constructor's inline PT_locking folded to unevaluated text in the query's place; only bodies had a reparse workaround): the locking alternatives of select_stmt and select_stmt_with_into now fold to PT_select_stmt{qe, into, locking}, and body.go's resolveLockingSelect and its regexes go. The analyzer types LOAD DATA as an INSERT of the file's rows (a view target 1288, the column list against the table, `@var` fields assigning nothing, SET expressions typed and placeholders taking the column's type; a NULL field for a NOT NULL column is 1263 not 1048, an omitted NOT NULL column takes the implicit default rather than 1364, IGNORE / REPLACE as INSERT's, all measured), LOCK TABLES (1146, 1066, the tables as read leaves; UNLOCK TABLES resolves nothing), SELECT ... INTO OUTFILE / DUMPFILE as the SELECT without columns; inside any body LOCK / UNLOCK / LOAD DATA / ALTER VIEW are the grammar's 1314 for every routine kind (OUTFILE is allowed even in a trigger or function, measured), mirrored at load like 1331. The loader applies ALTER VIEW as a whole replacement (1146 / 1347 / 1368 measured). TestCorpus guards that the hooked rules do not return to the missing list; the server test pins every measured code, LOAD DATA's on real files. Docs (en / ja): a "statements without a result set" section, ALTER VIEW under Views, the 1314 row; CHANGELOG [2.0.0].
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…only, the dialect-neutral generator / judge / report the migrate probe's shape suggested) generates schemas with rows and statements over them -- one leaf or two joined (inner or LEFT), the leaf a table, a view, a derived table or a CTE, WHERE conjuncts from the facts' own vocabulary (col = literal / $n / another column / a constant expression / an uncorrelated scalar subquery, IN lists and OR-disjunctions, IS [NOT] NULL, orderings as opaque predicates, correlated EXISTS and IN subqueries), GROUP BY the key, UNION ALL, LIMIT 1, and single-table UPDATE / DELETE / one-row INSERT -- asks the dialect's analyzer for each statement's facts, runs it, and refutes every claim with the rows the server returned: at most one row (TargetSingle for a write), every top-scope predicate on every row (EXISTS by brute force over the model's rows, an opaque predicate by the generator's own three-valued evaluation of the conjunct it wrote), a fixed column constant and non-NULL (an outer join's nullable side judged on present rows), NOT NULL, an Edge as a functional dependency, and the Const / Param values a write stores read back afterwards. Each statement's rows are also checked against the generator's own evaluation, so a wrong evaluator is a generator failure, not a finding; a claim the judge cannot evaluate is reported as unverified. An alphabet of 41 claim shapes is tallied and gated per dialect against a known-unreached list. Drivers: check/postgres/dialect and check/mysql/dialect TestFactsProbe (400 statements in 7 s on PostgreSQL 17, 300 in 11 s on MySQL 8.4). The first run found one hole per dialect, both fixed and pinned: MySQL accepted an UPDATE / DELETE whose subquery reads the target table (1093 in WHERE, EXISTS or IN alike; a derived table over it is materialized and allowed, INSERT ... SELECT from its own table too, all measured) or a view over it (1443) -- analyze.targetInSubquery, with the SET scalar subquery left unchecked since the facts do not record it; PostgreSQL recorded a subquery's reference to a joined table's column as a Known term when the subquery sits in the join's ON clause (the join's sides live in the passthrough scope the ON is analyzed in, not yet in the level's items), where an Outer term was meant -- facts.go's outerRef now reads the passthrough chain. Docs (en / ja): the 1093 / 1443 rule; design.md: the facts oracle beside the type and migration oracles; CHANGELOG [2.0.0]. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…be, one generator behind two probes: Run (the facts) and RunFailures, which draws writes against a parent / child schema carrying every constraint kind -- the primary key, a UNIQUE, a CHECK and a foreign key with each ON DELETE action, each named or left to the server's own naming, NOT NULL columns -- biased to collide (a duplicate id or key, a NULL or an omitted NOT NULL column, a non-positive check column, an unknown parent, a parent row's DELETE or key UPDATE; INSERT IGNORE / REPLACE / ON DUPLICATE KEY UPDATE on MySQL, ON CONFLICT DO NOTHING / DO UPDATE on PostgreSQL), runs each against fresh tables and requires every constraint error the server raises to be one of the analyzer's predicted violations under a key the runtime's Violates matches after WrapError, or a failure the analyzer already reports as certain (PostgreSQL's "every execution fails" note for an omitted NOT NULL column, which MySQL predicts as a 1364 violation instead). 300 writes pass on each dialect. postgres.WrapError and mysql.WrapError are new: the wrapping Run / Exec apply, for an error from a statement run outside them. Drivers: check/postgres/dialect and check/mysql/dialect TestFailureProbe, each check module now requiring its runtime module. Docs (runtime, mysql; en / ja) name WrapError; design.md and CHANGELOG [2.0.0] extend the probe's entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sql/internal/analyze's body probe (TestBodyProbe) generates PROCEDURE / FUNCTION / trigger bodies of every timing and event, mixing at most one construct the server may refuse when the body is created -- a duplicate DECLARE of a variable, condition or cursor, a duplicate HANDLER, a four-character SQLSTATE, a LEAVE with no label, a RETURN in a procedure or none in a function, a result set or COMMIT / CREATE TABLE / PREPARE / FLUSH in a function or trigger, LOCK TABLES / LOAD DATA / ALTER VIEW anywhere, NEW / OLD written, read or misnamed, an undeclared INTO variable or cursor -- with ordinary statements, CREATEs each on mysqld and DROPs it, and requires the checker to refuse exactly what the server refuses (AnalyzeRoutine / AnalyzeTrigger's CREATE-time code, or the loader's mirroring problem) and nothing the server accepts; both refusing under different numbers is tallied, not failed, since the server names grammar-time refusals first and the checker whatever its walk meets first. 300 bodies pass after three fixes it found: SET OLD.col is 1362 in an INSERT trigger too, checked before the event's 1363 (trigRowColumn's order); SET NEW.col / SET OLD.col outside a trigger is 1193 "Unknown system variable" (a read of NEW.col there is a run-time column reference and passes, measured); and a FUNCTION with no RETURN is mirrored by the loader as a 1320 problem (hasReturn), where the analyzer used to stop at an earlier run-time certainty and never say so. Left for later, in NOTES: an unqualified `SET nosuch = 1` is 1193 the checker cannot predict without the server's system-variable list. Docs (en / ja) name the two rules; CHANGELOG [2.0.0]. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…by side: check/mysql/internal/analyze's corpus probe (TestCorpusReplay, behind -corpus-run or $SQLSHAPE_CORPUS=1: five minutes on eight cores), the counterpart of PostgreSQL's regress probe. Each of the 1,281 .test files with SQL in it runs on a mysqld of its own (mysqltest.StartOwn, new: a private server even under Main, since the corpus creates and drops databases by name and changes accounts and global variables), connected to a fresh `test` database over one connection so SET and USE hold; after a statement that may have changed the schema the analyzer's schema is rebuilt from the server's SHOW CREATE (dump.Read, on a second connection with lock_wait_timeout 3 so a held metadata lock cannot hang it) under the session's sql_mode. A SELECT's columns are compared by name, type family and nullability, an error by number; a failure the data decides (a constraint, a conversion, a subquery's row count) is matched against the predicted violations instead. Not judged: statement kinds the analyzer does not read, references into information_schema / performance_schema / mysql / sys, statements touching a TEMPORARY TABLE (absent from SHOW CREATE), tables of another database, no-database-selected states, SHUTDOWN / RESTART / KILL, a file past its three-minute budget. Disagreements are classed LENIENT / STRICT / CODE / DIFF / RUNTIME / SCHEMA (the SHOW CREATE round trip the loader could not read) / PANIC (the analyzer crashing, recovered per statement), keyed by file, class and a hash of the text, and gated against testdata/corpus_baseline.txt (-corpus-update rewrites it). The first full run: 44,342 statements agree, 5,871 disagree (4,605 distinct, the baseline); the top classes are in the report and NOTES. Two analyzer crashes it found are fixed: a routine variable of the same name shadowed the column an INSERT column list or an UPDATE SET target in a body named, and the nil column was dereferenced -- both now resolve against the target table, 1054 when it has no such column. CHANGELOG [2.0.0]; design.md records the corpus as the third oracle beside the facts and failure probes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…863 (hits 5,870 -> 4,596) by fixing what its largest classes were, each pinned against mysqld by TestOracle. A window function's integer widens through the window's temporary table (sql_tmp_table.cc's create_tmp_field_from_item, which only an Item_sum with m_is_window_function reaches: INT / BIGINT to BIGINT, TINYINT / SMALLINT / MEDIUMINT to INT, YEAR to INT UNSIGNED, the signedness kept -- types.go's tmpTableInt, applied by classType when the call carries OVER; a plain aggregate keeps the column's type, and a ROLLUP group column widens only when the plan materializes the grouping, measured on a keyed table, so it is left alone). USER() / CURRENT_USER() / DATABASE() / SCHEMA() / CURRENT_ROLE() are utf8mb3 strings (Item_func_sysconst's constructor sets the collation, which the class's own resolve_type facts never showed). DATE'...' / TIME'...' / TIMESTAMP'...' literals are typed, with the fractional digits written, and never NULL (they were untyped, which made CAST(DATE'...' AS DOUBLE) nullable). <=> is never NULL (Item_func_equal::resolve_type), judged in the PTI_comp_op branch. Under ROLLUP only an item that reads the grouped columns turns nullable; a constant such as 1+1 keeps its own. SELECT ... INTO @var returns no columns in either position at the top level (selectInto now reads the trailing INTO too, so a body's `SELECT ... FROM t LIMIT 1 INTO v` counts as an INTO rather than a result set). INSERT INTO t VALUES () -- every row empty, with or without a column list -- inserts a row of defaults instead of 1136. The probe itself stops judging what is the file's environment rather than the statement's meaning: a statement that is not UTF-8 (the gb18030 / sjis / koi8r files), a statement after a DDL under LOCK TABLES (ALTER TABLE ... RENAME under the lock leaves the new name where the dump's connection cannot see it), a CALL whose body reads a system schema or a temporary table (the corpus's own p_verify_reprepare_count), a db.routine() call (1305 of another database), and a column name the connection's character set rewrote (non-BMP characters to '?', or bytes of a single-byte SET NAMES). Docs (en / ja): the widening and the never-NULL results under the Go type table, SELECT ... INTO @var and VALUES () under statements without a result set. CHANGELOG [2.0.0]. NOTES: the third round's targets (1416 on geometry, the literal-decided 1292 / 1264 / 1690, 1193's variable list, the column-name spellings) and the ceiling (plan-dependent temporary-table widening, literal max_length). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…store is the statement's own error. check/mysql/internal/analyze/store.go applies the server's Field::store rules to a literal written straight into a column by INSERT ... VALUES, UPDATE ... SET, ON DUPLICATE KEY UPDATE or REPLACE, in strict mode and outside IGNORE, at the top level (a body's own run-time failures stay its raised violations): an integer out of its range, a real rounded half to even or a decimal half away first (1264), a string holding no number (1366) or followed by other text (1265); a YEAR outside 0 / 1-99 / 1901-2155 (1264); a DECIMAL(M,D) with more than M-D integer digits once rounded to D places, a negative value into UNSIGNED, a FLOAT past FLT_MAX (1264); a DATE / DATETIME / TIMESTAMP string the ported str_to_datetime rejects under the sql_mode's NO_ZERO_DATE / NO_ZERO_IN_DATE / ALLOW_INVALID_DATES, a number number_to_datetime rejects, a TIMESTAMP outside 1970-01-01 00:00:01 .. 2038-01-19 03:14:07 UTC (exact with a displacement written, otherwise only beyond a day's margin), a TIME str_to_time / number_to_time rejects or past 838 hours (1292); a string longer than a CHAR / VARCHAR / BINARY / VARBINARY (n) once trailing spaces are dropped (1406); an ENUM member that does not exist and is not a member's index, a SET list with an unknown member (1265); a number or a string into any spatial column (1416). STRICT_TRANS_TABLES alone is strict for a nontransactional table only on the first row (strictFor, measured on MyISAM). TestLiteralStoreServer pins 144 stores against mysqld, TestLiteralStore the checker's side and the non-strict schema. Found by the corpus probe, whose baseline falls 3,863 -> 3,619; three harness faults of its own fixed on the way: the session's sql_mode was left out of the schema header when empty (SET sql_mode = '' then judged as the default, 97 false 1292s), the dump connection read the dictionary from a stale snapshot when a file set autocommit off globally (locking_readonly_db's 1146s), and a CALL whose body runs DDL did not dirty the schema (a body that creates a TEMPORARY table is now not judged either). Docs (en / ja): the literal store table under failure modes; CHANGELOG [2.0.0]; NOTES: the fourth round's candidates and the harness lessons. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ormatting only) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n expression key are violable, a temporal literal the server refuses is 1525. violations.go's violableKeyColumns lists the columns a key constrains for the duplicate-key prediction, a prefix part (UNIQUE (c(10)), judged on the first characters) as its column and an expression part (UNIQUE ((n * 2))) as the columns the expression reads, where keyColumns -- the reading for what an equality fixes, kept for the One proof and the facts -- excluded both and so no INSERT or UPDATE into such a table predicted 1062 (the corpus's ctype_utf8 prefix keys, 98 statements; TestViolationsServer pins the three shapes). A DATE'...' / TIME'...' / TIMESTAMP'...' literal must parse as exactly that type with no warning under TIME_FUZZY_DATE and the sql_mode's zero-date flags (item_create.cc's create_temporal_literal): a DATE with a time part, a TIMESTAMP without one, a displacement outside -14:00..+14:00 or misspelt, a TIME past 838 hours are the statement's 1525 "Incorrect DATE / TIME / DATETIME value" (store.go's temporalLiteral; errorCases pin eleven against mysqld, analyzeCases the shapes that pass). Two faults of the ported parser found on the way: a displacement followed by spaces is valid, and str_to_time's fallback from str_to_datetime distinguishes "not a datetime at all" (MYSQL_TIMESTAMP_NONE: the time shapes are tried) from "a datetime the value does not fit" (MYSQL_TIMESTAMP_ERROR) -- parseDatetime now returns the verdict and the datetimeShaped guess goes. Baseline 3,619 -> 3,525. Docs (en / ja): the key table's row and the literal bullet; CHANGELOG [2.0.0]; NOTES: 1193 is dropped as a target (its 130 are plugin variables the probe server lacks, which a variable list would turn into false errors for users who have the plugin), 1238 alone remains generatable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…. check/mysql/internal/analyze/geom.go carries a geometry value's type (POINT .. GEOMETRYCOLLECTION) as the typed's name when a constructor, a typed reader or a constant fixes it, and judges with it what the server does: ST_GeomFromText and the typed variants over a constant WKT parse it as spatial.cc's create_from_wkt / Gis_*::init_from_wkt do (a malformed text, a LINESTRING of one point, a ring of fewer than four points or not closed, a MULTIPOINT mixing its bracket styles or empty, an empty MULTIPOINT: 3037; another type than the typed variant's: 3516; GEOMCOLLECTION is a function name, not a WKT word), ST_GeomFromWKB and its variants over a constant WKB (a hex literal, an UNHEX of a constant) read it as create_from_wkb does, either byte order per header, trailing bytes refused (3037; a geometry value given to them is 3037 too, measured), a constant SRID outside 0 .. 4294967295 is 1690; the constructors LINESTRING / POLYGON / MULTI* refuse a constant argument of another type (1210), LINESTRING of one constant (3037) and a ring of POINT constants with fewer than four points or not closed (3037), while a column argument is left alone (val_str runs per row: polygon(t.a) over an empty table runs, measured); a class whose resolve_type calls reject_geometry_args (the catalog's fact: the arithmetic and bit operators, the numeric functions, BETWEEN) given a value known to be a geometry is 1210, except the comparisons, which accept two geometries (measured, against the Item_bool_func2 fact); and a value stored into a spatial column must be the internal format of the column's type -- a number or a string literal never is, a hex / UNHEX constant is read as Field_geom::store_internal's is_well_formed does (a 4-byte SRID, a little-endian well-formed WKB of the column's type, the MULTI* types under GEOMETRYCOLLECTION, no trailing bytes), a constant of another type fails on every execution (1416, whatever the sql_mode and under IGNORE, so geometryStore runs outside storeChecks) and a nullable expression of another type on every non-NULL value: the violation 1416, keyed "1416" the way 1442 is, which mysql.WrapError now keys too. TestGeometryServer pins 120 statements against mysqld (three of the checker's first guesses corrected by it: GEOMCOLLECTION, 3516, the accepted comparison), TestGeometry the types carried and the predicted violation. The oracle's and the probe's type keys fold every spatial name into geometry, which is all the wire says. A prefix key is violable (the round's first half), so the MySQL vet fixture's expect line over tenant_notes_tenant_body no longer wants the "no expansion can violate it" diagnostic. Baseline 3,525 -> 3,215 (hits 4,118 -> 3,768; LENIENT 1416 199 -> 0, 3037 90 -> 25, 1210 126 -> 65). Docs (en / ja): a spatial types section under failure modes; CHANGELOG [2.0.0]; NOTES: the fifth round's candidates and the geometry ceilings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cannot compute, and the select list's aliases where the server lets a name reach them. check/mysql/internal/analyze/fold.go evaluates a constant expression as item_func.cc does -- the literals, unary minus, + - * / DIV %, ABS, EXP, POW, COT, DEGREES, ROUND over an integer, RANDOM_BYTES, CAST to SIGNED / UNSIGNED / FLOAT / DOUBLE, comparisons and NOT for the conditions that decide -- and judges what the server refuses with 1690: an integer operator's exact result outside its BIGINT / BIGINT UNSIGNED (either operand unsigned makes the result so, subtraction not under NO_UNSIGNED_SUBTRACTION; a string operand makes a DOUBLE operator, a decimal a DECIMAL one, neither judged), a DIV over non-integers whose truncated quotient does not fit (magnitudes are divided, so -9223372036854775808 DIV 1 fails too), an infinite DOUBLE, a function's DOUBLE cast to an integer outside the longlong range (a literal is clamped), CAST AS FLOAT past FLT_MAX, ABS(-9223372036854775808), ROUND past the range, a RANDOM_BYTES length outside 1 .. 1024, each spelt as Item::print does. Where the failure lands follows the measured evaluation: a term the optimizer folds before reading rows -- a WHERE / HAVING / ON conjunct, a comparison or BETWEEN against a keyed column (the range optimizer), IN and LIKE always, a select item without a FROM, a derived table's or CTE's, INSERT ... VALUES -- is the statement's error; an expression that runs per row -- a select item over a FROM, an UPDATE's SET, ON DUPLICATE KEY UPDATE, a comparison against an unindexed column or an aggregate, RANDOM_BYTES anywhere -- is the violation 1690, keyed "1690" the way 1416 is (mysql.WrapError keys it too), and a query over no rows does not fail. What the server never evaluates is left alone: the operands after a constant that decides an AND / OR (a NULL decides an AND only in a condition; a nested AND / OR and a subquery are still processed there), the branch a constant IF / CASE / COALESCE / IFNULL condition does not take, the IN list after a constant match, x IS NULL over a never-NULL x, GROUP BY and ORDER BY items, an EXISTS's select list, LIMIT 0, a constant false WHERE without a FROM; a body is not judged. TestConstantFoldServer pins 240 statements against mysqld (the server corrected six first guesses: DEGREES multiplies by 180/pi, the unsigned cast's range is the longlong one, RANDOM_BYTES rounds its argument and is never folded, an unindexed comparison runs per row, ROUND rounds its places, a subquery's constant select item inherits the per-row position), TestConstantFold the checker's side. Name resolution: an unqualified name in a block's own GROUP BY / ORDER BY / HAVING expression resolves against the select list first (an aliased item wins over an unaliased column of the name, two different items are 1052; a table column shadows an alias in a bare GROUP BY as before), a nested query placed in an enclosing block's select list, GROUP BY, HAVING or ORDER BY sees that block's aliases (not one in its WHERE or ON): the ones declared before it -- a later one is 1247 "forward reference in item list" -- and an alias of an aggregate only from the nested query's own HAVING and never from a GROUP BY placement (1247 "reference to group function"), an alias of a window function 3594 (analyze.go's lookup / outerItem / lookupItem, a per-block list stack keyed by scope.tag replacing inHaving). HAVING never consults the block's own tables but does read an enclosing block's (fullgroup.go's outerHas; two of them are 1052), an aggregate in GROUP BY is 1056 when it is a select item as written, 1056 '???' through an alias, 1111 otherwise, GROUPING 1056. `_rowid` names a base table's first key after the server's ordering when it is unique over one NOT NULL integer column (relation.rowid), for a qualified reference or a single-table level. INSERT ... SELECT ... ON DUPLICATE KEY UPDATE resolves against the target and the SELECT's tables -- with the join's merged columns, the target last -- unless the SELECT is grouped or aggregated; VALUES(c) is the target's column alone and NULL outside the clause. TestNameResolutionServer pins 130 statements against mysqld, TestNameResolution the checker's side. Baseline 3,215 -> 3,050 (hits 3,768 -> 3,589; RUNTIME 1690 96 -> 32, STRICT 1054 69 -> 21, CODE 1247->1054 32 -> 0). Docs (en / ja): a constant arithmetic section under failure modes and the name resolution rules; CHANGELOG [2.0.0]; NOTES: the sixth round's candidates and the fifth's ceilings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ct write escalates to 1292, the temporal comparison's 1525, the TIMESTAMP literal's range. fold.go evaluates a constant CAST / CONVERT to DATE / DATETIME / TIME / YEAR / CHAR(n) / SIGNED / UNSIGNED / DOUBLE / FLOAT / DECIMAL, TIMESTAMP(x) of a constant, and a string operand of + - * / holding no full number, and judges the failing ones as the server does: an error only in a strict INSERT / REPLACE / UPDATE / DELETE outside IGNORE (strictWriteStmt; a SELECT, SET, DO or non-strict write runs them with a warning, rows present or not, measured), landing where the 1690 lands except that a comparison's constant operand always runs per row -- the violation 1292, keyed "1292" the way 1690 is (mysql.WrapError keys it too). convert.go carries the value-independent siblings: a DATE / DATETIME / TIMESTAMP value compared with a constant string that is no datetime is the statement's 1525 wherever the comparison sits (a select item, ON, HAVING, ORDER BY / GROUP BY, a dead branch; the store-shaped 1292 in a strict write; BETWEEN and IN do not convert eagerly), a numeric column compared with a string holding no number is the strict write's 1292 ('' passes; the BETWEEN form runs per row), a bit operator's constant string operand must be a whole integer, and a displacement over a zero month or day fails its conversion whatever the mode (1292 "Truncated incorrect temporal value", the datetime respelt without its fraction, the displacement's hour unpadded). store.go stores a DATE'...' / TIMESTAMP'...' literal into a TIMESTAMP column under the UTC range, exact when a displacement is written, with the fractional seconds rounded to the column's precision first (the carry counts: .999999 into TIMESTAMP(0) is 00:00:01, stored); a TIMESTAMP column ignores ALLOW_INVALID_DATES and range-checks a year 0 over a real month and day. TestConvertServer pins some 200 statements against mysqld across four modes (strict, non-strict, NO_ZERO_IN_DATE off, ALLOW_INVALID_DATES; the server corrected three first guesses: BETWEEN converts per row, a YEAR message spells the value, the store's displaced-zero message is the conversion's own), TestConvert the checker's side. Baseline 3,050 -> 2,985 (hits 3,589 -> 3,521; RUNTIME 1292 82 -> 25). Docs (en / ja): a constant conversions section under failure modes; the CHANGELOG's corpus bullet rewritten as the diff against 1.2 (no round narration), the round's rules folded into one failure-modes bullet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… routine body or a SHOW CREATE TABLE carries now folds. mysqlast/hooks_stmt.go writes the LEX-filling alternatives by hand as the Structs their siblings already are (GET DIAGNOSTICS, KILL, CHECKSUM TABLE, INSTALL PLUGIN, CREATE DATABASE / USER / SERVER and the tablespace family, DROP TABLESPACE, EXECUTE ... USING's variables, create_user with the whole identification family, ANALYZE TABLE with UPDATE HISTOGRAM, SHOW COLUMNS / KEYS, EXPLAIN's FORMAT=); hooks_ddl.go appends name_list (KEY (a, b) and RANGE / LIST COLUMNS partitioning) and reads ternary_option's 0 / 1 as the server's switch does (anything else stays its syntax error); hooks_dml.go folds INSERT / REPLACE ... SET to the PT_insert the VALUES form builds (the SET pairs as the one row), db.t.* to Item_asterisk, JSON_ARRAYAGG / JSON_OBJECTAGG to Item_sum_json_array / _object, and JSON_TABLE's typed PATH column. A partition naming its own subpartitions explicitly is modeled rather than refused: Partition.Subs carries the names, partitionDef reads them (an option other than ENGINE stays a problem), migrate renders them back in ADD PARTITION / REORGANIZE / the whole rewrite (TestProbeExplicitSubpartitions pins the DDL against mysqld, a renamed subpartition included) and diff compares them; the per-partition MAX_ROWS / DATA DIRECTORY / TABLESPACE options and HASH's explicit partition list stay the documented ceiling. Two analyzer faults the newly loading schemas exposed, both pinned against mysqld: an unaliased select item in parentheses is named by its source text, parentheses and inner spacing included, while a column, string, numeric or NULL literal keeps its own token (TestItemNameServer); and Item_sum_json_array / _object were not in the catalog, so ONLY_FULL_GROUP_BY treated JSON_ARRAYAGG(c) as a nonaggregated column (a false 1140) -- toolchain.go now lists the hook-built classes for the catalog generation, which also picks up Item_func_in and the richer Item_lead_lag facts (the full parsegen regeneration is refused for now: the current action reader is behind the committed shapes.go / views.go). Baseline 2,985 -> 2,803 (hits 3,521 -> 3,306; unsupported construct 186 -> 0, PARTITION definitions 57 -> 12). Docs (en / ja): the migrations comparison names explicit SUBPARTITION lists; CHANGELOG's partition bullet too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… relation carries the flags sql_resolver.cc computes when it merges (viewWritability, over the FROM leaves of the view's own query: updatable when any leaf is, insertable when all are, neither under an outer join, a merged view leaf flattened into its own leaves), and the three writes follow them, each rule pinned against mysqld (TestViewWriteServer, some 90 statements). An INSERT / REPLACE lands on the base table with the base table's own violations (1062 and the rest) plus the view's own: WITH CHECK OPTION is the 1369 the insert may hit -- an underlying view's option enforced under a plain outer one too (chainHasCheckOption), still naming the view written through -- and a base column with no default the statement leaves unassigned is the view's 1423, not the table's 1364 (keyed by the view's name, mysql.WrapError reading it back the same way; the corpus judge counts 1423 among the runtime codes). The refusals in the server's own order: a non-insertable view 1471, REPLACE into a join view 1395, a join view without a column list 1394, the fields resolved against the view (1054, 1136; a derived one among them -- listed, or every view column when no list is given -- its own 1348, a COLLATE wrapper transparent as field_for_view_update is), a derived column outside the fields or one base column behind two view columns 1471, the fields of a join view kept to one base table (1393, the ON DUPLICATE KEY UPDATE assignments included), and a view whose own body re-reads the target base table in a subquery 1471 (viewBodyRereads). An UPDATE needs the updatable flag (1288); an assigned expression of the view's select list is 1348, a column of a materialized leaf inside an updatable view the view's 1288 (Column.matLeaf), a SET spanning two base tables of a join view 1393, and its Writes now cover view targets (writeFacts takes the landing table). A DELETE takes any updatable single-leaf view -- a view of only expressions included, deleteTarget resolving the base through viewWritability -- with more than one leaf 1395, anything else (multiDelete's blanket view-1288 corrected, a CTE target kept) 1288. A subquery reading the very view a single-table statement writes is the target itself (1093, write.targetViews), where the multi-table UPDATE form stays the 1443 the server spells there. Baseline 2,803 -> 2,788; the judged corpus grows instead: not judged (analyzer limit) 671 -> 261, agree (write) 21,789 -> 22,025, agree (predicted failure) 655 -> 756, STRICT 139 -> 128, CODE 139 -> 123. Docs (en / ja): the views section carries the write rules, the pinned discharge drops its "UPDATE only" gap; CHANGELOG's view bullet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ad, the aliased function argument, the data dictionary functions. A new generated table (parsegen -sysvars reading sql/sys_vars.cc into catalog/sysvars.go: 448 variables, scope 's'/'g'/'b'; NOT_VISIBLE settings left out, a Sys_var_deprecated_alias taking its base's scope, the preprocessor walked under a stock release Linux build's defined set so a debug-only variable never enters) lets expr.go judge PTI_get_system_variable as the server does: @@session.x (@@local.x the same) of a GLOBAL-only variable and @@global.x of a SESSION-only one are the statement's 1238 wherever the read sits -- a dead branch, a subquery -- while an unqualified @@x never scope-fails and a name outside the table (a plugin's or component's variable, every @@prefix.name read) is not judged. TestSysVarScopeServer pins all 448 entries against mysqld read by read, TestSysVarPlacementServer the placements. A function call argument carrying an alias (f(x AS a), the loadable function syntax) is refused in the server's own order, before the argument is resolved (funcCallPartsAlias): a native function checks its count first (1582, whose message spells the name as written -- the previous ToUpper corrected against the server), then the alias is 1583 with the name lowercased; any other name, a stored function or one that does not exist alike, is 1584 before the function is looked up (the corpus's CODE 1584->1305 hits). A function the catalog marks internal (INTERNAL_TABLE_ROWS and the rest of the data dictionary's own) is 3566 before either check. TestFunctionAliasServer pins the order and each message. One analyzer fault the round exposed: a parenthesized query expression as a WITH statement's body -- WITH cte AS (...) (SELECT * FROM cte) -- lost the WITH clause's CTEs (bodyScope's PT_query_expression arm passed sc.outer; it now passes the scope carrying them, whose relations are still empty there). Baseline 2,788 -> 2,660 (hits 3,296 -> 3,170; LENIENT 1238 49 -> 4, all plugin or component variables, LENIENT 1583 42 -> 0, CODE 1584->1305 / 1583->1305 / 3566->1582 all 0; the two time-dependent max_statement_time 3024 lines left with them). Docs (en / ja): a function-call-and-system-variable section under failure modes; CHANGELOG's bullet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on decides by a constant argument's value. funcval.go judges them where fold.go places the folded constants (a per-row position is the violation keyed "1411" / "1210", which mysql.WrapError now keys too): INET_ATON (its own loose val_int parse), INET6_ATON (str_to_ipv4 / str_to_ipv6 ported from item_inetfunc.cc), UNHEX and STR_TO_DATE fail only a strict write outside IGNORE (1411, the argument spelt as the server spells it); UUID_TO_BIN / BIN_TO_UUID (1411, the value escaped as ErrConvString does) and PERIOD_ADD / PERIOD_DIFF (1210, a positive month-1-to-12 period) fail every statement whatever the sql_mode. STR_TO_DATE is a port of the server's extract_date_time (strtodate.go): fix_from_format's result type, the specifiers with the en_US names, the %V/%v weeks with their %X/%x years over calc_daynr / get_date_from_daynr, the %r / %T sub-parses whose failure says "time", check_date under the session's zero-date flags, date_should_be_null's refusal of any zero part under NO_ZERO_DATE, and a parsed value's non-space tail as the 1292 truncation, spelt with the format's own type word. A valid UNHEX carries its decoded bytes, so BIN_TO_UUID(UNHEX(...)) judges the inner length. A second family is refused at resolution, wherever the expression sits and rows or none (wrongArguments, measured): NAME_CONST takes two literals -- one unary minus or a COLLATE wrapper is fine, a folded 1+1, TRUE or a temporal literal is not (1210), and a NULL name is the reserved syntax's 1382; LIKE's ESCAPE takes a constant of at most one character, where the server's constant is const_for_execution -- a scalar subquery, a constant expression and an introducer'd literal are left to the execution, only a column outside any subquery is refused (the corpus corrected the first, narrower guess, twelve false errors among ESCAPE subqueries, HAVING aliases naming plain columns and a routine parameter in AGAINST); NTILE takes a positive count and NTH_VALUE a positive integer position; MATCH takes real columns of one relation (a select alias over an aggregate is 1210, one naming a plain column resolves as that column and passes; a derived table's columns pass) with a constant AGAINST. Baseline 2,660 -> 2,580 (hits 3,170 -> 3,090; LENIENT 1411 54 -> 0 but for the value-dependent rest, the judged 1210s gone; the two time-dependent max_statement_time 3024 lines returned with the timing). TestFuncValServer pins some 140 statements across four sql_mode groups, TestFuncValPerRowServer the per-row placements, TestFuncVal the checker's side; probe.golden's NAME_CONST(col, col) rows move to rejected-by-both. Docs (en / ja): a constant function arguments section under failure modes; CHANGELOG's bullet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…diff against 1.2.0, and the MySQL corpus baseline stated where the compatibility claims live. The changelog loses what 1.2.0 never saw -- the MySQL adversarial-rounds bullet whole (max_sp_recursion_depth moved to the server-declaration bullet, the rest is docs/mysql.md's), the probes' found-and-fixed narration, the round framing -- folds the three per-round value-failure bullets into two, and gains a Fixed section for the one behaviour 1.2.0 had wrong: a subquery in a join's ON clause reading a joined table's column, recorded as a pre-run value. The corpus honesty: README (en / ja) and docs/mysql.md's "What the checker embeds" (en / ja) now say the 137,000-statement mysql-test replay leaves 2,580 knowing disagreements, pinned one by one in corpus_baseline.txt, a new one failing the build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… showed the test job had never met its own MySQL half. The runner's mysqld is 8.0 and AppArmor-confined (initializing a data directory under $HOME is errno 13), so the job now installs the official 8.4.11 minimal tarball -- the version the analyzer is measured against, unconfined from $HOME, cached -- with Ubuntu 24.04's libaio.so.1t64 symlinked to the soname the tarball links. The same run filled the disk (TestMigrateProbe's "No space left on device", TestRegress's server dying mid-corpus), so the unused preinstalled toolchains are removed first, and the timeouts grow to what the doubled suite needs (job 45m, go test 40m). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ice" was never the disk: the test servers put their data directories on /dev/shm by design, and the runner's tmpfs (RAM/2) is too small for the parallel suite, so the Test step points both escape hatches (SQLSHAPE_ORACLE_DIR, SQLSHAPE_MYSQLTEST_DIR) at the runner's temp on the disk the free-space step cleared. And TestConvertServer's one displaced literal whose error number depends on the session's time zone -- TIMESTAMP'9999-12-31 23:59:59.999999+00:00' into a TIMESTAMP(0), where the .999999 carry passes the DATETIME maximum -- moves to its own list (convertTZEdge, each behaviour measured on 8.4.11): a session at exactly +00:00 overflows in the rounding itself (1441), any other offset converts the literal first (an eastern one wraps past year 9999 to year 0, a western one lands in range) and fails the TIMESTAMP range (1292). The checker does not know the session time zone and keeps 1292; the server may say either. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…がありうるかは、文の形によっても変わる」など)を自然な日本語に直し、ひと段落に詰め込まれていた列挙——定数算術の失敗位置、変換の落ち場所、TIMESTAMP格納の規則、関数エイリアスとシステム変数の順序、トリガ本体の失敗モードの伝播、スキーマ関数の解決と1442の衝突規則、名前解決の一枚岩、ビューの書き込みフラグ、LOAD DATAの特例——を箇条書きに割った。英語版はそのまま。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ys meant to. The /v2/ in the module paths silently defeated the coverpkg exclusion (-e /check/postgres/catalog/gen matched nothing), so the patterns name path segments now (/catalog/gen, /internal/parsegen -- MySQL's generator joins PostgreSQL's, its cmd was already out), and the data tables parsegen writes from the server source (mysqlast/views.go, mysqlparse's shapes.go / kinds.go, the catalog's functions.go / sysvars.go) drop from the profile beside pg_query.pb.go, the same accounting the protobuf accessors already had. The 90% gate stands: measured on the deterministic suite with pg_dump and mysqld present, the tree sits at 90.8% after this accounting, 81.9% before it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rst catch fixed. load_more_test.go pins what only the subprocess-run binary or nothing exercised: CREATE TABLE LIKE (columns, keys, checks and the table options copied as the new table's own; foreign keys and partitioning not, as measured), RENAME TABLE lists with a view renamed and a trigger following its table, the ROW_FORMAT spellings, LIST / RANGE COLUMNS bounds with their tuple rows and per-column MAXVALUE, an explicit partition list under HASH and a per-partition option the model refuses, SUBPARTITION BY LINEAR KEY ALGORITHM=2, the ALTER TABLE branches (SET DEFAULT, column and index visibility, NOT ENFORCED, CONVERT TO CHARACTER SET, RENAME TO, the no-such-X problems), HASH partition arithmetic and a RANGE's DROP / REORGANIZE PARTITION, CREATE VIEW's refusals, and the loader's problem statements. The catch: ALTER TABLE ... DROP PARTITION and REORGANIZE PARTITION ... INTO were silent no-ops -- the server's action reads the ident_string_list through a pointer deref (*$N) the generated shape keeps as marker text, so the loader saw no names, dropped nothing and reorganized nothing, with no problem raised. The two alternatives join hooks_ddl.go's hand-written folds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… one message told straight. x/dialect.Schema loses Relations() -- the interface's own doc says it exists for what the checker asks, and the checker asks by name (Relation) everywhere; both dialects' implementations go with it, as does check/postgres/dialect's NamedOf, orphaned since Type carried its own canonical name. The `// sqlshape: type` directive on a schema that is not PostgreSQL's no longer claims a PostgreSQL type is missing: it says the dialect has no named types to bind to (typebind.go, the message a MySQL user actually needs). And the promises that held only by inspection now hold by test: an obligation declared above a MySQL CREATE VIEW binds the view's readers (discharged by a pinned read or refused with the view's own message, an unknown column a problem under the view's name; a write through the view is the base table's, judged so on both dialects -- measured on PostgreSQL, pinned here), and vet runs a MySQL schema in-process at last (testdata/schema-mysql.sql: the view obligation and the type directive in mysqlvet, the -strict advisories -- ENGINE=MyISAM enforcing nothing, a CHECK declared NOT ENFORCED -- in mysqlstrict). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…by hand: NOT LIKE (its ESCAPE form too), REGEXP / RLIKE, NOT REGEXP and SOUNDS LIKE were "unsupported construct" errors on every statement that used them -- everyday SQL the checker refused. hooks_dml.go now builds what the server itself builds (NOT LIKE is the LIKE negated the way NOT IN already was, REGEXP is REGEXP_LIKE typed through the catalog, SOUNDS LIKE is SOUNDEX(a) = SOUNDEX(b)), and TestAnalyze / TestOracle pin the five forms' names, types and nullability against mysqld (a NOT NULL operand's REGEXP is NOT NULL, SOUNDS LIKE stays nullable, all bigint(1)). The corpus grows by what the fix uncovered: 14 RLIKE / REGEXP statements now load and their run-time regexp errors (3685 / 3687 / 3696 / 3995, a 3141) join the baseline as the knowing LENIENT disagreements they are -- 2,594 now, the number told in README (en / ja) and docs/mysql.md (en / ja); the flapping max_statement_time entries (LENIENT 3024, a timing the machine decides) stay pinned as they were. The vet fixture also pins what a MySQL statement's refusals look like to a user: a construct the AST layer does not fold (CAST ... AT TIME ZONE) is reported with the statement's own text quoted, and a syntax error carries MySQL's own 1064 at the offending token. Still refused and left for its own decision: INSERT ... VALUES (...) AS alias (opt_values_reference), the 8.0.19 row alias. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd them. The TEXT and BLOB families were typed crosswise: PT_blob_type's text-or-blob call read "has a charset" as TEXT, and the grammar passes my_charset_bin for the BLOB family and nothing for a bare TEXT variant -- so TINYTEXT loaded as tinyblob, TINYBLOB as tinytext, MEDIUM and LONG the same, LONG VARCHAR as mediumblob. The discriminator is now the charset's value, measured against SHOW CREATE on 8.4 for every spelling (TestBlobTextTypes): a TEXT variant declared CHARACTER SET binary degrades to the blob as the server stores it, TINYTEXT BINARY keeps the text with the binary collation, and 61 corpus baseline entries -- string->binary and binary->string DIFFs that were this bug pinned as knowing disagreements -- dissolve (2,534 now, the number told in README and docs/mysql.md, en / ja). And a FOREIGN KEY whose reference list is missing or does not match its column count loaded silently, with two fallbacks (violations.go, the dialect contract) pretending a bare REFERENCES names the parent's primary key; the server refuses the definition at CREATE (1239 "Key reference and table reference don't match", measured -- the bare form too), so the loader now does the same and both fallbacks go. Dead code cut where the digging found it: analyze's literalText (superseded by constText, its own call sites' comment says so), scope's findCTE wrapper, catalog's OperatorByOID with the opByOID index nothing read. And PL/pgSQL's RETURN of a plain variable -- the retvarno path beside the tested expression form -- is pinned in TestAnalyzePLpgSQL: refused in a void, SETOF or trigger function with the server's words, passed for a matching scalar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s own consequence. INSERT ... VALUES (...) AS new [(names)] (8.0.19) is read instead of refused: the fold the shape reader could not follow joins hooks_dml.go's hand-written ones, and ON DUPLICATE KEY UPDATE sees the inserted columns -- the insert's own fields, not the whole table, renamed positionally by the name list -- beside the target, every rule measured on 8.4 and pinned in TestAnalyze / TestOracle: a column outside the insert's list is 1054, the old names disappear under a name list, a name list of the wrong count is 1353, a duplicate name in it 1060, an alias colliding with the target 1066, an unqualified name both sides carry 1052, VALUES(c) stays usable beside the alias, and the SET form takes the alias too (docs/mysql.md's name-resolution section tells it, en / ja). The corpus probe stops judging what the machine's speed decides: a statement the server answers with 3024 or 1317 (max_execution_time exceeded, query interrupted) is counted "not judged (timing)" -- the max_statement_time entries flapped in and out of the baseline run to run, and nine more of the same kind hid elsewhere in it; all leave, the row alias's newly-judged statements settle, and the baseline stands at 2,525 (told in README and docs/mysql.md, en / ja). And checks.md (en / ja) now states the consequence the obligation code always had: a write through an updatable view is the base table's write, so an obligation declared on a view binds its readers only -- a rule that must hold for the writes belongs on the base table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the 2.0 release branch: MySQL 8.4 support end to end (analyzer built from the server's own parser and catalog, runtime on database/sql, mysqltest, migrations, triggers/routines/events, writes through views, the value-decided run-time failures), the module split (
/v2paths; declarations, per-database runtimes, GPLv2 checker modules), the dialect-neutralOneproof and obligations, and the measurement infrastructure (x/stmtprobe, the MySQL body and corpus probes, the migration probe on both dialects).The full diff against 1.2.0 is CHANGELOG.md's 2.0.0 section, rewritten in this branch's final commit to carry only that diff; README and docs/mysql.md now state the corpus baseline (2,580 knowing disagreements of the 137,000-statement mysql-test replay, pinned one by one).
🤖 Generated with Claude Code