Skip to content
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

Quarkus Common Removal: Data API queryBuilder #1022

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading