Skip to content
25 changes: 19 additions & 6 deletions src/sqlancer/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -542,12 +542,7 @@ public void run() throws Exception {
finalStatements.addAll(stateToRepro.getStatements());
finalStatements.addAll(oracleQueryStatements);
stateToRepro.setStatements(finalStatements);
String bugInformation = reproducer.getBugInformation();
if (bugInformation != null) {
for (String line : bugInformation.split(System.lineSeparator())) {
stateToRepro.logStatement(line);
}
}
logBugInformation(reproducer.getBugInformation());

StateLogger reduceLogger = newGlobalState.getLogger();
if (reduceLogger.reduceFileWriter != null) {
Expand All @@ -560,10 +555,28 @@ public void run() throws Exception {
}

throw new AssertionError("Found a potential bug, please check reducer log for detail.");
} else if (reproducer != null) {
// An oracle that supplies a reproducer does not propagate the AssertionError describing the bug it
// found; it hands back the reproducer instead (see ProviderAdapter#generateAndTestDatabase). We
// must report the bug here (throw AssertionError) after logging what the reproducer knows
// about the bug.
logBugInformation(reproducer.getBugInformation());
throw new AssertionError("Found a potential bug, please check log for detail.");
}
}
}

// Appends the reproducer's account of the bug to the test case, one statement per line, so that it is logged
// after the statements that set the bug up.
private void logBugInformation(String bugInformation) {
if (bugInformation == null) {
return;
}
for (String line : bugInformation.split(System.lineSeparator())) {
stateToRepro.logStatement(line);
}
}

private G getInitializedGlobalState(long seed) {
G state = createGlobalState();
stateToRepro = provider.getStateToReproduce(databaseName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,15 +425,17 @@ public List<CockroachDBExpression> getTableRefs() {
@Override
public String generateOptimizedQueryString(CockroachDBSelect select, CockroachDBExpression whereCondition,
boolean shouldUseAggregate) {
CockroachDBColumn c = new CockroachDBColumn("COUNT(*)", null, false, false);
select.setWhereClause(whereCondition);
if (shouldUseAggregate) {
CockroachDBAggregate aggr = new CockroachDBAggregate(CockroachDBAggregateFunction.COUNT,
List.of(new CockroachDBColumnReference(new CockroachDBColumn("*",
new CockroachDBCompositeDataType(CockroachDBDataType.INT, 0), false, false))));
select.setFetchColumns(List.of(aggr));
} else {
select.setFetchColumns(List.of(new CockroachDBColumnReference(c)));
// The rows must be fetched rather than counted here: the oracle counts the rows this query returns, so
// projecting an aggregate would make it report one row whatever the predicate matches.
select.setFetchColumns(
List.of(new CockroachDBColumnReference(new CockroachDBColumn("*", null, false, false))));
if (Randomly.getBooleanWithRatherLowProbability()) {
select.setOrderByClauses(getOrderingTerms());
}
Expand Down
348 changes: 289 additions & 59 deletions src/sqlancer/common/oracle/EETDMLOracle.java

Large diffs are not rendered by default.

27 changes: 4 additions & 23 deletions src/sqlancer/common/oracle/EETOracle.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,38 +132,19 @@ public void applyTransformationSites(Set<Integer> enabledSites, Set<Integer> con
int offset = 0;
for (int i = 0; i < fetchColumns.size(); i++) {
replayedFetchColumns.add(transformer.replay(fetchColumns.get(i), false, fetchColumnRecords.get(i),
directives(enabledSites, constantConditionSites, copiedDeadBranchSites, offset)));
EETTransformer.SiteDirectives.forSites(enabledSites, constantConditionSites,
copiedDeadBranchSites, offset)));
offset += fetchColumnRecords.get(i).getSiteCount();
}
E replayedWhereClause = transformer.replay(whereClause, true, whereClauseRecord,
directives(enabledSites, constantConditionSites, copiedDeadBranchSites, offset));
EETTransformer.SiteDirectives.forSites(enabledSites, constantConditionSites,
copiedDeadBranchSites, offset));
select.setFetchColumns(replayedFetchColumns);
select.setWhereClause(replayedWhereClause);
return select.asString();
});
}

// Translates the global-index site sets into a record-local directives view starting at the given offset.
private EETTransformer.SiteDirectives directives(Set<Integer> enabledSites, Set<Integer> constantConditionSites,
Set<Integer> copiedDeadBranchSites, int offset) {
return new EETTransformer.SiteDirectives() {
@Override
public boolean isEnabled(int site) {
return enabledSites.contains(offset + site);
}

@Override
public boolean useConstantCondition(int site) {
return constantConditionSites.contains(offset + site);
}

@Override
public boolean useCopiedDeadBranch(int site) {
return copiedDeadBranchSites.contains(offset + site);
}
};
}

@Override
protected List<String> evaluateOriginal(G globalState) throws SQLException {
// Re-execute against the current (reduced) database instead of comparing against a cached result set,
Expand Down
37 changes: 37 additions & 0 deletions src/sqlancer/common/oracle/EETTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,43 @@ public interface SiteDirectives {
* @return {@code true} if the site's dead branch is replaced by a copy of the live expression
*/
boolean useCopiedDeadBranch(int site);

/**
* Builds a record-local view of three global-index site sets: a record-local site {@code s} maps to the global
* index {@code offset + s}. This lets a reproducer whose transformed query is built from several records assign
* each record a contiguous block of global site indices and translate the global sets into the per-record
* directives {@link #replay} consults.
*
* @param enabledSites
* the global indices of the enabled sites
* @param constantConditionSites
* the global indices of the sites whose condition is rendered as a literal constant
* @param copiedDeadBranchSites
* the global indices of the sites whose dead branch is replaced by a copy of the live expression
* @param offset
* the global index of this record's first site
*
* @return the record-local directives view
*/
static SiteDirectives forSites(Set<Integer> enabledSites, Set<Integer> constantConditionSites,
Set<Integer> copiedDeadBranchSites, int offset) {
return new SiteDirectives() {
@Override
public boolean isEnabled(int site) {
return enabledSites.contains(offset + site);
}

@Override
public boolean useConstantCondition(int site) {
return constantConditionSites.contains(offset + site);
}

@Override
public boolean useCopiedDeadBranch(int site) {
return copiedDeadBranchSites.contains(offset + site);
}
};
}
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/sqlancer/hsqldb/gen/HSQLDBExpressionGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import sqlancer.hsqldb.HSQLDBSchema;
import sqlancer.hsqldb.HSQLDBSchema.HSQLDBColumn;
import sqlancer.hsqldb.HSQLDBSchema.HSQLDBTable;
import sqlancer.hsqldb.HSQLDBToStringVisitor;
import sqlancer.hsqldb.ast.HSQLDBBinaryOperation;
import sqlancer.hsqldb.ast.HSQLDBColumnReference;
import sqlancer.hsqldb.ast.HSQLDBConstant;
Expand Down Expand Up @@ -285,7 +286,13 @@ public String generateOptimizedQueryString(HSQLDBSelect select, HSQLDBExpression

@Override
public String generateUnoptimizedQueryString(HSQLDBSelect select, HSQLDBExpression whereCondition) {
HSQLDBColumn c = new HSQLDBColumn("COUNT(*) as count", null, null);
// The unoptimized query must evaluate the predicate per row in the projection rather than filter with it, so
// that summing the projection yields the row count the optimized query computes with its WHERE clause. A CASE
// expression is used because HSQLDB does not allow casting a BOOLEAN to an integer; its ELSE branch also
// covers the NULL (unknown) case, which the WHERE clause of the optimized query does not count either.
HSQLDBColumn c = new HSQLDBColumn(
"CASE WHEN " + HSQLDBToStringVisitor.asString(whereCondition) + " THEN 1 ELSE 0 END as count", null,
null);
select.setFetchColumns(List.of(new HSQLDBColumnReference(c)));
select.setWhereClause(null);
return "SELECT SUM(count) FROM (" + select.asString() + ") as res";
Expand Down
1 change: 1 addition & 0 deletions src/sqlancer/yugabyte/ysql/YSQLErrors.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public static List<String> getCommonExpressionErrors() {
errors.add("is of type boolean but expression is of type text");
errors.add("a negative number raised to a non-integer power yields a complex result");
errors.add("could not determine polymorphic type because input has type unknown");
errors.add("character number must be positive");

errors.addAll(getToCharFunctionErrors());
errors.addAll(getBitStringOperationErrors());
Expand Down
3 changes: 2 additions & 1 deletion src/sqlancer/yugabyte/ysql/gen/YSQLExpressionGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,9 @@ public String generateOptimizedQueryString(YSQLSelect select, YSQLExpression whe
if (Randomly.getBooleanWithSmallProbability()) {
select.setOrderByClauses(generateOrderBys());
}
select.setWhereClause(whereCondition);
}
// Both branches must filter with the predicate; the aggregate one would otherwise count every row.
select.setWhereClause(whereCondition);

return select.asString();
}
Expand Down
Loading