Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
babyfish-ct committed Oct 30, 2024
1 parent 1266ab7 commit 3ca6ba8
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 87 deletions.
2 changes: 1 addition & 1 deletion project/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
group=org.babyfish.jimmer
version=0.9.4
version=0.9.5
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,10 @@ private void addSpecificationConverter(DtoProp<ImmutableType, ImmutableProp> pro
);
}
break;
case "null":
case "notNull":
baseTypeName = TypeName.BOOLEAN;
break;
case "valueIn":
case "valueNotIn":
baseTypeName = ParameterizedTypeName.get(
Expand All @@ -1027,10 +1031,11 @@ private void addSpecificationConverter(DtoProp<ImmutableType, ImmutableProp> pro
baseTypeName = baseProp.getTypeName();
}
baseTypeName = baseTypeName.box();
TypeName dtoPropTypeName = getPropTypeName(prop);
MethodSpec.Builder builder = MethodSpec
.methodBuilder(StringUtil.identifier("__convert", prop.getName()))
.addModifiers(Modifier.PRIVATE)
.addParameter(getPropTypeName(prop), "value")
.addParameter(dtoPropTypeName, "value")
.returns(baseTypeName);
CodeBlock.Builder cb = CodeBlock.builder();
cb.beginControlFlow("if ($L == null)", prop.getName());
Expand Down Expand Up @@ -1524,12 +1529,15 @@ private boolean isSpecificationConverterRequired(DtoProp<ImmutableType, Immutabl
}

private ConverterMetadata converterMetadataOf(DtoProp<ImmutableType, ImmutableProp> prop) {
String funcName = prop.getFuncName();
if ("null".equals(funcName) || "notNull".equals(funcName)) {
return null;
}
ImmutableProp baseProp = prop.toTailProp().getBaseProp();
ConverterMetadata metadata = baseProp.getConverterMetadata();
if (metadata != null) {
return metadata;
}
String funcName = prop.getFuncName();
if ("id".equals(funcName)) {
metadata = baseProp.getTargetType().getIdProp().getConverterMetadata();
if (metadata != null && baseProp.isList() && !dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,16 @@ class DtoPropBuilder<T extends BaseType, P extends BaseProp> implements DtoPropI
}
break;
case "null":
if (!baseProp.isNullable()) {
throw ctx.exception(
prop.func.getLine(),
prop.func.getCharPositionInLine(),
"Cannot call the function \"" + funcName + "\" because the current prop \"" +
baseProp +
"\" is non-null"
);
}
// Don't break
case "notNull":
if (baseProp.isList() && baseProp.isAssociation(true)) {
throw ctx.exception(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ private void handlePositiveProp0(DtoPropBuilder<T, P> propBuilder, P baseProp) {
String oldFuncName = builders.get(0).getFuncName();
String newFuncName = propBuilder.getFuncName();
if (!Objects.equals(oldFuncName, newFuncName) &&
Constants.QBE_FUNC_NAMES.contains(oldFuncName) &&
(Constants.QBE_FUNC_NAMES.contains(newFuncName))) {
("flat".equals(oldFuncName) || Constants.QBE_FUNC_NAMES.contains(oldFuncName)) &&
("flat".equals(newFuncName) || Constants.QBE_FUNC_NAMES.contains(newFuncName))) {
valid = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.babyfish.jimmer.sql.ast.impl.Ast
import org.babyfish.jimmer.sql.ast.impl.AstContext
import org.babyfish.jimmer.sql.ast.impl.AstVisitor
import org.babyfish.jimmer.sql.ast.impl.render.AbstractSqlBuilder
import org.babyfish.jimmer.sql.ast.impl.table.JoinUtils
import org.babyfish.jimmer.sql.ast.impl.table.IsNullUtils
import org.babyfish.jimmer.sql.ast.impl.table.TableImplementor
import org.babyfish.jimmer.sql.ast.table.spi.PropExpressionImplementor
import org.babyfish.jimmer.sql.kt.ast.expression.KExpression
Expand Down Expand Up @@ -59,22 +59,8 @@ internal class IsNullPredicate(
) : NullityPredicate(expression) {

init {
if (expression is PropExpressionImplementor<*>) {
if (!expression.prop.isNullable && !JoinUtils.hasLeftJoin(expression.table)) {
throw IllegalArgumentException(
"Unable to instantiate `is null` predicate which attempts to check if a " +
"non-null property of root table or inner joined table is null " +
"(eg: `table.parent.isNull()`). " +
"There are two solutions: " +
"1. Use associated id property " +
"(eg: `table.parentId.isNull()`), " +
"2. This non-property must belong to a join table " +
"and table join path needs to have at least one left join " +
"(eg: `table.`parent?`.isNull()`)." +
"The non-null property is `${expression.prop.name}` " +
"of table `${expression.table.immutableType.javaClass.name}`."
)
}
if (!isNegative() && expression is PropExpressionImplementor<*>) {
IsNullUtils.isValidIsNullExpression(expression)
}
}

Expand Down
10 changes: 10 additions & 0 deletions project/jimmer-sql-kotlin/src/test/dto/Employee.dto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export org.babyfish.jimmer.sql.kt.model.hr.Employee
-> package org.babyfish.jimmer.sql.kt.model.hr.dto

specification EmployeeSpecificationForIssue735 {
like/i(name)
null(department)
flat(department) {
name as departmentName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.babyfish.jimmer.sql.kt.dto

import org.babyfish.jimmer.sql.kt.common.AbstractQueryTest
import org.babyfish.jimmer.sql.kt.model.hr.Employee
import org.babyfish.jimmer.sql.kt.model.hr.dto.EmployeeSpecificationForIssue735
import org.junit.Test

class EmployeeSpecificationTest : AbstractQueryTest() {

@Test
fun testParentEmpty() {
val spec = EmployeeSpecificationForIssue735()
executeAndExpect(
sqlClient.createQuery(Employee::class) {
where(spec)
select(table)
}
) {
sql(
"""select tb_1_.ID, tb_1_.NAME, tb_1_.DEPARTMENT_ID, tb_1_.DELETED_UUID
|from EMPLOYEE tb_1_
|where tb_1_.DELETED_UUID is null""".trimMargin()
)
rows { }
}
}

@Test
fun testParentIsNull() {
val spec = EmployeeSpecificationForIssue735(isDepartmentNull = true)
executeAndExpect(
sqlClient.createQuery(Employee::class) {
where(spec)
select(table)
}
) {
sql(
"""select tb_1_.ID, tb_1_.NAME, tb_1_.DEPARTMENT_ID, tb_1_.DELETED_UUID
|from EMPLOYEE tb_1_
|where tb_1_.DEPARTMENT_ID is null and
|tb_1_.DELETED_UUID is null""".trimMargin()
)
}
}

@Test
fun testParentIsNullAndDepartmentName() {
val spec = EmployeeSpecificationForIssue735(
isDepartmentNull = true,
departmentName = "ABC"
)
executeAndExpect(
sqlClient.createQuery(Employee::class) {
where(spec)
select(table)
}
) {
sql(
"""select tb_1_.ID, tb_1_.NAME, tb_1_.DEPARTMENT_ID, tb_1_.DELETED_UUID
|from EMPLOYEE tb_1_
|inner join DEPARTMENT tb_2_ on tb_1_.DEPARTMENT_ID = tb_2_.ID
|where tb_1_.DEPARTMENT_ID is null and
|tb_2_.NAME = ?
|and tb_1_.DELETED_UUID is null
|and tb_2_.DELETED_TIME is null""".trimMargin()
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.babyfish.jimmer.sql.ast.Predicate;
import org.babyfish.jimmer.sql.ast.PropExpression;
import org.babyfish.jimmer.sql.ast.impl.render.AbstractSqlBuilder;
import org.babyfish.jimmer.sql.ast.impl.table.JoinUtils;
import org.babyfish.jimmer.sql.ast.impl.table.IsNullUtils;
import org.babyfish.jimmer.sql.ast.impl.table.TableImplementor;
import org.babyfish.jimmer.sql.ast.impl.table.TableProxies;
import org.babyfish.jimmer.sql.ast.table.spi.PropExpressionImplementor;
Expand All @@ -23,25 +23,9 @@ class NullityPredicate extends AbstractPredicate {
private final boolean negative;

public NullityPredicate(Expression<?> expression, boolean negative) {
if (!negative) {
if (expression instanceof PropExpression<?>) {
PropExpressionImplementor<?> propExpr = (PropExpressionImplementor<?>) expression;
if (!propExpr.getProp().isNullable() && !JoinUtils.hasLeftJoin(propExpr.getTable())) {
throw new IllegalArgumentException(
"Unable to instantiate `is null` predicate which attempts to check if a " +
"non-null property of root table or inner joined table is null " +
"(eg: `table.parent().isNull()`). " +
"There are two solutions: " +
"1. Use associated id property " +
"(eg: `table.parentId().isNull()`), " +
"2. This non-property must belong to a join table " +
"and table join path needs to have at least one left join " +
"(eg: `table.parent(JoinType.LEFT).isNull()`). " +
"The non-null property is `" + propExpr.getProp().getName() +
"` of table `" + propExpr.getTable().getImmutableType().getClass().getName() + "`."
);
}
}
if (!negative && expression instanceof PropExpression<?>) {
PropExpressionImplementor<?> propExpr = (PropExpressionImplementor<?>) expression;
IsNullUtils.isValidIsNullExpression(propExpr);
}
this.expression = expression;
this.negative = negative;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.babyfish.jimmer.sql.ast.impl.table;

import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.sql.JoinType;
import org.babyfish.jimmer.sql.ast.table.Table;
import org.babyfish.jimmer.sql.ast.table.spi.PropExpressionImplementor;
import org.babyfish.jimmer.sql.ast.table.spi.TableProxy;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class IsNullUtils {

private IsNullUtils() {}

public static void isValidIsNullExpression(@NotNull PropExpressionImplementor<?> propExpression) {
for (PropExpressionImplementor<?> pe = propExpression; pe != null; pe = pe.getBase()) {
if (pe.getProp().isNullable()) {
return;
}
}
for (Table<?> table = propExpression.getTable(); table != null; ) {
if (table instanceof TableProxy<?>) {
TableProxy<?> proxy = (TableProxy<?>) table;
ImmutableProp prop = proxy.__prop();
if (proxy.__isInverse()) {
prop = prop.getOpposite();
}
if (prop != null) {
JoinType currentJoinType = proxy.__joinType();
if (currentJoinType == JoinType.LEFT || currentJoinType == JoinType.FULL) {
return;
}
}
table = proxy.__parent();
} else {
TableImplementor<?> impl = (TableImplementor<?>) table;
ImmutableProp prop = impl.getJoinProp();
if (impl.isInverse()) {
prop = prop.getOpposite();
}
if (prop != null) {
JoinType currentJoinType = impl.getJoinType();
if (currentJoinType == JoinType.LEFT || currentJoinType == JoinType.FULL) {
return;
}
}
table = impl.getParent();
}
}

List<String> pathNames = new LinkedList<>();
for (PropExpressionImplementor<?> pe = propExpression; pe != null; pe = pe.getBase()) {
if (pe.getProp().isNullable()) {
pathNames.add(0, pe.getProp().getName());
}
}
boolean joined = false;
for (Table<?> table = propExpression.getTable(); table != null; ) {
if (table instanceof TableProxy<?>) {
TableProxy<?> proxy = (TableProxy<?>) table;
ImmutableProp prop = proxy.__prop();
if (proxy.__isInverse()) {
prop = prop.getOpposite();
}
if (prop != null) {
pathNames.add(0, prop.getName());
} else if (proxy.__weakJoinHandle() != null) {
pathNames.add(
0,
"weakJoin<" + proxy.__weakJoinHandle().getWeakJoinType().getSimpleName() + ">"
);
} else {
pathNames.add(0, table.getImmutableType().getJavaClass().getSimpleName());
}
table = proxy.__parent();
} else {
TableImplementor<?> impl = (TableImplementor<?>) table;
ImmutableProp prop = impl.getJoinProp();
if (impl.isInverse()) {
prop = prop.getOpposite();
}
if (prop != null) {
pathNames.add(0, prop.getName());
} if (impl.getWeakJoinHandle() != null) {
pathNames.add(
0,
"weakJoin<" + impl.getWeakJoinHandle().getWeakJoinType().getSimpleName() + ">"
);
} else {
pathNames.add(0, table.getImmutableType().getJavaClass().getSimpleName());
}
table = impl.getParent();
}
}
String path = String.join(".", pathNames);
if (!joined) {
throw new IllegalArgumentException(
"Unable to instantiate the \"is null\" predicate, the path \"" +
path +
"\" is non-null expression"
);
}
throw new IllegalArgumentException(
"Unable to instantiate the \"is null\" predicate, the path \"" +
path +
"\" is neither non-null expression " +
"nor path with left or full table join"
);
}
}

This file was deleted.

2 changes: 1 addition & 1 deletion project/jimmer-sql/src/test/dto/Department.dto
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ specification DepartmentSpecification2 {
like/i(name)
}
}
}
}
Loading

0 comments on commit 3ca6ba8

Please sign in to comment.