33namespace Composite \DB ;
44
55use Composite \DB \MultiQuery \MultiInsert ;
6+ use Composite \DB \MultiQuery \MultiSelect ;
67use Composite \Entity \Helpers \DateTimeHelper ;
78use Composite \Entity \AbstractEntity ;
89use Composite \DB \Exceptions \DbException ;
910use Composite \Entity \Exceptions \EntityException ;
1011use Doctrine \DBAL \Connection ;
1112use Doctrine \DBAL \Platforms \PostgreSQLPlatform ;
1213use Doctrine \DBAL \Query \QueryBuilder ;
14+ use Ramsey \Uuid \UuidInterface ;
1315
1416abstract class AbstractTable
1517{
@@ -70,35 +72,20 @@ public function save(AbstractEntity &$entity): void
7072 $ entity ->updated_at = new \DateTimeImmutable ();
7173 $ changedColumns ['updated_at ' ] = DateTimeHelper::dateTimeToString ($ entity ->updated_at );
7274 }
73-
74- if ($ this ->config ->hasOptimisticLock () && isset ($ entity ->version )) {
75- $ currentVersion = $ entity ->version ;
76- try {
77- $ connection ->beginTransaction ();
78- $ connection ->update (
79- $ this ->getTableName (),
80- $ changedColumns ,
81- $ where
82- );
83- $ versionUpdated = $ connection ->update (
84- $ this ->getTableName (),
85- ['version ' => $ currentVersion + 1 ],
86- $ where + ['version ' => $ currentVersion ]
87- );
88- if (!$ versionUpdated ) {
89- throw new DbException ('Failed to update entity version, concurrency modification, rolling back. ' );
90- }
91- $ connection ->commit ();
92- } catch (\Throwable $ e ) {
93- $ connection ->rollBack ();
94- throw $ e ;
95- }
96- } else {
97- $ connection ->update (
98- $ this ->getTableName (),
99- $ changedColumns ,
100- $ where
101- );
75+ if ($ this ->config ->hasOptimisticLock ()
76+ && method_exists ($ entity , 'getVersion ' )
77+ && method_exists ($ entity , 'incrementVersion ' )) {
78+ $ where ['lock_version ' ] = $ entity ->getVersion ();
79+ $ entity ->incrementVersion ();
80+ $ changedColumns ['lock_version ' ] = $ entity ->getVersion ();
81+ }
82+ $ entityUpdated = $ connection ->update (
83+ table: $ this ->getTableName (),
84+ data: $ changedColumns ,
85+ criteria: $ where ,
86+ );
87+ if ($ this ->config ->hasOptimisticLock () && !$ entityUpdated ) {
88+ throw new Exceptions \LockException ('Failed to update entity version, concurrency modification, rolling back. ' );
10289 }
10390 $ entity ->resetChangedColumns ();
10491 }
@@ -211,66 +198,31 @@ protected function findByPkInternal(mixed $pk): ?array
211198
212199 /**
213200 * @param array<string, mixed> $where
201+ * @param array<string, string>|string $orderBy
214202 * @return array<string, mixed>|null
215203 * @throws \Doctrine\DBAL\Exception
216204 */
217- protected function findOneInternal (array $ where ): ?array
205+ protected function findOneInternal (array $ where, array | string $ orderBy = [] ): ?array
218206 {
219207 $ query = $ this ->select ();
220208 $ this ->buildWhere ($ query , $ where );
209+ $ this ->applyOrderBy ($ query , $ orderBy );
221210 return $ query ->fetchAssociative () ?: null ;
222211 }
223212
224213 /**
225214 * @param array<int|string|array<string,mixed>> $pkList
226215 * @return array<array<string, mixed>>
227216 * @throws DbException
228- * @throws EntityException
229217 * @throws \Doctrine\DBAL\Exception
230218 */
231219 protected function findMultiInternal (array $ pkList ): array
232220 {
233221 if (!$ pkList ) {
234222 return [];
235223 }
236- /** @var class-string<AbstractEntity> $class */
237- $ class = $ this ->config ->entityClass ;
238-
239- $ pkColumns = [];
240- foreach ($ this ->config ->primaryKeys as $ primaryKeyName ) {
241- $ pkColumns [$ primaryKeyName ] = $ class ::schema ()->getColumn ($ primaryKeyName );
242- }
243- if (count ($ pkColumns ) === 1 ) {
244- if (!array_is_list ($ pkList )) {
245- throw new DbException ('Input argument $pkList must be list ' );
246- }
247- /** @var \Composite\Entity\Columns\AbstractColumn $pkColumn */
248- $ pkColumn = reset ($ pkColumns );
249- $ preparedPkValues = array_map (fn ($ pk ) => $ pkColumn ->uncast ($ pk ), $ pkList );
250- $ query = $ this ->select ();
251- $ this ->buildWhere ($ query , [$ pkColumn ->name => $ preparedPkValues ]);
252- } else {
253- $ query = $ this ->select ();
254- $ expressions = [];
255- foreach ($ pkList as $ i => $ pkArray ) {
256- if (!is_array ($ pkArray )) {
257- throw new DbException ('For tables with composite keys, input array must consist associative arrays ' );
258- }
259- $ pkOrExpr = [];
260- foreach ($ pkArray as $ pkName => $ pkValue ) {
261- if (is_string ($ pkName ) && isset ($ pkColumns [$ pkName ])) {
262- $ preparedPkValue = $ pkColumns [$ pkName ]->cast ($ pkValue );
263- $ pkOrExpr [] = $ query ->expr ()->eq ($ pkName , ': ' . $ pkName . $ i );
264- $ query ->setParameter ($ pkName . $ i , $ preparedPkValue );
265- }
266- }
267- if ($ pkOrExpr ) {
268- $ expressions [] = $ query ->expr ()->and (...$ pkOrExpr );
269- }
270- }
271- $ query ->where ($ query ->expr ()->or (...$ expressions ));
272- }
273- return $ query ->executeQuery ()->fetchAllAssociative ();
224+ $ multiSelect = new MultiSelect ($ this ->getConnection (), $ this ->config , $ pkList );
225+ return $ multiSelect ->getQueryBuilder ()->executeQuery ()->fetchAllAssociative ();
274226 }
275227
276228 /**
@@ -294,22 +246,7 @@ protected function findAllInternal(
294246 $ query ->setParameter ($ param , $ value );
295247 }
296248 }
297- if ($ orderBy ) {
298- if (is_array ($ orderBy )) {
299- foreach ($ orderBy as $ column => $ direction ) {
300- $ query ->addOrderBy ($ column , $ direction );
301- }
302- } else {
303- foreach (explode (', ' , $ orderBy ) as $ orderByPart ) {
304- $ orderByPart = trim ($ orderByPart );
305- if (preg_match ('/(.+)\s(asc|desc)$/i ' , $ orderByPart , $ orderByPartMatch )) {
306- $ query ->addOrderBy ($ orderByPartMatch [1 ], $ orderByPartMatch [2 ]);
307- } else {
308- $ query ->addOrderBy ($ orderByPart );
309- }
310- }
311- }
312- }
249+ $ this ->applyOrderBy ($ query , $ orderBy );
313250 if ($ limit > 0 ) {
314251 $ query ->setMaxResults ($ limit );
315252 }
@@ -366,7 +303,7 @@ final protected function createEntities(mixed $data, ?string $keyColumnName = nu
366303 * @return array<string, mixed>
367304 * @throws EntityException
368305 */
369- protected function getPkCondition (int |string |array |AbstractEntity $ data ): array
306+ protected function getPkCondition (int |string |array |AbstractEntity | UuidInterface $ data ): array
370307 {
371308 $ condition = [];
372309 if ($ data instanceof AbstractEntity) {
@@ -395,7 +332,7 @@ protected function select(string $select = '*'): QueryBuilder
395332 /**
396333 * @param array<string, mixed> $where
397334 */
398- private function buildWhere (QueryBuilder $ query , array $ where ): void
335+ private function buildWhere (\ Doctrine \ DBAL \ Query \ QueryBuilder $ query , array $ where ): void
399336 {
400337 foreach ($ where as $ column => $ value ) {
401338 if ($ value === null ) {
@@ -433,4 +370,28 @@ private function formatData(array $data): array
433370 }
434371 return $ data ;
435372 }
373+
374+ /**
375+ * @param array<string, string>|string $orderBy
376+ */
377+ private function applyOrderBy (QueryBuilder $ query , string |array $ orderBy ): void
378+ {
379+ if (!$ orderBy ) {
380+ return ;
381+ }
382+ if (is_array ($ orderBy )) {
383+ foreach ($ orderBy as $ column => $ direction ) {
384+ $ query ->addOrderBy ($ column , $ direction );
385+ }
386+ } else {
387+ foreach (explode (', ' , $ orderBy ) as $ orderByPart ) {
388+ $ orderByPart = trim ($ orderByPart );
389+ if (preg_match ('/(.+)\s(asc|desc)$/i ' , $ orderByPart , $ orderByPartMatch )) {
390+ $ query ->addOrderBy ($ orderByPartMatch [1 ], $ orderByPartMatch [2 ]);
391+ } else {
392+ $ query ->addOrderBy ($ orderByPart );
393+ }
394+ }
395+ }
396+ }
436397}
0 commit comments