Skip to content

Commit 8e7ef81

Browse files
committed
Introduce remove/reduce oracle to SQLancer for MongoDB
This oracle generates a random query, executes it and if the result set is not empty, chooses a document at random and removes it from the collection. The query is executed again to check if the document is really removed and at the end a new document is generated and inserted to make sure that the dataset is not decreasing in size.
1 parent 97f5e0b commit 8e7ef81

5 files changed

Lines changed: 237 additions & 1 deletion

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package sqlancer.common.oracle;
2+
3+
import sqlancer.GlobalState;
4+
import sqlancer.common.gen.ExpressionGenerator;
5+
6+
public abstract class RemoveReduceOracleBase<E, S extends GlobalState<?, ?, ?>> implements TestOracle {
7+
8+
protected E predicate;
9+
10+
protected final S state;
11+
12+
protected RemoveReduceOracleBase(S state) {
13+
this.state = state;
14+
}
15+
16+
protected void initializeRemoveReduceOracle() {
17+
ExpressionGenerator<E> gen = getGen();
18+
if (gen == null) {
19+
throw new IllegalStateException();
20+
}
21+
predicate = gen.generatePredicate();
22+
if (predicate == null) {
23+
throw new IllegalStateException();
24+
}
25+
}
26+
27+
protected abstract ExpressionGenerator<E> getGen();
28+
29+
}

src/sqlancer/mongodb/MongoDBOptions.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sqlancer.mongodb;
22

33
import static sqlancer.mongodb.MongoDBOptions.MongoDBOracleFactory.QUERY_PARTITIONING;
4+
import static sqlancer.mongodb.MongoDBOptions.MongoDBOracleFactory.REMOVE_REDUCE;
45

56
import java.util.ArrayList;
67
import java.util.Arrays;
@@ -13,6 +14,7 @@
1314
import sqlancer.common.oracle.CompositeTestOracle;
1415
import sqlancer.common.oracle.TestOracle;
1516
import sqlancer.mongodb.test.MongoDBQueryPartitioningWhereTester;
17+
import sqlancer.mongodb.test.MongoDBRemoveReduceTester;
1618

1719
public class MongoDBOptions implements DBMSSpecificOptions<MongoDBOptions.MongoDBOracleFactory> {
1820

@@ -41,7 +43,7 @@ public class MongoDBOptions implements DBMSSpecificOptions<MongoDBOptions.MongoD
4143
public boolean nullSafety;
4244

4345
@Parameter(names = "--oracle")
44-
public List<MongoDBOracleFactory> oracles = Arrays.asList(QUERY_PARTITIONING);
46+
public List<MongoDBOracleFactory> oracles = Arrays.asList(QUERY_PARTITIONING, REMOVE_REDUCE);
4547

4648
@Override
4749
public List<MongoDBOracleFactory> getTestOracleFactory() {
@@ -56,6 +58,14 @@ public TestOracle create(MongoDBProvider.MongoDBGlobalState globalState) throws
5658
oracles.add(new MongoDBQueryPartitioningWhereTester(globalState));
5759
return new CompositeTestOracle(oracles, globalState);
5860
}
61+
},
62+
REMOVE_REDUCE {
63+
@Override
64+
public TestOracle create(MongoDBProvider.MongoDBGlobalState globalState) throws Exception {
65+
List<TestOracle> oracles = new ArrayList<>();
66+
oracles.add(new MongoDBRemoveReduceTester(globalState));
67+
return new CompositeTestOracle(oracles, globalState);
68+
}
5969
}
6070
}
6171
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package sqlancer.mongodb.query;
2+
3+
import org.bson.Document;
4+
import org.bson.types.ObjectId;
5+
6+
import com.mongodb.client.result.DeleteResult;
7+
8+
import sqlancer.GlobalState;
9+
import sqlancer.Main;
10+
import sqlancer.common.query.ExpectedErrors;
11+
import sqlancer.mongodb.MongoDBConnection;
12+
import sqlancer.mongodb.MongoDBQueryAdapter;
13+
import sqlancer.mongodb.MongoDBSchema;
14+
15+
public class MongoDBRemoveQuery extends MongoDBQueryAdapter {
16+
17+
private final String objectId;
18+
private final MongoDBSchema.MongoDBTable table;
19+
20+
public MongoDBRemoveQuery(MongoDBSchema.MongoDBTable table, String objectId) {
21+
this.objectId = objectId;
22+
this.table = table;
23+
}
24+
25+
@Override
26+
public boolean couldAffectSchema() {
27+
return true;
28+
}
29+
30+
@Override
31+
public <G extends GlobalState<?, ?, MongoDBConnection>> boolean execute(G globalState, String... fills)
32+
throws Exception {
33+
try {
34+
DeleteResult result = globalState.getConnection().getDatabase().getCollection(table.getName())
35+
.deleteOne(new Document("_id", new ObjectId(objectId)));
36+
if (result.wasAcknowledged()) {
37+
Main.nrSuccessfulActions.addAndGet(1);
38+
} else {
39+
Main.nrUnsuccessfulActions.addAndGet(1);
40+
}
41+
return result.wasAcknowledged();
42+
} catch (Exception e) {
43+
Main.nrUnsuccessfulActions.addAndGet(1);
44+
return false;
45+
}
46+
}
47+
48+
@Override
49+
public ExpectedErrors getExpectedErrors() {
50+
return new ExpectedErrors();
51+
}
52+
53+
@Override
54+
public String getLogString() {
55+
StringBuilder stringBuilder = new StringBuilder();
56+
stringBuilder.append("db.").append(table.getName()).append(".remove({'_id': '").append(objectId).append("'})");
57+
return stringBuilder.toString();
58+
}
59+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package sqlancer.mongodb.test;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import sqlancer.Randomly;
7+
import sqlancer.common.ast.newast.Node;
8+
import sqlancer.common.gen.ExpressionGenerator;
9+
import sqlancer.common.oracle.RemoveReduceOracleBase;
10+
import sqlancer.common.oracle.TestOracle;
11+
import sqlancer.mongodb.MongoDBProvider;
12+
import sqlancer.mongodb.MongoDBSchema;
13+
import sqlancer.mongodb.ast.MongoDBExpression;
14+
import sqlancer.mongodb.ast.MongoDBSelect;
15+
import sqlancer.mongodb.gen.MongoDBComputedExpressionGenerator;
16+
import sqlancer.mongodb.gen.MongoDBMatchExpressionGenerator;
17+
18+
public class MongoDBRemoveReduceBase extends
19+
RemoveReduceOracleBase<Node<MongoDBExpression>, MongoDBProvider.MongoDBGlobalState> implements TestOracle {
20+
21+
protected MongoDBSchema schema;
22+
protected MongoDBSchema.MongoDBTables targetTables;
23+
protected MongoDBSchema.MongoDBTable mainTable;
24+
protected List<MongoDBColumnTestReference> targetColumns;
25+
protected MongoDBMatchExpressionGenerator expressionGenerator;
26+
protected MongoDBSelect<MongoDBExpression> select;
27+
28+
protected MongoDBRemoveReduceBase(MongoDBProvider.MongoDBGlobalState state) {
29+
super(state);
30+
}
31+
32+
@Override
33+
public void check() throws Exception {
34+
schema = state.getSchema();
35+
targetTables = schema.getRandomTableNonEmptyTables();
36+
mainTable = targetTables.getTables().get(0);
37+
generateTargetColumns();
38+
expressionGenerator = new MongoDBMatchExpressionGenerator(state).setColumns(targetColumns);
39+
initializeRemoveReduceOracle();
40+
select = new MongoDBSelect<>(mainTable.getName(), targetColumns.get(0));
41+
select.setProjectionList(targetColumns);
42+
if (Randomly.getBooleanWithRatherLowProbability()) {
43+
select.setLookupList(targetColumns);
44+
} else {
45+
select.setLookupList(Randomly.nonEmptySubset(targetColumns));
46+
}
47+
if (state.getDmbsSpecificOptions().testComputedValues) {
48+
generateComputedColumns();
49+
}
50+
}
51+
52+
private void generateTargetColumns() {
53+
targetColumns = new ArrayList<>();
54+
for (MongoDBSchema.MongoDBColumn c : mainTable.getColumns()) {
55+
targetColumns.add(new MongoDBColumnTestReference(c, true));
56+
}
57+
List<MongoDBColumnTestReference> joinsOtherTables = new ArrayList<>();
58+
if (!state.getDmbsSpecificOptions().nullSafety) {
59+
for (int i = 1; i < targetTables.getTables().size(); i++) {
60+
MongoDBSchema.MongoDBTable procTable = targetTables.getTables().get(i);
61+
for (MongoDBSchema.MongoDBColumn c : procTable.getColumns()) {
62+
joinsOtherTables.add(new MongoDBColumnTestReference(c, false));
63+
}
64+
}
65+
}
66+
if (!joinsOtherTables.isEmpty()) {
67+
int randNumber = state.getRandomly().getInteger(1, Math.min(joinsOtherTables.size(), 4));
68+
List<MongoDBColumnTestReference> subsetJoinsOtherTables = Randomly.nonEmptySubset(joinsOtherTables,
69+
randNumber);
70+
targetColumns.addAll(subsetJoinsOtherTables);
71+
}
72+
}
73+
74+
private void generateComputedColumns() {
75+
List<Node<MongoDBExpression>> computedColumns = new ArrayList<>();
76+
int numberComputedColumns = state.getRandomly().getInteger(1, 4);
77+
MongoDBComputedExpressionGenerator generator = new MongoDBComputedExpressionGenerator(state)
78+
.setColumns(targetColumns);
79+
for (int i = 0; i < numberComputedColumns; i++) {
80+
computedColumns.add(generator.generateExpression());
81+
}
82+
select.setComputedClause(computedColumns);
83+
}
84+
85+
@Override
86+
protected ExpressionGenerator<Node<MongoDBExpression>> getGen() {
87+
return expressionGenerator;
88+
}
89+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package sqlancer.mongodb.test;
2+
3+
import static sqlancer.mongodb.MongoDBComparatorHelper.getResultSetAsDocumentList;
4+
5+
import java.util.List;
6+
7+
import org.bson.Document;
8+
9+
import sqlancer.Randomly;
10+
import sqlancer.mongodb.MongoDBProvider;
11+
import sqlancer.mongodb.MongoDBQueryAdapter;
12+
import sqlancer.mongodb.gen.MongoDBInsertGenerator;
13+
import sqlancer.mongodb.query.MongoDBRemoveQuery;
14+
import sqlancer.mongodb.query.MongoDBSelectQuery;
15+
16+
public class MongoDBRemoveReduceTester extends MongoDBRemoveReduceBase {
17+
public MongoDBRemoveReduceTester(MongoDBProvider.MongoDBGlobalState state) {
18+
super(state);
19+
}
20+
21+
@Override
22+
public void check() throws Exception {
23+
super.check();
24+
25+
select.setWithCountClause(false);
26+
27+
select.setFilterClause(predicate);
28+
MongoDBSelectQuery selectQuery = new MongoDBSelectQuery(select);
29+
List<Document> firstResultSet = getResultSetAsDocumentList(selectQuery, state);
30+
if (firstResultSet == null || firstResultSet.isEmpty()) {
31+
return;
32+
}
33+
34+
Document documentToRemove = Randomly.fromList(firstResultSet);
35+
MongoDBRemoveQuery removeQuery = new MongoDBRemoveQuery(mainTable, documentToRemove.get("_id").toString());
36+
state.executeStatement(removeQuery);
37+
38+
selectQuery = new MongoDBSelectQuery(select);
39+
List<Document> secondResultSet = getResultSetAsDocumentList(selectQuery, state);
40+
41+
MongoDBQueryAdapter insertQuery = MongoDBInsertGenerator.getQuery(state);
42+
state.executeStatement(insertQuery);
43+
44+
if (secondResultSet.size() + 1 != firstResultSet.size()) {
45+
String assertMessage = "The Result Sizes mismatches!";
46+
throw new AssertionError(assertMessage);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)