private function updateOrInsertTableRow(TableRow $data, $forceInsert = false, Statement $insertStmt = null)
 {
     $id = false;
     $pk = null;
     if ($data->description()->hasIdentifier()) {
         $pk = $data::toDbColumnName($data->description()->identifierName());
         $id = $data->property($data->description()->identifierName())->value();
     }
     $dbTypes = $this->getDbTypesForProperties($data);
     $itemType = $data->prototype()->of();
     $data = $this->convertToDbData($data);
     //In try update mode we try to delete the table row first and then insert it again
     if (!$forceInsert) {
         $query = $this->connection->createQueryBuilder();
         //Due to Sqlite error when using an alias we don't assign one here, so delete queries are limit to one table
         //However, a action event listener can rebuild the query and use a platform specific delete with joins if required
         $query->delete($this->table);
         if ($id) {
             $query->where($query->expr()->eq($pk, ':identifier'));
             $query->setParameter('identifier', $id, $dbTypes[$pk]);
         } elseif ($this->triggerActions) {
             $actionEvent = $this->actions->getNewActionEvent('delete_table_row', $this, ['query' => $query, 'item_type' => $itemType, 'item_db_data' => $data, 'item_db_types' => $dbTypes, 'skip_row' => false]);
             $this->actions->dispatch($actionEvent);
             if ($actionEvent->getParam('skip_row')) {
                 return $insertStmt;
             }
             $query = $actionEvent->getParam('query');
             if ($query && empty($query->getQueryPart('where'))) {
                 $query = null;
             }
         } else {
             $query = null;
         }
         //We only perform the delete query if it has at least one condition set.
         if ($query) {
             $query->execute();
         }
     }
     return $this->performInsert($data, $dbTypes, $insertStmt);
 }