Copilot commented on code in PR #11443:
URL: https://github.com/apache/cloudstack/pull/11443#discussion_r3153325352


##########
server/src/main/java/com/cloud/server/ManagementServerImpl.java:
##########
@@ -1225,57 +1224,64 @@ protected void checkPortParameters(final String 
publicPort, final String private
     }
 
     @Override
-    public boolean archiveEvents(final ArchiveEventsCmd cmd) {
-        final Account caller = getCaller();
-        final List<Long> ids = cmd.getIds();
-        boolean result = true;
-        List<Long> permittedAccountIds = new ArrayList<>();
+    public boolean archiveEvents(ArchiveEventsCmd cmd) {
+        List<Long> ids = cmd.getIds();
+        String type = cmd.getType();
+        Date startDate = cmd.getStartDate();
+        Date endDate = cmd.getEndDate();
+
+        validateEventOperationParameters(ids, type, endDate, startDate);
+
+        Long accountId = null;
+        List<Long> domainIds = null;
+        Account caller = getCaller();
+        Account.Type callerType = caller.getType();
 
-        if (_accountService.isNormalUser(caller.getId()) || caller.getType() 
== Account.Type.PROJECT) {
-            permittedAccountIds.add(caller.getId());
+        if (callerType == Account.Type.NORMAL || callerType == 
Account.Type.PROJECT) {
+            accountId = caller.getId();
         } else {
-            final DomainVO domain = _domainDao.findById(caller.getDomainId());
-            final List<Long> permittedDomainIds = 
_domainDao.getDomainChildrenIds(domain.getPath());
-            permittedAccountIds = 
_accountDao.getAccountIdsForDomains(permittedDomainIds);
+            domainIds = 
_domainDao.getDomainAndChildrenIds(caller.getDomainId());
         }
 
-        final List<EventVO> events = 
_eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), 
cmd.getEndDate(), permittedAccountIds);
-        final ControlledEntity[] sameOwnerEvents = events.toArray(new 
ControlledEntity[events.size()]);
-        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), 
null, false, sameOwnerEvents);
+        long totalArchived = _eventDao.archiveEvents(ids, type, startDate, 
endDate, accountId, domainIds,
+                ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value());
 
-        if (ids != null && events.size() < ids.size()) {
-            return false;
-        }
-        _eventDao.archiveEvents(events);
-        return result;
+        return totalArchived > 0;

Review Comment:
   When `ids` is provided, this method reports success if *any* matching rows 
were archived. That means a request like `ids=[1,2,3]` can return success even 
if only a subset was actually archived (e.g., some IDs don’t exist or aren’t in 
the caller’s scope). If the API is expected to be all-or-nothing for explicit 
IDs, consider returning failure (or throwing) when `totalArchived != 
ids.size()` when `ids` is non-empty, or otherwise make the partial-success 
behavior explicit.



##########
engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java:
##########
@@ -33,83 +38,90 @@
 
 @Component
 public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements 
EventDao {
-    protected final SearchBuilder<EventVO> CompletedEventSearch;
+
     protected final SearchBuilder<EventVO> ToArchiveOrDeleteEventSearch;
 
     public EventDaoImpl() {
-        CompletedEventSearch = createSearchBuilder();
-        CompletedEventSearch.and("state", 
CompletedEventSearch.entity().getState(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("startId", 
CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("archived", 
CompletedEventSearch.entity().getArchived(), Op.EQ);
-        CompletedEventSearch.done();
-
         ToArchiveOrDeleteEventSearch = createSearchBuilder();
+        ToArchiveOrDeleteEventSearch.select("id", SearchCriteria.Func.NATIVE, 
ToArchiveOrDeleteEventSearch.entity().getId());
         ToArchiveOrDeleteEventSearch.and("id", 
ToArchiveOrDeleteEventSearch.entity().getId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("type", 
ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ);
-        ToArchiveOrDeleteEventSearch.and("accountIds", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN);
+        ToArchiveOrDeleteEventSearch.and("accountId", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.EQ);
+        ToArchiveOrDeleteEventSearch.and("domainIds", 
ToArchiveOrDeleteEventSearch.entity().getDomainId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("createdDateB", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.BETWEEN);
         ToArchiveOrDeleteEventSearch.and("createdDateL", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ);
+        ToArchiveOrDeleteEventSearch.and("createdDateLT", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT);
         ToArchiveOrDeleteEventSearch.and("archived", 
ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ);
         ToArchiveOrDeleteEventSearch.done();
     }
 
-    @Override
-    public List<EventVO> searchAllEvents(SearchCriteria<EventVO> sc, Filter 
filter) {
-        return listIncludingRemovedBy(sc, filter);
-    }
-
-    @Override
-    public List<EventVO> listOlderEvents(Date oldTime) {
-        if (oldTime == null)
-            return null;
-        SearchCriteria<EventVO> sc = createSearchCriteria();
-        sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime);
-        sc.addAnd("archived", SearchCriteria.Op.EQ, false);
-        return listIncludingRemovedBy(sc, null);
-    }
-
-    @Override
-    public EventVO findCompletedEvent(long startId) {
-        SearchCriteria<EventVO> sc = CompletedEventSearch.create();
-        sc.setParameters("state", State.Completed);
-        sc.setParameters("startId", startId);
-        sc.setParameters("archived", false);
-        return findOneIncludingRemovedBy(sc);
-    }
-
-    @Override
-    public List<EventVO> listToArchiveOrDeleteEvents(List<Long> ids, String 
type, Date startDate, Date endDate, List<Long> accountIds) {
+    private SearchCriteria<EventVO> createEventSearchCriteria(List<Long> ids, 
String type, Date startDate, Date endDate,
+                                                              Date limitDate, 
Long accountId, List<Long> domainIds) {
         SearchCriteria<EventVO> sc = ToArchiveOrDeleteEventSearch.create();
-        if (ids != null) {
-            sc.setParameters("id", ids.toArray(new Object[ids.size()]));
+
+        if (CollectionUtils.isNotEmpty(ids)) {
+            sc.setParameters("id", ids.toArray(new Object[0]));
         }
-        if (type != null) {
-            sc.setParameters("type", type);
+        if (CollectionUtils.isNotEmpty(domainIds)) {
+            sc.setParameters("domainIds", domainIds.toArray(new Object[0]));
         }
         if (startDate != null && endDate != null) {
             sc.setParameters("createdDateB", startDate, endDate);
         } else if (endDate != null) {
             sc.setParameters("createdDateL", endDate);
         }
-        if (accountIds != null && !accountIds.isEmpty()) {
-            sc.setParameters("accountIds", accountIds.toArray(new 
Object[accountIds.size()]));
-        }
+        sc.setParametersIfNotNull("accountId", accountId);
+        sc.setParametersIfNotNull("createdDateLT", limitDate);
+        sc.setParametersIfNotNull("type", type);
         sc.setParameters("archived", false);
-        return search(sc, null);
+
+        return sc;
     }
 
     @Override
-    public void archiveEvents(List<EventVO> events) {
-        if (events != null && !events.isEmpty()) {
-            TransactionLegacy txn = TransactionLegacy.currentTxn();
-            txn.start();
-            for (EventVO event : events) {
-                event = lockRow(event.getId(), true);
-                event.setArchived(true);
-                update(event.getId(), event);
-                txn.commit();
+    public long archiveEvents(List<Long> ids, String type, Date startDate, 
Date endDate, Long accountId, List<Long> domainIds,
+                              long limitPerQuery) {
+        SearchCriteria<EventVO> sc = createEventSearchCriteria(ids, type, 
startDate, endDate, null, accountId, domainIds);
+        Filter filter = null;
+        if (limitPerQuery > 0) {
+            filter = new Filter(limitPerQuery);
+        }
+
+        long archived;
+        long totalArchived = 0L;
+
+        do {
+            List<EventVO> events = search(sc, filter);
+            if (events.isEmpty()) {
+                break;
             }
-            txn.close();
+
+            archived = archiveEventsInternal(events);
+            totalArchived += archived;
+        } while (limitPerQuery > 0 && archived >= limitPerQuery);

Review Comment:
   `archiveEvents` can load *all* matching events into memory when 
`limitPerQuery <= 0` (the default for `delete.query.batch.size` is 0), then 
builds a single `UPDATE ... WHERE id IN (...)` statement containing every ID. 
On large event tables this risks OOMs, oversized SQL statements, and very 
long-running queries. Consider always archiving in batches (even when the 
config is 0), or implement a batched `UPDATE ... LIMIT <batch>` loop similar to 
`batchExpunge` so the operation remains bounded.



##########
api/src/main/java/org/apache/cloudstack/api/command/user/event/ArchiveEventsCmd.java:
##########
@@ -97,17 +112,12 @@ public long getEntityOwnerId() {
 
     @Override
     public void execute() {
-        if (ids == null && type == null && endDate == null) {
-            throw new InvalidParameterValueException("either ids, type or 
enddate must be specified");
-        } else if (startDate != null && endDate == null) {
-            throw new InvalidParameterValueException("enddate must be 
specified with startdate parameter");
-        }
         boolean result = _mgr.archiveEvents(this);
         if (result) {
             SuccessResponse response = new SuccessResponse(getCommandName());
             setResponseObject(response);
-        } else {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable 
to archive Events, one or more parameters has invalid values");
+            return;
         }
+        throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to 
archive Events. One or more parameters have invalid values.");
     }

Review Comment:
   `_mgr.archiveEvents` returns `false` when `totalArchived == 0`, which can 
happen with valid parameters that match zero rows. Throwing an INTERNAL_ERROR 
that claims invalid parameters is misleading in that case. Consider 
distinguishing "no matching events" from actual validation errors (which should 
throw `InvalidParameterValueException`) and adjusting the message accordingly.



##########
engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java:
##########
@@ -33,83 +38,90 @@
 
 @Component
 public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements 
EventDao {
-    protected final SearchBuilder<EventVO> CompletedEventSearch;
+
     protected final SearchBuilder<EventVO> ToArchiveOrDeleteEventSearch;
 
     public EventDaoImpl() {
-        CompletedEventSearch = createSearchBuilder();
-        CompletedEventSearch.and("state", 
CompletedEventSearch.entity().getState(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("startId", 
CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("archived", 
CompletedEventSearch.entity().getArchived(), Op.EQ);
-        CompletedEventSearch.done();
-
         ToArchiveOrDeleteEventSearch = createSearchBuilder();
+        ToArchiveOrDeleteEventSearch.select("id", SearchCriteria.Func.NATIVE, 
ToArchiveOrDeleteEventSearch.entity().getId());
         ToArchiveOrDeleteEventSearch.and("id", 
ToArchiveOrDeleteEventSearch.entity().getId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("type", 
ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ);
-        ToArchiveOrDeleteEventSearch.and("accountIds", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN);
+        ToArchiveOrDeleteEventSearch.and("accountId", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.EQ);
+        ToArchiveOrDeleteEventSearch.and("domainIds", 
ToArchiveOrDeleteEventSearch.entity().getDomainId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("createdDateB", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.BETWEEN);
         ToArchiveOrDeleteEventSearch.and("createdDateL", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ);
+        ToArchiveOrDeleteEventSearch.and("createdDateLT", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT);
         ToArchiveOrDeleteEventSearch.and("archived", 
ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ);
         ToArchiveOrDeleteEventSearch.done();
     }
 
-    @Override
-    public List<EventVO> searchAllEvents(SearchCriteria<EventVO> sc, Filter 
filter) {
-        return listIncludingRemovedBy(sc, filter);
-    }
-
-    @Override
-    public List<EventVO> listOlderEvents(Date oldTime) {
-        if (oldTime == null)
-            return null;
-        SearchCriteria<EventVO> sc = createSearchCriteria();
-        sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime);
-        sc.addAnd("archived", SearchCriteria.Op.EQ, false);
-        return listIncludingRemovedBy(sc, null);
-    }
-
-    @Override
-    public EventVO findCompletedEvent(long startId) {
-        SearchCriteria<EventVO> sc = CompletedEventSearch.create();
-        sc.setParameters("state", State.Completed);
-        sc.setParameters("startId", startId);
-        sc.setParameters("archived", false);
-        return findOneIncludingRemovedBy(sc);
-    }
-
-    @Override
-    public List<EventVO> listToArchiveOrDeleteEvents(List<Long> ids, String 
type, Date startDate, Date endDate, List<Long> accountIds) {
+    private SearchCriteria<EventVO> createEventSearchCriteria(List<Long> ids, 
String type, Date startDate, Date endDate,
+                                                              Date limitDate, 
Long accountId, List<Long> domainIds) {
         SearchCriteria<EventVO> sc = ToArchiveOrDeleteEventSearch.create();
-        if (ids != null) {
-            sc.setParameters("id", ids.toArray(new Object[ids.size()]));
+
+        if (CollectionUtils.isNotEmpty(ids)) {
+            sc.setParameters("id", ids.toArray(new Object[0]));
         }
-        if (type != null) {
-            sc.setParameters("type", type);
+        if (CollectionUtils.isNotEmpty(domainIds)) {
+            sc.setParameters("domainIds", domainIds.toArray(new Object[0]));
         }
         if (startDate != null && endDate != null) {
             sc.setParameters("createdDateB", startDate, endDate);
         } else if (endDate != null) {
             sc.setParameters("createdDateL", endDate);
         }
-        if (accountIds != null && !accountIds.isEmpty()) {
-            sc.setParameters("accountIds", accountIds.toArray(new 
Object[accountIds.size()]));
-        }
+        sc.setParametersIfNotNull("accountId", accountId);
+        sc.setParametersIfNotNull("createdDateLT", limitDate);
+        sc.setParametersIfNotNull("type", type);
         sc.setParameters("archived", false);
-        return search(sc, null);
+
+        return sc;
     }
 
     @Override
-    public void archiveEvents(List<EventVO> events) {
-        if (events != null && !events.isEmpty()) {
-            TransactionLegacy txn = TransactionLegacy.currentTxn();
-            txn.start();
-            for (EventVO event : events) {
-                event = lockRow(event.getId(), true);
-                event.setArchived(true);
-                update(event.getId(), event);
-                txn.commit();
+    public long archiveEvents(List<Long> ids, String type, Date startDate, 
Date endDate, Long accountId, List<Long> domainIds,
+                              long limitPerQuery) {
+        SearchCriteria<EventVO> sc = createEventSearchCriteria(ids, type, 
startDate, endDate, null, accountId, domainIds);
+        Filter filter = null;
+        if (limitPerQuery > 0) {
+            filter = new Filter(limitPerQuery);
+        }
+
+        long archived;
+        long totalArchived = 0L;
+
+        do {
+            List<EventVO> events = search(sc, filter);
+            if (events.isEmpty()) {
+                break;
             }
-            txn.close();
+
+            archived = archiveEventsInternal(events);
+            totalArchived += archived;
+        } while (limitPerQuery > 0 && archived >= limitPerQuery);
+
+        return totalArchived;
+    }
+
+    @DB
+    private long archiveEventsInternal(List<EventVO> events) {
+        final String idsAsString = events.stream()
+                .map(e -> Long.toString(e.getId()))
+                .collect(Collectors.joining(","));
+        final String query = String.format("UPDATE event SET archived=true 
WHERE id IN (%s)", idsAsString);
+
+        try (TransactionLegacy txn = TransactionLegacy.currentTxn();
+             PreparedStatement pstmt = txn.prepareStatement(query)) {
+            return pstmt.executeUpdate();
+        } catch (SQLException e) {
+            throw new CloudRuntimeException(e);
         }
     }
+
+    @Override
+    public long purgeAll(List<Long> ids, Date startDate, Date endDate, Date 
limitDate, String type, Long accountId,
+                         List<Long> domainIds, long limitPerQuery) {
+        SearchCriteria<EventVO> sc = createEventSearchCriteria(ids, type, 
startDate, endDate, limitDate, accountId, domainIds);
+        return batchExpunge(sc, limitPerQuery);
+    }

Review Comment:
   New batching semantics in `archiveEvents(...)`/`purgeAll(...)` are 
non-trivial (limit handling, iteration/termination, criteria composition) but 
there are currently no unit tests covering these paths (unlike other DAO 
batch-expunge usages in `engine/schema/src/test`). Consider adding a focused 
DAO unit test validating that multiple batches are processed correctly and that 
`limitPerQuery <= 0` behaves as intended.



##########
server/src/main/java/com/cloud/server/ManagementServerImpl.java:
##########
@@ -1225,57 +1224,64 @@ protected void checkPortParameters(final String 
publicPort, final String private
     }
 
     @Override
-    public boolean archiveEvents(final ArchiveEventsCmd cmd) {
-        final Account caller = getCaller();
-        final List<Long> ids = cmd.getIds();
-        boolean result = true;
-        List<Long> permittedAccountIds = new ArrayList<>();
+    public boolean archiveEvents(ArchiveEventsCmd cmd) {
+        List<Long> ids = cmd.getIds();
+        String type = cmd.getType();
+        Date startDate = cmd.getStartDate();
+        Date endDate = cmd.getEndDate();
+
+        validateEventOperationParameters(ids, type, endDate, startDate);
+
+        Long accountId = null;
+        List<Long> domainIds = null;
+        Account caller = getCaller();
+        Account.Type callerType = caller.getType();
 
-        if (_accountService.isNormalUser(caller.getId()) || caller.getType() 
== Account.Type.PROJECT) {
-            permittedAccountIds.add(caller.getId());
+        if (callerType == Account.Type.NORMAL || callerType == 
Account.Type.PROJECT) {
+            accountId = caller.getId();
         } else {
-            final DomainVO domain = _domainDao.findById(caller.getDomainId());
-            final List<Long> permittedDomainIds = 
_domainDao.getDomainChildrenIds(domain.getPath());
-            permittedAccountIds = 
_accountDao.getAccountIdsForDomains(permittedDomainIds);
+            domainIds = 
_domainDao.getDomainAndChildrenIds(caller.getDomainId());
         }
 
-        final List<EventVO> events = 
_eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), 
cmd.getEndDate(), permittedAccountIds);
-        final ControlledEntity[] sameOwnerEvents = events.toArray(new 
ControlledEntity[events.size()]);
-        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), 
null, false, sameOwnerEvents);
+        long totalArchived = _eventDao.archiveEvents(ids, type, startDate, 
endDate, accountId, domainIds,
+                ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value());
 
-        if (ids != null && events.size() < ids.size()) {
-            return false;
-        }
-        _eventDao.archiveEvents(events);
-        return result;
+        return totalArchived > 0;
     }
 
     @Override
-    public boolean deleteEvents(final DeleteEventsCmd cmd) {
-        final Account caller = getCaller();
-        final List<Long> ids = cmd.getIds();
-        boolean result = true;
-        List<Long> permittedAccountIds = new ArrayList<>();
+    public boolean deleteEvents(DeleteEventsCmd cmd) {
+        List<Long> ids = cmd.getIds();
+        String type = cmd.getType();
+        Date startDate = cmd.getStartDate();
+        Date endDate = cmd.getEndDate();
 
-        if (_accountMgr.isNormalUser(caller.getId()) || caller.getType() == 
Account.Type.PROJECT) {
-            permittedAccountIds.add(caller.getId());
+        validateEventOperationParameters(ids, type, endDate, startDate);
+
+        Long accountId = null;
+        List<Long> domainIds = null;
+        Account caller = getCaller();
+        Account.Type callerType = caller.getType();
+
+        if (callerType == Account.Type.NORMAL || callerType == 
Account.Type.PROJECT) {
+            accountId = caller.getId();
         } else {
-            final DomainVO domain = _domainDao.findById(caller.getDomainId());
-            final List<Long> permittedDomainIds = 
_domainDao.getDomainChildrenIds(domain.getPath());
-            permittedAccountIds = 
_accountDao.getAccountIdsForDomains(permittedDomainIds);
+            domainIds = 
_domainDao.getDomainAndChildrenIds(caller.getDomainId());
         }
 
-        final List<EventVO> events = 
_eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), 
cmd.getEndDate(), permittedAccountIds);
-        final ControlledEntity[] sameOwnerEvents = events.toArray(new 
ControlledEntity[events.size()]);
-        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), 
null, false, sameOwnerEvents);
+        long totalRemoved = _eventDao.purgeAll(ids, startDate, endDate, null, 
type, accountId, domainIds,
+                ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value());
 
-        if (ids != null && events.size() < ids.size()) {
-            return false;
+        return totalRemoved > 0;
+    }
+
+    protected void validateEventOperationParameters(List<Long> ids, String 
type, Date endDate, Date startDate) {
+        if (CollectionUtils.isEmpty(ids) && ObjectUtils.allNull(type, 
endDate)) {
+            throw new InvalidParameterValueException("Either 'ids', 'type' or 
'enddate' must be specified.");
         }
-        for (final EventVO event : events) {
-            _eventDao.remove(event.getId());
+        if (startDate != null && endDate == null) {
+            throw new InvalidParameterValueException("'startdate' must be 
specified with 'enddate' parameter.");

Review Comment:
   `validateEventOperationParameters` throws when `startDate != null && endDate 
== null`, but the exception message says "'startdate' must be specified with 
'enddate'", which is the opposite of what’s wrong in this case. Update the 
message to indicate that `enddate` is required when `startdate` is provided 
(and keep parameter naming consistent with the API).
   ```suggestion
               throw new InvalidParameterValueException("'enddate' must be 
specified when 'startdate' parameter is provided.");
   ```



##########
server/src/main/java/com/cloud/server/ManagementServerImpl.java:
##########
@@ -1225,57 +1224,64 @@ protected void checkPortParameters(final String 
publicPort, final String private
     }
 
     @Override
-    public boolean archiveEvents(final ArchiveEventsCmd cmd) {
-        final Account caller = getCaller();
-        final List<Long> ids = cmd.getIds();
-        boolean result = true;
-        List<Long> permittedAccountIds = new ArrayList<>();
+    public boolean archiveEvents(ArchiveEventsCmd cmd) {
+        List<Long> ids = cmd.getIds();
+        String type = cmd.getType();
+        Date startDate = cmd.getStartDate();
+        Date endDate = cmd.getEndDate();
+
+        validateEventOperationParameters(ids, type, endDate, startDate);
+
+        Long accountId = null;
+        List<Long> domainIds = null;
+        Account caller = getCaller();
+        Account.Type callerType = caller.getType();
 
-        if (_accountService.isNormalUser(caller.getId()) || caller.getType() 
== Account.Type.PROJECT) {
-            permittedAccountIds.add(caller.getId());
+        if (callerType == Account.Type.NORMAL || callerType == 
Account.Type.PROJECT) {
+            accountId = caller.getId();
         } else {
-            final DomainVO domain = _domainDao.findById(caller.getDomainId());
-            final List<Long> permittedDomainIds = 
_domainDao.getDomainChildrenIds(domain.getPath());
-            permittedAccountIds = 
_accountDao.getAccountIdsForDomains(permittedDomainIds);
+            domainIds = 
_domainDao.getDomainAndChildrenIds(caller.getDomainId());
         }
 
-        final List<EventVO> events = 
_eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), 
cmd.getEndDate(), permittedAccountIds);
-        final ControlledEntity[] sameOwnerEvents = events.toArray(new 
ControlledEntity[events.size()]);
-        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), 
null, false, sameOwnerEvents);
+        long totalArchived = _eventDao.archiveEvents(ids, type, startDate, 
endDate, accountId, domainIds,
+                ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value());
 
-        if (ids != null && events.size() < ids.size()) {
-            return false;
-        }
-        _eventDao.archiveEvents(events);
-        return result;
+        return totalArchived > 0;
     }
 
     @Override
-    public boolean deleteEvents(final DeleteEventsCmd cmd) {
-        final Account caller = getCaller();
-        final List<Long> ids = cmd.getIds();
-        boolean result = true;
-        List<Long> permittedAccountIds = new ArrayList<>();
+    public boolean deleteEvents(DeleteEventsCmd cmd) {
+        List<Long> ids = cmd.getIds();
+        String type = cmd.getType();
+        Date startDate = cmd.getStartDate();
+        Date endDate = cmd.getEndDate();
 
-        if (_accountMgr.isNormalUser(caller.getId()) || caller.getType() == 
Account.Type.PROJECT) {
-            permittedAccountIds.add(caller.getId());
+        validateEventOperationParameters(ids, type, endDate, startDate);
+
+        Long accountId = null;
+        List<Long> domainIds = null;
+        Account caller = getCaller();
+        Account.Type callerType = caller.getType();
+
+        if (callerType == Account.Type.NORMAL || callerType == 
Account.Type.PROJECT) {
+            accountId = caller.getId();
         } else {
-            final DomainVO domain = _domainDao.findById(caller.getDomainId());
-            final List<Long> permittedDomainIds = 
_domainDao.getDomainChildrenIds(domain.getPath());
-            permittedAccountIds = 
_accountDao.getAccountIdsForDomains(permittedDomainIds);
+            domainIds = 
_domainDao.getDomainAndChildrenIds(caller.getDomainId());
         }
 
-        final List<EventVO> events = 
_eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), 
cmd.getEndDate(), permittedAccountIds);
-        final ControlledEntity[] sameOwnerEvents = events.toArray(new 
ControlledEntity[events.size()]);
-        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), 
null, false, sameOwnerEvents);
+        long totalRemoved = _eventDao.purgeAll(ids, startDate, endDate, null, 
type, accountId, domainIds,
+                ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value());
 

Review Comment:
   When `ids` is provided, this method reports success if *any* rows were 
deleted. This can silently ignore IDs that don’t match the criteria / caller 
scope while still returning success, which is potentially misleading for 
callers expecting all requested IDs to be removed. Consider aligning the return 
semantics with the requested operation (e.g., fail when `ids` is non-empty and 
`totalRemoved != ids.size()`, or explicitly document/return partial results).
   ```suggestion
   
           if (CollectionUtils.isNotEmpty(ids)) {
               return totalRemoved == ids.size();
           }
   ```



##########
engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java:
##########
@@ -33,83 +38,90 @@
 
 @Component
 public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements 
EventDao {
-    protected final SearchBuilder<EventVO> CompletedEventSearch;
+
     protected final SearchBuilder<EventVO> ToArchiveOrDeleteEventSearch;
 
     public EventDaoImpl() {
-        CompletedEventSearch = createSearchBuilder();
-        CompletedEventSearch.and("state", 
CompletedEventSearch.entity().getState(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("startId", 
CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("archived", 
CompletedEventSearch.entity().getArchived(), Op.EQ);
-        CompletedEventSearch.done();
-
         ToArchiveOrDeleteEventSearch = createSearchBuilder();
+        ToArchiveOrDeleteEventSearch.select("id", SearchCriteria.Func.NATIVE, 
ToArchiveOrDeleteEventSearch.entity().getId());
         ToArchiveOrDeleteEventSearch.and("id", 
ToArchiveOrDeleteEventSearch.entity().getId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("type", 
ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ);
-        ToArchiveOrDeleteEventSearch.and("accountIds", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN);
+        ToArchiveOrDeleteEventSearch.and("accountId", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.EQ);
+        ToArchiveOrDeleteEventSearch.and("domainIds", 
ToArchiveOrDeleteEventSearch.entity().getDomainId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("createdDateB", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.BETWEEN);
         ToArchiveOrDeleteEventSearch.and("createdDateL", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ);
+        ToArchiveOrDeleteEventSearch.and("createdDateLT", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT);
         ToArchiveOrDeleteEventSearch.and("archived", 
ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ);
         ToArchiveOrDeleteEventSearch.done();
     }
 
-    @Override
-    public List<EventVO> searchAllEvents(SearchCriteria<EventVO> sc, Filter 
filter) {
-        return listIncludingRemovedBy(sc, filter);
-    }
-
-    @Override
-    public List<EventVO> listOlderEvents(Date oldTime) {
-        if (oldTime == null)
-            return null;
-        SearchCriteria<EventVO> sc = createSearchCriteria();
-        sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime);
-        sc.addAnd("archived", SearchCriteria.Op.EQ, false);
-        return listIncludingRemovedBy(sc, null);
-    }
-
-    @Override
-    public EventVO findCompletedEvent(long startId) {
-        SearchCriteria<EventVO> sc = CompletedEventSearch.create();
-        sc.setParameters("state", State.Completed);
-        sc.setParameters("startId", startId);
-        sc.setParameters("archived", false);
-        return findOneIncludingRemovedBy(sc);
-    }
-
-    @Override
-    public List<EventVO> listToArchiveOrDeleteEvents(List<Long> ids, String 
type, Date startDate, Date endDate, List<Long> accountIds) {
+    private SearchCriteria<EventVO> createEventSearchCriteria(List<Long> ids, 
String type, Date startDate, Date endDate,
+                                                              Date limitDate, 
Long accountId, List<Long> domainIds) {
         SearchCriteria<EventVO> sc = ToArchiveOrDeleteEventSearch.create();
-        if (ids != null) {
-            sc.setParameters("id", ids.toArray(new Object[ids.size()]));
+
+        if (CollectionUtils.isNotEmpty(ids)) {
+            sc.setParameters("id", ids.toArray(new Object[0]));
         }
-        if (type != null) {
-            sc.setParameters("type", type);
+        if (CollectionUtils.isNotEmpty(domainIds)) {
+            sc.setParameters("domainIds", domainIds.toArray(new Object[0]));
         }
         if (startDate != null && endDate != null) {
             sc.setParameters("createdDateB", startDate, endDate);
         } else if (endDate != null) {
             sc.setParameters("createdDateL", endDate);
         }
-        if (accountIds != null && !accountIds.isEmpty()) {
-            sc.setParameters("accountIds", accountIds.toArray(new 
Object[accountIds.size()]));
-        }
+        sc.setParametersIfNotNull("accountId", accountId);
+        sc.setParametersIfNotNull("createdDateLT", limitDate);
+        sc.setParametersIfNotNull("type", type);
         sc.setParameters("archived", false);
-        return search(sc, null);
+
+        return sc;
     }
 
     @Override
-    public void archiveEvents(List<EventVO> events) {
-        if (events != null && !events.isEmpty()) {
-            TransactionLegacy txn = TransactionLegacy.currentTxn();
-            txn.start();
-            for (EventVO event : events) {
-                event = lockRow(event.getId(), true);
-                event.setArchived(true);
-                update(event.getId(), event);
-                txn.commit();
+    public long archiveEvents(List<Long> ids, String type, Date startDate, 
Date endDate, Long accountId, List<Long> domainIds,
+                              long limitPerQuery) {
+        SearchCriteria<EventVO> sc = createEventSearchCriteria(ids, type, 
startDate, endDate, null, accountId, domainIds);
+        Filter filter = null;
+        if (limitPerQuery > 0) {
+            filter = new Filter(limitPerQuery);
+        }
+
+        long archived;
+        long totalArchived = 0L;
+
+        do {
+            List<EventVO> events = search(sc, filter);
+            if (events.isEmpty()) {
+                break;
             }
-            txn.close();
+
+            archived = archiveEventsInternal(events);
+            totalArchived += archived;
+        } while (limitPerQuery > 0 && archived >= limitPerQuery);
+
+        return totalArchived;
+    }
+
+    @DB
+    private long archiveEventsInternal(List<EventVO> events) {
+        final String idsAsString = events.stream()
+                .map(e -> Long.toString(e.getId()))
+                .collect(Collectors.joining(","));
+        final String query = String.format("UPDATE event SET archived=true 
WHERE id IN (%s)", idsAsString);
+
+        try (TransactionLegacy txn = TransactionLegacy.currentTxn();
+             PreparedStatement pstmt = txn.prepareStatement(query)) {

Review Comment:
   `archiveEventsInternal` builds SQL by string-concatenating the IDs into an 
`IN (...)` clause. Besides the query-size risk, it bypasses parameter binding 
and can stress SQL parsing/plan caching. Consider using a parameterized 
statement (placeholders) or a different batching strategy (e.g., update by 
criteria with `LIMIT`) to keep statements small and reusable.
   ```suggestion
           if (CollectionUtils.isEmpty(events)) {
               return 0L;
           }
   
           final String placeholders = events.stream()
                   .map(event -> "?")
                   .collect(Collectors.joining(","));
           final String query = String.format("UPDATE event SET archived=true 
WHERE id IN (%s)", placeholders);
   
           try (TransactionLegacy txn = TransactionLegacy.currentTxn();
                PreparedStatement pstmt = txn.prepareStatement(query)) {
               for (int i = 0; i < events.size(); i++) {
                   pstmt.setLong(i + 1, events.get(i).getId());
               }
   ```



##########
engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java:
##########
@@ -33,83 +38,90 @@
 
 @Component
 public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements 
EventDao {
-    protected final SearchBuilder<EventVO> CompletedEventSearch;
+
     protected final SearchBuilder<EventVO> ToArchiveOrDeleteEventSearch;
 
     public EventDaoImpl() {
-        CompletedEventSearch = createSearchBuilder();
-        CompletedEventSearch.and("state", 
CompletedEventSearch.entity().getState(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("startId", 
CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ);
-        CompletedEventSearch.and("archived", 
CompletedEventSearch.entity().getArchived(), Op.EQ);
-        CompletedEventSearch.done();
-
         ToArchiveOrDeleteEventSearch = createSearchBuilder();
+        ToArchiveOrDeleteEventSearch.select("id", SearchCriteria.Func.NATIVE, 
ToArchiveOrDeleteEventSearch.entity().getId());
         ToArchiveOrDeleteEventSearch.and("id", 
ToArchiveOrDeleteEventSearch.entity().getId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("type", 
ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ);
-        ToArchiveOrDeleteEventSearch.and("accountIds", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN);
+        ToArchiveOrDeleteEventSearch.and("accountId", 
ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.EQ);
+        ToArchiveOrDeleteEventSearch.and("domainIds", 
ToArchiveOrDeleteEventSearch.entity().getDomainId(), Op.IN);
         ToArchiveOrDeleteEventSearch.and("createdDateB", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.BETWEEN);
         ToArchiveOrDeleteEventSearch.and("createdDateL", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ);
+        ToArchiveOrDeleteEventSearch.and("createdDateLT", 
ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT);
         ToArchiveOrDeleteEventSearch.and("archived", 
ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ);
         ToArchiveOrDeleteEventSearch.done();
     }
 
-    @Override
-    public List<EventVO> searchAllEvents(SearchCriteria<EventVO> sc, Filter 
filter) {
-        return listIncludingRemovedBy(sc, filter);
-    }
-
-    @Override
-    public List<EventVO> listOlderEvents(Date oldTime) {
-        if (oldTime == null)
-            return null;
-        SearchCriteria<EventVO> sc = createSearchCriteria();
-        sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime);
-        sc.addAnd("archived", SearchCriteria.Op.EQ, false);
-        return listIncludingRemovedBy(sc, null);
-    }
-
-    @Override
-    public EventVO findCompletedEvent(long startId) {
-        SearchCriteria<EventVO> sc = CompletedEventSearch.create();
-        sc.setParameters("state", State.Completed);
-        sc.setParameters("startId", startId);
-        sc.setParameters("archived", false);
-        return findOneIncludingRemovedBy(sc);
-    }
-
-    @Override
-    public List<EventVO> listToArchiveOrDeleteEvents(List<Long> ids, String 
type, Date startDate, Date endDate, List<Long> accountIds) {
+    private SearchCriteria<EventVO> createEventSearchCriteria(List<Long> ids, 
String type, Date startDate, Date endDate,
+                                                              Date limitDate, 
Long accountId, List<Long> domainIds) {
         SearchCriteria<EventVO> sc = ToArchiveOrDeleteEventSearch.create();
-        if (ids != null) {
-            sc.setParameters("id", ids.toArray(new Object[ids.size()]));
+
+        if (CollectionUtils.isNotEmpty(ids)) {
+            sc.setParameters("id", ids.toArray(new Object[0]));
         }
-        if (type != null) {
-            sc.setParameters("type", type);
+        if (CollectionUtils.isNotEmpty(domainIds)) {
+            sc.setParameters("domainIds", domainIds.toArray(new Object[0]));
         }
         if (startDate != null && endDate != null) {
             sc.setParameters("createdDateB", startDate, endDate);
         } else if (endDate != null) {
             sc.setParameters("createdDateL", endDate);
         }
-        if (accountIds != null && !accountIds.isEmpty()) {
-            sc.setParameters("accountIds", accountIds.toArray(new 
Object[accountIds.size()]));
-        }
+        sc.setParametersIfNotNull("accountId", accountId);
+        sc.setParametersIfNotNull("createdDateLT", limitDate);
+        sc.setParametersIfNotNull("type", type);
         sc.setParameters("archived", false);
-        return search(sc, null);
+
+        return sc;
     }
 
     @Override
-    public void archiveEvents(List<EventVO> events) {
-        if (events != null && !events.isEmpty()) {
-            TransactionLegacy txn = TransactionLegacy.currentTxn();
-            txn.start();
-            for (EventVO event : events) {
-                event = lockRow(event.getId(), true);
-                event.setArchived(true);
-                update(event.getId(), event);
-                txn.commit();
+    public long archiveEvents(List<Long> ids, String type, Date startDate, 
Date endDate, Long accountId, List<Long> domainIds,
+                              long limitPerQuery) {
+        SearchCriteria<EventVO> sc = createEventSearchCriteria(ids, type, 
startDate, endDate, null, accountId, domainIds);
+        Filter filter = null;
+        if (limitPerQuery > 0) {
+            filter = new Filter(limitPerQuery);
+        }
+
+        long archived;
+        long totalArchived = 0L;
+
+        do {
+            List<EventVO> events = search(sc, filter);
+            if (events.isEmpty()) {
+                break;
             }
-            txn.close();
+
+            archived = archiveEventsInternal(events);
+            totalArchived += archived;
+        } while (limitPerQuery > 0 && archived >= limitPerQuery);

Review Comment:
   The loop termination condition uses the number of rows updated (`archived`) 
to decide whether to fetch the next batch. JDBC drivers/DBs can report fewer 
affected rows than selected rows (e.g., if some rows are updated concurrently 
or if an update sets the column to its current value), which can prematurely 
stop batching while more unarchived events still match the criteria. Consider 
basing the loop continuation on the batch size returned by `search` (e.g., 
`events.size() == limitPerQuery`) instead of the update count, or otherwise 
ensure the termination condition can’t end early.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to