Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions inc/native/class-wp-markdown-native-query-executor.php
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ private function execute_query_plan( WP_Markdown_Native_Query_Plan $plan, bool $
? array()
: array_values( array_filter( $predicates, static fn( WP_Markdown_Native_Query_Predicate $predicate ): bool => $predicate !== $pushdown ) );
$provider_projection = $plan->counts_all() ? array() : array_merge( $projection, $scalar_columns );
// A residual predicate is matched here, after the provider read, so the
// provider has to return the columns it reads. A provider that resolves
// a column lazily returns it empty when it is absent from the
// projection, and the residual then matches against that empty value.
foreach ( $residual as $predicate ) { $provider_projection = array_merge( $provider_projection, $predicate->columns() ); }
foreach ( $scalar_predicates as $predicate ) { $provider_projection = array_merge( $provider_projection, $predicate->columns() ); }
if ( null !== $boolean_predicate ) { $provider_projection = array_merge( $provider_projection, $boolean_predicate->columns() ); }
foreach ( array_merge( $plan->subqueries(), $this->boolean_subqueries( $boolean_predicate ) ) as $subquery ) {
Expand Down
9 changes: 8 additions & 1 deletion inc/native/class-wp-markdown-native-query-runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,14 @@ private static function shared_storage( string $content_root, bool $network_root
$key = ( $network_root ? 'network:' : 'site:' ) . rtrim( $content_root, '/\\' );
if ( ! isset( self::$storages[ $key ] ) ) {
// The network root owns sites/{blog_id}; it is not a post-type tree.
self::$storages[ $key ] = new WP_Markdown_Storage( $content_root, $network_root ? array( 'sites' ) : array() );
$storage = new WP_Markdown_Storage( $content_root, $network_root ? array( 'sites' ) : array() );
// A post with a parent belongs inside its parent's directory, and
// the writer walks the ancestor chain to find that directory. Without
// a resolver it cannot read an ancestor, so it writes the child flat
// at the post-type root while the row still records the parent. The
// path then disagrees with the hierarchy the row declares.
$storage->set_post_resolver( static fn( int $post_id ): ?object => $storage->read_post( $post_id ) );
self::$storages[ $key ] = $storage;
}
return self::$storages[ $key ];
}
Expand Down
29 changes: 29 additions & 0 deletions tests/smoke-native-markdown-post-write.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@
$wp_id = (int) $wp_insert->wpdb_state()['insert_id'];
$wp_read = $runtime->execute( new WP_Markdown_Query_Request( 'SELECT post_title, comment_count FROM wp_posts WHERE ID = ' . $wp_id, 'wp_' ) );

// A child post belongs inside its parent's directory. The writer walks the
// ancestor chain through the runtime's post resolver to find that directory,
// so a runtime without one writes the child flat while the row still records
// the parent, and the canonical path then disagrees with the hierarchy.
$parent_insert = $runtime->execute(
new WP_Markdown_Query_Request(
"INSERT INTO wp_posts (post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count) VALUES (1, '2026-08-27 12:00:00', '2026-08-27 12:00:00', 'Parent body', 'Parent', '', 'publish', 'open', 'open', '', 'parent-slug', '', '', '2026-08-27 12:00:00', '2026-08-27 12:00:00', '', 0, 'http://localhost/parent-slug/', 0, 'post', '', 0)",
'wp_'
)
);
$parent_id = (int) ( $parent_insert->wpdb_state()['insert_id'] ?? 0 );
$child_insert = $runtime->execute(
new WP_Markdown_Query_Request(
"INSERT INTO wp_posts (post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count) VALUES (1, '2026-08-27 12:00:00', '2026-08-27 12:00:00', 'Child body needle', 'Child', '', 'publish', 'open', 'open', '', 'child-slug', '', '', '2026-08-27 12:00:00', '2026-08-27 12:00:00', '', {$parent_id}, 'http://localhost/child-slug/', 0, 'post', '', 0)",
'wp_'
)
);
$child_id = (int) ( $child_insert->wpdb_state()['insert_id'] ?? 0 );
$child_files = array_values( array_filter( $markdown_files( $content ), static fn( string $path ): bool => str_ends_with( $path, 'child-slug.md' ) ) );
$child_nested = array() !== $child_files && str_ends_with( $child_files[0], '/post/parent-slug/child-slug.md' );
// A body predicate is matched after the provider read, so a child whose file
// the provider cannot resolve returns empty content and matches nothing.
$child_body = $runtime->execute( new WP_Markdown_Query_Request( "SELECT ID FROM wp_posts WHERE post_content LIKE '%needle%'", 'wp_' ) );
$child_body_ids = array_map( static fn( object $row ): int => (int) $row->ID, $child_body->wpdb_state()['last_result'] ?? array() );

$checks = array(
'an INSERT assigns an identity and writes markdown' => 1 === $insert->return_value()
&& 1 === $insert->wpdb_state()['insert_id']
Expand All @@ -62,6 +87,8 @@
'a DELETE removes the canonical file' => 1 === $delete->return_value()
&& 0 === $after_delete->return_value()
&& array() === $files_after_delete,
'a child post is written inside its parent directory' => $parent_id > 0 && $child_id > 0 && $child_nested,
'a child post body is searchable at its canonical path' => in_array( $child_id, $child_body_ids, true ),
'a WordPress wp_insert_post row fills integer defaults' => 1 === $wp_insert->return_value()
&& $wp_id > 0
&& 'Native Save Probe' === ( $wp_read->wpdb_state()['last_result'][0]->post_title ?? null )
Expand All @@ -74,6 +101,8 @@
$failed = $failed || ! $passed;
}

array_map( 'unlink', glob( $content . '/post/parent-slug/*' ) ?: array() );
@rmdir( $content . '/post/parent-slug' );
array_map( 'unlink', glob( $content . '/post/*' ) ?: array() );
@rmdir( $content . '/post' );
@rmdir( $content );
Expand Down