8000 Document Oriented Query Partitioning Technique Implementation by pfu3tz · Pull Request #319 · sqlancer/sqlancer · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
38f1972
Introduce MongoDB to SQLancer and support creating collections
pfu3tz Jan 3, 2021
24e8897
Add data inserts and index creation for MongoDB
pfu3tz Jan 6, 2021
46d1680
Add query execution for MongoDB with lookup and project stages
pfu3tz Jan 6, 2021
53a25cf
Implement expression generation and filter query execution for MongoDB
pfu3tz Jan 7, 2021
1cd7c47
Expand expression generation to computed functions for MongoDB
pfu3tz Jan 26, 2021
147ce91
Add regular expressions as part of the randomized expression generati…
pfu3tz Jan 26, 2021
5d28044
Ignore expected errors in MongoDB queries and add expected errors for…
pfu3tz Jan 31, 2021
bd525f6
Add expected errors for computed fields in project stage MongoDB
pfu3tz Jan 31, 2021
6aa5385
Add logging of computed fields in MongoDB
pfu3tz Jan 31, 2021
6020a07
Add randomized regex options for the regular expression node in MongoDB
pfu3tz Jan 31, 2021
5897c86
Add a variation where the count is compared to the number of returned…
pfu3tz Feb 1, 2021
85d1415
Rework negation of MongoDB expression to better cover the api
pfu3tz Feb 2, 2021
498b9de
Fix MongoDB unsuccessful and successful query counts
pfu3tz Feb 2, 2021
78a67d1
Add a new way to negate a query that replaces the old placeholder
pfu3tz Feb 6, 2021
dfae8af
Introduce ArangoDB to SQLancer and support creating collection and in…
pfu3tz Feb 28, 2021
66919d4
Complete log implementation for inserts and collection creation
pfu3tz Feb 28, 2021
ec3f710
Implement ternary partitioning logic where tester for ArangoDB
pfu3tz Mar 2, 2021
acf3d34
Support adding indexes when inserting data for ArangoDB
pfu3tz Mar 2, 2021
242c245
Add support for computed functions for ArangoDB
pfu3tz Mar 2, 2021
6de066e
Enable computation values in filter expressions for ArangoDB
pfu3tz Mar 2, 2021
97f5e0b
Make query generation null safe with option in MongoDB
pfu3tz Mar 2, 2021
8e7ef81
Introduce remove/reduce oracle to SQLancer for MongoDB
pfu3tz Mar 3, 2021
b2afeef
Add Cosmos as a database configuration
pfu3tz Mar 22, 2021
e0b272d
Clean up and improve code for pull request
pfu3tz Mar 28, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,16 @@
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>com.arangodb</groupId>
<artifactId>arangodb-java-driver</artifactId>
<version>6.9.0</version>
</dependency>
</dependencies>
<reporting>
<plugins>
Expand Down
6 changes: 3 additions & 3 deletions src/sqlancer/GlobalState.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ private ExecutionTimer executePrologue(Query<?> q) throws Exception {
timer = new ExecutionTimer().start();
}
if (getOptions().printAllStatements()) {
System.out.println(q.getQueryString());
System.out.println(q.getLogString());
}
if (getOptions().logEachSelect()) {
if (logExecutionTime) {
getLogger().writeCurrentNoLineBreak(q.getQueryString());
getLogger().writeCurrentNoLineBreak(q.getLogString());
} else {
getLogger().writeCurrent(q.getQueryString());
getLogger().writeCurrent(q.getLogString());
}
}
return timer;
Expand Down
8 changes: 7 additions & 1 deletion src/sqlancer/M 4D2B ain.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@
import com.beust.jcommander.JCommander;
import com.beust.jcommander.JCommander.Builder;

import sqlancer.arangodb.ArangoDBProvider;
import sqlancer.citus.CitusProvider;
import sqlancer.clickhouse.ClickHouseProvider;
import sqlancer.cockroachdb.CockroachDBProvider;
import sqlancer.common.log.Loggable;
import sqlancer.common.query.Query;
import sqlancer.common.query.SQLancerResultSet;
import sqlancer.cosmos.CosmosProvider;
import sqlancer.duckdb.DuckDBProvider;
import sqlancer.h2.H2Provider;
import sqlancer.mariadb.MariaDBProvider;
import sqlancer.mongodb.MongoDBProvider;
import sqlancer.mysql.MySQLProvider;
import sqlancer.postgres.PostgresProvider;
import sqlancer.sqlite3.SQLite3Provider;
Expand Down Expand Up @@ -209,7 +212,7 @@ private void printState(FileWriter writer, StateToReproduce state) {
.getInfo(state.getDatabaseName(), state.getDatabaseVersion(), state.getSeedValue()).getLogString());

for (Query<?> s : state.getStatements()) {
sb.append(s.getQueryString());
sb.append(s.getLogString());
sb.append('\n');
}
try {
Expand Down Expand Up @@ -554,6 +557,9 @@ private boolean run(MainOptions options, ExecutorService execService,
providers.add(new ClickHouseProvider());
providers.add(new DuckDBProvider());
providers.add(new H2Provider());
providers.add(new MongoDBProvider());
providers.add(new CosmosProvider());
providers.add(new ArangoDBProvider());
return providers;
}

Expand Down
73 changes: 73 additions & 0 deletions src/sqlancer/arangodb/ArangoDBComparatorHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package sqlancer.arangodb;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.arangodb.entity.BaseDocument;

import sqlancer.IgnoreMeException;
import sqlancer.Main;
import sqlancer.arangodb.query.ArangoDBSelectQuery;
import sqlancer.common.query.ExpectedErrors;

public final class ArangoDBComparatorHelper {

private ArangoDBComparatorHelper() {

}

public static List<BaseDocument> getResultSetAsDocumentList(ArangoDBSelectQuery query,
ArangoDBProvider.ArangoDBGlobalState state) throws Exception {
ExpectedErrors errors = query.getExpectedErrors();
List<BaseDocument> result;
try {
query.executeAndGet(state);
Main.nrSuccessfulActions.addAndGet(1);
result = query.getResultSet();
return result;
} catch (Exception e) {
if (e instanceof IgnoreMeException) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner to put this in a separate catch block above (i.e., catch (IgnoreMeException e) { throw e; } catch (Exception e) ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. Note that I took the general structure from ComparatorHelper, but there the number of exception types is much greater and counting is not done directly in this helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After running PMD, I now remember, why I put it there. AvoidRethrowingException PMD rule does not allow this. So I just put it in front of the counter for now.

throw e;
}
Main.nrUnsuccessfulActions.addAndGet(1);
if (e.getMessage() == null) {
throw new AssertionError(query.getLogString(), e);
}
if (errors.errorIsExpected(e.getMessage())) {
throw new IgnoreMeException();
}
throw new AssertionError(query.getLogString(), e);
}

}

public static void assumeResultSetsAreEqual(List<BaseDocument> resultSet, List<BaseDocument> secondResultSet,
ArangoDBSelectQuery originalQuery) {
if (resultSet.size() != secondResultSet.size()) {
String assertionMessage = String.format("The Size of the result sets mismatch (%d and %d)!\n%s",
resultSet.size(), secondResultSet.size(), originalQuery.getLogString());
throw new AssertionError(assertionMessage);
}
Set<BaseDocument> firstHashSet = new HashSet<>(resultSet);
Set<BaseDocument> secondHashSet = new HashSet<>(secondResultSet);

if (!firstHashSet.equals(secondHashSet)) {
Set<BaseDocument> firstResultSetMisses = new HashSet<>(firstHashSet);
firstResultSetMisses.removeAll(secondHashSet);
Set<BaseDocument> secondResultSetMisses = new HashSet<>(secondHashSet);
secondResultSetMisses.removeAll(firstHashSet);
StringBuilder firstMisses = new StringBuilder();
for (BaseDocument document : firstResultSetMisses) {
firstMisses.append(document).append(" ");
}
StringBuilder secondMisses = new StringBuilder();
for (BaseDocument document : secondResultSetMisses) {
secondMisses.append(document).append(" ");
}
String assertMessage = String.format("The Content of the result sets mismatch!\n %s \n %s\n %s",
firstMisses.toString(), secondMisses.toString(), originalQuery.getLogString());
throw new AssertionError(assertMessage);
}
}
}
31 changes: 31 additions & 0 deletions src/sqlancer/arangodb/ArangoDBConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package sqlancer.arangodb;

import com.arangodb.ArangoDB;
import com.arangodb.ArangoDatabase;

import sqlancer.SQLancerDBConnection;

public class ArangoDBConnection implements SQLancerDBConnection {

private final ArangoDB client;
private final ArangoDatabase database;

public ArangoDBConnection(ArangoDB client, ArangoDatabase database) {
this.client = client;
this.database = database;
}

@Override
public String getDatabaseVersion() throws Exception {
return client.getVersion().getVersion();
}

@Override
public void close() throws Exception {
client.shutdown();
}

public ArangoDatabase getDatabase() {
return database;
}
}
40 changes: 40 additions & 0 deletions src/sqlancer/arangodb/ArangoDBLoggableFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package sqlancer.arangodb;

import java.util.Arrays;

import sqlancer.common.log.Loggable;
import sqlancer.common.log.LoggableFactory;
import sqlancer.common.log.LoggedString;
import sqlancer.common.query.Query;

public class ArangoDBLoggableFactory extends LoggableFactory {
@Override
protected Loggable createLoggable(String input, String suffix) {
return new LoggedString(input + suffix);
}

@Override
public Query<?> getQueryForStateToReproduce(String queryString) {
throw new UnsupportedOperationException();
}

@Override
public Query<?> commentOutQuery(Query<?> query) {
throw new UnsupportedOperationException();
}

@Override
protected Loggable infoToLoggable(String time, String databaseName, String databaseVersion, long seedValue) {
StringBuilder sb = new StringBuilder();
sb.append("// Time: ").append(time).append("\n");
sb.append("// Database: ").append(databaseName).append("\n");
sb.append("// Database version: ").append(databaseVersion).append("\n");
sb.append("// seed value: ").append(seedValue).append("\n");
return new LoggedString(sb.toString());
}

@Override
public Loggable convertStacktraceToLoggable(Throwable throwable) {
return new LoggedString(Arrays.toString(throwable.getStackTrace()) + "\n" + throwable.getMessage());
}
}
44 changes: 44 additions & 0 deletions src/sqlancer/arangodb/ArangoDBOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package sqlancer.arangodb;

import static sqlancer.arangodb.ArangoDBOptions.ArangoDBOracleFactory.QUERY_PARTITIONING;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.beust.jcommander.Parameter;

import sqlancer.DBMSSpecificOptions;
import sqlancer.OracleFactory;
import sqlancer.arangodb.test.ArangoDBQueryPartitioningWhereTester;
import sqlancer.common.oracle.CompositeTestOracle;
import sqlancer.common.oracle.TestOracle;

public class ArangoDBOptions implements DBMSSpecificOptions<ArangoDBOptions.ArangoDBOracleFactory> {

@Parameter(names = "--oracle")
public List<ArangoDBOracleFactory> oracles = Arrays.asList(QUERY_PARTITIONING);

@Parameter(names = "--test-random-type-inserts", description = "Insert random types instead of schema types.")
public boolean testRandomTypeInserts;

@Parameter(names = "--max-number-indexes", description = "The maximum number of indexes used.", arity = 1)
public int maxNumberIndexes = 15;

@Override
public List<ArangoDBOracleFactory> getTestOracleFactory() {
return oracles;
}

public enum ArangoDBOracleFactory implements OracleFactory<ArangoDBProvider.ArangoDBGlobalState> {
QUERY_PARTITIONING {
@Override
public TestOracle create(ArangoDBProvider.ArangoDBGlobalState globalState) throws Exception {
List<TestOracle> oracles = new ArrayList<>();
oracles.add(new ArangoDBQueryPartitioningWhereTester(globalState));
return new CompositeTestOracle(oracles, globalState);
}
}

}
}
134 changes: 134 additions & 0 deletions src/sqlancer/arangodb/ArangoDBProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package sqlancer.arangodb;

import java.util.ArrayList;
import java.util.List;

import com.arangodb.ArangoDB;
import com.arangodb.ArangoDatabase;

import sqlancer.AbstractAction;
import sqlancer.ExecutionTimer;
import sqlancer.GlobalState;
import sqlancer.IgnoreMeException;
import sqlancer.ProviderAdapter;
import sqlancer.Randomly;
import sqlancer.StatementExecutor;
import sqlancer.arangodb.gen.ArangoDBCreateIndexGenerator;
import sqlancer.arangodb.gen.ArangoDBInsertGenerator;
import sqlancer.arangodb.gen.ArangoDBTableGenerator;
import sqlancer.common.log.LoggableFactory;
import sqlancer.common.query.Query;

public class ArangoDBProvider
extends ProviderAdapter<ArangoDBProvider.ArangoDBGlobalState, ArangoDBOptions, ArangoDBConnection> {

public ArangoDBProvider() {
super(ArangoDBGlobalState.class, ArangoDBOptions.class);
}

enum Action implements AbstractAction<ArangoDBGlobalState> {
INSERT(ArangoDBInsertGenerator::getQuery), CREATE_INDEX(ArangoDBCreateIndexGenerator::getQuery);

private final ArangoDBQueryProvider<ArangoDBGlobalState> queryProvider;

Action(ArangoDBQueryProvider<ArangoDBGlobalState> queryProvider) {
this.queryProvider = queryProvider;
}

@Override
public Query<?> getQuery(ArangoDBGlobalState globalState) throws Exception {
return queryProvider.getQuery(globalState);
}
}

private static int mapActions(ArangoDBGlobalState globalState, Action a) {
Randomly r = globalState.getRandomly();
switch (a) {
case INSERT:
return r.getInteger(0, globalState.getOptions().getMaxNumberInserts());
case CREATE_INDEX:
return r.getInteger(0, globalState.getDmbsSpecificOptions().maxNumberIndexes);
default:
throw new AssertionError(a);
}
}

public static class ArangoDBGlobalState extends GlobalState<ArangoDBOptions, ArangoDBSchema, ArangoDBConnection> {

private final List<ArangoDBSchema.ArangoDBTable> schemaTables = new ArrayList<>();

public void addTable(ArangoDBSchema.ArangoDBTable table) {
schemaTables.add(table);
}

@Override
protected void executeEpilogue(Query<?> q, boolean success, ExecutionTimer timer) throws Exception {
boolean logExecutionTime = getOptions().logExecutionTime();
if (suc BDAD cess && getOptions().printSucceedingStatements()) {
System.out.println(q.getLogString());
}
if (logExecutionTime) {
getLogger().writeCurrent("//" + timer.end().asString());
}
if (q.couldAffectSchema()) {
updateSchema();
}
}

@Override
protected ArangoDBSchema readSchema() throws Exception {
return new ArangoDBSchema(schemaTables);
}
}

@Override
protected void checkViewsAreValid(ArangoDBGlobalState globalState) {

}

@Override
public void generateDatabase(ArangoDBGlobalState globalState) throws Exception {
for (int i = 0; i < Randomly.fromOptions(4, 5, 6); i++) {
boolean success;
do {
ArangoDBQueryAdapter queryAdapter = new ArangoDBTableGenerator().getQuery(globalState);
success = globalState.executeStatement(queryAdapter);
} while (!success);
}
StatementExecutor<ArangoDBGlobalState, Action> se = new StatementExecutor<>(globalState, Action.values(),
ArangoDBProvider::mapActions, (q) -> {
if (globalState.getSchema().getDatabaseTables().isEmpty()) {
throw new IgnoreMeException();
}
});
se.executeStatements();
}

@Override
public ArangoDBConnection createDatabase(ArangoDBGlobalState globalState) throws Exception {
ArangoDB arangoDB = new ArangoDB.Builder().user(globalState.getOptions().getUserName())
.password(globalState.getOptions().getPassword()).build();
ArangoDatabase database = arangoDB.db(globalState.getDatabaseName());
try {
database.drop();
// When the database does not exist, an ArangoDB exception is thrown. Since we are not sure
// if this is the first time the database is used, the simplest is dropping it and ignoring
// the exception.
} catch (Exception ignored) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add a comment here to explain why the exception is ignored.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment. It is just simpler to ignore the exception than checking if the database already exists in the database. For some reason in arangodb it throws an exception if the database does not exist.

In MongoDB for instance I can drop databases without worrying about the existance...

}
arangoDB.createDatabase(globalState.getDatabaseName());
database = arangoDB.db(globalState.getDatabaseName());
return new ArangoDBConnection(arangoDB, database);
}

@Override
public String getDBMSName() {
return "arangodb";
}

@Override
public LoggableFactory getLoggableFactory() {
return new ArangoDBLoggableFactory();
}
}
Loading
0