| @@ -418,15 +418,15 @@ | ||
| 418 | 418 | $live_rows = isset( $row['digest']['live_rows'] ) ? (string) $row['digest']['live_rows'] : ''; |
| 419 | 419 | // Products are the one collection whose servability is NARROWER than a live |
| 420 | 420 | // row: POS visibility and the readable-catalog scope both apply, so the |
| 421 | 421 | // richer reader owns them. Every other id-space is exactly its live row. |
| 422 | - $products = 'products' === $collection; | |
| 423 | - if ( ! $products && ( '' === $live_rows || ! method_exists( $this, $live_rows ) ) ) { | |
| 422 | + $servable = $row['digest']['servable'] ?? null; | |
| 423 | + if ( null === $servable && ( '' === $live_rows || ! method_exists( $this, $live_rows ) ) ) { | |
| 424 | 424 | return $ids; |
| 425 | 425 | } |
| 426 | 426 | $wpdb->last_error = ''; |
| 427 | - if ( $products ) { | |
| 428 | - $servable_ids = $this->servable_product_ids( $ids, true ); | |
| 427 | + if ( null !== $servable ) { | |
| 428 | + $servable_ids = $this->$servable( $ids, true ); | |
| 429 | 429 | } else { |
| 430 | 430 | $predicate = (string) \call_user_func( array( $this, $live_rows ), 'requested.id' ); |
| 431 | 431 | $requested = implode( ' UNION ALL ', array_fill( 0, \count( $ids ), 'SELECT %d AS id' ) ); |
| 432 | 432 | $servable_ids = $wpdb->get_col( |
| @@ -536,18 +536,18 @@ | ||
| 536 | 536 | global $wpdb; |
| 537 | 537 | $bucket_size = max( 1, (int) ( $range['bucket_size'] ?? 1 ) ); |
| 538 | 538 | $window_start = max( 0, (int) ( $range['start'] ?? 0 ) ); |
| 539 | 539 | $window_end = max( 0, (int) ( $range['end'] ?? 0 ) ); |
| 540 | - $publish = 'products' === $collection && 'publish' === ( $filters['status'] ?? '' ); | |
| 541 | - $object_types = self::OBJECT_TYPES_SQL; | |
| 542 | - $current_sql = $this->row_digest_select_sql( 'p.ID >= %d AND p.ID < %d' ); | |
| 543 | - if ( 'customers' === $collection ) { | |
| 544 | - $object_types = "('customer')"; | |
| 545 | - $current_sql = $this->customer_digest_select_sql( 'u.ID >= %d AND u.ID < %d' ); | |
| 546 | - } elseif ( 'orders' === $collection ) { | |
| 547 | - $object_types = "('order')"; | |
| 548 | - $current_sql = $this->order_digest_select_sql( '{id} >= %d AND {id} < %d' ); | |
| 540 | + $digest = Collections::row( $collection )['digest'] ?? null; | |
| 541 | + if ( null === $digest ) { | |
| 542 | + return array( | |
| 543 | + 'buckets' => array(), | |
| 544 | + 'max_id' => 0, | |
| 545 | + ); | |
| 549 | 546 | } |
| 547 | + $publish = isset( Collections::row( $collection )['digest']['published_ids'] ) && 'publish' === ( $filters['status'] ?? '' ); | |
| 548 | + $object_types = "('" . implode( "','", $digest['object_types'] ) . "')"; | |
| 549 | + $current_sql = $this->{$digest['select']}( $digest['id_column'] . ' >= %d AND ' . $digest['id_column'] . ' < %d' ); | |
| 550 | 550 | $current_scope = $publish ? $this->product_servable_predicate_sql( 't.id', true ) : array( |
| 551 | 551 | 'sql' => '', |
| 552 | 552 | 'args' => array(), |
| 553 | 553 | ); |
| @@ -655,8 +655,9 @@ | ||
| 655 | 655 | public function bucket_drift( array $range ): array { |
| 656 | 656 | global $wpdb; |
| 657 | 657 | $range_start = max( 0, (int) ( $range['start'] ?? 0 ) ); |
| 658 | 658 | $range_end = max( 0, (int) ( $range['end'] ?? 0 ) ); |
| 659 | + $digest = Collections::row( 'products' )['digest']; | |
| 659 | 660 | $table = $this->table_name(); |
| 660 | 661 | |
| 661 | 662 | // Same-formula invariant: the current side must digest identically to the stored side. |
| 662 | 663 | $this->raise_group_concat_max_len(); |
| @@ -664,17 +665,17 @@ | ||
| 664 | 665 | $wpdb->prepare( |
| 665 | 666 | 'SELECT cur.id AS id,' |
| 666 | 667 | . " CASE WHEN d.digest IS NULL THEN 'missing_stored' ELSE 'changed' END AS status," |
| 667 | 668 | . ' d.digest AS stored_digest, cur.crc AS current_digest, cur.object_type AS object_type' |
| 668 | - . ' FROM (' . $this->row_digest_select_sql( 'p.ID >= %d AND p.ID < %d' ) . ') cur' | |
| 669 | + . ' FROM (' . $this->{$digest['select']}( $digest['id_column'] . ' >= %d AND ' . $digest['id_column'] . ' < %d' ) . ') cur' | |
| 669 | 670 | . " LEFT JOIN {$table} d ON d.object_id = cur.id AND d.object_type = cur.object_type" |
| 670 | 671 | . ' WHERE d.digest IS NULL OR d.digest <> cur.crc' |
| 671 | 672 | . ' UNION ALL' |
| 672 | 673 | . " SELECT d.object_id AS id, 'deleted' AS status, d.digest AS stored_digest, NULL AS current_digest, d.object_type AS object_type" |
| 673 | 674 | . " FROM {$table} d" |
| 674 | - . ' WHERE d.object_type IN ' . self::OBJECT_TYPES_SQL | |
| 675 | + . ' WHERE d.object_type IN ' . "('" . implode( "','", $digest['object_types'] ) . "')" | |
| 675 | 676 | . ' AND d.object_id >= %d AND d.object_id < %d' |
| 676 | - . ' AND NOT ' . $this->live_row_exists_sql( 'd.object_id' ) | |
| 677 | + . ' AND NOT ' . $this->{$digest['live_rows']}( 'd.object_id' ) | |
| 677 | 678 | . ' ORDER BY id ASC', |
| 678 | 679 | $range_start, |
| 679 | 680 | $range_end, |
| 680 | 681 | $range_start, |
| @@ -725,15 +726,14 @@ | ||
| 725 | 726 | |
| 726 | 727 | $servable_join = ''; |
| 727 | 728 | $servable_filter = ''; |
| 728 | 729 | $servable_args = array(); |
| 729 | - if ( 'customers' === $collection ) { | |
| 730 | - $inner_sql = $this->customer_digest_select_sql( 'u.ID >= %d AND u.ID < %d' ); | |
| 731 | - } elseif ( 'orders' === $collection ) { | |
| 732 | - // Orders bucket over their own id-space (HPOS o.id / CPT p.ID) via the {id} placeholder. | |
| 733 | - $inner_sql = $this->order_digest_select_sql( '{id} >= %d AND {id} < %d' ); | |
| 734 | - } else { | |
| 735 | - $inner_sql = $this->row_digest_select_sql( 'p.ID >= %d AND p.ID < %d' ); | |
| 730 | + $digest = Collections::row( $collection )['digest'] ?? null; | |
| 731 | + if ( null === $digest ) { | |
| 732 | + return array(); | |
| 733 | + } | |
| 734 | + $inner_sql = $this->{$digest['select']}( $digest['id_column'] . ' >= %d AND ' . $digest['id_column'] . ' < %d' ); | |
| 735 | + if ( isset( $digest['servable'] ) ) { | |
| 736 | 736 | $servable_join = " INNER JOIN {$wpdb->posts} catalog_post ON catalog_post.ID = cur.id"; |
| 737 | 737 | if ( 'publish' === ( $filters['status'] ?? '' ) ) { |
| 738 | 738 | $servable_join .= " LEFT JOIN {$wpdb->posts} parent_product ON parent_product.ID = catalog_post.post_parent" |
| 739 | 739 | . " AND catalog_post.post_type = 'product_variation'"; |
| @@ -904,28 +904,43 @@ | ||
| 904 | 904 | * |
| 905 | 905 | * @return array{sql: string, args: array<int, int>} |
| 906 | 906 | */ |
| 907 | 907 | private function live_max_id_sql( string $collection, bool $publish ): array { |
| 908 | - global $wpdb; | |
| 909 | - if ( 'customers' === $collection ) { | |
| 908 | + $digest = Collections::row( $collection )['digest'] ?? null; | |
| 909 | + if ( null === $digest ) { | |
| 910 | 910 | return array( |
| 911 | - 'sql' => "SELECT MAX(u.ID) FROM {$wpdb->users} u", | |
| 911 | + 'sql' => 'SELECT NULL WHERE 1 = 0', | |
| 912 | 912 | 'args' => array(), |
| 913 | 913 | ); |
| 914 | 914 | } |
| 915 | - if ( 'orders' === $collection ) { | |
| 916 | - if ( $this->orders_are_hpos() ) { | |
| 917 | - $orders_table = $wpdb->prefix . 'wc_orders'; | |
| 918 | - return array( | |
| 919 | - 'sql' => "SELECT MAX(o.id) FROM {$orders_table} o WHERE " . $this->live_order_predicate_sql( 'o' ), | |
| 920 | - 'args' => array(), | |
| 921 | - ); | |
| 922 | - } | |
| 915 | + return $this->{$digest['live_max']}( $publish ); | |
| 916 | + } | |
| 917 | + | |
| 918 | + private function customer_live_max_id_sql(): array { | |
| 919 | + global $wpdb; | |
| 920 | + return array( | |
| 921 | + 'sql' => "SELECT MAX(u.ID) FROM {$wpdb->users} u", | |
| 922 | + 'args' => array(), | |
| 923 | + ); | |
| 924 | + } | |
| 925 | + | |
| 926 | + private function order_live_max_id_sql(): array { | |
| 927 | + global $wpdb; | |
| 928 | + if ( $this->orders_are_hpos() ) { | |
| 929 | + $orders_table = $wpdb->prefix . 'wc_orders'; | |
| 923 | 930 | return array( |
| 924 | - 'sql' => "SELECT MAX(p.ID) FROM {$wpdb->posts} p WHERE " . $this->live_order_predicate_sql( 'p' ), | |
| 931 | + 'sql' => "SELECT MAX(o.id) FROM {$orders_table} o WHERE " . $this->live_order_predicate_sql( 'o' ), | |
| 925 | 932 | 'args' => array(), |
| 926 | 933 | ); |
| 927 | 934 | } |
| 935 | + return array( | |
| 936 | + 'sql' => "SELECT MAX(p.ID) FROM {$wpdb->posts} p WHERE " . $this->live_order_predicate_sql( 'p' ), | |
| 937 | + 'args' => array(), | |
| 938 | + ); | |
| 939 | + } | |
| 940 | + | |
| 941 | + private function product_live_max_id_sql( bool $publish ): array { | |
| 942 | + global $wpdb; | |
| 928 | 943 | if ( ! $publish ) { |
| 929 | 944 | return array( |
| 930 | 945 | 'sql' => "SELECT MAX(p.ID) FROM {$wpdb->posts} p WHERE " . $this->live_product_predicate_sql( 'p' ), |
| 931 | 946 | 'args' => array(), |