1616use Utopia \Migration \Transfer ;
1717use Utopia \Storage \Device ;
1818
19- class Csv extends Source
19+ class CSV extends Source
2020{
2121 private string $ filePath ;
2222
@@ -25,25 +25,25 @@ class Csv extends Source
2525 */
2626 private string $ resourceId ;
2727
28- private Device $ deviceForImports ;
28+ private Device $ device ;
2929
3030 private Reader $ database ;
3131
3232 public function __construct (
3333 string $ resourceId ,
3434 string $ filePath ,
35- Device $ deviceForImports ,
35+ Device $ device ,
3636 ?UtopiaDatabase $ dbForProject
3737 ) {
38+ $ this ->device = $ device ;
3839 $ this ->filePath = $ filePath ;
3940 $ this ->resourceId = $ resourceId ;
40- $ this ->deviceForImports = $ deviceForImports ;
4141 $ this ->database = new DatabaseReader ($ dbForProject );
4242 }
4343
4444 public static function getName (): string
4545 {
46- return 'Csv ' ;
46+ return 'CSV ' ;
4747 }
4848
4949 public static function getSupportedResources (): array
@@ -53,9 +53,26 @@ public static function getSupportedResources(): array
5353 ];
5454 }
5555
56+ // called before the `exportGroupDatabases`.
5657 public function report (array $ resources = []): array
5758 {
58- return [];
59+ $ report = [];
60+
61+ $ this ->withCsvStream (function ($ stream ) use (&$ report ) {
62+ $ headers = fgetcsv ($ stream );
63+ if (! is_array ($ headers ) || count ($ headers ) === 0 ) {
64+ return ;
65+ }
66+
67+ $ rowCount = 0 ;
68+ while (fgetcsv ($ stream ) !== false ) {
69+ $ rowCount ++;
70+ }
71+
72+ $ report [Resource::TYPE_DOCUMENT ] = $ rowCount ;
73+ });
74+
75+ return $ report ;
5976 }
6077
6178 /**
@@ -84,7 +101,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void
84101 );
85102 } finally {
86103 // delete the temporary file!
87- $ this ->deviceForImports ->delete ($ this ->filePath );
104+ $ this ->device ->delete ($ this ->filePath );
88105 }
89106 }
90107
@@ -93,20 +110,6 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void
93110 */
94111 private function exportDocuments (int $ batchSize ): void
95112 {
96- if (! $ this ->deviceForImports ->exists ($ this ->filePath )) {
97- return ;
98- }
99-
100- $ stream = fopen ($ this ->filePath , 'r ' );
101- if (! $ stream ) {
102- return ;
103- }
104-
105- $ headers = fgetcsv ($ stream );
106- if (! is_array ($ headers ) || count ($ headers ) === 0 ) {
107- fclose ($ stream );
108- return ;
109- }
110113
111114 $ attributes = [];
112115 $ lastAttribute = null ;
@@ -141,7 +144,6 @@ private function exportDocuments(int $batchSize): void
141144 $ key = $ attribute ['key ' ];
142145
143146 if (
144- // Skip child-side relationships entirely
145147 $ attribute ['type ' ] === Attribute::TYPE_RELATIONSHIP &&
146148 ($ attribute ['side ' ] ?? '' ) === UtopiaDatabase::RELATION_SIDE_CHILD
147149 ) {
@@ -159,113 +161,64 @@ private function exportDocuments(int $batchSize): void
159161 }
160162 }
161163
162- $ buffer = [];
163-
164- while (($ row = fgetcsv ($ stream )) !== false ) {
165- $ data = array_combine ($ headers , $ row );
166- if ($ data === false ) {
167- continue ;
164+ $ this ->withCSVStream (function ($ stream ) use ($ attributeTypes , $ manyToManyKeys , $ collection , $ batchSize ) {
165+ $ headers = fgetcsv ($ stream );
166+ if (! is_array ($ headers ) || count ($ headers ) === 0 ) {
167+ return ;
168168 }
169169
170- $ parsedData = $ data ;
171-
172- foreach ($ data as $ key => $ value ) {
173- if (! isset ($ attributeTypes [$ key ])) {
174- continue ;
175- }
176-
177- $ type = $ attributeTypes [$ key ];
178- $ parsedValue = trim ($ value );
170+ $ buffer = [];
179171
180- if ($ parsedValue === '' ) {
172+ while (($ row = fgetcsv ($ stream )) !== false ) {
173+ $ data = array_combine ($ headers , $ row );
174+ if ($ data === false ) {
181175 continue ;
182176 }
183177
184- if (in_array ($ key , $ manyToManyKeys , true )) {
185- $ parsedData [$ key ] = str_contains ($ parsedValue , ', ' )
186- ? array_map ('trim ' , explode (', ' , $ parsedValue ))
187- : [$ parsedValue ];
178+ $ parsedData = $ data ;
188179
189- continue ;
190- }
180+ foreach ($ data as $ key => $ value ) {
181+ $ parsedValue = trim ($ value );
182+ $ type = $ attributeTypes [$ key ];
191183
192- switch ($ type ) {
193- case Attribute::TYPE_INTEGER :
194- $ parsedData [$ key ] = is_numeric ($ parsedValue ) ? (int ) $ parsedValue : null ;
195- break ;
184+ if (! isset ($ type ) || $ parsedValue === '' ) {
185+ continue ;
186+ }
196187
197- case Attribute::TYPE_FLOAT :
198- $ parsedData [$ key ] = is_numeric ($ parsedValue ) ? (float ) $ parsedValue : null ;
199- break ;
188+ if (in_array ($ key , $ manyToManyKeys , true )) {
189+ $ parsedData [$ key ] = str_contains ($ parsedValue , ', ' )
190+ ? array_map ('trim ' , explode (', ' , $ parsedValue ))
191+ : [$ parsedValue ];
200192
201- case Attribute::TYPE_BOOLEAN :
202- $ parsedData [$ key ] = filter_var ($ parsedValue , FILTER_VALIDATE_BOOLEAN );
203- break ;
193+ continue ;
194+ }
204195
205- default :
206- break ;
196+ $ parsedData [$ key ] = match ($ type ) {
197+ Attribute::TYPE_INTEGER => is_numeric ($ parsedValue ) ? (int ) $ parsedValue : null ,
198+ Attribute::TYPE_FLOAT => is_numeric ($ parsedValue ) ? (float ) $ parsedValue : null ,
199+ Attribute::TYPE_BOOLEAN => filter_var ($ parsedValue , FILTER_VALIDATE_BOOLEAN ),
200+ default => $ parsedValue ,
201+ };
207202 }
208- }
209203
210- $ permissions = [];
211- $ documentId = $ parsedData ['$id ' ] ?? 'unique() ' ;
204+ $ documentId = $ parsedData ['$id ' ] ?? 'unique() ' ;
212205
213- if (isset ($ parsedData ['$permissions ' ])) {
214- $ permissions = $ this ->parsePermissions ($ parsedData ['$permissions ' ]);
215- }
206+ // `$id`, `$permissions` in the doc can cause issues!
207+ unset($ parsedData ['$id ' ], $ parsedData ['$permissions ' ]);
216208
217- unset( $ parsedData [ ' $id ' ] );
218- unset( $ parsedData [ ' $permissions ' ]) ;
209+ $ document = new Document ( $ documentId , $ collection , $ parsedData );
210+ $ buffer [] = $ document ;
219211
220- $ document = new Document ($ documentId , $ collection , $ parsedData , $ permissions );
221- $ buffer [] = $ document ;
212+ if (count ($ buffer ) === $ batchSize ) {
213+ $ this ->callback ($ buffer );
214+ $ buffer = [];
215+ }
216+ }
222217
223- if (count ($ buffer ) === $ batchSize ) {
218+ if (! empty ($ buffer )) {
224219 $ this ->callback ($ buffer );
225- $ buffer = [];
226220 }
227- }
228-
229- fclose ($ stream );
230-
231- if (! empty ($ buffer )) {
232- $ this ->callback ($ buffer );
233- }
234- }
235-
236- /**
237- * Parses a stringified permission array into a string[].
238- *
239- * Example:
240- * ```
241- * "[read(\"user:user1234\"),read(\"user:user4321\")]"
242- * ```
243- * Into:
244- * ```
245- * [
246- * "read(\"user:user1234\")",
247- * "read(\"user:user4321\")"
248- * ]
249- * ```
250- *
251- * @param string $raw
252- * @return string[]
253- */
254- private function parsePermissions (string $ raw ): array
255- {
256- $ raw = trim ($ raw , ' "[] ' );
257-
258- if (empty ($ raw )) {
259- return [];
260- }
261-
262- $ parts = preg_split ('/,(?![^(]*\))/ ' , $ raw );
263-
264- return array_map (function ($ item ) {
265- $ item = trim ($ item );
266-
267- return str_replace ('\" ' , '" ' , $ item );
268- }, $ parts );
221+ });
269222 }
270223
271224 /**
@@ -307,4 +260,22 @@ protected function exportGroupFunctions(int $batchSize, array $resources): void
307260 {
308261 throw new \Exception ('Not Implemented ' );
309262 }
263+
264+ private function withCsvStream (callable $ fn ): void
265+ {
266+ if (! $ this ->device ->exists ($ this ->filePath )) {
267+ return ;
268+ }
269+
270+ $ stream = fopen ($ this ->filePath , 'r ' );
271+ if (! $ stream ) {
272+ return ;
273+ }
274+
275+ try {
276+ $ fn ($ stream );
277+ } finally {
278+ fclose ($ stream );
279+ }
280+ }
310281}
0 commit comments