-
Notifications
You must be signed in to change notification settings - Fork 397
Document Oriented Query Partitioning Technique Implementation #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
38f1972
24e8897
46d1680
53a25cf
1cd7c47
147ce91
5d28044
bd525f6
6aa5385
6020a07
5897c86
85d1415
498b9de
78a67d1
dfae8af
66919d4
ec3f710
acf3d34
242c245
6de066e
97f5e0b
8e7ef81
b2afeef
e0b272d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) { | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
| 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; | ||
| } | ||
| } |
| 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()); | ||
| } | ||
| } |
| 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); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| } |
| 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) { | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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) ...There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.