summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.txt1
-rwxr-xr-xmigrate.info2
-rw-r--r--migrate_example/migrate_example.install13
-rw-r--r--migrate_example/wine.inc31
-rw-r--r--migrate_example/wine.install.inc71
-rw-r--r--plugins/destinations/table.inc197
-rw-r--r--tests/plugins/destinations/table.test44
7 files changed, 359 insertions, 0 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index dad5b8f..2340de4 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -16,6 +16,7 @@ before upgrading from Migrate 2.0 to this version if you have migrate_ui enabled
Features and enhancements
- #1017246 - Added support for running migrations from the dashboard.
+- #1004812 - Added schema-driven table destination plugin.
- #1005090 - Added filefield property import from JSON input.
- #730980 - Added more detailed reporting on import.
- #1142384 - Extended file field support to copy from remote URLs.
diff --git a/migrate.info b/migrate.info
index 137ed12..4985b67 100755
--- a/migrate.info
+++ b/migrate.info
@@ -24,6 +24,7 @@ files[] = plugins/destinations/comment.inc
files[] = plugins/destinations/path.inc
files[] = plugins/destinations/fields.inc
files[] = plugins/destinations/profile.inc
+files[] = plugins/destinations/table.inc
files[] = plugins/destinations/table_copy.inc
files[] = plugins/sources/csv.inc
files[] = plugins/sources/json.inc
@@ -35,6 +36,7 @@ files[] = plugins/sources/mssql.inc
files[] = plugins/sources/xml.inc
files[] = tests/plugins/destinations/comment.test
files[] = tests/plugins/destinations/node.test
+files[] = tests/plugins/destinations/table.test
files[] = tests/plugins/destinations/term.test
files[] = tests/plugins/destinations/user.test
files[] = tests/plugins/sources/xml.test
diff --git a/migrate_example/migrate_example.install b/migrate_example/migrate_example.install
index a2080df..407d164 100644
--- a/migrate_example/migrate_example.install
+++ b/migrate_example/migrate_example.install
@@ -145,3 +145,16 @@ function migrate_example_update_6004() {
$ret[] = t('Reconfigured sample data for file fields.');
return $ret;
}
+
+/**
+ * Sample data for table destinations.
+ */
+function migrate_example_update_6005() {
+ $ret = array();
+ db_create_table($ret, 'migrate_example_wine_table_source', migrate_example_wine_schema_table_source());
+ db_create_table($ret, 'migrate_example_wine_table_dest', migrate_example_wine_schema_table_dest());
+ migrate_example_wine_data_table_source();
+
+ $ret[] = t('Added sample data for table destinations.');
+ return $ret;
+}
diff --git a/migrate_example/wine.inc b/migrate_example/wine.inc
index 82d183f..46e50c7 100644
--- a/migrate_example/wine.inc
+++ b/migrate_example/wine.inc
@@ -578,6 +578,37 @@ class WineCommentMigration extends AdvancedExampleMigration {
}
}
+// TIP: An easy way to simply migrate into a Drupal table (i.e., one defined
+// through the Schema API) is to use the MigrateDestinationTable destination.
+// Just pass the table name to getKeySchema and the MigrateDestinationTable constructor.
+class WineTableMigration extends AdvancedExampleMigration {
+ public function __construct() {
+ parent::__construct();
+ $this->description = 'Miscellaneous table data';
+ $this->softDependencies = array('WineComment');
+ $table_name = 'migrate_example_wine_table_dest';
+ $this->map = new MigrateSQLMap($this->machineName,
+ array('fooid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ )
+ ),
+ MigrateDestinationTable::getKeySchema($table_name)
+ );
+ $query = db_select('migrate_example_wine_table_source', 't')
+ ->fields('t', array('fooid', 'field1', 'field2'));
+ $this->source = new MigrateSourceSQL($query);
+ $this->destination = new MigrateDestinationTable($table_name);
+
+ // Mapped fields
+ $this->addFieldMapping('drupal_text', 'field1');
+ $this->addFieldMapping('drupal_int', 'field2');
+
+ $this->addUnmigratedDestinations(array('recordid'));
+ }
+}
+
class WineFinishMigration extends MigrationBase {
public function __construct() {
parent::__construct();
diff --git a/migrate_example/wine.install.inc b/migrate_example/wine.install.inc
index cdeb3ab..9842f45 100644
--- a/migrate_example/wine.install.inc
+++ b/migrate_example/wine.install.inc
@@ -18,6 +18,8 @@ function migrate_example_wine_schema() {
$schema['migrate_example_wine_comment'] = migrate_example_wine_schema_comment();
$schema['migrate_example_wine_comment_updates'] = migrate_example_wine_schema_comment_updates();
$schema['migrate_example_wine_files'] = migrate_example_wine_schema_files();
+ $schema['migrate_example_wine_table_source'] = migrate_example_wine_schema_table_source();
+ $schema['migrate_example_wine_table_dest'] = migrate_example_wine_schema_table_dest();
return $schema;
}
@@ -40,6 +42,7 @@ function migrate_example_wine_install() {
migrate_example_wine_data_category_producer();
migrate_example_wine_data_comment();
migrate_example_wine_data_comment_updates();
+ migrate_example_wine_data_table_source();
}
function migrate_example_wine_uninstall() {
@@ -564,6 +567,60 @@ function migrate_example_wine_schema_files() {
);
}
+function migrate_example_wine_schema_table_source() {
+ return array(
+ 'description' => 'Source data to go into a custom Drupal table',
+ 'fields' => array(
+ 'fooid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Primary key',
+ ),
+ 'field1' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'First field',
+ ),
+ 'field2' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Second field',
+ ),
+ ),
+ 'primary key' => array('fooid'),
+ );
+}
+
+function migrate_example_wine_schema_table_dest() {
+ return array(
+ 'description' => 'Custom Drupal table to receive source data directly',
+ 'fields' => array(
+ 'recordid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Primary key',
+ ),
+ 'drupal_text' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'description' => 'First field',
+ ),
+ 'drupal_int' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Second field',
+ ),
+ ),
+ 'primary key' => array('recordid'),
+ );
+}
+
function migrate_example_wine_content_types() {
// This code based on from standard.profile.
// Insert default user-defined node types into the database.
@@ -1043,3 +1100,17 @@ function migrate_example_wine_data_files() {
$row);
}
}
+
+function migrate_example_wine_data_table_source() {
+ $data = array(
+ array(3, 'Some sample data', 58),
+ array(15, 'Whatever', 2),
+ array(646, 'More sample data', 34989),
+ );
+ foreach ($data as $row) {
+ db_query("INSERT INTO {migrate_example_wine_table_source}
+ (fooid, field1, field2)
+ VALUES(%d, '%s', %d)",
+ $row);
+ }
+}
diff --git a/plugins/destinations/table.inc b/plugins/destinations/table.inc
new file mode 100644
index 0000000..e5f5a55
--- /dev/null
+++ b/plugins/destinations/table.inc
@@ -0,0 +1,197 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Support for tables defined through the Schema API.
+ */
+
+/**
+ * Destination class implementing migration into a single table defined through
+ * the Schema API.
+ */
+class MigrateDestinationTable extends MigrateDestination {
+ /**
+ * The schema of the current table.
+ *
+ * @var array
+ */
+ protected $schema = NULL;
+
+ /**
+ * The name of the current table.
+ *
+ * @var string
+ */
+ protected $tableName = NULL;
+
+ public function __construct($table_name) {
+ $this->schema = drupal_get_schema($table_name);
+ $this->tableName = $table_name;
+ }
+
+ static public function getKeySchema($table_name = NULL) {
+ if (empty($table_name)) {
+ return array();
+ }
+ $schema = drupal_get_schema($table_name);
+ $keys = array();
+ foreach ($schema['primary key'] as $primary_key) {
+ // We can't have any form of serial fields here, since the mapping table
+ // already has it's own.
+ $schema['fields'][$primary_key]['auto_increment'] = FALSE;
+ if ($schema['fields'][$primary_key]['type'] == 'serial') {
+ $schema['fields'][$primary_key]['type'] = 'int';
+ }
+
+ $keys[$primary_key] = $schema['fields'][$primary_key];
+ }
+
+ return $keys;
+ }
+
+ public function __toString() {
+ $output = t('Table !name', array('!name' => $this->tableName));
+ return $output;
+ }
+
+ /**
+ * Delete a single row.
+ *
+ * @param $id
+ * Primary key(s) and their values.
+ */
+ public function rollback(array $id) {
+ migrate_instrument_start('table rollback');
+ $delete = db_delete($this->tableName);
+ foreach ($id as $key => $value) {
+ $delete->condition($key, $value);
+ }
+ $delete->execute();
+ migrate_instrument_stop('table rollback');
+ }
+
+ /**
+ * Import a single row.
+ *
+ * @param $entity
+ * Object object to build. Prefilled with any fields mapped in the Migration.
+ * @param $row
+ * Raw source data object - passed through to prepare/complete handlers.
+ * @return array
+ * Array of key fields of the object that was saved if
+ * successful. FALSE on failure.
+ */
+ public function import(stdClass $entity, stdClass $row) {
+ if (empty($this->schema['primary key'])) {
+ throw new MigrateException(t("The destination table has no primary key defined."));
+ }
+
+ // Only filled when doing an update.
+ $primary_key = array();
+
+ $migration = Migration::currentMigration();
+ // Updating previously-migrated content?
+ if (isset($row->migrate_map_destid1)) {
+ $i = 1;
+ foreach ($this->schema['primary key'] as $key) {
+ $destination_id = $row->{'migrate_map_destid' . $i};
+ if (isset($entity->{$key})) {
+ if ($entity->{$key} != $destination_id) {
+ throw new MigrateException(t("Incoming id !id and map destination id !destid don't match",
+ array('!id' => $entity->{$key}, '!destid' => $destination_id)));
+ }
+ }
+ else {
+ $entity->{$key} = $destination_id;
+ }
+ $i++;
+ }
+ }
+
+ if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
+ foreach ($this->schema['primary key'] as $key) {
+ if (!isset($entity->{$key})) {
+ throw new MigrateException(t('System-of-record is DESTINATION, but no destination id provided'));
+ }
+ }
+
+ $select = db_select($this->tableName);
+ foreach ($this->schema['primary key'] as $key) {
+ $select->condition($key, $entity->{$key});
+ }
+ $old_entity = $select->execute()->fetchObject();
+
+ foreach ($entity as $field => $value) {
+ $old_entity->$field = $entity->$field;
+ }
+ $entity = $old_entity;
+ }
+
+ $this->prepare($entity, $row);
+ $status = drupal_write_record($this->tableName, $entity, $primary_key);
+ $this->complete($entity, $row);
+
+ if ($status) {
+ $id = array();
+ foreach ($this->schema['primary key'] as $key) {
+ $id[] = $entity->{$key};
+ }
+
+ return $id;
+ }
+ }
+
+ /**
+ * Returns a list of fields available to be mapped.
+ *
+ * @return array
+ * Keys: machine names of the fields (to be passed to addFieldMapping)
+ * Values: Human-friendly descriptions of the fields.
+ */
+ public function fields() {
+ $fields = array();
+ foreach ($this->schema['fields'] as $column => $schema) {
+ $fields[$column] = t('Type: !type', array('!type' => $schema['type']));
+ }
+ return $fields;
+ }
+
+ /**
+ * Give handlers a shot at modifying the object before saving it.
+ *
+ * @param $entity
+ * Entity object to build. Prefilled with any fields mapped in the Migration.
+ * @param $source_row
+ * Raw source data object - passed through to prepare handlers.
+ */
+ public function prepare($entity, stdClass $source_row) {
+ $migration = Migration::currentMigration();
+ $entity->migrate = array(
+ 'machineName' => $migration->getMachineName(),
+ );
+
+ // Call any prepare handler for this specific Migration.
+ if (method_exists($migration, 'prepare')) {
+ $migration->prepare($entity, $source_row);
+ }
+ }
+
+ /**
+ * Give handlers a shot at modifying the object (or taking additional action)
+ * after saving it.
+ *
+ * @param $object
+ * Entity object to build. This is the complete object after saving.
+ * @param $source_row
+ * Raw source data object - passed through to complete handlers.
+ */
+ public function complete($entity, stdClass $source_row) {
+ $migration = Migration::currentMigration();
+
+ // Call any complete handler for this specific Migration.
+ if (method_exists($migration, 'complete')) {
+ $migration->complete($entity, $source_row);
+ }
+ }
+}
diff --git a/tests/plugins/destinations/table.test b/tests/plugins/destinations/table.test
new file mode 100644
index 0000000..275b741
--- /dev/null
+++ b/tests/plugins/destinations/table.test
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Test table migration.
+ */
+class MigrateTableUnitTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Table migration',
+ 'description' => 'Test migration of table data',
+ 'group' => 'Migrate',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('autoload', 'dbtng', 'taxonomy', 'content', 'text', 'number',
+ 'date_api', 'date_timezone', 'date', 'filefield', 'imagefield',
+ 'migrate', 'migrate_extras', 'migrate_example');
+ drupal_flush_all_caches();
+ }
+
+ function testTableImport() {
+ $migration = Migration::getInstance('WineTable');
+ $result = $migration->processImport();
+ $this->assertEqual($result, Migration::RESULT_COMPLETED,
+ t('Table import returned RESULT_COMPLETED'));
+
+ $result = db_query(
+ "SELECT COUNT(*)
+ FROM {migrate_example_wine_table_source} s
+ INNER JOIN {migrate_map_winetable} map ON s.fooid=map.sourceid1
+ INNER JOIN {migrate_example_wine_table_dest} d ON map.destid1=d.recordid"
+ );
+
+ $this->assertEqual(db_result($result), 3,
+ t('Count of imported records is correct'));
+
+ // Test rollback
+/* Implement when http://drupal.org/node/1147366 is fixed
+ $result = $migration->processRollback();
+ $this->assertEqual($result, Migration::RESULT_COMPLETED,
+ t('Variety term rollback returned RESULT_COMPLETED'));*/
+ }
+}