Skip to content

Commit e22f842

Browse files
Allow limit queries without random ordering (#12598)
1 parent 18d6659 commit e22f842

File tree

6 files changed

+143
-6
lines changed

6 files changed

+143
-6
lines changed

engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1348,7 +1348,7 @@ public HostVO findAnyStateHypervisorHostInCluster(long clusterId) {
13481348
SearchCriteria<HostVO> sc = TypeClusterStatusSearch.create();
13491349
sc.setParameters("type", Host.Type.Routing);
13501350
sc.setParameters("cluster", clusterId);
1351-
List<HostVO> list = listBy(sc, new Filter(1));
1351+
List<HostVO> list = listBy(sc, new Filter(1, true));
13521352
return list.isEmpty() ? null : list.get(0);
13531353
}
13541354

engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public ImageStoreVO findOneByZoneAndProtocol(long dataCenterId, String protocol)
202202
sc.setParameters("dataCenterId", dataCenterId);
203203
sc.setParameters("protocol", protocol);
204204
sc.setParameters("role", DataStoreRole.Image);
205-
Filter filter = new Filter(1);
205+
Filter filter = new Filter(1, true);
206206
List<ImageStoreVO> results = listBy(sc, filter);
207207
return results.size() == 0 ? null : results.get(0);
208208
}

framework/db/src/main/java/com/cloud/utils/db/Filter.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,18 @@ public Filter(Class<?> clazz, String field, boolean ascending) {
5757
}
5858

5959
public Filter(long limit) {
60-
_orderBy = " ORDER BY RAND()";
60+
this(limit, false);
61+
}
62+
63+
/**
64+
* Constructor for creating a filter with random ordering
65+
* @param limit the maximum number of results to return
66+
* @param randomize if true, orders results randomly
67+
*/
68+
public Filter(long limit, boolean randomize) {
69+
if (randomize) {
70+
_orderBy = " ORDER BY RAND()" ;
71+
}
6172
_limit = limit;
6273
}
6374

framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public List<T> lockRows(final SearchCriteria<T> sc, final Filter filter, final b
347347
@Override
348348
@DB()
349349
public T lockOneRandomRow(final SearchCriteria<T> sc, final boolean exclusive) {
350-
final Filter filter = new Filter(1);
350+
final Filter filter = new Filter(1, true);
351351
final List<T> beans = search(sc, filter, exclusive, true);
352352
return beans.isEmpty() ? null : beans.get(0);
353353
}
@@ -927,7 +927,7 @@ public Class<T> getEntityBeanType() {
927927

928928
@DB()
929929
protected T findOneIncludingRemovedBy(final SearchCriteria<T> sc) {
930-
Filter filter = new Filter(1);
930+
Filter filter = new Filter(1, true);
931931
List<T> results = searchIncludingRemoved(sc, filter, null, false);
932932
assert results.size() <= 1 : "Didn't the limiting worked?";
933933
return results.size() == 0 ? null : results.get(0);
@@ -1324,7 +1324,7 @@ public int batchExpunge(final SearchCriteria<T> sc, final Long batchSize) {
13241324
Filter filter = null;
13251325
final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L);
13261326
if (batchSizeFinal > 0) {
1327-
filter = new Filter(null, batchSizeFinal);
1327+
filter = new Filter(batchSizeFinal);
13281328
}
13291329
int expunged = 0;
13301330
int currentExpunged = 0;

framework/db/src/test/java/com/cloud/utils/db/FilterTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,62 @@ public void testAddOrderBy() {
4141
Assert.assertTrue(filter.getOrderBy().split(",").length == 3);
4242
Assert.assertTrue(filter.getOrderBy().split(",")[2].trim().toLowerCase().equals("test.fld_int asc"));
4343
}
44+
45+
46+
@Test
47+
public void testFilterWithLimitOnly() {
48+
Filter filter = new Filter(5);
49+
50+
Assert.assertEquals(Long.valueOf(5), filter.getLimit());
51+
Assert.assertNull(filter.getOrderBy());
52+
Assert.assertNull(filter.getOffset());
53+
}
54+
55+
@Test
56+
public void testFilterWithLimitAndRandomizeFalse() {
57+
Filter filter = new Filter(10, false);
58+
59+
Assert.assertEquals(Long.valueOf(10), filter.getLimit());
60+
Assert.assertNull(filter.getOrderBy());
61+
Assert.assertNull(filter.getOffset());
62+
}
63+
64+
@Test
65+
public void testFilterWithLimitAndRandomizeTrue() {
66+
Filter filter = new Filter(3, true);
67+
68+
Assert.assertNull(filter.getLimit());
69+
Assert.assertNotNull(filter.getOrderBy());
70+
Assert.assertTrue(filter.getOrderBy().contains("ORDER BY RAND()"));
71+
Assert.assertTrue(filter.getOrderBy().contains("LIMIT 3"));
72+
Assert.assertEquals(" ORDER BY RAND() LIMIT 3", filter.getOrderBy());
73+
}
74+
75+
@Test
76+
public void testFilterRandomizeWithDifferentLimits() {
77+
Filter filter1 = new Filter(1, true);
78+
Filter filter10 = new Filter(10, true);
79+
Filter filter100 = new Filter(100, true);
80+
81+
Assert.assertEquals(" ORDER BY RAND() LIMIT 1", filter1.getOrderBy());
82+
Assert.assertEquals(" ORDER BY RAND() LIMIT 10", filter10.getOrderBy());
83+
Assert.assertEquals(" ORDER BY RAND() LIMIT 100", filter100.getOrderBy());
84+
}
85+
86+
@Test
87+
public void testFilterConstructorBackwardsCompatibility() {
88+
// Test that Filter(long) behaves differently now (no ORDER BY RAND())
89+
// compared to Filter(long, true) which preserves old behavior
90+
Filter simpleLimitFilter = new Filter(1);
91+
Filter randomFilter = new Filter(1, true);
92+
93+
// Simple limit filter should just set limit
94+
Assert.assertEquals(Long.valueOf(1), simpleLimitFilter.getLimit());
95+
Assert.assertNull(simpleLimitFilter.getOrderBy());
96+
97+
// Random filter should set orderBy with RAND()
98+
Assert.assertNull(randomFilter.getLimit());
99+
Assert.assertNotNull(randomFilter.getOrderBy());
100+
Assert.assertTrue(randomFilter.getOrderBy().contains("RAND()"));
101+
}
44102
}

framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.sql.SQLException;
2121
import java.util.ArrayList;
2222
import java.util.Collection;
23+
import java.util.List;
2324

2425
import org.junit.Assert;
2526
import org.junit.Before;
@@ -263,4 +264,71 @@ public void multiJoinSameTableTest() {
263264
" INNER JOIN tableA tableA2Alias ON tableC.column3=tableA2Alias.column2 " +
264265
" INNER JOIN tableA tableA3Alias ON tableD.column4=tableA3Alias.column3 AND tableD.column5=? ", joinString.toString());
265266
}
267+
268+
269+
@Test
270+
public void testLockOneRandomRowUsesRandomFilter() {
271+
// Create a mock DAO to test lockOneRandomRow behavior
272+
GenericDaoBase<DbTestVO, Long> testDao = Mockito.mock(GenericDaoBase.class);
273+
274+
// Capture the filter passed to the search method
275+
final Filter[] capturedFilter = new Filter[1];
276+
277+
Mockito.when(testDao.lockOneRandomRow(Mockito.any(SearchCriteria.class), Mockito.anyBoolean()))
278+
.thenCallRealMethod();
279+
280+
Mockito.when(testDao.search(Mockito.any(SearchCriteria.class), Mockito.any(Filter.class),
281+
Mockito.anyBoolean(), Mockito.anyBoolean()))
282+
.thenAnswer(invocation -> {
283+
capturedFilter[0] = invocation.getArgument(1);
284+
return new ArrayList<DbTestVO>();
285+
});
286+
287+
SearchCriteria<DbTestVO> sc = Mockito.mock(SearchCriteria.class);
288+
testDao.lockOneRandomRow(sc, true);
289+
290+
// Verify that the filter uses random ordering
291+
Assert.assertNotNull(capturedFilter[0]);
292+
Assert.assertNotNull(capturedFilter[0].getOrderBy());
293+
Assert.assertTrue(capturedFilter[0].getOrderBy().contains("ORDER BY RAND()"));
294+
Assert.assertTrue(capturedFilter[0].getOrderBy().contains("LIMIT 1"));
295+
}
296+
297+
@Test
298+
public void testLockOneRandomRowReturnsNullOnEmptyResult() {
299+
GenericDaoBase<DbTestVO, Long> testDao = Mockito.mock(GenericDaoBase.class);
300+
301+
Mockito.when(testDao.lockOneRandomRow(Mockito.any(SearchCriteria.class), Mockito.anyBoolean()))
302+
.thenCallRealMethod();
303+
304+
Mockito.when(testDao.search(Mockito.any(SearchCriteria.class), Mockito.any(Filter.class),
305+
Mockito.anyBoolean(), Mockito.anyBoolean()))
306+
.thenReturn(new ArrayList<DbTestVO>());
307+
308+
SearchCriteria<DbTestVO> sc = Mockito.mock(SearchCriteria.class);
309+
DbTestVO result = testDao.lockOneRandomRow(sc, true);
310+
311+
Assert.assertNull(result);
312+
}
313+
314+
@Test
315+
public void testLockOneRandomRowReturnsFirstElement() {
316+
GenericDaoBase<DbTestVO, Long> testDao = Mockito.mock(GenericDaoBase.class);
317+
DbTestVO expectedResult = new DbTestVO();
318+
List<DbTestVO> resultList = new ArrayList<>();
319+
resultList.add(expectedResult);
320+
321+
Mockito.when(testDao.lockOneRandomRow(Mockito.any(SearchCriteria.class), Mockito.anyBoolean()))
322+
.thenCallRealMethod();
323+
324+
Mockito.when(testDao.search(Mockito.any(SearchCriteria.class), Mockito.any(Filter.class),
325+
Mockito.anyBoolean(), Mockito.anyBoolean()))
326+
.thenReturn(resultList);
327+
328+
SearchCriteria<DbTestVO> sc = Mockito.mock(SearchCriteria.class);
329+
DbTestVO result = testDao.lockOneRandomRow(sc, true);
330+
331+
Assert.assertNotNull(result);
332+
Assert.assertEquals(expectedResult, result);
333+
}
266334
}

0 commit comments

Comments
 (0)