Skip to content

Commit

Permalink
Fix importing for FKs.
Browse files Browse the repository at this point in the history
  • Loading branch information
samwilson committed Feb 10, 2016
1 parent 3fc3c05 commit f99beb7
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 108 deletions.
29 changes: 13 additions & 16 deletions src/CSV.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace WordPress\Tabulate;

use WordPress\Tabulate\DB\ChangeTracker;

/**
* A class for parsing a CSV file has either just been uploaded (i.e. $_FILES is
* set), or is stored as a temporary file (as defined herein).
Expand Down Expand Up @@ -57,8 +59,8 @@ private function save_file( $uploaded ) {
unlink( $uploaded['file'] );
throw new \Exception( 'Only CSV files can be imported.' );
}
$this->hash = md5( time() );
rename( $uploaded['file'], get_temp_dir().'/'.$this->hash );
$this->hash = uniqid( TABULATE_SLUG );
rename( $uploaded['file'], get_temp_dir() . '/' . $this->hash );
}

/**
Expand Down Expand Up @@ -215,35 +217,30 @@ public function match_fields($table, $column_map) {
*/
public function import_data($table, $column_map) {
global $wpdb;
$change_tracker = new \WordPress\Tabulate\DB\ChangeTracker( $wpdb );
$change_tracker = new ChangeTracker( $wpdb );
$change_tracker->open_changeset( 'CSV import.', true );
$count = 0;
$headers = $this->remap( $column_map );
for ( $row_num = 1; $row_num <= $this->row_count(); $row_num++ ) {
$row = array();
foreach ( $this->data[$row_num] as $col_num => $value ) {
foreach ( $this->data[ $row_num ] as $col_num => $value ) {
if ( !isset( $headers[$col_num] ) ) {
continue;
}
$db_column_name = $headers[$col_num];
$column = $table->get_column( $db_column_name );

// Get actual foreign key value
if ( $column->is_foreign_key() ) {
if ( empty( $value ) ) {
// Ignore empty-string FKs.
continue;
} else {
$fk_rows = $this->get_fk_rows( $column->get_referenced_table(), $value );
$foreign_row = array_shift( $fk_rows );
$value = $foreign_row->get_primary_key();
}
// Get actual foreign key value.
if ( $column->is_foreign_key() && ! empty( $value ) ) {
$fk_rows = $this->get_fk_rows( $column->get_referenced_table(), $value );
$foreign_row = array_shift( $fk_rows );
$value = $foreign_row->get_primary_key();
}

// All other values are used as they are
// All other values are used as they are.
$row[$db_column_name] = $value;
}

$pk_name = $table->get_pk_column()->get_name();
$pk_value = ( isset( $row[ $pk_name ] ) ) ? $row[ $pk_name ] : null;
$table->save_record( $row, $pk_value );
Expand Down
219 changes: 127 additions & 92 deletions tests/ImportTest.php
Original file line number Diff line number Diff line change
@@ -1,92 +1,127 @@
<?php

class ImportTest extends TestBase {

public function setUp() {
parent::setUp();
// Let the current user do anything.
global $current_user;
$current_user->add_cap( 'promote_users' );
}

/**
* Save some CSV data to a file, and create a quasi-$_FILES entry for it.
* @param string $data
* @return string|array
*/
private function save_data_file( $data ) {
$test_filename = get_temp_dir() . '/test_' . uniqid() . '.csv';
file_put_contents( $test_filename, $data );
$uploaded = array(
'type' => 'text/csv',
'file' => $test_filename,
);
return $uploaded;
}

/**
* @testdox Rows can be imported from CSV.
* @test
*/
public function basic_import() {
$testtypes_table = $this->db->get_table( 'test_types' );
$csv = '"ID","Title"' . "\r\n"
. '"1","One"' . "\r\n"
. '"2","Two"' . "\r\n";
$uploaded = $this->save_data_file( $csv );
$csv = new WordPress\Tabulate\CSV( null, $uploaded );
$csv->load_data();
$column_map = array( 'title' => 'Title' );
$csv->import_data( $testtypes_table, $column_map );
// Make sure 2 records were imported.
$this->assertEquals( 2, $testtypes_table->count_records() );
$rec1 = $testtypes_table->get_record( 1 );
$this->assertEquals( 'One', $rec1->title() );
// And that 1 changeset was created, with 4 changes.
$change_tracker = new \WordPress\Tabulate\DB\ChangeTracker( $this->wpdb );
$sql = "SELECT COUNT(id) FROM ".$change_tracker->changesets_name();
$this->assertEquals( 1, $this->wpdb->get_var( $sql ) );
$sql = "SELECT COUNT(id) FROM ".$change_tracker->changes_name();
$this->assertEquals( 4, $this->wpdb->get_var( $sql ) );
}

/**
* @testdox Import rows that specify an existing PK will update existing records.
* @test
*/
public function primary_key() {
$testtable = $this->db->get_table( 'test_table' );
$rec1 = $testtable->save_record( array( 'title' => 'PK Test' ) );
$this->assertEquals( 1, $testtable->count_records() );
$this->assertNull( $rec1->description() );

// Add a field's value.
$csv = '"ID","Title","Description"' . "\r\n"
. '"1","One","A description"' . "\r\n";
$uploaded = $this->save_data_file( $csv );
$csv = new WordPress\Tabulate\CSV( null, $uploaded );
$csv->load_data();
$column_map = array( 'id' => 'ID', 'title' => 'Title', 'description' => 'Description' );
$csv->import_data( $testtable, $column_map );
// Make sure there's still only one record, and that it's been updated.
$this->assertEquals( 1, $testtable->count_records() );
$rec2 = $testtable->get_record( 1 );
$this->assertEquals( 'One', $rec2->title() );
$this->assertEquals( 'A description', $rec2->description() );

// Leave out a required field.
$csv = '"ID","Description"' . "\r\n"
. '"1","New description"' . "\r\n";
$uploaded2 = $this->save_data_file( $csv );
$csv2 = new WordPress\Tabulate\CSV( null, $uploaded2 );
$csv2->load_data();
$column_map2 = array( 'id' => 'ID', 'description' => 'Description' );
$csv2->import_data( $testtable, $column_map2 );
// Make sure there's still only one record, and that it's been updated.
$this->assertEquals( 1, $testtable->count_records() );
$rec3 = $testtable->get_record( 1 );
$this->assertEquals( 'One', $rec3->title() );
$this->assertEquals( 'New description', $rec3->description() );
}

}
<?php

use WordPress\Tabulate\CSV;
use WordPress\Tabulate\DB\ChangeTracker;

class ImportTest extends TestBase {

public function setUp() {
parent::setUp();
// Let the current user do anything.
global $current_user;
$current_user->add_cap( 'promote_users' );
}

/**
* Save some CSV data to a file, and create a quasi-$_FILES entry for it.
* @param string $data The CSV string to save.
* @return string[] With two keys: 'type' and 'file'.
*/
private function save_data_file( $data ) {
$test_filename = get_temp_dir() . '/test_' . uniqid() . '.csv';
file_put_contents( $test_filename, $data );
$uploaded = array(
'type' => 'text/csv',
'file' => $test_filename,
);
return $uploaded;
}

/**
* @testdox Rows can be imported from CSV.
* @test
*/
public function basic_import() {
$testtypes_table = $this->db->get_table( 'test_types' );
$csv = '"ID","Title"' . "\r\n"
. '"1","One"' . "\r\n"
. '"2","Two"' . "\r\n";
$uploaded = $this->save_data_file( $csv );
$csv = new CSV( null, $uploaded );
$csv->load_data();
$column_map = array( 'title' => 'Title' );
$csv->import_data( $testtypes_table, $column_map );
// Make sure 2 records were imported.
$this->assertEquals( 2, $testtypes_table->count_records() );
$rec1 = $testtypes_table->get_record( 1 );
$this->assertEquals( 'One', $rec1->title() );
// And that 1 changeset was created, with 4 changes.
$change_tracker = new ChangeTracker( $this->wpdb );
$sql = "SELECT COUNT(id) FROM ".$change_tracker->changesets_name();
$this->assertEquals( 1, $this->wpdb->get_var( $sql ) );
$sql = "SELECT COUNT(id) FROM ".$change_tracker->changes_name();
$this->assertEquals( 4, $this->wpdb->get_var( $sql ) );
}

/**
* @testdox Import rows that specify an existing PK will update existing records.
* @test
*/
public function primary_key() {
$testtable = $this->db->get_table( 'test_table' );
$rec1 = $testtable->save_record( array( 'title' => 'PK Test' ) );
$this->assertEquals( 1, $testtable->count_records() );
$this->assertNull( $rec1->description() );

// Add a field's value.
$csv = '"ID","Title","Description"' . "\r\n"
. '"1","One","A description"' . "\r\n";
$uploaded = $this->save_data_file( $csv );
$csv = new CSV( null, $uploaded );
$csv->load_data();
$column_map = array( 'id' => 'ID', 'title' => 'Title', 'description' => 'Description' );
$csv->import_data( $testtable, $column_map );
// Make sure there's still only one record, and that it's been updated.
$this->assertEquals( 1, $testtable->count_records() );
$rec2 = $testtable->get_record( 1 );
$this->assertEquals( 'One', $rec2->title() );
$this->assertEquals( 'A description', $rec2->description() );

// Leave out a required field.
$csv = '"ID","Description"' . "\r\n"
. '"1","New description"' . "\r\n";
$uploaded2 = $this->save_data_file( $csv );
$csv2 = new CSV( null, $uploaded2 );
$column_map2 = array( 'id' => 'ID', 'description' => 'Description' );
$csv2->import_data( $testtable, $column_map2 );
// Make sure there's still only one record, and that it's been updated.
$this->assertEquals( 1, $testtable->count_records() );
$rec3 = $testtable->get_record( 1 );
$this->assertEquals( 'One', $rec3->title() );
$this->assertEquals( 'New description', $rec3->description() );
}

/**
* @testdox Importing a nullable FK removes the value from that field.
* @test
*/
public function nullable_foreign_keys() {
// Set up foreign record and some other things.
$this->db->get_table( 'test_types' )->save_record( [ 'title' => 'A type' ] );
$test_table = $this->db->get_table( 'test_table' );
$column_map = array( 'id' => 'ID', 'title' => 'Title', 'type_id' => 'Type' );

// Import data.
$csv_data_1 = '"Title","Type"' . "\r\n"
. '"One","A type"' . "\r\n";
$csv_file_1 = $this->save_data_file( $csv_data_1 );
$csv = new CSV( null, $csv_file_1 );
$csv->import_data( $test_table, $column_map );

// Check imported data.
$rec1 = $test_table->get_record( 1 );
$this->assertEquals( 'A type', $rec1->type_idFKTITLE() );

// Import new data.
$csv_data_2 = '"ID","Title","Type"' . "\n"
. '"1","Changed FK",""' . "\n";
$csv_file_2 = $this->save_data_file( $csv_data_2 );
$this->assertNotEquals( $csv_file_1, $csv_file_2 );
$csv2 = new CSV( null, $csv_file_2 );
$csv2->import_data( $test_table, $column_map );

// Re-check imported data.
$rec2 = $test_table->get_record( 1 );
$this->assertEquals( null, $rec2->type_idFKTITLE() );
}
}
31 changes: 31 additions & 0 deletions tests/RecordsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,35 @@ public function multiple_filters_not_found() {
$this->assertEquals( array( 'T2.0', 'Not5' ), $not_found );
*/
}

/**
* @testdox Empty values can be saved (where a field permits it).
* @test
*/
public function save_null_values() {
// Get a table and make sure it is what we want it to be.
$test_table = $this->db->get_table( 'test_table' );
$this->assertTrue( $test_table->get_column( 'description' )->nullable() );

// Save a record and check it.
$rec1a = $test_table->save_record( array( 'title' => "Test One", 'description' => 'Desc' ) );
$this->assertEquals( 1, $rec1a->id() );
$this->assertEquals( 'Desc', $rec1a->description() );

// Save the same record to set 'description' to null.
$rec1b = $test_table->save_record( array( 'description' => '' ), 1 );
$this->assertEquals( 1, $rec1b->id() );
$this->assertEquals( null, $rec1b->description() );

// Now repeat that, but with a nullable foreign key.
$this->db->get_table( 'test_types' )->save_record( [ 'title' => 'A type'] );
$rec2a = $test_table->save_record( array( 'title' => 'Test Two', 'type_id' => 1 ) );
$this->assertEquals( 2, $rec2a->id() );
$this->assertEquals( 'Test Two', $rec2a->title() );
$this->assertEquals( 'A type', $rec2a->type_idFKTITLE() );
$rec2b = $test_table->save_record( array( 'type_id' => '' ), 2 );
$this->assertEquals( 2, $rec2b->id() );
$this->assertEquals( 'Test Two', $rec2b->title() );
$this->assertEquals( null, $rec2b->type_idFKTITLE() );
}
}

0 comments on commit f99beb7

Please sign in to comment.