blob: 23ec7abefc4bda994036563995d3c2db4ef2dc90 [file] [log] [blame]
package build.bazel.dashboard.github.issuequery;
import build.bazel.dashboard.github.issuequery.GithubIssueQueryParser.Query;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import io.r2dbc.postgresql.codec.Json;
import io.r2dbc.postgresql.codec.PostgresqlObjectId;
import io.r2dbc.spi.Parameters;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.r2dbc.core.DatabaseClient;
import reactor.adapter.rxjava.RxJava3Adapter;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
@Slf4j
@RequiredArgsConstructor
public class GithubIssueQueryExecutorPg implements GithubIssueQueryExecutor {
private final ObjectMapper objectMapper;
private final GithubIssueQueryParser queryParser;
private final DatabaseClient databaseClient;
@Override
public QueryResult execute(String owner, String repo, String query) {
var items = fetchQueryResult(owner, repo, query);
var count = fetchQueryResultCount(owner, repo, query);
return QueryResult.builder().totalCount(count).items(items).build();
}
private ImmutableList<JsonNode> fetchQueryResult(String owner, String repo, String query) {
SqlCondition condition = buildSqlCondition(owner, repo, query);
StringBuilder sql =
new StringBuilder(
"SELECT data FROM github_issue_data WHERE (owner, repo, issue_number) IN "
+ "(SELECT owner, repo, issue_number FROM github_issue");
if (condition.condition.length() > 0) {
sql.append(" WHERE ").append(condition.condition);
}
sql.append(")");
DatabaseClient.GenericExecuteSpec spec = databaseClient.sql(sql.toString());
for (Map.Entry<String, Object> entry : condition.bindings.entrySet()) {
spec = spec.bind(entry.getKey(), entry.getValue());
}
return spec.map(
row -> {
try {
return objectMapper.readTree(
(requireNonNull(row.get("data", Json.class))).asArray());
} catch (IOException e) {
throw new IllegalStateException(e);
}
})
.all()
.collect(toImmutableList())
.block();
}
@Override
public Integer fetchQueryResultCount(String owner, String repo, String query) {
SqlCondition condition = buildSqlCondition(owner, repo, query);
StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS count FROM github_issue");
if (condition.condition.length() > 0) {
sql.append(" WHERE ").append(condition.condition);
}
DatabaseClient.GenericExecuteSpec spec = databaseClient.sql(sql.toString());
for (Map.Entry<String, Object> entry : condition.bindings.entrySet()) {
spec = spec.bind(entry.getKey(), entry.getValue());
}
return spec.map(row -> row.get("count", Integer.class)).one().block();
}
private void and(StringBuilder conditions, String condition) {
if (conditions.length() > 0) {
conditions.insert(0, "(");
conditions.append(") AND ");
}
conditions.append(condition);
}
@Builder
@Value
static class SqlCondition {
String condition;
Map<String, Object> bindings;
}
private SqlCondition buildSqlCondition(String owner, String repo, String query) {
Query parsedQuery = queryParser.parse(query);
Map<String, Object> bindings = new HashMap<>();
StringBuilder condition = new StringBuilder();
and(condition, "owner = :owner");
bindings.put("owner", owner);
and(condition, "repo = :repo");
bindings.put("repo", repo);
if (parsedQuery.getState() != null) {
and(condition, "state = :state");
bindings.put("state", parsedQuery.getState());
}
if (parsedQuery.getIsPullRequest() != null) {
and(condition, "is_pull_request = :is_pull_request");
bindings.put("is_pull_request", parsedQuery.getIsPullRequest());
}
if (parsedQuery.getNoMilestone() != null && parsedQuery.getNoMilestone()) {
and(condition, "milestone = ''");
}
if (!parsedQuery.getLabels().isEmpty()) {
and(condition, "labels @> :labels");
bindings.put(
"labels",
Parameters.in(
PostgresqlObjectId.UNSPECIFIED, parsedQuery.getLabels().toArray(new String[0])));
}
if (!parsedQuery.getExcludeLabels().isEmpty()) {
and(condition, "NOT labels && :excluded_labels");
bindings.put(
"excluded_labels",
Parameters.in(
PostgresqlObjectId.UNSPECIFIED,
parsedQuery.getExcludeLabels().toArray(new String[0])));
}
return SqlCondition.builder().condition(condition.toString()).bindings(bindings).build();
}
}