Add migrations for operational data table.

This commit is contained in:
vedanshujain 2022-03-10 19:50:01 +05:30
parent 6cc34e8616
commit 786561d6fd
3 changed files with 170 additions and 33 deletions

View File

@ -39,10 +39,12 @@ class MetaToCustomTableMigrator {
* @var string[] * @var string[]
*/ */
private $wpdb_placeholder_for_type = array( private $wpdb_placeholder_for_type = array(
'int' => '%d', 'int' => '%d',
'decimal' => '%f', 'decimal' => '%f',
'string' => '%s', 'string' => '%s',
'date' => '%s', 'date' => '%s',
'date_epoch' => '%s',
'bool' => '%d',
); );
/** /**
@ -181,18 +183,18 @@ class MetaToCustomTableMigrator {
// TODO: Add code to validate params. // TODO: Add code to validate params.
$entity_table_query = $this->build_entity_table_query( $where_clause, $batch_size, $order_by ); $entity_table_query = $this->build_entity_table_query( $where_clause, $batch_size, $order_by );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared. // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared.
$entity_data = $wpdb->get_results( $entity_table_query ); $entity_data = $wpdb->get_results( $entity_table_query );
if ( empty( $entity_data ) ) { if ( empty( $entity_data ) ) {
return array( return array(
'data' => array(), 'data' => array(),
'errors' => array(), 'errors' => array(),
); );
} }
$entity_ids = array_column( $entity_data, 'entity_rel_column' ); $entity_ids = array_column( $entity_data, 'entity_rel_column' );
$meta_table_query = $this->build_meta_data_query( $entity_ids ); $meta_table_query = $this->build_meta_data_query( $entity_ids );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared. // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared.
$meta_data = $wpdb->get_results( $meta_table_query ); $meta_data = $wpdb->get_results( $meta_table_query );
return $this->process_and_sanitize_data( $entity_data, $meta_data ); return $this->process_and_sanitize_data( $entity_data, $meta_data );
} }
@ -208,10 +210,10 @@ class MetaToCustomTableMigrator {
*/ */
private function build_entity_table_query( $where_clause, $batch_size, $order_by ) { private function build_entity_table_query( $where_clause, $batch_size, $order_by ) {
global $wpdb; global $wpdb;
$entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] ); $entity_table = $this->escape_backtick( $this->schema_config['entity_schema']['table_name'] );
$primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] ); $primary_id_column = $this->escape_backtick( $this->schema_config['entity_schema']['primary_id'] );
$entity_rel_column = $this->escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] ); $entity_rel_column = $this->escape_backtick( $this->schema_config['entity_meta_relation']['entity_rel_column'] );
$entity_keys = array(); $entity_keys = array();
foreach ( $this->core_column_mapping as $column_name => $column_schema ) { foreach ( $this->core_column_mapping as $column_name => $column_schema ) {
if ( isset( $column_schema['select_clause'] ) ) { if ( isset( $column_schema['select_clause'] ) ) {
$select_clause = $column_schema['select_clause']; $select_clause = $column_schema['select_clause'];
@ -222,7 +224,7 @@ class MetaToCustomTableMigrator {
} }
$entity_column_string = implode( ', ', $entity_keys ); $entity_column_string = implode( ', ', $entity_keys );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $entity_table, $primary_id_column and $entity_column_string is escaped for backticks. $where clause and $order_by should already be escaped. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $entity_table, $primary_id_column and $entity_column_string is escaped for backticks. $where clause and $order_by should already be escaped.
$query = $wpdb->prepare( $query = $wpdb->prepare(
" "
SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_rel_column, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d; SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_rel_column, $entity_column_string FROM $entity_table WHERE $where_clause ORDER BY $order_by LIMIT %d;
", ",
@ -230,6 +232,7 @@ SELECT `$primary_id_column` as primary_key_id, `$entity_rel_column` AS entity_re
$batch_size, $batch_size,
) )
); );
// phpcs:enable // phpcs:enable
return $query; return $query;
@ -279,6 +282,7 @@ WHERE
$meta_keys $meta_keys
) )
); );
// phpcs:enable // phpcs:enable
return $query; return $query;
@ -304,7 +308,7 @@ WHERE
$this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data ); $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data );
return array( return array(
'data' => $sanitized_entity_data, 'data' => $sanitized_entity_data,
'errors' => $error_records, 'errors' => $error_records,
); );
} }
@ -345,7 +349,7 @@ WHERE
$column_schema = $this->meta_column_mapping[ $datum->meta_key ]; $column_schema = $this->meta_column_mapping[ $datum->meta_key ];
$value = $this->validate_data( $datum->meta_value, $column_schema['type'] ); $value = $this->validate_data( $datum->meta_value, $column_schema['type'] );
if ( is_wp_error( $value ) ) { if ( is_wp_error( $value ) ) {
$error_records[ $datum->entity_id ][ $column_schema['destination'] ] = $value->get_error_message(); $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = "{$value->get_error_code()}: {$value->get_error_message()}";
} else { } else {
$sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value; $sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value;
} }
@ -368,10 +372,28 @@ WHERE
case 'int': case 'int':
$value = (int) $value; $value = (int) $value;
break; break;
case 'bool':
$value = wc_string_to_bool( $value );
break;
case 'date': case 'date':
// TODO: Test this validation in unit tests. // TODO: Test this validation in unit tests.
try { try {
$value = ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' ); if ( '' === $value ) {
$value = null;
} else {
$value = ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' );
}
} catch ( \Exception $e ) {
return new \WP_Error( $e->getMessage() );
}
break;
case 'date_epoch':
try {
if ( '' === $value ) {
$value = null;
} else {
$value = ( new \DateTime( "@$value" ) )->format( 'Y-m-d H:i:s' );
}
} catch ( \Exception $e ) { } catch ( \Exception $e ) {
return new \WP_Error( $e->getMessage() ); return new \WP_Error( $e->getMessage() );
} }

View File

@ -42,6 +42,13 @@ class WPPostToCOTMigrator {
*/ */
private $shipping_address_table_migrator; private $shipping_address_table_migrator;
/**
* Migrator instance to migrate operational data.
*
* @var MetaToCustomTableMigrator
*/
private $operation_data_table_migrator;
/** /**
* Names of different order tables. * Names of different order tables.
* *
@ -62,13 +69,17 @@ class WPPostToCOTMigrator {
'op_data' => $wpdb->prefix . 'wc_order_operational_data', 'op_data' => $wpdb->prefix . 'wc_order_operational_data',
); );
$order_config = $this->get_config_for_order_table(); $order_config = $this->get_config_for_order_table();
$billing_address_config = $this->get_config_for_address_table_billing(); $billing_address_config = $this->get_config_for_address_table_billing();
$shipping_address_config = $this->get_config_for_address_table_shipping(); $shipping_address_config = $this->get_config_for_address_table_shipping();
$operation_data_config = $this->get_config_for_operational_data_table();
$this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] ); $this->order_table_migrator = new MetaToCustomTableMigrator( $order_config['schema'], $order_config['meta'], $order_config['core'] );
$this->billing_address_table_migrator = new MetaToCustomTableMigrator( $billing_address_config['schema'], $billing_address_config['meta'], $billing_address_config['core'] ); $this->billing_address_table_migrator = new MetaToCustomTableMigrator( $billing_address_config['schema'], $billing_address_config['meta'], $billing_address_config['core'] );
$this->shipping_address_table_migrator = new MetaToCustomTableMigrator( $shipping_address_config['schema'], $shipping_address_config['meta'], $shipping_address_config['core'] ); $this->shipping_address_table_migrator = new MetaToCustomTableMigrator( $shipping_address_config['schema'], $shipping_address_config['meta'], $shipping_address_config['core'] );
$this->error_logger = new MigrationErrorLogger(); $this->operation_data_table_migrator = new MetaToCustomTableMigrator( $operation_data_config['schema'], $operation_data_config['meta'], $operation_data_config['core'] );
$this->error_logger = new MigrationErrorLogger();
} }
/** /**
@ -76,7 +87,7 @@ class WPPostToCOTMigrator {
* *
* @return array Config for order table. * @return array Config for order table.
*/ */
public function get_config_for_order_table() { private function get_config_for_order_table() {
global $wpdb; global $wpdb;
$order_table_schema_config = array( $order_table_schema_config = array(
'entity_schema' => array( 'entity_schema' => array(
@ -170,7 +181,7 @@ class WPPostToCOTMigrator {
* *
* @return array Billing address migration config. * @return array Billing address migration config.
*/ */
public function get_config_for_address_table_billing() { private function get_config_for_address_table_billing() {
return $this->get_config_for_address_table( 'billing' ); return $this->get_config_for_address_table( 'billing' );
} }
@ -179,7 +190,7 @@ class WPPostToCOTMigrator {
* *
* @return array Shipping address migration config. * @return array Shipping address migration config.
*/ */
public function get_config_for_address_table_shipping() { private function get_config_for_address_table_shipping() {
return $this->get_config_for_address_table( 'shipping' ); return $this->get_config_for_address_table( 'shipping' );
} }
@ -277,6 +288,108 @@ class WPPostToCOTMigrator {
); );
} }
/**
* Generate config for operational data.
*
* @return array Config for operational data table.
*/
private function get_config_for_operational_data_table() {
global $wpdb;
$schema_config = array(
'entity_schema' => array(
'primary_id' => 'post_id',
'table_name' => $this->table_names['orders'],
),
'entity_meta_schema' => array(
'meta_key_column' => 'meta_key',
'meta_value_column' => 'meta_value',
'table_name' => $wpdb->postmeta,
),
'destination_table' => $this->table_names['op_data'],
'entity_meta_relation' => array(
'entity_rel_column' => 'post_id',
'meta_rel_column' => 'post_id',
),
);
$core_config = array(
'id' => array(
'type' => 'int',
'destination' => 'order_id',
),
);
$meta_config = array(
'_created_via' => array(
'type' => 'string',
'destination' => 'created_via',
),
'_order_version' => array(
'type' => 'string',
'destination' => 'woocommerce_version',
),
'_prices_include_tax' => array(
'type' => 'bool',
'destination' => 'prices_include_tax',
),
'_recorded_coupon_usage_counts' => array(
'type' => 'bool',
'destination' => 'coupon_usages_are_counted',
),
'_download_permissions_granted' => array(
'type' => 'bool',
'destination' => 'download_permissions_granted',
),
'_cart_hash' => array(
'type' => 'string',
'destination' => 'cart_hash',
),
'_new_order_email_sent' => array(
'type' => 'bool',
'destination' => 'new_order_email_sent',
),
'_order_key' => array(
'type' => 'string',
'destination' => 'order_key',
),
'_order_stock_reduced' => array(
'type' => 'bool',
'destination' => 'order_stock_reduced',
),
'_date_paid' => array(
'type' => 'date_epoch',
'destination' => 'date_paid_gmt',
),
'_date_completed' => array(
'type' => 'date_epoch',
'destination' => 'date_completed_gmt',
),
'_order_shipping_tax' => array(
'type' => 'decimal',
'destination' => 'shipping_tax_amount',
),
'_order_shipping' => array(
'type' => 'decimal',
'destination' => 'shipping_total_amount',
),
'_cart_discount_tax' => array(
'type' => 'decimal',
'destination' => 'discount_tax_amount',
),
'_cart_discount' => array(
'type' => 'decimal',
'destination' => 'discount_total_amount',
),
);
return array(
'schema' => $schema_config,
'core' => $core_config,
'meta' => $meta_config,
);
}
/** /**
* Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far. * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far.
* *
@ -307,8 +420,9 @@ class WPPostToCOTMigrator {
} }
$order_post_ids = array_column( $order_data['data'], 'post_id' ); $order_post_ids = array_column( $order_data['data'], 'post_id' );
$this->process_next_address_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by ); $this->process_next_migrator_batch( $this->billing_address_table_migrator, $order_post_ids, $order_by );
$this->process_next_address_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by ); $this->process_next_migrator_batch( $this->shipping_address_table_migrator, $order_post_ids, $order_by );
$this->process_next_migrator_batch( $this->operation_data_table_migrator, $order_post_ids, $order_by );
$last_post_migrated = max( array_keys( $order_data['data'] ) ); $last_post_migrated = max( array_keys( $order_data['data'] ) );
$this->update_checkpoint( $last_post_migrated ); $this->update_checkpoint( $last_post_migrated );
@ -323,17 +437,18 @@ class WPPostToCOTMigrator {
* @param array $order_post_ids Array of post IDs for orders. * @param array $order_post_ids Array of post IDs for orders.
* @param string $order_by Order by clause. * @param string $order_by Order by clause.
*/ */
private function process_next_address_batch( $migrator, $order_post_ids, $order_by ) { private function process_next_migrator_batch( $migrator, $order_post_ids, $order_by ) {
global $wpdb; global $wpdb;
$post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' ); $post_ids_where_clause = $this->get_where_id_clause( $order_post_ids, 'post_id' );
$batch_size = count( $order_post_ids ); $batch_size = count( $order_post_ids );
$address_data = $migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by ); $data = $migrator->fetch_data_for_migration( $post_ids_where_clause, $batch_size, $order_by );
foreach ( $address_data['errors'] as $order_id => $error ) { foreach ( $data['errors'] as $order_id => $error ) {
$this->error_logger->log( 'info', "Error in importing address data for Order ID $order_id: " . print_r( $error, true ) ); // TODO: Add name of the migrator in error message.
$this->error_logger->log( 'info', "Error in importing data for Order ID $order_id: " . print_r( $error, true ) );
} }
$address_queries = $migrator->generate_insert_sql_for_batch( $address_data['data'], 'insert' ); $queries = $migrator->generate_insert_sql_for_batch( $data['data'], 'insert' );
$result = $wpdb->query( $address_queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped. $result = $wpdb->query( $queries ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Insert statements should already be escaped.
if ( count( $address_data['data'] ) !== $result ) { if ( count( $data['data'] ) !== $result ) {
// Some rows were not inserted. // Some rows were not inserted.
// TODO: Find and log the entity ids that were not inserted. // TODO: Find and log the entity ids that were not inserted.
echo 'error'; echo 'error';

View File

@ -234,7 +234,7 @@ CREATE TABLE $operational_data_table_name (
woocommerce_version varchar(20) NULL, woocommerce_version varchar(20) NULL,
prices_include_tax tinyint(1) NULL, prices_include_tax tinyint(1) NULL,
coupon_usages_are_counted tinyint(1) NULL, coupon_usages_are_counted tinyint(1) NULL,
download_permissionis_granted tinyint(1) NULL, download_permission_granted tinyint(1) NULL,
cart_hash varchar(100) NULL, cart_hash varchar(100) NULL,
new_order_email_sent tinyint(1) NULL, new_order_email_sent tinyint(1) NULL,
order_key varchar(100) NULL, order_key varchar(100) NULL,
@ -242,7 +242,7 @@ CREATE TABLE $operational_data_table_name (
date_paid_gmt datetime NULL, date_paid_gmt datetime NULL,
date_completed_gmt datetime NULL, date_completed_gmt datetime NULL,
shipping_tax_amount decimal(26, 8) NULL, shipping_tax_amount decimal(26, 8) NULL,
shopping_total_amount decimal(26, 8) NULL, shipping_total_amount decimal(26, 8) NULL,
discount_tax_amount decimal(26, 8) NULL, discount_tax_amount decimal(26, 8) NULL,
discount_total_amount decimal(26, 8) NULL, discount_total_amount decimal(26, 8) NULL,
KEY order_id (order_id), KEY order_id (order_id),