-
Notifications
You must be signed in to change notification settings - Fork 4
Feat CSV source #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Feat CSV source #72
Changes from 5 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
5c4e6c6
feat: csv source [WIP].
ItzNotABug c8a70fc
update: improve and fix things.
ItzNotABug 387541e
update: improve and fix things.
ItzNotABug b308d91
update: pagination and attribute type handling.
ItzNotABug 45e0d93
Merge branch 'main' into feat-csv
ItzNotABug c1b0ddd
update: support for `$permissions`.
ItzNotABug 9847a13
bump: composer deps.
ItzNotABug 52fb030
fix: lint?
ItzNotABug 60b7301
Merge branch 'main' into feat-csv
ItzNotABug 59fa4dd
update: change name.
ItzNotABug 93cee62
address comments.
ItzNotABug 9662eb1
Merge remote-tracking branch 'origin/feat-csv' into feat-csv
ItzNotABug d1c6bc0
address comments.
ItzNotABug 9088ef1
fix: `Undefined array key`.
ItzNotABug 7357615
address comment: use `SPLFileObject`.
ItzNotABug ff392fe
address comment: use `SPLFileObject` with `seek`.
ItzNotABug f321d08
update: `$rowCount`.
ItzNotABug File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\Migration\Sources; | ||
|
|
||
| use Utopia\Database\Database as UtopiaDatabase; | ||
| use Utopia\Migration\Exception; | ||
| use Utopia\Migration\Resource; | ||
| use Utopia\Migration\Resources\Database\Attribute; | ||
| use Utopia\Migration\Resources\Database\Collection; | ||
| use Utopia\Migration\Resources\Database\Database; | ||
| use Utopia\Migration\Resources\Database\Document; | ||
| use Utopia\Migration\Resources\Storage\File; | ||
| use Utopia\Migration\Source; | ||
| use Utopia\Migration\Sources\Appwrite\Reader; | ||
| use Utopia\Migration\Sources\Appwrite\Reader\Database as DatabaseReader; | ||
| use Utopia\Migration\Transfer; | ||
| use Utopia\Storage\Device; | ||
|
|
||
| class Csv extends Source | ||
| { | ||
| private string $filePath; | ||
|
|
||
| /** | ||
| * format: `{databaseId:collectionId}` | ||
| */ | ||
| private string $resourceId; | ||
|
|
||
| private Device $deviceForCsvImports; | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
|
|
||
| private Reader $database; | ||
|
|
||
| public function __construct( | ||
| string $resourceId, | ||
| string $filePath, | ||
| Device $deviceForCsvImports, | ||
| ?UtopiaDatabase $dbForProject | ||
| ) { | ||
| $this->filePath = $filePath; | ||
| $this->resourceId = $resourceId; | ||
| $this->deviceForCsvImports = $deviceForCsvImports; | ||
| $this->database = new DatabaseReader($dbForProject); | ||
| } | ||
|
|
||
| public static function getName(): string | ||
| { | ||
| return 'Csv'; | ||
| } | ||
|
|
||
| public static function getSupportedResources(): array | ||
| { | ||
| return [ | ||
| Resource::TYPE_DOCUMENT, | ||
| ]; | ||
| } | ||
|
|
||
| public function report(array $resources = []): array | ||
| { | ||
| return []; | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| protected function exportGroupAuth(int $batchSize, array $resources): void | ||
| { | ||
| throw new \Exception('Not Implemented'); | ||
| } | ||
|
|
||
| protected function exportGroupDatabases(int $batchSize, array $resources): void | ||
| { | ||
| try { | ||
| if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { | ||
| $this->exportDocuments($batchSize); | ||
| } | ||
| } catch (\Throwable $e) { | ||
| $this->addError( | ||
| new Exception( | ||
| Resource::TYPE_DOCUMENT, | ||
| Transfer::GROUP_DATABASES, | ||
| message: $e->getMessage(), | ||
| code: $e->getCode(), | ||
| previous: $e | ||
| ) | ||
| ); | ||
| } finally { | ||
| // delete the temporary file! | ||
| $this->deviceForCsvImports->delete($this->filePath); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @throws Exception|\Utopia\Database\Exception | ||
| */ | ||
| private function exportDocuments(int $batchSize): void | ||
| { | ||
| if (! $this->deviceForCsvImports->exists($this->filePath)) { | ||
| return; | ||
| } | ||
|
|
||
| $stream = fopen($this->filePath, 'r'); | ||
| if (! $stream) { | ||
| return; | ||
| } | ||
|
|
||
| $headers = fgetcsv($stream); | ||
| if (! is_array($headers) || count($headers) === 0) { | ||
| fclose($stream); | ||
| return; | ||
| } | ||
|
|
||
| $allAttributes = []; | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
| $lastAttribute = null; | ||
|
|
||
| [$databaseId, $collectionId] = explode(':', $this->resourceId); | ||
| // TODO: @itznotabug, @jake - do we need to check for permissions here or db handles it? | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
| $collection = new Collection(new Database($databaseId, ''), '', $collectionId); | ||
|
|
||
| while (true) { | ||
| // paginate over the attributes | ||
| $queries = [$this->database->queryLimit($batchSize)]; | ||
| if ($lastAttribute) { | ||
| $queries[] = $this->database->queryCursorAfter($lastAttribute); | ||
| } | ||
|
|
||
| $fetched = $this->database->listAttributes($collection, $queries); | ||
| if (empty($fetched)) { | ||
| break; | ||
| } | ||
|
|
||
| $allAttributes = array_merge($allAttributes, $fetched); | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
| $lastAttribute = $fetched[count($fetched) - 1]; | ||
|
|
||
| if (count($fetched) < $batchSize) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| $attributeTypes = []; | ||
| $manyToManyKeys = []; | ||
|
|
||
| foreach ($allAttributes as $attribute) { | ||
| $key = $attribute['key']; | ||
|
|
||
| if ( | ||
| // Skip child-side relationships entirely | ||
| $attribute['type'] === Attribute::TYPE_RELATIONSHIP && | ||
| ($attribute['side'] ?? '') === UtopiaDatabase::RELATION_SIDE_CHILD | ||
| ) { | ||
| continue; | ||
| } | ||
|
|
||
| $attributeTypes[$key] = $attribute['type']; | ||
|
|
||
| if ( | ||
| $attribute['type'] === Attribute::TYPE_RELATIONSHIP && | ||
| ($attribute['relationType'] ?? '') === 'manyToMany' && | ||
| ($attribute['side'] ?? '') === 'parent' | ||
| ) { | ||
| $manyToManyKeys[] = $key; | ||
| } | ||
| } | ||
|
|
||
| $buffer = []; | ||
|
|
||
| while (($row = fgetcsv($stream)) !== false) { | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
| $data = array_combine($headers, $row); | ||
| if ($data === false) { | ||
| continue; | ||
| } | ||
|
|
||
| $parsedData = $data; | ||
|
|
||
| foreach ($data as $key => $value) { | ||
| if (! isset($attributeTypes[$key])) { | ||
| continue; | ||
| } | ||
|
|
||
| $type = $attributeTypes[$key]; | ||
| $parsedValue = trim($value); | ||
|
|
||
| if ($parsedValue === '') { | ||
| $parsedData[$key] = null; | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
|
|
||
| continue; | ||
| } | ||
|
|
||
| // TODO: @itznotabug, @jake - should we support Relationships like these? | ||
| if (in_array($key, $manyToManyKeys, true)) { | ||
| $parsedData[$key] = str_contains($parsedValue, ',') | ||
| ? array_map('trim', explode(',', $parsedValue)) | ||
| : [$parsedValue]; | ||
|
|
||
| continue; | ||
| } | ||
|
ItzNotABug marked this conversation as resolved.
Outdated
|
||
|
|
||
| switch ($type) { | ||
| case Attribute::TYPE_INTEGER: | ||
| $parsedData[$key] = is_numeric($parsedValue) ? (int) $parsedValue : null; | ||
| break; | ||
|
|
||
| case Attribute::TYPE_FLOAT: | ||
| $parsedData[$key] = is_numeric($parsedValue) ? (float) $parsedValue : null; | ||
| break; | ||
|
|
||
| case Attribute::TYPE_BOOLEAN: | ||
| $parsedData[$key] = filter_var($parsedValue, FILTER_VALIDATE_BOOLEAN); | ||
| break; | ||
|
|
||
| default: | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| $docId = $parsedData['$id'] ?? 'unique()'; | ||
| $document = new Document($docId, $collection, $parsedData); | ||
|
|
||
| $buffer[] = $document; | ||
|
|
||
| if (count($buffer) === $batchSize) { | ||
| $this->callback($buffer); | ||
| $buffer = []; | ||
| } | ||
| } | ||
|
|
||
| fclose($stream); | ||
|
|
||
| if (! empty($buffer)) { | ||
| $this->callback($buffer); | ||
| } | ||
| } | ||
|
|
||
| protected function exportGroupStorage(int $batchSize, array $resources): void | ||
| { | ||
| throw new \Exception('Not Implemented'); | ||
| } | ||
|
|
||
| protected function exportBuckets(int $batchSize): void | ||
| { | ||
| throw new \Exception('Not Implemented'); | ||
| } | ||
|
|
||
| private function exportFiles(int $batchSize): void | ||
| { | ||
| throw new \Exception('Not Implemented'); | ||
| } | ||
|
|
||
| private function exportFile(File $file): void | ||
| { | ||
| throw new \Exception('Not Implemented'); | ||
| } | ||
|
|
||
| protected function exportGroupFunctions(int $batchSize, array $resources): void | ||
| { | ||
| throw new \Exception('Not Implemented'); | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.