Test Failed
Push — main ( a2096a...c7561b )
by Bingo
15:21
created

BatchDbSqlSession::postProcessJdbcBatchResult()   B

Complexity

Conditions 9
Paths 22

Size

Total Lines 54
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 54
rs 8.0555
c 0
b 0
f 0
cc 9
nc 22
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Jabe\Engine\Impl\Db\Sql;
4
5
use Doctrine\DBAL\Connection;
6
use Jabe\Engine\Impl\ProcessEngineLogger;
7
use Jabe\Engine\Impl\Db\{
8
    DbEntityInterface,
9
    EnginePersistenceLogger,
10
    FlushResult,
11
    StatementInterface
12
};
13
use Jabe\Engine\Impl\Db\EntityManager\Operation\{
14
    DbBulkOperation,
15
    DbEntityOperation,
16
    DbOperation,
17
    DbOperationType
18
};
19
use Jabe\Engine\Impl\Util\{
20
    CollectionUtil,
21
    EnsureUtil,
22
    ExceptionUtil
23
};
24
25
class BatchDbSqlSession extends DbSqlSession
26
{
27
    public function __construct(DbSqlSessionFactory $dbSqlSessionFactory, Connection $connection = null, string $catalog = null, string $schema = null)
28
    {
29
        parent::__construct($dbSqlSessionFactory, $connection, $catalog, $schema);
30
    }
31
32
    public function executeDbOperations(array $operations): FlushResult
33
    {
34
        foreach ($operations as $operation) {
35
            try {
36
                // stage operation
37
                $this->executeDbOperation($operation);
38
            } catch (\Exception $ex) {
39
                // exception is wrapped later
40
                throw $ex;
41
            }
42
        }
43
44
        $batchResults = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $batchResults is dead and can be removed.
Loading history...
45
        try {
46
            // applies all operations
47
            $batchResults = $this->flushBatchOperations();
48
        } catch (\Exception $e) {
49
            return $this->postProcessBatchFailure($operations, $e);
50
        }
51
52
        return $this->postProcessBatchSuccess($operations, $batchResults);
53
    }
54
55
    protected function postProcessBatchSuccess(array $operations, array $batchResults): FlushResult
56
    {
57
        $operationsIt = new \ArrayIterator($operations);
58
        $failedOperations = [];
59
        foreach ($batchResults as $successfulBatch) {
60
            // even if all batches are successful, there can be concurrent modification failures
61
            // (e.g. 0 rows updated)
62
            $this->postProcessJdbcBatchResult($operationsIt, $successfulBatch->getUpdateCounts(), null, $failedOperations);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type Exception expected by parameter $failure of Jabe\Engine\Impl\Db\Sql\...rocessJdbcBatchResult(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
            $this->postProcessJdbcBatchResult($operationsIt, $successfulBatch->getUpdateCounts(), /** @scrutinizer ignore-type */ null, $failedOperations);
Loading history...
63
        }
64
65
        // there should be no more operations remaining
66
        if ($operationsIt->valid()) {
67
            //throw LOG.wrongBatchResultsSizeException(operations);
68
            throw new \Exception("wrongBatchResultsSizeException");
69
        }
70
71
        return FlushResult::withFailures($failedOperations);
72
    }
73
74
    protected function postProcessBatchFailure(array $operations, \Exception $exception): FlushResult
75
    {
76
        $batchExecutorException = ExceptionUtil::findBatchExecutorException($exception);
77
78
        if ($batchExecutorException == null) {
79
            // Unexpected exception
80
            throw $exception;
81
        }
82
83
        $successfulBatches = $batchExecutorException->getSuccessfulBatchResults();
84
        $cause = $batchExecutorException->getBatchUpdateException();
85
86
        $operationsIt = new \ArrayIterator($operations);
87
        $failedOperations = [];
88
89
        foreach ($successfulBatches as $successfulBatch) {
90
            $this->postProcessJdbcBatchResult($operationsIt, $successfulBatch->getUpdateCounts(), null, $failedOperations);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type Exception expected by parameter $failure of Jabe\Engine\Impl\Db\Sql\...rocessJdbcBatchResult(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
            $this->postProcessJdbcBatchResult($operationsIt, $successfulBatch->getUpdateCounts(), /** @scrutinizer ignore-type */ null, $failedOperations);
Loading history...
91
        }
92
93
        $failedBatchUpdateCounts = $cause->getUpdateCounts();
94
        $this->postProcessJdbcBatchResult($operationsIt, $failedBatchUpdateCounts, $exception, $failedOperations);
95
96
        $remainingOperations = CollectionUtil::collectInList($operationsIt);
0 ignored issues
show
Bug introduced by
$operationsIt of type ArrayIterator is incompatible with the type array expected by parameter $data of Jabe\Engine\Impl\Util\Co...onUtil::collectInList(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
        $remainingOperations = CollectionUtil::collectInList(/** @scrutinizer ignore-type */ $operationsIt);
Loading history...
97
        return FlushResult::withFailuresAndRemaining($failedOperations, $remainingOperations);
98
    }
99
100
    /**
101
     * <p>This method can be called with three cases:
102
     *
103
     * <ul>
104
     * <li>Case 1: Success. statementResults contains the number of
105
     * affected rows for all operations.
106
     * <li>Case 2: Failure. statementResults contains the number of
107
     * affected rows for all successful operations that were executed
108
     * before the failed operation.
109
     * <li>Case 3: Failure. statementResults contains the number of
110
     * affected rows for all operations of the batch, i.e. further
111
     * statements were executed after the first failed statement.
112
     * </ul>
113
     *
114
     * <p>See {@link BatchUpdateException#getUpdateCounts()} for the specification
115
     * of cases 2 and 3.
116
     *
117
     * @return all failed operations
118
     */
119
    protected function postProcessJdbcBatchResult(
120
        \ArrayIterator $operationsIt,
121
        array $statementResults,
122
        \Exception $failure,
123
        array &$failedOperations
124
    ): void {
125
        $failureHandled = false;
126
127
        foreach ($statementResults as $statementResult) {
128
            EnsureUtil::ensureTrue("More batch results than scheduled operations detected. This indicates a bug", $operationsIt->valid());
129
130
            $operation = $operationsIt->current();
131
132
            if ($statementResult == StatementInterface::SUCCESS_NO_INFO) {
133
                if ($this->requiresAffectedRows($operation->getOperationType())) {
134
                    //throw LOG.batchingNotSupported(operation);
135
                    throw new \Exception("batchingNotSupported");
136
                } else {
137
                    $this->postProcessOperationPerformed($operation, 1, null);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type Exception expected by parameter $failure of Jabe\Engine\Impl\Db\Sql\...essOperationPerformed(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

137
                    $this->postProcessOperationPerformed($operation, 1, /** @scrutinizer ignore-type */ null);
Loading history...
138
                }
139
            } elseif ($statementResult == StatementInterface::EXECUTE_FAILED) {
140
                /*
141
                * All operations are marked with the root failure exception; this is not quite
142
                * correct and leads to the situation that we treat all failed operations in the
143
                * same way, whereas they might fail for different reasons.
144
                *
145
                * More precise would be to use BatchUpdateException#getNextException.
146
                * E.g. if we have three failed statements in a batch, #getNextException can be used to
147
                * access each operation's individual failure. However, this behavior is not
148
                * guaranteed by the java.sql javadocs (it doesn't specify that the number
149
                * and order of next exceptions matches the number of failures, unlike for row counts),
150
                * so we decided to not rely on it.
151
                */
152
                $this->postProcessOperationPerformed($operation, 0, $failure);
153
                $failureHandled = true;
154
            } else { // it is the number of affected rows
155
                $this->postProcessOperationPerformed($operation, $statementResult, null);
156
            }
157
158
            if ($operation->isFailed()) {
159
                $failedOperations[] = $operation; // the operation is added to the list only if it's marked as failed
160
            }
161
        }
162
163
        /*
164
        * case 2: The next operation is the one that failed
165
        */
166
        if ($failure != null && !$failureHandled) {
167
            EnsureUtil::ensureTrue("More batch results than scheduled operations detected. This indicates a bug", $operationsIt->valid());
168
169
            $failedOperation = $operationsIt->current();
170
            $this->postProcessOperationPerformed($failedOperation, 0, $failure);
171
            if ($failedOperation->isFailed()) {
172
                $failedOperations[] = $failedOperation; // the operation is added to the list only if it's marked as failed
173
            }
174
        }
175
    }
176
177
    protected function requiresAffectedRows(string $operationType): bool
178
    {
179
        /*
180
        * Affected rows required:
181
        * - UPDATE and DELETE: optimistic locking
182
        * - BULK DELETE: history cleanup
183
        * - BULK UPDATE: not required currently, but we'll require it for consistency with deletes
184
        *
185
        * Affected rows not required:
186
        * - INSERT: not required for any functionality and some databases
187
        *   have performance optimizations that sacrifice this (e.g. Postgres with reWriteBatchedInserts)
188
        */
189
        return $operationType != DbOperationType::INSERT;
190
    }
191
192
    protected function postProcessOperationPerformed(
193
        DbOperation $operation,
194
        int $rowsAffected,
195
        \Exception $failure
196
    ): void {
197
        switch ($operation->getOperationType()) {
198
            case DbOperationType::INSERT:
199
                $this->entityInsertPerformed($operation, $rowsAffected, $failure);
200
                break;
201
            case DbOperationType::DELETE:
202
                $this->entityDeletePerformed($operation, $rowsAffected, $failure);
203
                break;
204
            case DbOperationType::DELETE_BULK:
205
                $this->bulkDeletePerformed($operation, $rowsAffected, $failure);
206
                break;
207
            case DbOperationType::UPDATE:
208
                $this->entityUpdatePerformed($operation, $rowsAffected, $failure);
209
                break;
210
            case DbOperationType::UPDATE_BULK:
211
                $this->bulkUpdatePerformed($operation, $rowsAffected, $failure);
212
                break;
213
        }
214
    }
215
216
    protected function updateEntity(DbEntityOperation $operation): void
217
    {
218
        $dbEntity = $operation->getEntity();
219
220
        $updateStatement = $this->dbSqlSessionFactory->getUpdateStatement($dbEntity);
0 ignored issues
show
Bug introduced by
It seems like $dbEntity can also be of type null; however, parameter $object of Jabe\Engine\Impl\Db\Sql\...y::getUpdateStatement() does only seem to accept Jabe\Engine\Impl\Db\DbEntityInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
        $updateStatement = $this->dbSqlSessionFactory->getUpdateStatement(/** @scrutinizer ignore-type */ $dbEntity);
Loading history...
221
        EnsureUtil::ensureNotNull("no update statement for " . get_class($dbEntity) . " in the ibatis mapping files", "updateStatement", $updateStatement);
0 ignored issues
show
Bug introduced by
It seems like $dbEntity can also be of type null; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
        EnsureUtil::ensureNotNull("no update statement for " . get_class(/** @scrutinizer ignore-type */ $dbEntity) . " in the ibatis mapping files", "updateStatement", $updateStatement);
Loading history...
222
223
        //LOG.executeDatabaseOperation("UPDATE", dbEntity);
224
        $this->executeUpdate($updateStatement, $dbEntity);
225
    }
226
227
    protected function updateBulk(DbBulkOperation $operation): void
228
    {
229
        $statement = $operation->getStatement();
230
        $parameter = $operation->getParameter();
231
232
        //LOG.executeDatabaseBulkOperation("UPDATE", statement, parameter);
233
234
        $this->executeUpdate($statement, $parameter);
235
    }
236
237
    protected function deleteBulk(DbBulkOperation $operation): void
238
    {
239
        $statement = $operation->getStatement();
240
        $parameter = $operation->getParameter();
241
242
        //LOG.executeDatabaseBulkOperation("DELETE", statement, parameter);
243
244
        $this->executeDelete($statement, $parameter);
245
    }
246
247
    protected function deleteEntity(DbEntityOperation $operation): void
248
    {
249
        $dbEntity = $operation->getEntity();
250
251
        // get statement
252
        $deleteStatement = $thios->dbSqlSessionFactory->getDeleteStatement(get_class($dbEntity));
0 ignored issues
show
Bug introduced by
It seems like $dbEntity can also be of type null; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

252
        $deleteStatement = $thios->dbSqlSessionFactory->getDeleteStatement(get_class(/** @scrutinizer ignore-type */ $dbEntity));
Loading history...
Comprehensibility Best Practice introduced by
The variable $thios seems to be never defined.
Loading history...
253
        EnsureUtil::ensureNotNull("no delete statement for " . get_class($dbEntity) . " in the ibatis mapping files", "deleteStatement", $deleteStatement);
254
255
        //LOG.executeDatabaseOperation("DELETE", dbEntity);
256
257
        // execute the delete
258
        $this->executeDelete($deleteStatement, $dbEntity);
259
    }
260
261
    protected function executeSelectForUpdate(string $statement, $parameter): void
262
    {
263
        $this->executeSelectList($statement, $parameter);
264
    }
265
}
266