3434use Psr \Container \ContainerExceptionInterface ;
3535use Psr \Container \NotFoundExceptionInterface ;
3636use Psr \Log \LoggerInterface ;
37- use Throwable ;
3837use TypeError ;
3938use function file_exists ;
4039use function is_string ;
@@ -55,9 +54,11 @@ class ImportService extends SuperService {
5554 private ?int $ viewId = null ;
5655 private array $ columns = [];
5756 private bool $ createUnknownColumns = true ;
57+ private ?int $ idColumnIndex = null ;
5858 private int $ countMatchingColumns = 0 ;
5959 private int $ countCreatedColumns = 0 ;
6060 private int $ countInsertedRows = 0 ;
61+ private int $ countUpdatedRows = 0 ;
6162 private int $ countErrors = 0 ;
6263 private int $ countParsingErrors = 0 ;
6364
@@ -152,14 +153,19 @@ private function getPreviewData(Worksheet $worksheet): array {
152153 $ column = $ this ->columns [$ colIndex ];
153154 $ columns [] = $ column ;
154155 } else {
155- $ columns [] = [
156+ $ column = [
156157 'title ' => $ title ,
157158 'type ' => $ this ->rawColumnDataTypes [$ colIndex ]['type ' ],
158159 'subtype ' => $ this ->rawColumnDataTypes [$ colIndex ]['subtype ' ] ?? null ,
159160 'numberDecimals ' => $ this ->rawColumnDataTypes [$ colIndex ]['number_decimals ' ] ?? 0 ,
160161 'numberPrefix ' => $ this ->rawColumnDataTypes [$ colIndex ]['number_prefix ' ] ?? '' ,
161162 'numberSuffix ' => $ this ->rawColumnDataTypes [$ colIndex ]['number_suffix ' ] ?? '' ,
162163 ];
164+ if (mb_strtolower ($ title ) === Column::META_ID_TITLE ) {
165+ $ column ['id ' ] = Column::TYPE_META_ID ;
166+ }
167+
168+ $ columns [] = $ column ;
163169 }
164170 }
165171
@@ -179,7 +185,7 @@ private function getPreviewData(Worksheet $worksheet): array {
179185 $ colIndex = $ cellIterator ->getCurrentColumnIndex () - 1 ;
180186 $ column = $ this ->columns [$ colIndex ];
181187
182- if (!array_key_exists ($ colIndex , $ this -> columns )) {
188+ if (!array_key_exists ($ colIndex , $ columns )) {
183189 continue ;
184190 }
185191
@@ -324,6 +330,7 @@ public function import(?int $tableId, ?int $viewId, string $path, bool $createMi
324330 'matching_columns_count ' => $ this ->countMatchingColumns ,
325331 'created_columns_count ' => $ this ->countCreatedColumns ,
326332 'inserted_rows_count ' => $ this ->countInsertedRows ,
333+ 'updated_rows_count ' => $ this ->countUpdatedRows ,
327334 'errors_parsing_count ' => $ this ->countParsingErrors ,
328335 'errors_count ' => $ this ->countErrors ,
329336 ];
@@ -354,7 +361,7 @@ private function loop(Worksheet $worksheet): void {
354361
355362 foreach ($ worksheet ->getRowIterator (2 ) as $ row ) {
356363 // parse row data
357- $ this ->createRow ($ row );
364+ $ this ->upsertRow ($ row );
358365 }
359366 }
360367
@@ -387,25 +394,46 @@ private function parseValueByColumnType(string $value, Column $column): string {
387394 * @throws MultipleObjectsReturnedException
388395 * @throws NotFoundError
389396 */
390- private function createRow (Row $ row ): void {
397+ private function upsertRow (Row $ row ): void {
391398 $ cellIterator = $ row ->getCellIterator ();
392399 $ cellIterator ->setIterateOnlyExistingCells (false );
393400
394401 try {
395402 $ i = -1 ;
396403 $ data = [];
397404 $ hasData = false ;
405+ $ id = null ;
398406 foreach ($ cellIterator as $ cell ) {
399407 $ i ++;
400408
409+ if ($ this ->idColumnIndex !== null && $ i === $ this ->idColumnIndex ) {
410+ // if this is the ID column, we need to get the ID from the cell
411+ if ($ cell && $ cell ->getValue () !== null ) {
412+ $ id = $ cell ->getValue ();
413+ }
414+ if ($ id !== null && !is_numeric ($ id )) {
415+ $ this ->logger ->warning ('ID column value is not numeric: ' . $ id );
416+ $ this ->countErrors ++;
417+ return ;
418+ }
419+ $ id = (int )$ id ;
420+ continue ;
421+ }
422+
423+ $ columnKey = $ i ;
424+ if ($ this ->columnsConfig && $ this ->idColumnIndex !== null && $ i > $ this ->idColumnIndex ) {
425+ // if we have an ID column, we need to adjust the index
426+ $ columnKey = $ i - 1 ;
427+ }
428+
401429 // only add the dataset if column is known
402- if (!isset ($ this ->columns [$ i ]) || $ this ->columns [$ i ] === '' ) {
430+ if (!isset ($ this ->columns [$ columnKey ]) || $ this ->columns [$ columnKey ] === '' ) {
403431 $ this ->logger ->debug ('Column unknown while fetching rows data for importing. ' );
404432 continue ;
405433 }
406434
407435 /** @var Column $column */
408- $ column = $ this ->columns [$ i ];
436+ $ column = $ this ->columns [$ columnKey ];
409437
410438 // if cell is empty
411439 if (!$ cell || $ cell ->getValue () === null ) {
@@ -446,26 +474,31 @@ private function createRow(Row $row): void {
446474 ];
447475 }
448476
449- if ($ hasData ) {
477+ if (!$ hasData ) {
478+ $ this ->logger ->debug ('Skipped empty row ' . $ row ->getRowIndex () . ' during import ' );
479+ return ;
480+ }
481+
482+ if ($ id ) {
483+ $ this ->rowService ->updateSet ($ id , $ this ->viewId , $ data , $ this ->userId , $ this ->tableId );
484+ $ this ->countUpdatedRows ++;
485+ } else {
450486 $ this ->rowService ->create ($ this ->tableId , $ this ->viewId , $ data );
451487 $ this ->countInsertedRows ++;
452- } else {
453- $ this ->logger ->debug ('Skipped empty row ' . $ row ->getRowIndex () . ' during import ' );
454488 }
455489 } catch (PermissionError $ e ) {
456- $ this ->logger ->error ('Could not create row while importing, no permission. ' , ['exception ' => $ e ]);
490+ $ this ->logger ->error ('Could not create/update row while importing, no permission. ' , ['exception ' => $ e ]);
457491 $ this ->countErrors ++;
458492 } catch (BadRequestError |InternalError $ e ) {
459- $ this ->logger ->error ('Error while creating new row for import. ' , ['exception ' => $ e ]);
493+ $ this ->logger ->error ('Error while creating/updating new row for import. ' , ['exception ' => $ e ]);
460494 $ this ->countErrors ++;
461495 } catch (NotFoundError $ e ) {
462496 $ this ->logger ->error ($ e ->getMessage (), ['exception ' => $ e ]);
463- throw new NotFoundError (get_class ($ this ) . ' - ' . __FUNCTION__ . ': ' . $ e ->getMessage ());
464- } catch (Throwable $ e ) {
497+ throw new NotFoundError (get_class ($ this ) . ' - ' . __FUNCTION__ . ': ' . $ e ->getMessage (), 0 , $ e );
498+ } catch (\ Throwable $ e ) {
465499 $ this ->countErrors ++;
466- $ this ->logger ->error ('Error while creating new row for import. ' , ['exception ' => $ e ]);
500+ $ this ->logger ->error ('Error while creating/updating new row for import. ' , ['exception ' => $ e ]);
467501 }
468-
469502 }
470503
471504 private function valueToDateTimeImmutable (mixed $ value ): ?DateTimeImmutable {
@@ -522,9 +555,25 @@ private function getColumns(Row $firstRow, Row $secondRow): void {
522555 if ($ cell && $ cell ->getValue () !== null && $ cell ->getValue () !== '' ) {
523556 $ title = $ cell ->getValue ();
524557
558+ if (!$ this ->columnsConfig && mb_strtolower ($ title ) === Column::META_ID_TITLE ) {
559+ $ this ->idColumnIndex = $ index ;
560+ $ titles [] = $ title ;
561+ $ dataTypes [] = ['type ' => Column::TYPE_META_ID ];
562+ $ secondRowCellIterator ->next ();
563+ $ index ++;
564+ continue ;
565+ }
525566 if (isset ($ this ->columnsConfig [$ index ]) && $ this ->columnsConfig [$ index ]['action ' ] === 'exist ' && $ this ->columnsConfig [$ index ]['existColumn ' ]) {
526567 $ title = $ this ->columnsConfig [$ index ]['existColumn ' ]['label ' ];
527568 $ countMatchingColumnsFromConfig ++;
569+
570+ // no need to create the ID (Meta) column as it used for update
571+ if ($ this ->columnsConfig [$ index ]['existColumn ' ]['id ' ] === Column::TYPE_META_ID ) {
572+ $ this ->idColumnIndex = $ index ;
573+ $ secondRowCellIterator ->next ();
574+ $ index ++;
575+ continue ;
576+ }
528577 }
529578 if (isset ($ this ->columnsConfig [$ index ]) && $ this ->columnsConfig [$ index ]['action ' ] === 'new ' && $ this ->createUnknownColumns ) {
530579 $ column = $ this ->columnService ->create (
0 commit comments