Skip to content
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 @@ -21,4 +21,10 @@ public Table<? extends Record> getTableWithAliasFor(@NotNull InternalQueryNode.R
public Condition getConditionFor(@NotNull String relationshipName, @NotNull Table<?> left, @NotNull Table<?> right) {
return ConfigKt.getConditionForRelationship(relationshipName, left, right);
}

@Nullable
@Override
public Table<?> getRelatedTable(@NotNull String relationshipName, @NotNull Table<?> from) {
return ConfigKt.getRelatedTable(relationshipName, from);
}
}
65 changes: 65 additions & 0 deletions src/integration-test/kotlin/example/ByosApplicationTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package example

import com.fasterxml.jackson.databind.ObjectMapper
import graphql.parser.Parser
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
Expand All @@ -9,6 +11,69 @@ internal class ByosApplicationTest {
@Autowired
private lateinit var graphQLService: GraphQLService

private val parser = Parser()
private val objectMapper = ObjectMapper()

@Test
fun queryWithAndVariableObjectCondition() {
val query = """
query FilmsList(
${'$'}limit: Int!,
${'$'}filter: String,
${'$'}andCondition1: FilmWhere!
) {
films: filmByIds(
where: { _and: [
{ _or: [
{ title: { _ilike: ${'$'}filter } }
] },
${'$'}andCondition1
] },
limit: ${'$'}limit
) {
edges {
node {
film_id
title
}
}
}
}
""".trimIndent()

val variables = mapOf(
"limit" to objectMapper.readTree("2"),
"filter" to objectMapper.readTree("\"%A%\""),
"andCondition1" to objectMapper.readTree("""{ "film_id": { "_gte": 2 } }""")
)

val expectedResult = """
{
"data": {
"films": {
"edges": [
{
"node": {
"film_id": 2,
"title": "ACE GOLDFINGER"
}
},
{
"node": {
"film_id": 3,
"title": "ADAPTATION HOLES"
}
}
]
}
}
}
""".trimIndent()

val requestInfo = RequestInfo(parser.parseDocument(query), "FilmsList", variables)
assertJsonEquals(expectedResult, graphQLService.executeGraphQLQuery(requestInfo))
}

@Test
fun simpleQuery() {
val query = """
Expand Down
17 changes: 17 additions & 0 deletions src/integration-test/kotlin/example/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,20 @@ fun getConditionForRelationship(relationshipName: String, left: Table<*>, right:

else -> null
}

fun getRelatedTable(relationshipName: String, from: Table<*>): Table<*>? =
when {
relationshipName == "actors" && from is Film -> Tables.ACTOR
relationshipName == "films" && from is Actor -> Tables.FILM
relationshipName == "stores" && from is Film -> Tables.STORE
relationshipName == "films" && from is Store -> Tables.FILM
relationshipName == "language" && from is Film -> Tables.LANGUAGE
relationshipName == "original_language" && from is Film -> Tables.LANGUAGE
relationshipName == "inventories" && from is Store -> Tables.INVENTORY
relationshipName == "film" && from is Inventory -> Tables.FILM
relationshipName == "categories" && from is Film -> Tables.CATEGORY
relationshipName == "films" && from is Category -> Tables.FILM
relationshipName == "parent_category" && from is Category -> Tables.CATEGORY
relationshipName == "subcategories" && from is Category -> Tables.CATEGORY
else -> null
}
136 changes: 112 additions & 24 deletions src/main/java/byos/ConditionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,27 @@
// https://www.graphql-java.com/documentation/data-mapping#scalars
public class ConditionFactory {

public static Condition getWhereCondition(Argument whereArgument, Map<String, JsonNode> variables, Table<?> table) {
return getCondition(getWhereObject(whereArgument.getValue()), variables, table);
public static Condition getWhereCondition(
Argument whereArgument,
Map<String, JsonNode> variables,
Table<?> table,
TableAndConditionService tableAndConditionService
) {
return getCondition(getWhereObject(whereArgument.getValue()), variables, table, tableAndConditionService);
}

public static Condition getCondition(ObjectValue objectValue, Map<String, JsonNode> variables, Table<?> table) {
public static Condition getCondition(ObjectValue objectValue, Map<String, JsonNode> variables, Table<?> table, TableAndConditionService tableAndConditionService) {
switch (objectValue.getObjectFields().size()) {
case 0: {
return DSL.noCondition();
}
case 1: {
return getCondition(objectValue.getObjectFields().get(0), variables, table);
return getCondition(objectValue.getObjectFields().get(0), variables, table, tableAndConditionService);
}
default: {
// multiple fields conditions are "add" concatenated by default
return DSL.and(objectValue.getObjectFields().stream()
.map(objectField -> getCondition(objectField, variables, table))
.map(objectField -> getCondition(objectField, variables, table, tableAndConditionService))
.collect(Collectors.toSet()));
}
}
Expand All @@ -53,32 +58,114 @@ private static ObjectValue getWhereObject(Value value) {
throw new IllegalArgumentException("Value of whereArgument must be an object");
}

private static Condition getCondition(ObjectField objectField, Map<String, JsonNode> variables, Table<?> table) {
private static Condition getCondition(ObjectField objectField, Map<String, JsonNode> variables, Table<?> table, TableAndConditionService tableAndConditionService) {
final String name = objectField.getName();
switch (name) {
case CONDITION_AND: {
return DSL.and(asArrayValue(objectField.getValue()).getValues().stream()
.map(objectValue -> getCondition((ObjectValue) objectValue, variables, table))
.collect(Collectors.toSet()));
Value rawValue = objectField.getValue();
if (rawValue instanceof VariableReference) {
JsonNode resolved = variables.get(((VariableReference) rawValue).getName());
return DSL.and(List.of(getConditionFromJsonNode(resolved, variables, table, tableAndConditionService)));
}
return DSL.and(asArrayValue(rawValue).getValues().stream()
.map(value -> resolveToCondition(value, variables, table, tableAndConditionService))
.collect(Collectors.toList()));
}
case CONDITION_OR: {
return DSL.or(asArrayValue(objectField.getValue()).getValues().stream()
.map(objectValue -> getCondition((ObjectValue) objectValue, variables, table))
.collect(Collectors.toSet()));
Value rawValue = objectField.getValue();
if (rawValue instanceof VariableReference) {
JsonNode resolved = variables.get(((VariableReference) rawValue).getName());
return DSL.or(List.of(getConditionFromJsonNode(resolved, variables, table, tableAndConditionService)));
}
return DSL.or(asArrayValue(rawValue).getValues().stream()
.map(value -> resolveToCondition(value, variables, table, tableAndConditionService))
.collect(Collectors.toList()));
}
case CONDITION_NOT: {
return DSL.not(getCondition((ObjectValue) objectField.getValue(), variables, table));
return DSL.not(getCondition((ObjectValue) objectField.getValue(), variables, table, tableAndConditionService));
}
default: {

final Field field = table.field(name);
if (field != null) {
return getCondition(field, variables, objectField.getValue());
}
// if the name does not match with any columns in the current table and it is is a nested object
// (not a scalar)
if (tableAndConditionService != null && objectField.getValue() instanceof ObjectValue) {
ObjectValue nestedWhere = (ObjectValue) objectField.getValue();

Table<?> relatedTable = tableAndConditionService.getRelatedTable(name, table);
if (relatedTable != null) {
Condition joinCondition = tableAndConditionService.getConditionFor(name, table, relatedTable);
Condition nestedCondition = getCondition(nestedWhere, variables, relatedTable, tableAndConditionService);
return DSL.exists(
DSL.selectOne()
.from(relatedTable)
.where(joinCondition)
.and(nestedCondition)
);
}
}
}
}
return DSL.noCondition();
}

private static Condition resolveToCondition(Value value, Map<String, JsonNode> variables, Table<?> table, TableAndConditionService tableAndConditionService) {
if (value instanceof ObjectValue) {
return getCondition((ObjectValue) value, variables, table, tableAndConditionService);
} else if (value instanceof VariableReference) {
JsonNode resolved = variables.get(((VariableReference) value).getName());
return getConditionFromJsonNode(resolved, variables, table, tableAndConditionService);
}
throw new IllegalArgumentException("Unsupported value type in condition array: " + value.getClass());
}

private static Condition getConditionFromJsonNode(JsonNode node, Map<String, JsonNode> variables, Table<?> table, TableAndConditionService tableAndConditionService) {
if (node.isObject()) {
ObjectValue.Builder builder = ObjectValue.newObjectValue();
node.fields().forEachRemaining(entry -> {
builder.objectField(ObjectField.newObjectField()
.name(entry.getKey())
.value(jsonNodeToValue(entry.getValue()))
.build());
});
return getCondition(builder.build(), variables, table, tableAndConditionService);
}
return DSL.noCondition();
}

private static Value jsonNodeToValue(JsonNode node) {
if (node.isTextual()) {
return StringValue.newStringValue(node.asText()).build();
} else if (node.isInt() || node.isLong()) {
return IntValue.newIntValue(BigInteger.valueOf(node.asLong())).build();
} else if (node.isFloat() || node.isDouble()) {
return FloatValue.newFloatValue(BigDecimal.valueOf(node.asDouble())).build();
} else if (node.isBoolean()) {
return BooleanValue.newBooleanValue(node.asBoolean()).build();
} else if (node.isArray()) {
ArrayValue.Builder arrayBuilder = ArrayValue.newArrayValue();
node.forEach(element -> arrayBuilder.value(jsonNodeToValue(element)));
return arrayBuilder.build();
} else if (node.isObject()) {
ObjectValue.Builder objBuilder = ObjectValue.newObjectValue();
node.fields().forEachRemaining(entry ->
objBuilder.objectField(
ObjectField.newObjectField()
.name(entry.getKey())
.value(jsonNodeToValue(entry.getValue()))
.build()
)
);
return objBuilder.build();
} else if (node.isNull()) {
return NullValue.newNullValue().build();
}
throw new IllegalArgumentException("Unsupported JsonNode type: " + node.getNodeType());
}

private static ArrayValue asArrayValue(Value value) {
if (value instanceof ArrayValue) {
return (ArrayValue) value;
Expand Down Expand Up @@ -122,29 +209,31 @@ protected static Condition getCondition(Field field, Map<String, JsonNode> varia

public static Object extractValue(Value value, Map<String, JsonNode> variables) {
if (value instanceof StringValue) {
return ((StringValue)value).getValue();
return ((StringValue) value).getValue();
} else if (value instanceof IntValue) {
return ((IntValue)value).getValue();
return ((IntValue) value).getValue();
} else if (value instanceof BooleanValue) {
return ((BooleanValue)value).isValue();
return ((BooleanValue) value).isValue();
} else if (value instanceof FloatValue) {
return ((FloatValue) value).getValue();
} else if (value instanceof ArrayValue) {
return ((ArrayValue)value).getValues().stream().map(v -> extractValue(v, variables)).collect(Collectors.toList());
return ((ArrayValue) value).getValues().stream().map(v -> extractValue(v, variables)).collect(Collectors.toList());
} else if (value instanceof ObjectValue) {
return ((ObjectValue)value).getObjectFields();
return ((ObjectValue) value).getObjectFields();
} else if (value instanceof VariableReference) {
final JsonNode jsonNode = variables.get(((VariableReference) value).getName());
if (jsonNode != null) {
if (jsonNode.isArray()) {
return extractArrayValue((ArrayNode) jsonNode);
}
else {
} else if (jsonNode.isObject()) {
return jsonNode;
} else {
return jsonNode.asText();
}
}
return null;
}

throw new IllegalArgumentException("nyi");
}

Expand All @@ -158,15 +247,14 @@ public static IntValue extractIntValue(Value value, Map<String, JsonNode> variab
if (value == null) {
return null;
}
if (value instanceof IntValue) {
if (value instanceof IntValue) {
return (IntValue) value;
}
final Object extractedValue = extractValue(value, variables);
if (extractedValue instanceof BigDecimal) {
return new IntValue((BigInteger) extractedValue);
}
else if (extractedValue instanceof String) {
return new IntValue(new BigInteger((String)extractedValue));
} else if (extractedValue instanceof String) {
return new IntValue(new BigInteger((String) extractedValue));
}
return null;
}
Expand Down
15 changes: 12 additions & 3 deletions src/main/kotlin/byos/GraphQLService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import graphql.parser.Parser
import graphql.schema.GraphQLSchema
import graphql.validation.Validator
import org.jooq.DSLContext
import org.jooq.impl.DSL
import java.util.*

data class RequestInfo(
Expand Down Expand Up @@ -87,10 +88,18 @@ class GraphQLService(val schema: GraphQLSchema, private val tableAndConditionSer

val queryTrees = queryTranspiler.buildInternalQueryTrees(ast, fragments)
val results =
queryTrees.map { tree -> run {}
jooq.select(queryTranspiler.resolveInternalQueryTree(tree, requestInfo.variables)).fetch()
}
queryTrees.map { tree -> run {}
val queryPart = queryTranspiler.resolveInternalQueryTree(tree, requestInfo.variables)
val sqlString = jooq.renderInlined(DSL.select(queryPart))
jooq.fetch(sqlString)
}

// val results =
// queryTrees.map { tree -> run {}
// jooq.select(queryTranspiler.resolveInternalQueryTree(tree, requestInfo.variables)).fetch()
// }

return results.formatGraphQLResponse()
}
}

6 changes: 6 additions & 0 deletions src/main/kotlin/byos/TableAndConditionService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@ interface TableAndConditionService {
* Returns the join condition to be applied between the given two tables for the given relationship.
*/
fun getConditionFor(relationshipName: String, left: Table<*>, right: Table<*>): Condition?

/**
* Get all the related tables, mainly important for nested lookups
*/
fun getRelatedTable(relationshipName: String, from: Table<*>): Table<*>?

}
4 changes: 2 additions & 2 deletions src/main/kotlin/byos/WhereCondition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class WhereCondition(private val tableAndConditionService: TableAndConditionServ
}
}

fun getForWhere(argument: Argument, variables: Map<String, JsonNode> , table: Table<*>): Condition {
return ConditionFactory.getWhereCondition(argument, variables, table)
fun getForWhere(argument: Argument, variables: Map<String, JsonNode>, table: Table<*>): Condition {
return ConditionFactory.getWhereCondition(argument, variables, table, tableAndConditionService)
}

private fun extractValue(value: Value<Value<*>>): Any? =
Expand Down
Loading