protected function safeUpdate($stmts, $criteria, $deleteSql, $datas, $fields = null) { $stmts['existed']->execute($criteria); $existedIds = $stmts['existed']->fetchAll(\PDO::FETCH_COLUMN); $stmts['existed']->closeCursor(); /* prepare insertData and updateData */ $insertData = []; $updateData = []; $keepIds = []; foreach ($datas as $data) { if (!empty($data['id']) && in_array($data['id'], $existedIds)) { $updateData[] = $data; $keepIds[] = $data['id']; } else { $insertData[] = $data; } } /* delete existed data */ $inParams = []; $undeleteSql = "AND (id NOT IN (" . PDO::prepareIn(':_undeleted_', $keepIds, $inParams) . "))"; $stmt = $this->getPdo()->prepare(sprintf($deleteSql, empty($inParams) ? "" : $undeleteSql)); $stmt->execute(array_merge($criteria, $inParams)); $stmt->closeCursor(); $fields_update = null; $fields_insert = null; if (is_array($fields)) { if (array_key_exists('update', $fields)) { $fields_update = $fields['update']; } if (array_key_exists('insert', $fields)) { $fields_insert = $fields['insert']; } } /* update data before insert to prevent conflict constrain */ $driver = $this->getPdo()->getAttribute(\PDO::ATTR_DRIVER_NAME); $tryUpdateData = $updateData; $lastLength = count($tryUpdateData) + 1; $lastExcp = null; while (count($tryUpdateData) > 0) { if ($lastLength == count($tryUpdateData)) { throw $lastExcp; } $nextUpdateData = []; $lastLength = count($tryUpdateData); foreach ($tryUpdateData as $data) { if ($driver == 'pgsql') { $this->getPdo()->query("SAVEPOINT tryupdate_child_data_if_avaliable"); } try { $stmts['update']->execute($fields_update !== null ? array_intersect_key($data, array_flip($fields_update)) : $data); } catch (\PDOException $excp) { $lastExcp = $excp; $nextUpdateData[] = $data; if ($driver == 'pgsql') { $this->getPdo()->query("ROLLBACK TO SAVEPOINT tryupdate_child_data_if_avaliable"); } } if ($driver == 'pgsql') { $this->getPdo()->query("RELEASE SAVEPOINT tryupdate_child_data_if_avaliable"); } $stmts['update']->closeCursor(); $tryUpdateData = $nextUpdateData; } } /* insert data */ foreach ($insertData as $data) { $stmts['insert']->execute($fields_insert !== null ? array_intersect_key($data, array_flip($fields_insert)) : $data); $stmts['insert']->closeCursor(); } }