diff --git a/core/common/lib/sql-lib/src/main/java/org/eclipse/edc/sql/translation/JsonArrayTranslator.java b/core/common/lib/sql-lib/src/main/java/org/eclipse/edc/sql/translation/JsonArrayTranslator.java new file mode 100644 index 00000000000..3030b507bd9 --- /dev/null +++ b/core/common/lib/sql-lib/src/main/java/org/eclipse/edc/sql/translation/JsonArrayTranslator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.sql.translation; + +import org.eclipse.edc.spi.query.Criterion; +import org.eclipse.edc.util.reflection.PathItem; + +import java.util.Collection; +import java.util.List; + +import static org.eclipse.edc.sql.translation.FieldTranslator.toParameters; +import static org.eclipse.edc.sql.translation.FieldTranslator.toValuePlaceholder; + +/** + * This is a specialized translator, that targets object properties that are JSON array, for example {@code someObject.jsonArray}, + * and a criterion of "jsonArray contains foobar". + *

+ * This is necessary, because with a conventional {@link JsonFieldTranslator} we would get {@code (jsonArray -> 'jsonArray')::jsonb ?? ?}, + * because it always assumes one indirection from columnName to object name. + *

+ * The {@link JsonArrayTranslator} explicitly ignores this indirection. + */ +public class JsonArrayTranslator implements FieldTranslator { + + @Override + public String getLeftOperand(List path, Class rightOperandType) { + if (Collection.class.isAssignableFrom(rightOperandType)) { + throw new IllegalArgumentException("JsonArrayTranslator only supports scalar right-operands, found '%s' ".formatted(rightOperandType)); + } + if (path.size() == 1) { + return path.get(0).toString(); + } + throw new IllegalArgumentException("Invalid path for JsonArrayTranslator: must have one element, but found '%s'".formatted(path.size())); + } + + @Override + public WhereClause toWhereClause(List path, Criterion criterion, SqlOperator operator) { + var leftOperand = getLeftOperand(path, criterion.getOperandRight().getClass()); + + if (!operator.representation().equals("??")) { + throw new IllegalArgumentException("Invalid operator for JsonArrayTranslator: must be '??', but was '%s'".formatted(operator.representation())); + } + + return new WhereClause("%s::jsonb ?? %s".formatted(leftOperand, toValuePlaceholder(criterion)), toParameters(criterion)); + } +} diff --git a/core/common/lib/sql-lib/src/test/java/org/eclipse/edc/sql/translation/JsonArrayTranslatorTest.java b/core/common/lib/sql-lib/src/test/java/org/eclipse/edc/sql/translation/JsonArrayTranslatorTest.java new file mode 100644 index 00000000000..d94ea150d99 --- /dev/null +++ b/core/common/lib/sql-lib/src/test/java/org/eclipse/edc/sql/translation/JsonArrayTranslatorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.sql.translation; + +import org.eclipse.edc.util.reflection.PathItem; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.spi.query.Criterion.criterion; + +class JsonArrayTranslatorTest { + + private final JsonArrayTranslator translator = new JsonArrayTranslator(); + + @Test + void getLeftOperand() { + var result = translator.getLeftOperand(PathItem.parse("array"), Object.class); + assertThat(result).isEqualTo("array"); + } + + @Test + void getLeftOperand_invalidPath() { + assertThatThrownBy(() -> translator.getLeftOperand(PathItem.parse("someobject.array"), Object.class)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void toWhereClause() { + var operator = new SqlOperator("??", Object.class); + var criterion = criterion("array", "contains", "value"); + + var result = translator.toWhereClause(PathItem.parse("array"), criterion, operator); + + assertThat(result.sql()).isEqualTo("array::jsonb ?? ?"); + assertThat(result.parameters()).containsExactly("value"); + } + + @Test + void toWhereClause_rightOperandIsList_invalid() { + var operator = new SqlOperator("??", Object.class); + var criterion = criterion("array", "contains", List.of("val1", "val2")); + + assertThatThrownBy(() -> translator.toWhereClause(PathItem.parse("array"), criterion, operator)) + .isInstanceOf(IllegalArgumentException.class); + + } + + @Test + void toWhereClause_invalidOperator() { + var operator = new SqlOperator("=", Object.class); + var criterion = criterion("array", "contains", "value"); + + + assertThatThrownBy(() -> translator.toWhereClause(PathItem.parse("array"), criterion, operator)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file