Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
80d42c3
native: support bounded SQL metadata reads
chubes4 Sep 9, 2026
42081b8
native: match bounded shadow SQL forms
chubes4 Sep 9, 2026
63804ef
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
8f5d577
native: support tableless scalar projections
chubes4 Sep 9, 2026
9b64bdf
test: trace native shadow runtime phases
chubes4 Sep 9, 2026
abbaf14
test: expose native shadow trace path
chubes4 Sep 9, 2026
387d202
test: keep shadow tracing non-blocking
chubes4 Sep 9, 2026
c4f7cdb
fix(native): preserve catalog predicate semantics
chubes4 Sep 9, 2026
bb7cf5e
Merge branch 'main' into fix/mdi-parity-sql-families-20260909
chubes4 Sep 9, 2026
52860b1
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
89e739e
fix(native): bound reviewed SQL semantics
chubes4 Sep 9, 2026
610c39e
fix(native): match MySQL JSON depth
chubes4 Sep 9, 2026
2a0aea3
test(shadow): retain token type traces
chubes4 Sep 9, 2026
26a7fae
fix(shadow): keep runtime trace optional
chubes4 Sep 9, 2026
6cfcf4b
fix(shadow): capture tableless and catalog reads
chubes4 Sep 9, 2026
bc71a84
fix(shadow): retain snapshot capture evidence
chubes4 Sep 9, 2026
28ab023
test(shadow): retain redacted mismatch receipts
chubes4 Sep 9, 2026
c316a46
fix(native): match MySQL field descriptors
chubes4 Sep 9, 2026
086d08d
fix(shadow): bind authoritative catalog schema
chubes4 Sep 9, 2026
577b4d9
test(shadow): retain catalog schema receipts
chubes4 Sep 9, 2026
f6dbd61
test(shadow): retain snapshot provenance
chubes4 Sep 9, 2026
f39abfa
fix(native): match catalog column ordering
chubes4 Sep 9, 2026
8cf423c
fix(shadow): compare unordered catalog rows as bags
chubes4 Sep 9, 2026
3d946ef
test(shadow): retain catalog lifecycle receipt
chubes4 Sep 9, 2026
ea40bc0
fix(shadow): exclude temporary catalog tables
chubes4 Sep 9, 2026
0d66361
fix(shadow): capture permanent catalog schemas
chubes4 Sep 9, 2026
c181939
fix(shadow): atomically bound trace output
chubes4 Sep 9, 2026
5b065e7
Merge branch main into fix/mdi-parity-sql-families-20260909
chubes4 Sep 9, 2026
99e032b
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
9d87378
fix(native): bound post title lookups
chubes4 Sep 9, 2026
b85dfe1
fix(native): cap title lookup source work
chubes4 Sep 9, 2026
f3c9768
fix(native): migrate legacy multisite user snapshots
chubes4 Sep 9, 2026
471d8cd
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 9, 2026
a5b28d8
fix(native): support bounded not-equal table deletes
chubes4 Sep 9, 2026
afad2b7
fix(native): implicitly commit table DDL
chubes4 Sep 9, 2026
548042c
test(native): cover event date schema DDL
chubes4 Sep 9, 2026
e4a639b
fix: ignore stale missing cached post paths
chubes4 Sep 10, 2026
bf12788
fix(native): support LIMIT OFFSET queries
chubes4 Sep 10, 2026
18d2351
fix(native): restore temporary schema lifecycle
chubes4 Sep 10, 2026
7d977b3
fix(native): preserve temporary DDL across rollback
chubes4 Sep 10, 2026
66a20dc
fix(native): isolate temporary table generations
chubes4 Sep 10, 2026
81a8a55
fix(native): share temporary tables across site runtimes
chubes4 Sep 10, 2026
393a0aa
fix(native): project temporary tables across site registries
chubes4 Sep 10, 2026
8b29b20
Revert "fix(native): project temporary tables across site registries"
chubes4 Sep 10, 2026
0d7b860
Revert "fix(native): share temporary tables across site runtimes"
chubes4 Sep 10, 2026
c233045
fix(native): route core temporary table mutations
chubes4 Sep 10, 2026
9af43cc
fix(native): parse and validate table index hints
chubes4 Sep 10, 2026
932c916
test(native): protect composed index hint semantics
chubes4 Sep 10, 2026
ad5b3a2
fix(native): preserve mixed LIKE alternatives
chubes4 Sep 10, 2026
c56c7e6
fix(native): preserve aliased column projections
chubes4 Sep 10, 2026
5e9cfad
fix(native): retain all explicit grouping columns
chubes4 Sep 10, 2026
08f0203
fix(native): honor SQL mode for omitted column defaults
chubes4 Sep 10, 2026
904d427
fix(native): retain strict missing-default diagnostics
chubes4 Sep 10, 2026
243b06f
fix(native): bind unqualified joined columns by schema
chubes4 Sep 10, 2026
1397586
fix(native): evaluate typed duplicate-key assignments
chubes4 Sep 10, 2026
5f02fa0
fix(native): apply multiple ALTER actions atomically
chubes4 Sep 10, 2026
e75f32f
fix(native): filter non-indexed snapshot columns
chubes4 Sep 10, 2026
6b10e4a
fix(native): aggregate typed numeric scalar expressions
chubes4 Sep 10, 2026
8f50078
fix(native): evaluate scalar updates with nested predicates
chubes4 Sep 10, 2026
8e49a8a
Merge remote-tracking branch 'origin/main' into fix/mdi-parity-sql-fa…
chubes4 Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -849,3 +849,19 @@ Tested on WordPress 6.9 with SQLite-backed local and Playground-style runtimes:
## License

GPL v2 or later.
## Native SQL Mode Defaults

The native WordPress connection starts with an empty SQL mode, matching its
existing non-strict `SHOW VARIABLES` contract. Generic table inserts use MySQL
implicit defaults for supported numeric, string and temporal columns when an
explicit default is absent. Nullable columns remain NULL; explicit defaults
take precedence. Missing implicit defaults produce bounded `SHOW WARNINGS`
records and an exact `@@warning_count`.

`SET [SESSION] sql_mode` and `SELECT @@[SESSION.]sql_mode` support the empty
mode, `STRICT_TRANS_TABLES`, `STRICT_ALL_TABLES` and `NO_ENGINE_SUBSTITUTION`.
Strict mode rejects an omitted required column with error 1364. Modes whose
additional semantics are not implemented are rejected without changing the
session. This is not a claim of complete MySQL coercion or SQL-mode coverage.
The mode is connection-local, survives blog switches and resets on logical
close; it is not persisted in canonical storage.
18 changes: 18 additions & 0 deletions REMAINING_102_GAPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Remaining Native Consumer Gaps

The exact full DME1053 pair at candidate `99e032be6c5cf65b85a64ce863eb0e0065084682` recorded native 942 passed, 70 failures, 32 errors, and 9 skipped. The MySQL control recorded 1,022 passed, 24 errors, and 7 skipped.

## Raw-Diagnostic Classification

- The MySQL control errors are WP_CLI bootstrap failures and must remain separate from native engine parity.
- Native errors include the same missing WP_CLI class plus three physical `mysqli` root-access failures in `VenueProfileMutationsTest`; neither proves a native SQL mismatch.
- Native assertion failures include harness/application state differences such as user initialization and the physical-`mysqli` expectation in `WordPressLifecycleTest`.
- A repeated native query symptom is empty event candidate sets in `EventDateQueryAbilitiesTest` and duplicate/upsert paths. The posts schema did not classify exact `post_title` predicates as lookups. This branch supports ASCII case-insensitive, trailing-space-padded `=` and `IN` comparisons, with non-ASCII values failing closed. A title lookup without a reusable scoped snapshot explicitly fails after 1,024 canonical source files; ASCII validation is a collation constraint, not a scan-cost bound.

## Still Unresolved

- Full Unicode MySQL collation semantics for title lookups remain unsupported.
- Title lookups over larger uncached canonical corpora require a reusable source index before they can execute without the explicit 1,024-file work limit.
- Physical `mysqli` and WP_CLI-dependent tests require separate Codebox/bootstrap ownership.
- Transaction semantics require a dedicated end-to-end framework repair; reporting an InnoDB engine string alone would not supply them.
- The remaining native assertions need paired, per-test diagnosis after this focused repair; aggregate full-suite counts are not parity evidence.
6 changes: 6 additions & 0 deletions db.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ function markdown_database_integration_native_plugin_dir( string $content_dir ):
define( 'MARKDOWN_DB_NATIVE_SHADOW_REPORT_PATH', $markdown_db_shadow_report_path );
}
}
if ( ! defined( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' ) ) {
$markdown_db_shadow_trace_path = getenv( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' );
if ( is_string( $markdown_db_shadow_trace_path ) && '' !== $markdown_db_shadow_trace_path ) {
define( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH', $markdown_db_shadow_trace_path );
}
}

// Downstream capability resolution, health, and CLI read the same identifier,
// so the backend is settled before it is published. An operator who named a
Expand Down
2 changes: 1 addition & 1 deletion inc/class-wp-markdown-storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ private function safe_unlink( string $path ): bool {

/** Remove a cached post path only when it still carries that post's ID. */
private function safe_unlink_owned_by( string $path, int $post_id ): bool {
return $post_id === $this->extract_id_from_file( $path ) && $this->safe_unlink( $path );
return $this->existing_path_is_safe( $path ) && $post_id === $this->extract_id_from_file( $path ) && $this->safe_unlink( $path );
}

/**
Expand Down
113 changes: 107 additions & 6 deletions inc/native/class-wp-markdown-native-authoritative-snapshot-runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ final class WP_Markdown_Native_Authoritative_Snapshot_Runtime implements WP_Mark
private const MAX_ROWS_PER_TABLE = 10000;
private const MAX_BYTES_PER_TABLE = 8388608;

/** @param array<int,array{table:string,exists:bool,rows?:int,sha256?:string,schema_sha256?:string}> $provenance */
public function __construct( private WP_Markdown_Query_Runtime $runtime, private array $provenance ) {}
/** @param array<int,array{table:string,exists:bool,temporary?:bool,rows?:int,sha256?:string,schema_sha256?:string}> $provenance */
public function __construct( private WP_Markdown_Query_Runtime $runtime, private array $provenance, private ?string $database_name = null ) {}

public static function capture( object $database, string $sql, string $prefix ): self {
self::trace_runtime_phase( 'capture', $sql );
$connection = method_exists( $database, 'markdown_db_mysql_connection' )
? $database->markdown_db_mysql_connection()
: ( $database->dbh ?? null );
Expand All @@ -26,6 +27,8 @@ public static function capture( object $database, string $sql, string $prefix ):
}

$prefixes = self::schema_prefixes( $database, $prefix );
$database_name = self::database_name( $connection );
$catalog_tables = WP_Markdown_Native_Schema_Introspection::requested_information_schema_tables( $sql );
$registry = new WP_Markdown_Native_Table_Registry();
$provenance = array();
foreach ( $tables as $table ) {
Expand All @@ -43,11 +46,60 @@ public static function capture( object $database, string $sql, string $prefix ):
if ( ! $schema instanceof WP_Markdown_Native_Table_Schema ) {
throw new WP_Markdown_Native_Snapshot_Input_Exception( 'markdown_db_native_snapshot_input_unavailable', 'source_schema_unavailable' );
}
$temporary = 1 === preg_match( '/^CREATE\s+TEMPORARY\s+TABLE\b/i', $definition );
if ( $temporary && is_array( $catalog_tables ) && in_array( $table, $catalog_tables, true ) ) {
// SHOW CREATE resolves the session temporary table; metadata must come from the permanent catalog.
$definition = self::permanent_catalog_definition( $connection, $table );
$compiled = '' === $definition ? array() : WP_Markdown_Native_Schema_Catalog::compile( $definition, $prefixes, array( $table ) );
$schema_definition = 1 === count( $compiled ) ? reset( $compiled ) : null;
$schema = is_array( $schema_definition ) ? WP_Markdown_Native_Schema_Catalog::indexed_snapshot_schema( $schema_definition ) : null;
if ( ! $schema instanceof WP_Markdown_Native_Table_Schema ) {
throw new WP_Markdown_Native_Snapshot_Input_Exception( 'markdown_db_native_snapshot_input_unavailable', 'permanent_catalog_schema_unavailable' );
}
$registry->register( $table, $schema, new WP_Markdown_Native_Authoritative_Snapshot_Provider( array(), $schema ) );
$provenance[] = array( 'table' => $table, 'exists' => true, 'temporary' => true, 'schema_sha256' => hash( 'sha256', $definition ) );
continue;
}
$rows = self::rows( $connection, 'SELECT * FROM ' . $quoted . ' LIMIT ' . ( self::MAX_ROWS_PER_TABLE + 1 ) );
$registry->register( $table, $schema, new WP_Markdown_Native_Authoritative_Snapshot_Provider( $rows, $schema ) );
$provenance[] = array( 'table' => $table, 'exists' => true, 'rows' => count( $rows ), 'sha256' => hash( 'sha256', self::encode_rows( $rows ) ), 'schema_sha256' => hash( 'sha256', $definition ) );
$provenance[] = array( 'table' => $table, 'exists' => true, 'temporary' => $temporary, 'rows' => count( $rows ), 'sha256' => hash( 'sha256', self::encode_rows( $rows ) ), 'schema_sha256' => hash( 'sha256', $definition ) );
}
return new self( new WP_Markdown_Native_Query_Runtime( $registry, database_name: $database_name ), $provenance, $database_name );
}

private static function trace_runtime_phase( string $phase, ?string $sql = null ): void {
$path = defined( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' ) ? MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH : getenv( 'MARKDOWN_DB_NATIVE_SHADOW_TRACE_PATH' );
if ( ! is_string( $path ) || '' === $path ) {
return;
}
if ( ( is_file( $path ) ? (int) filesize( $path ) : 0 ) >= 65536 ) {
return;
}
$event = array( 'phase' => $phase, 'file_sha256' => hash_file( 'sha256', __FILE__ ) );
if ( null !== $sql && strlen( $sql ) <= 65536 ) {
try {
$event['sql_sha256'] = hash( 'sha256', $sql );
$event['token_types'] = array_slice( array_map( static fn( WP_Markdown_Native_SQL_Token $token ): string => $token->type(), ( new WP_Markdown_Native_SQL_Tokenizer() )->tokenize( $sql ) ), 0, 128 );
$event['table_count'] = count( self::tables_in( $sql ) );
} catch ( WP_Markdown_Native_Snapshot_Input_Exception|WP_Markdown_Native_SQL_Parse_Error ) {
$event['token_types'] = array( 'parse_error' );
}
}
$encoded = json_encode( $event, JSON_UNESCAPED_SLASHES ) . "\n";
if ( strlen( $encoded ) <= 4096 && false !== ( $trace = @fopen( $path, 'c' ) ) ) {
try {
if ( flock( $trace, LOCK_EX ) ) {
$size = fstat( $trace )['size'] ?? 0;
if ( $size + strlen( $encoded ) <= 65536 ) {
fseek( $trace, 0, SEEK_END );
fwrite( $trace, $encoded );
}
flock( $trace, LOCK_UN );
}
} finally {
fclose( $trace );
}
}
return new self( new WP_Markdown_Native_Query_Runtime( $registry ), $provenance );
}

/** @return array<int,string> */
Expand All @@ -59,6 +111,12 @@ private static function schema_prefixes( object $database, string $prefix ): arr
return array_values( array_unique( array_filter( $prefixes, static fn( string $candidate ): bool => '' !== $candidate ) ) );
}

private static function database_name( object $connection ): ?string {
$row = self::one_row( $connection, 'SELECT DATABASE()' );
$value = is_array( $row ) ? reset( $row ) : null;
return is_string( $value ) ? $value : null;
}

public function execute( WP_Markdown_Query_Request $request ): WP_Markdown_Query_Result {
$result = $this->runtime->execute( $request );
$diagnostic = $result->diagnostic() ?? array();
Expand All @@ -85,15 +143,58 @@ private function has_explicitly_absent_source( string $sql ): bool {
return array() !== array_intersect( self::tables_in( $sql ), $absent );
}

/** @return array{read_connection:string,tables:array<int,array{table:string,exists:bool,rows?:int,sha256?:string,schema_sha256?:string}>} */
/** @return array{read_connection:string,database_sha256:?string,tables:array<int,array{table:string,exists:bool,rows?:int,sha256?:string,schema_sha256?:string}>} */
public function provenance(): array {
return array( 'read_connection' => 'authoritative_mysql_connection_pre_query', 'tables' => $this->provenance );
return array_filter(
array(
'read_connection' => 'authoritative_mysql_connection_pre_query',
'database_sha256' => null === $this->database_name ? null : hash( 'sha256', $this->database_name ),
'tables' => $this->provenance,
),
static fn( mixed $value ): bool => null !== $value
);
}

private static function permanent_catalog_definition( object $connection, string $table ): string {
$escaped = str_replace( "'", "''", $table );
$rows = self::rows( $connection, "SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$escaped}' ORDER BY ORDINAL_POSITION" );
if ( array() === $rows ) {
return '';
}
$columns = array();
$primary = array();
foreach ( $rows as $row ) {
$name = (string) ( $row['COLUMN_NAME'] ?? '' );
$type = (string) ( $row['COLUMN_TYPE'] ?? '' );
if ( 1 !== preg_match( '/^[A-Za-z0-9_]+$/', $name ) || 1 !== preg_match( '/^[A-Za-z]+(?:\([0-9,]+\))?(?:\s+unsigned)?$/i', $type ) ) {
return '';
}
$line = '`' . $name . '` ' . $type . ( 'NO' === ( $row['IS_NULLABLE'] ?? null ) ? ' NOT NULL' : '' );
if ( null !== ( $row['COLUMN_DEFAULT'] ?? null ) ) {
$line .= " DEFAULT '" . str_replace( "'", "''", (string) $row['COLUMN_DEFAULT'] ) . "'";
}
if ( str_contains( strtolower( (string) ( $row['EXTRA'] ?? '' ) ), 'auto_increment' ) ) {
$line .= ' AUTO_INCREMENT';
}
$columns[] = $line;
if ( 'PRI' === ( $row['COLUMN_KEY'] ?? null ) ) {
$primary[] = '`' . $name . '`';
}
}
if ( array() !== $primary ) {
$columns[] = 'PRIMARY KEY (' . implode( ',', $primary ) . ')';
}
return 'CREATE TABLE `' . $table . '` (' . implode( ',', $columns ) . ')';
}

/** @return array<int,string> */
private static function tables_in( string $sql ): array {
$plan = ( new WP_Markdown_Native_Query_Parser() )->parse( $sql );
if ( $plan instanceof WP_Markdown_Query_Result ) {
$catalog_tables = WP_Markdown_Native_Schema_Introspection::requested_information_schema_tables( $sql );
if ( null !== $catalog_tables ) {
return $catalog_tables;
}
$diagnostic = $plan->diagnostic() ?? array();
throw new WP_Markdown_Native_Snapshot_Input_Exception(
(string) ( $diagnostic['code'] ?? 'markdown_db_native_unsupported_query' ),
Expand Down
8 changes: 7 additions & 1 deletion inc/native/class-wp-markdown-native-post-mutations.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ private function write( WP_Markdown_Query_Request $request ): WP_Markdown_Query_
return $write;
}
foreach ( $write->predicates() as $predicate ) {
if ( $predicate instanceof WP_Markdown_Native_Table_Predicate_Group ) {
return $this->failure( 'unsupported_predicate', 'mdi-native post mutations require simple conjunctive restrictions.' );
}
if ( $predicate instanceof WP_Markdown_Native_Table_Subquery_Predicate ) {
return $this->failure( 'unsupported_subquery_shape', 'mdi-native post mutations do not support IN subqueries.' );
}
Expand All @@ -89,7 +92,10 @@ private function write( WP_Markdown_Query_Request $request ): WP_Markdown_Query_
return $this->failure( 'unsupported_mutation_column', 'The WHERE restriction names a column outside the wp_posts schema.' );
}
}
foreach ( array_keys( $write->values() ) as $column ) {
foreach ( $write->values() as $column => $value ) {
if ( $value instanceof WP_Markdown_Native_Query_Scalar_Expression ) {
return $this->failure( 'unsupported_assignment', 'mdi-native post mutations do not yet evaluate scalar assignments.' );
}
if ( ! $schema->has_column( (string) $column ) ) {
return $this->failure( 'unsupported_mutation_column', 'The assignment names a column outside the wp_posts schema.' );
}
Expand Down
11 changes: 7 additions & 4 deletions inc/native/class-wp-markdown-native-query-ast.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ public function __construct(
private readonly array $union_orders = array(),
private readonly ?int $union_limit = null,
private readonly int $union_limit_offset = 0,
private readonly array $group_expressions = array()
private readonly array $group_expressions = array(),
private readonly array $index_hints = array()
) {}

public function selects_all(): bool {
Expand Down Expand Up @@ -387,15 +388,17 @@ public function union_all(): bool { return $this->union_all; }
public function union_orders(): array { return $this->union_orders; }
public function union_limit(): ?int { return $this->union_limit; }
public function union_limit_offset(): int { return $this->union_limit_offset; }
/** @return array<int,array{table:string,mode:string,indexes:array<int,string>}> */
public function index_hints(): array { return $this->index_hints; }

/** Attach a query-expression branch without confusing its local clauses with UNION clauses. */
public function with_union( self $union, bool $all ): self {
return new self( $this->select_all, $this->count_all, $this->projection, $this->table, $this->predicates, $this->orders, $this->limit, $this->alias, $this->joins, $this->calculates_found_rows, $this->limit_offset, $this->distinct, $this->contradiction, $this->group_by, $this->aggregates, $this->scalar_projection, $this->having, $this->subqueries, $union, $this->scalar_predicates, $this->scalar_having, $this->group_expression, $this->boolean_predicate, $this->derived, $all, $this->union_orders, $this->union_limit, $this->union_limit_offset );
return new self( $this->select_all, $this->count_all, $this->projection, $this->table, $this->predicates, $this->orders, $this->limit, $this->alias, $this->joins, $this->calculates_found_rows, $this->limit_offset, $this->distinct, $this->contradiction, $this->group_by, $this->aggregates, $this->scalar_projection, $this->having, $this->subqueries, $union, $this->scalar_predicates, $this->scalar_having, $this->group_expression, $this->boolean_predicate, $this->derived, $all, $this->union_orders, $this->union_limit, $this->union_limit_offset, $this->group_expressions, $this->index_hints );
}

/** @param array<int,array{column:WP_Markdown_Native_SQL_Identifier,descending:bool}> $orders */
public function with_union_tail( array $orders, ?int $limit, int $offset ): self {
return new self( $this->select_all, $this->count_all, $this->projection, $this->table, $this->predicates, $this->orders, $this->limit, $this->alias, $this->joins, $this->calculates_found_rows, $this->limit_offset, $this->distinct, $this->contradiction, $this->group_by, $this->aggregates, $this->scalar_projection, $this->having, $this->subqueries, $this->union, $this->scalar_predicates, $this->scalar_having, $this->group_expression, $this->boolean_predicate, $this->derived, $this->union_all, $orders, $limit, $offset );
return new self( $this->select_all, $this->count_all, $this->projection, $this->table, $this->predicates, $this->orders, $this->limit, $this->alias, $this->joins, $this->calculates_found_rows, $this->limit_offset, $this->distinct, $this->contradiction, $this->group_by, $this->aggregates, $this->scalar_projection, $this->having, $this->subqueries, $this->union, $this->scalar_predicates, $this->scalar_having, $this->group_expression, $this->boolean_predicate, $this->derived, $this->union_all, $orders, $limit, $offset, $this->group_expressions, $this->index_hints );
}

public function append_union( self $branch, bool $all ): self {
Expand All @@ -406,6 +409,6 @@ public function append_union( self $branch, bool $all ): self {

/** Remove clauses that syntactically follow an unparenthesized UNION branch. */
public function without_order_limit(): self {
return new self( $this->select_all, $this->count_all, $this->projection, $this->table, $this->predicates, array(), null, $this->alias, $this->joins, $this->calculates_found_rows, 0, $this->distinct, $this->contradiction, $this->group_by, $this->aggregates, $this->scalar_projection, $this->having, $this->subqueries, $this->union, $this->scalar_predicates, $this->scalar_having, $this->group_expression, $this->boolean_predicate, $this->derived, $this->union_all, $this->union_orders, $this->union_limit, $this->union_limit_offset );
return new self( $this->select_all, $this->count_all, $this->projection, $this->table, $this->predicates, array(), null, $this->alias, $this->joins, $this->calculates_found_rows, 0, $this->distinct, $this->contradiction, $this->group_by, $this->aggregates, $this->scalar_projection, $this->having, $this->subqueries, $this->union, $this->scalar_predicates, $this->scalar_having, $this->group_expression, $this->boolean_predicate, $this->derived, $this->union_all, $this->union_orders, $this->union_limit, $this->union_limit_offset, $this->group_expressions, $this->index_hints );
}
}
6 changes: 5 additions & 1 deletion inc/native/class-wp-markdown-native-query-contracts.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,17 @@ public function __construct(
private readonly array $union_order_by = array(),
private readonly ?int $union_limit = null,
private readonly int $union_limit_offset = 0,
private readonly array $group_expressions = array()
private readonly array $group_expressions = array(),
private readonly array $index_hints = array()
) {}

public function table(): string {
return $this->table;
}

/** @return array<int,array{table:string,mode:string,indexes:array<int,string>}> */
public function index_hints(): array { return $this->index_hints; }

/** @return array<int,string> */
public function projection(): array {
return $this->projection;
Expand Down
Loading