Skip to content

Commit

Permalink
Quarkus Common Removal: Data API queryBuilder (#1022)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yuqi-Du authored Apr 11, 2024
1 parent 46a2af0 commit 5e442d2
Show file tree
Hide file tree
Showing 14 changed files with 924 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,13 @@ public enum ErrorCode {
"Collection creation failure (unable to create table). Recommend re-creating the collection"),
INVALID_SCHEMA_VERSION(
"Collection has invalid schema version. Recommend re-creating the collection"),
INVALID_ID_TYPE("Invalid Id type");
INVALID_ID_TYPE("Invalid Id type"),

UNSUPPORTED_CQL_QUERY_TYPE("Unsupported cql query type"),

MISSING_VECTOR_VALUE("Missing the vector value when building cql"),

INVALID_LOGIC_OPERATOR("Invalid logical operator");

private final String message;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright DataStax, Inc. and/or The Stargate Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.stargate.sgv2.jsonapi.service.cql;

import io.stargate.sgv2.api.common.cql.ReservedKeywords;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ColumnUtils {

private static final Pattern PATTERN_DOUBLE_QUOTE = Pattern.compile("\"", Pattern.LITERAL);
private static final String ESCAPED_DOUBLE_QUOTE = Matcher.quoteReplacement("\"\"");
/**
* Updated regex pattern to support selecting collection entry lime map_column['entry_key'],
* set_column['set_value']
*/
private static final Pattern UNQUOTED_IDENTIFIER =
Pattern.compile("[a-z][a-z0-9_]*(\\['.*'\\])?");

/**
* Given the raw (as stored internally) text of an identifier, return its CQL representation. That
* is, unless the text is full lowercase and use only characters allowed in unquoted identifiers,
* the result is double-quoted.
*/
public static String maybeQuote(String text) {
if (UNQUOTED_IDENTIFIER.matcher(text).matches() && !ReservedKeywords.isReserved(text)) {
return text;
}
return '"' + PATTERN_DOUBLE_QUOTE.matcher(text).replaceAll(ESCAPED_DOUBLE_QUOTE) + '"';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright The Stargate Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.stargate.sgv2.jsonapi.service.cql;

import static io.stargate.sgv2.jsonapi.exception.ErrorCode.INVALID_LOGIC_OPERATOR;

import com.bpodgursky.jbool_expressions.And;
import com.bpodgursky.jbool_expressions.Expression;
import com.bpodgursky.jbool_expressions.Or;
import java.util.List;

/**
* Convenience expression builder
*
* <p>when construct jbool expression without specifying a comparator, it will use hashComparator by
* default which will cause the order of expression indeterminate, and cause JSONAPI unit tests
* failure By using this ExpressionUtils class, we pass a default comparator to keep expression
* order as it is
*/
public class ExpressionUtils<K> {

public static <K> And<K> andOf(Expression<K>... expressions) {
// expression as creation order
return And.of(expressions, (e1, e2) -> 1);
}

public static <K> And<K> andOf(List<? extends Expression<K>> expressions) {
// expression as creation order
return And.of(expressions.toArray(new Expression[expressions.size()]), (e1, e2) -> 1);
}

public static <K> Or<K> orOf(List<? extends Expression<K>> expressions) {
// expression as creation order
return Or.of(expressions.toArray(new Expression[expressions.size()]), (e1, e2) -> 1);
}

public static <K> Or<K> orOf(Expression<K>... expressions) {
// expression as creation order
return Or.of(expressions, (e1, e2) -> 1);
}

public static <K> Expression<K> buildExpression(
List<? extends Expression<K>> expressions, String logicOperator) {
switch (logicOperator) {
case "$and" -> {
return andOf(expressions);
}
case "$or" -> {
return orOf(expressions);
}
default -> throw INVALID_LOGIC_OPERATOR.toApiException();
}
}

public static <K> Expression<K>[] getAsArray(Expression<K>... expressions) {
return expressions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package io.stargate.sgv2.jsonapi.service.cql.builder;

import io.stargate.sgv2.api.common.cql.ColumnUtils;
import io.stargate.sgv2.jsonapi.service.operation.model.impl.JsonTerm;
import java.util.Objects;

public final class BuiltCondition {

public LHS lhs;

public Predicate predicate;

public JsonTerm jsonTerm;

public BuiltCondition(LHS lhs, Predicate predicate, JsonTerm jsonTerm) {
this.lhs = lhs;
this.predicate = predicate;
this.jsonTerm = jsonTerm;
}

public static BuiltCondition of(LHS lhs, Predicate predicate, JsonTerm jsonTerm) {
return new BuiltCondition(lhs, predicate, jsonTerm);
}

public static BuiltCondition of(String columnName, Predicate predicate, JsonTerm jsonTerm) {
return of(LHS.column(columnName), predicate, jsonTerm);
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
// Append the LHS part of the condition
if (lhs != null) {
lhs.appendToBuilder(builder);
} else {
builder.append("null");
}
// Append the predicate part of the condition
if (predicate != null) {
builder.append(" ").append(predicate);
} else {
builder.append(" null");
}
// Append the JSON term part of the condition
if (jsonTerm != null) {
builder.append(" ").append(jsonTerm);
} else {
builder.append(" null");
}
return builder.toString();
}

/**
* Represents the left hand side of a condition.
*
* <p>This is usually a column name, but technically can be:
*
* <ul>
* <li>a column name ("c = ...")
* <li>a specific element in a map column ("c[v] = ...")
* <li>a tuple of column name ("(c, d, e) = ...") (not supported)
* <li>the token of a tuple of column name ("TOKEN(c, d, e) = ...") (not supported)
* </ul>
*/
public abstract static class LHS {
public static LHS column(String columnName) {
return new ColumnName(columnName);
}

public static LHS mapAccess(String columnName, String key) {
return new MapElement(columnName, key);
}

abstract void appendToBuilder(StringBuilder builder);

static final class ColumnName extends LHS {
private final String columnName;

private ColumnName(String columnName) {
this.columnName = columnName;
}

void appendToBuilder(StringBuilder builder) {
builder.append(ColumnUtils.maybeQuote(columnName));
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof ColumnName) {
ColumnName that = (ColumnName) other;
return Objects.equals(this.columnName, that.columnName);
} else {
return false;
}
}

@Override
public int hashCode() {
return Objects.hash(columnName);
}
}

static final class MapElement extends LHS {
private final String columnName;
private final String key;

MapElement(String columnName, String key) {
this.columnName = columnName;
this.key = key;
}

void appendToBuilder(StringBuilder builder) {
builder.append(ColumnUtils.maybeQuote(columnName)).append("[?]");
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof MapElement) {
MapElement that = (MapElement) other;
return Objects.equals(this.columnName, that.columnName)
&& Objects.equals(this.key, that.key);
} else {
return false;
}
}

@Override
public int hashCode() {
return Objects.hash(columnName, key);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.stargate.sgv2.jsonapi.service.cql.builder;

public enum Predicate {
EQ("="),
NEQ("!="),
LT("<"),
GT(">"),
LTE("<="),
GTE(">="),
IN("IN"),
CONTAINS("CONTAINS"),
NOT_CONTAINS("NOT CONTAINS"),
CONTAINS_KEY("CONTAINS KEY"),
;

private final String cql;

Predicate(String cql) {
this.cql = cql;
}

@Override
public String toString() {
return cql;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.stargate.sgv2.jsonapi.service.cql.builder;

import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import java.util.List;

/**
* @param cql The query string. It can contain anonymous placeholders identified by a question mark
* (?), or named placeholders prefixed by a column (:name).
* @param values The values to fill the placeholders in the query string.
*/
public record Query(String cql, List<Object> values) {

public SimpleStatement queryToStatement() {
SimpleStatement simpleStatement = SimpleStatement.newInstance(cql);
return simpleStatement.setPositionalValues(values);
}
}
Loading

0 comments on commit 5e442d2

Please sign in to comment.