1010use DateTime ;
1111use DateTimeImmutable ;
1212use OCA \Tables \Constants \UsergroupType ;
13+ use OCA \Tables \Db \RowLoader \CachedRowLoader ;
14+ use OCA \Tables \Db \RowLoader \NormalizedRowLoader ;
1315use OCA \Tables \Errors \InternalError ;
1416use OCA \Tables \Errors \NotFoundError ;
1517use OCA \Tables \Helper \ColumnsHelper ;
1820use OCP \AppFramework \Db \MultipleObjectsReturnedException ;
1921use OCP \AppFramework \Db \TTransactional ;
2022use OCP \DB \Exception ;
21- use OCP \DB \IResult ;
2223use OCP \DB \QueryBuilder \IQueryBuilder ;
2324use OCP \IDBConnection ;
24- use OCP \Server ;
25- use Psr \Container \ContainerExceptionInterface ;
26- use Psr \Container \NotFoundExceptionInterface ;
2725use Psr \Log \LoggerInterface ;
2826use Throwable ;
2927
3028class Row2Mapper {
31- private const MAX_DB_PARAMETERS = 65535 ;
32-
3329 use TTransactional;
3430
3531 private RowSleeveMapper $ rowSleeveMapper ;
@@ -40,15 +36,26 @@ class Row2Mapper {
4036 protected ColumnMapper $ columnMapper ;
4137
4238 private ColumnsHelper $ columnsHelper ;
39+ /**
40+ * @var array<RowLoader\RowLoader::LOADER_*, RowLoader\RowLoader>
41+ */
42+ private array $ rowLoaders ;
4343
44- public function __construct (?string $ userId , IDBConnection $ db , LoggerInterface $ logger , UserHelper $ userHelper , RowSleeveMapper $ rowSleeveMapper , ColumnsHelper $ columnsHelper , ColumnMapper $ columnMapper ) {
44+ public function __construct (?string $ userId , IDBConnection $ db , LoggerInterface $ logger , UserHelper $ userHelper , RowSleeveMapper $ rowSleeveMapper , ColumnsHelper $ columnsHelper , ColumnMapper $ columnMapper ,
45+ NormalizedRowLoader $ normalizedRowLoader ,
46+ CachedRowLoader $ cachedRowLoader ,
47+ ) {
4548 $ this ->rowSleeveMapper = $ rowSleeveMapper ;
4649 $ this ->userId = $ userId ;
4750 $ this ->db = $ db ;
4851 $ this ->logger = $ logger ;
4952 $ this ->userHelper = $ userHelper ;
5053 $ this ->columnsHelper = $ columnsHelper ;
5154 $ this ->columnMapper = $ columnMapper ;
55+ $ this ->rowLoaders = [
56+ RowLoader \RowLoader::LOADER_NORMALIZED => $ normalizedRowLoader ,
57+ RowLoader \RowLoader::LOADER_CACHED => $ cachedRowLoader ,
58+ ];
5259 }
5360
5461 /**
@@ -60,7 +67,7 @@ public function delete(Row2 $row): Row2 {
6067 $ this ->db ->beginTransaction ();
6168 try {
6269 foreach ($ this ->columnsHelper ->columns as $ columnType ) {
63- $ this ->getCellMapperFromType ($ columnType )->deleteAllForRow ($ row ->getId ());
70+ $ this ->columnsHelper -> getCellMapperFromType ($ columnType )->deleteAllForRow ($ row ->getId ());
6471 }
6572 $ this ->rowSleeveMapper ->deleteById ($ row ->getId ());
6673 $ this ->db ->commit ();
@@ -178,7 +185,7 @@ public function findAll(array $showColumnIds, int $tableId, ?int $limit = null,
178185 $ wantedRowIdsArray = $ this ->getWantedRowIds ($ userId , $ tableId , $ filter , $ sort , $ limit , $ offset );
179186
180187 // Get rows without SQL sorting
181- $ rows = $ this ->getRows ($ wantedRowIdsArray , $ showColumnIds );
188+ $ rows = $ this ->getRows ($ wantedRowIdsArray , $ showColumnIds, RowLoader \RowLoader:: LOADER_CACHED );
182189
183190 // Sort rows in PHP to preserve the order from getWantedRowIds
184191 return $ this ->sortRowsByIds ($ rows , $ wantedRowIdsArray );
@@ -191,92 +198,16 @@ public function findAll(array $showColumnIds, int $tableId, ?int $limit = null,
191198 /**
192199 * @param array $rowIds
193200 * @param array $columnIds
201+ * @param RowLoader\RowLoader::LOADER_* $loader
194202 * @return Row2[]
195203 * @throws InternalError
196204 */
197- private function getRows (array $ rowIds , array $ columnIds ): array {
205+ private function getRows (array $ rowIds , array $ columnIds, string $ loader = RowLoader \RowLoader:: LOADER_NORMALIZED ): array {
198206 if (empty ($ rowIds )) {
199207 return [];
200208 }
201209
202- $ columnTypesCount = count ($ this ->columnsHelper ->columns );
203- if ($ columnTypesCount === 0 ) {
204- return [];
205- }
206-
207- $ columnsCount = count ($ columnIds );
208- $ maxParamsPerType = floor (self ::MAX_DB_PARAMETERS / $ columnTypesCount ) ;
209- $ calculatedChunkSize = (int )($ maxParamsPerType - $ columnsCount );
210- $ chunkSize = max (1 , $ calculatedChunkSize );
211-
212- $ rowIdChunks = array_chunk ($ rowIds , $ chunkSize );
213- $ chunks = [];
214- foreach ($ rowIdChunks as $ chunkedRowIds ) {
215- $ chunks [] = $ this ->getRowsChunk ($ chunkedRowIds , $ columnIds );
216- }
217-
218- return array_merge (...$ chunks );
219- }
220-
221- /**
222- * Builds and executes the UNION ALL query for a specific chunk of rows.
223- * @param array $rowIds
224- * @param array $columnIds
225- * @return Row2[]
226- * @throws InternalError
227- */
228- private function getRowsChunk (array $ rowIds , array $ columnIds ): array {
229- $ qb = $ this ->db ->getQueryBuilder ();
230-
231- $ qbSqlForColumnTypes = null ;
232- foreach ($ this ->columnsHelper ->columns as $ columnType ) {
233- $ qbTmp = $ this ->db ->getQueryBuilder ();
234- $ qbTmp ->select ('row_id ' , 'column_id ' , 'last_edit_at ' , 'last_edit_by ' )
235- ->selectAlias ($ qb ->expr ()->castColumn ('value ' , IQueryBuilder::PARAM_STR ), 'value ' );
236-
237- // This is not ideal but I cannot think of a good way to abstract this away into the mapper right now
238- // Ideally we dynamically construct this query depending on what additional selects the column type requires
239- // however the union requires us to match the exact number of selects for each column type
240- if ($ columnType === Column::TYPE_USERGROUP ) {
241- $ qbTmp ->selectAlias ($ qb ->expr ()->castColumn ('value_type ' , IQueryBuilder::PARAM_STR ), 'value_type ' );
242- } else {
243- $ qbTmp ->selectAlias ($ qbTmp ->createFunction ('NULL ' ), 'value_type ' );
244- }
245-
246- $ qbTmp
247- ->from ('tables_row_cells_ ' . $ columnType )
248- ->where ($ qb ->expr ()->in ('column_id ' , $ qb ->createNamedParameter ($ columnIds , IQueryBuilder::PARAM_INT_ARRAY , ':columnIds ' )))
249- ->andWhere ($ qb ->expr ()->in ('row_id ' , $ qb ->createNamedParameter ($ rowIds , IQueryBuilder::PARAM_INT_ARRAY , ':rowsIds ' )));
250-
251- if ($ qbSqlForColumnTypes ) {
252- $ qbSqlForColumnTypes .= ' UNION ALL ' . $ qbTmp ->getSQL () . ' ' ;
253- } else {
254- $ qbSqlForColumnTypes = '( ' . $ qbTmp ->getSQL ();
255- }
256- }
257- $ qbSqlForColumnTypes .= ') ' ;
258-
259- $ qb ->select ('row_id ' , 'column_id ' , 'created_by ' , 'created_at ' , 't1.last_edit_by ' , 't1.last_edit_at ' , 'value ' , 'table_id ' )
260- // Also should be more generic (see above)
261- ->addSelect ('value_type ' )
262- ->from ($ qb ->createFunction ($ qbSqlForColumnTypes ), 't1 ' )
263- ->innerJoin ('t1 ' , 'tables_row_sleeves ' , 'rs ' , 'rs.id = t1.row_id ' );
264-
265- try {
266- $ result = $ qb ->executeQuery ();
267- } catch (Exception $ e ) {
268- $ this ->logger ->error ($ e ->getMessage (), ['exception ' => $ e ]);
269- throw new InternalError (get_class ($ this ) . ' - ' . __FUNCTION__ . ': ' . $ e ->getMessage (), );
270- }
271-
272- try {
273- $ sleeves = $ this ->rowSleeveMapper ->findMultiple ($ rowIds );
274- } catch (Exception $ e ) {
275- $ this ->logger ->error ($ e ->getMessage (), ['exception ' => $ e ]);
276- throw new InternalError (get_class ($ this ) . ' - ' . __FUNCTION__ . ': ' . $ e ->getMessage ());
277- }
278-
279- return $ this ->parseEntities ($ result , $ sleeves );
210+ return $ this ->rowLoaders [$ loader ]->getRows ($ rowIds , $ columnIds );
280211 }
281212
282213 /**
@@ -652,61 +583,6 @@ private function getSqlOperator(string $operator, IQueryBuilder $qb, string $col
652583 }
653584 }
654585
655- /**
656- * @param IResult $result
657- * @param RowSleeve[] $sleeves
658- * @return Row2[]
659- * @throws InternalError
660- */
661- private function parseEntities (IResult $ result , array $ sleeves ): array {
662- $ rows = [];
663- foreach ($ sleeves as $ sleeve ) {
664- $ rows [$ sleeve ->getId ()] = new Row2 ();
665- $ rows [$ sleeve ->getId ()]->setId ($ sleeve ->getId ());
666- $ rows [$ sleeve ->getId ()]->setCreatedBy ($ sleeve ->getCreatedBy ());
667- $ rows [$ sleeve ->getId ()]->setCreatedAt ($ sleeve ->getCreatedAt ());
668- $ rows [$ sleeve ->getId ()]->setLastEditBy ($ sleeve ->getLastEditBy ());
669- $ rows [$ sleeve ->getId ()]->setLastEditAt ($ sleeve ->getLastEditAt ());
670- $ rows [$ sleeve ->getId ()]->setTableId ($ sleeve ->getTableId ());
671- }
672-
673- $ rowValues = [];
674- $ keyToColumnId = [];
675- $ keyToRowId = [];
676- $ cellMapperCache = [];
677-
678- while ($ rowData = $ result ->fetch ()) {
679- if (!isset ($ rowData ['row_id ' ], $ rows [$ rowData ['row_id ' ]])) {
680- break ;
681- }
682-
683- $ column = $ this ->columnMapper ->find ($ rowData ['column_id ' ]);
684- $ columnType = $ column ->getType ();
685- if (!isset ($ cellMapperCache [$ columnType ])) {
686- $ cellMapperCache [$ columnType ] = $ this ->getCellMapperFromType ($ columnType );
687- }
688- $ value = $ cellMapperCache [$ columnType ]->formatRowData ($ column , $ rowData );
689- $ compositeKey = (string )$ rowData ['row_id ' ] . ', ' . (string )$ rowData ['column_id ' ];
690- if ($ cellMapperCache [$ columnType ]->hasMultipleValues ()) {
691- if (array_key_exists ($ compositeKey , $ rowValues )) {
692- $ rowValues [$ compositeKey ][] = $ value ;
693- } else {
694- $ rowValues [$ compositeKey ] = [$ value ];
695- }
696- } else {
697- $ rowValues [$ compositeKey ] = $ value ;
698- }
699- $ keyToColumnId [$ compositeKey ] = $ rowData ['column_id ' ];
700- $ keyToRowId [$ compositeKey ] = $ rowData ['row_id ' ];
701- }
702-
703- foreach ($ rowValues as $ compositeKey => $ value ) {
704- $ rows [$ keyToRowId [$ compositeKey ]]->addCell ($ keyToColumnId [$ compositeKey ], $ value );
705- }
706-
707- return array_values ($ rows );
708- }
709-
710586 /**
711587 * @throws InternalError
712588 */
@@ -735,7 +611,7 @@ public function insert(Row2 $row): Row2 {
735611 foreach ($ row ->getData () as $ cell ) {
736612 $ cachedCells [$ cell ['columnId ' ]] = $ this ->insertCell ($ rowSleeve ->getId (), $ cell ['columnId ' ], $ cell ['value ' ], $ rowSleeve ->getLastEditAt (), $ rowSleeve ->getLastEditBy ());
737613 }
738- $ rowSleeve ->setCells ( json_encode ( $ cachedCells) );
614+ $ rowSleeve ->setCachedCellsArray ( $ cachedCells );
739615 $ this ->rowSleeveMapper ->update ($ rowSleeve );
740616
741617 return $ row ;
@@ -768,7 +644,7 @@ public function update(Row2 $row): Row2 {
768644 foreach ($ changedCells as $ cell ) {
769645 $ cachedCells [$ cell ['columnId ' ]] = $ this ->insertOrUpdateCell ($ rowSleeve ->getId (), $ cell ['columnId ' ], $ cell ['value ' ]);
770646 }
771- $ rowSleeve ->setCachedCells ( json_encode ( $ cachedCells) );
647+ $ rowSleeve ->setCachedCellsArray ( $ cachedCells );
772648 $ this ->rowSleeveMapper ->update ($ rowSleeve );
773649
774650 return $ row ;
@@ -916,18 +792,7 @@ private function insertOrUpdateCell(int $rowId, int $columnId, $value): array {
916792 }
917793
918794 private function getCellMapper (Column $ column ): RowCellMapperSuper {
919- return $ this ->getCellMapperFromType ($ column ->getType ());
920- }
921-
922- private function getCellMapperFromType (string $ columnType ): RowCellMapperSuper {
923- $ cellMapperClassName = 'OCA\Tables\Db\RowCell ' . ucfirst ($ columnType ) . 'Mapper ' ;
924- /** @var RowCellMapperSuper $cellMapper */
925- try {
926- return Server::get ($ cellMapperClassName );
927- } catch (NotFoundExceptionInterface |ContainerExceptionInterface $ e ) {
928- $ this ->logger ->error ($ e ->getMessage (), ['exception ' => $ e ]);
929- throw new InternalError (get_class ($ this ) . ' - ' . __FUNCTION__ . ': ' . $ e ->getMessage ());
930- }
795+ return $ this ->columnsHelper ->getCellMapperFromType ($ column ->getType ());
931796 }
932797
933798 /**
0 commit comments