diff --git a/src/Builder/Builder.php b/src/Builder/Builder.php index c52de0a..cc1341c 100644 --- a/src/Builder/Builder.php +++ b/src/Builder/Builder.php @@ -18,6 +18,7 @@ class Builder use Concerns\HasLines; use Concerns\IgnoresLines; use Concerns\SetsValues; + use Concerns\HasXmlRows; private DatabaseManager $databaseManager; private Grammar $grammar; diff --git a/src/Builder/Concerns/HasFile.php b/src/Builder/Concerns/HasFile.php index 5c09373..eb205b3 100644 --- a/src/Builder/Concerns/HasFile.php +++ b/src/Builder/Concerns/HasFile.php @@ -8,10 +8,12 @@ trait HasFile private bool $local = false; private ?string $table = null; private ?string $charset = null; + private ?string $extension = null; public function file(string $file, ?bool $local = null): self { $this->file = $file; + $this->extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (isset($local)) { $this->local($local); @@ -57,4 +59,9 @@ public function getCharset(): ?string { return $this->charset; } + + public function isXML(): bool + { + return $this->extension === 'xml'; + } } diff --git a/src/Builder/Concerns/HasXmlRows.php b/src/Builder/Concerns/HasXmlRows.php new file mode 100644 index 0000000..529bf8d --- /dev/null +++ b/src/Builder/Concerns/HasXmlRows.php @@ -0,0 +1,22 @@ + + */ + public function rowIdentifiedBy(string $tag): self + { + $this->rowIdentifier = $tag; + return $this; + } + + public function getRowIdentifier(): string + { + return $this->rowIdentifier; + } +} diff --git a/src/Grammar.php b/src/Grammar.php index 9da0749..01d7829 100644 --- a/src/Grammar.php +++ b/src/Grammar.php @@ -23,7 +23,11 @@ public function compileLoadFile(Builder $query): CompiledQuery throw CompilationException::noFileOrTableSupplied(); } - $querySegments->push('load data'); + if ($query->isXML()) { + $querySegments->push('load xml'); + } else { + $querySegments->push('load data'); + } if ($query->isLowPriority()) { $querySegments->push('low_priority'); @@ -53,17 +57,23 @@ public function compileLoadFile(Builder $query): CompiledQuery $querySegments->push('character set ' . $this->quoteString($charset)); } - $querySegments->push($this->compileFields( - $query->getFieldsTerminatedBy(), - $query->getFieldsEnclosedBy(), - $query->getFieldsEscapedBy(), - $query->getFieldsOptionallyEnclosed(), - )); + if (!$query->isXML()) { + $querySegments->push($this->compileFields( + $query->getFieldsTerminatedBy(), + $query->getFieldsEnclosedBy(), + $query->getFieldsEscapedBy(), + $query->getFieldsOptionallyEnclosed(), + )); + + $querySegments->push($this->compileLines($query->getLinesStartingBy(), $query->getLinesTerminatedBy())); - $querySegments->push($this->compileLines($query->getLinesStartingBy(), $query->getLinesTerminatedBy())); + if ($query->getIgnoreLines() > 0) { + $querySegments->push("ignore {$query->getIgnoreLines()} lines"); + } + } - if ($query->getIgnoreLines() > 0) { - $querySegments->push("ignore {$query->getIgnoreLines()} lines"); + if ($query->isXML()) { + $querySegments->push('rows identified by ' . $this->quoteString($query->getRowIdentifier())); } $columns = $query->getColumns(); diff --git a/tests/Feature/LoadFileTest.php b/tests/Feature/LoadFileTest.php index 07831a6..67485e6 100644 --- a/tests/Feature/LoadFileTest.php +++ b/tests/Feature/LoadFileTest.php @@ -10,7 +10,7 @@ class LoadFileTest extends TestCase public function testSimpleLoad() { LoadFile::connection('mysql') - ->file(realpath(__DIR__ . '/../data/people-simple.csv'), true) + ->file(realpath(__DIR__ . '/../data/csv/people-simple.csv'), true) ->into('people') ->charset('utf8mb4') ->columns(['name', 'dob', 'greeting']) @@ -23,7 +23,7 @@ public function testSimpleLoad() public function testLoadWithSet() { - LoadFile::file(realpath(__DIR__ . '/../data/people.csv'), true) + LoadFile::file(realpath(__DIR__ . '/../data/csv/people.csv'), true) ->into('people') ->columns([DB::raw('@forename'), DB::raw('@surname'), 'dob']) ->set([ @@ -49,7 +49,7 @@ public function testLoadWithSet() public function testIgnoreRow() { - LoadFile::file(realpath(__DIR__ . '/../data/people-simple.csv'), true) + LoadFile::file(realpath(__DIR__ . '/../data/csv/people-simple.csv'), true) ->into('people') ->ignoreLines(1) ->columns(['name', 'dob', 'greeting']) @@ -71,7 +71,7 @@ public function testIgnoreRow() public function testReplace() { - LoadFile::file(realpath(__DIR__ . '/../data/people-simple.csv'), true) + LoadFile::file(realpath(__DIR__ . '/../data/csv/people-simple.csv'), true) ->replace() ->into('people') ->columns(['name', 'dob', 'greeting']) @@ -83,7 +83,7 @@ public function testReplace() public function testIgnore() { - LoadFile::file(realpath(__DIR__ . '/../data/people-simple.csv'), true) + LoadFile::file(realpath(__DIR__ . '/../data/csv/people-simple.csv'), true) ->ignore() ->into('people') ->columns(['name', 'dob', 'greeting']) @@ -95,7 +95,7 @@ public function testIgnore() public function testLowPriority(): void { - LoadFile::file(realpath(__DIR__ . '/../data/people-simple.csv'), true) + LoadFile::file(realpath(__DIR__ . '/../data/csv/people-simple.csv'), true) ->lowPriority() ->into('people') ->columns(['name', 'dob', 'greeting']) @@ -107,7 +107,7 @@ public function testLowPriority(): void public function testConcurrent(): void { - LoadFile::file(realpath(__DIR__ . '/../data/people-simple.csv'), true) + LoadFile::file(realpath(__DIR__ . '/../data/csv/people-simple.csv'), true) ->concurrent() ->into('people') ->columns(['name', 'dob', 'greeting']) diff --git a/tests/Feature/LoadFileXMLTest.php b/tests/Feature/LoadFileXMLTest.php new file mode 100644 index 0000000..e1960a5 --- /dev/null +++ b/tests/Feature/LoadFileXMLTest.php @@ -0,0 +1,138 @@ +file(realpath(__DIR__ . '/../data/xml/people-simple.xml'), true) + ->rowIdentifiedBy('') + ->into('people') + ->charset('utf8mb4') + ->columns(['name', 'dob', 'greeting']) + ->load(); + + $this->assertJohnAndJaneExist(); + } + + public function testLoadWithSet() + { + LoadFile::file(realpath(__DIR__ . '/../data/xml/people.xml'), true) + ->into('people') + ->rowIdentifiedBy('') + ->columns([DB::raw('@forename'), DB::raw('@surname'), 'dob']) + ->set([ + 'greeting' => 'Hello', + 'name' => DB::raw("concat(@forename, ' ', @surname)"), + 'imported_at' => DB::raw('current_timestamp'), + ]) + ->load(); + + $this->assertDatabaseHas('people', [ + 'name' => 'John Doe', + 'dob' => '1980-01-01', + 'greeting' => 'Hello', + ]); + + $this->assertDatabaseHas('people', [ + 'name' => 'Jane Doe', + 'dob' => '1975-06-30', + 'greeting' => 'Hello', + ]); + } + + public function testIgnoreRow() + { + self::markTestSkipped('Add support for ignore rows'); + + LoadFile::file(realpath(__DIR__ . '/../data/xml/people-simple.xml'), true) + ->into('people') + ->ignoreLines(1) + ->columns(['name', 'dob', 'greeting']) + ->fields(',', '"', '\\\\', true) + ->load(); + + $this->assertDatabaseMissing('people', [ + 'name' => 'John Doe', + 'dob' => '1980-01-01', + 'greeting' => 'Bonjour', + ]); + + $this->assertDatabaseHas('people', [ + 'name' => 'Jane Doe', + 'dob' => '1975-06-30', + 'greeting' => 'Hello', + ]); + } + + public function testReplace() + { + LoadFile::file(realpath(__DIR__ . '/../data/xml/people-simple.xml'), true) + ->rowIdentifiedBy('') + ->replace() + ->into('people') + ->columns(['name', 'dob', 'greeting']) + ->load(); + + $this->assertJohnAndJaneExist(); + } + + public function testIgnore() + { + LoadFile::file(realpath(__DIR__ . '/../data/xml/people-simple.xml'), true) + ->rowIdentifiedBy('') + ->ignore() + ->into('people') + ->columns(['name', 'dob', 'greeting']) + ->load(); + + $this->assertJohnAndJaneExist(); + } + + public function testLowPriority(): void + { + LoadFile::file(realpath(__DIR__ . '/../data/xml/people-simple.xml'), true) + ->rowIdentifiedBy('') + ->lowPriority() + ->into('people') + ->columns(['name', 'dob', 'greeting']) + ->fieldsTerminatedBy(',') + ->load(); + + $this->assertJohnAndJaneExist(); + } + + public function testConcurrent(): void + { + LoadFile::file(realpath(__DIR__ . '/../data/xml/people-simple.xml'), true) + ->rowIdentifiedBy('') + ->concurrent() + ->into('people') + ->columns(['name', 'dob', 'greeting']) + ->fieldsTerminatedBy(',') + ->load(); + + $this->assertJohnAndJaneExist(); + } + + private function assertJohnAndJaneExist(): void + { + $this->assertDatabaseHas('people', [ + 'name' => 'John Doe', + 'dob' => '1980-01-01', + 'greeting' => 'Bonjour', + ]); + + $this->assertDatabaseHas('people', [ + 'name' => 'Jane Doe', + 'dob' => '1975-06-30', + 'greeting' => 'Hello', + ]); + } +} diff --git a/tests/data/people-simple.csv b/tests/data/csv/people-simple.csv similarity index 100% rename from tests/data/people-simple.csv rename to tests/data/csv/people-simple.csv diff --git a/tests/data/people.csv b/tests/data/csv/people.csv similarity index 100% rename from tests/data/people.csv rename to tests/data/csv/people.csv diff --git a/tests/data/xml/people-simple.xml b/tests/data/xml/people-simple.xml new file mode 100644 index 0000000..d90a115 --- /dev/null +++ b/tests/data/xml/people-simple.xml @@ -0,0 +1,13 @@ + + + + John Doe + 1980-01-01 + Bonjour + + + Jane Doe + 1975-06-30 + Hello + + diff --git a/tests/data/xml/people.xml b/tests/data/xml/people.xml new file mode 100644 index 0000000..f6f5c71 --- /dev/null +++ b/tests/data/xml/people.xml @@ -0,0 +1,13 @@ + + + + John + Doe + 1980-01-01 + + + Jane + Doe + 1975-06-30 + +