Skip to content
Open
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
15 changes: 15 additions & 0 deletions src/main/java/cn/beagile/dslquery/ColumnFields.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public <T> ColumnFields(DSLQuery dslQuery) {
initSelectIgnores(this.clz);
initDeepJoins(this.clz);
readFields(this.clz);
validateRuntimeJoinOnPaths();
}

private <T> void readFields(Class<T> clz) {
Expand Down Expand Up @@ -84,6 +85,20 @@ private void readJoins(Class clz, List<Field> parents) {
readJoinFields(clz, parents, JoinColumns.class);
}

private void validateRuntimeJoinOnPaths() {
Set<String> runtimeJoinOnPaths = Optional.ofNullable(dslQuery)
.map(DSLQuery::getJoinOns)
.map(Map::keySet)
.orElse(Collections.emptySet());
Set<String> joinedPaths = joinFields.stream().map(JoinField::parentNames).collect(Collectors.toSet());
runtimeJoinOnPaths.stream()
.filter(path -> !joinedPaths.contains(path))
.findFirst()
.ifPresent(path -> {
throw new RuntimeException("join path not found: " + path);
});
}

private boolean isJoinInclude(Field field, List<Field> parents) {
if (parents.isEmpty()) {
return true;
Expand Down
1 change: 1 addition & 0 deletions src/main/java/cn/beagile/dslquery/DSLQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public DSLQuery<T> joinOn(String path, String dsl) {
if (path == null || path.isEmpty() || dsl == null || dsl.isEmpty()) {
return this;
}
Validators.validateJoinPath(path);
joinOns.computeIfAbsent(path, key -> new ArrayList<>()).add(dsl);
return this;
}
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/cn/beagile/dslquery/JoinOnSQLBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.stream.Stream;

class JoinOnSQLBuilder implements SQLBuilder {
private static final int MAX_JOIN_ON_EXPRESSION_LENGTH = 2048;
private static final int MAX_JOIN_ON_NESTING_DEPTH = 10;
private final Map<String, ColumnField> fields = new LinkedHashMap<>();
private final Map<String, Object> params;
private final String paramPrefix;
Expand All @@ -31,11 +33,33 @@ String build(List<String> joinOns) {
}
WhereParser parser = new WhereParser();
return joinOns.stream()
.peek(this::validateJoinOn)
.map(parser::parse)
.map(expression -> expression.toSQL(this))
.collect(Collectors.joining(" and "));
}

private void validateJoinOn(String joinOn) {
if (joinOn.length() > MAX_JOIN_ON_EXPRESSION_LENGTH) {
throw new RuntimeException("join on expression too long");
}
int depth = 0;
int maxDepth = 0;
for (int i = 0; i < joinOn.length(); i++) {
char ch = joinOn.charAt(i);
if (ch == '(') {
depth++;
maxDepth = Math.max(maxDepth, depth);
}
if (ch == ')') {
depth--;
}
}
if (maxDepth > MAX_JOIN_ON_NESTING_DEPTH) {
throw new RuntimeException("join on nesting too deep");
}
}

private void initFields(Field joinField, List<Field> parents) {
Class<?> rootClass = parents.get(0).getDeclaringClass();
addScope("", readColumns(joinField.getType(), joinField.getType(), parents, true));
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/cn/beagile/dslquery/Validators.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ static void validateFieldName(String field) {
throw new RuntimeException("invalid field:" + field);
}
}

static void validateJoinPath(String path) {
Pattern validJoinPathPattern = Pattern.compile("[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)*$");
if (!validJoinPathPattern.matcher(path).matches()) {
throw new RuntimeException("invalid join path:" + path);
}
}
}
43 changes: 43 additions & 0 deletions src/test/java/cn/beagile/dslquery/JoinOnQueryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,47 @@ void should_fail_fast_when_join_on_field_is_unknown() {
RuntimeException exception = assertThrows(RuntimeException.class, () -> sqlBuilder.build(nullsOrder));
assertEquals("field not found: missing", exception.getMessage());
}

@Test
void should_reject_invalid_runtime_join_on_path() {
DSLQuery<User> dslQuery = new DSLQuery<>(null, User.class);

RuntimeException exception = assertThrows(RuntimeException.class,
() -> dslQuery.joinOn("org;drop", "(and(type eq SALES))"));

assertEquals("invalid join path:org;drop", exception.getMessage());
}

@Test
void should_fail_when_runtime_join_on_path_is_not_joined() {
DSLQuery<User> dslQuery = new DSLQuery<>(null, User.class)
.joinOn("org.area", "(and(active eq true))");

RuntimeException exception = assertThrows(RuntimeException.class,
() -> new DSLSQLBuilder<>(dslQuery).build(nullsOrder));
assertEquals("join path not found: org.area", exception.getMessage());
}

@Test
void should_fail_when_join_on_expression_is_too_deep() {
DSLQuery<User> dslQuery = new DSLQuery<>(null, User.class)
.joinOn("org", "(and(or(and(or(and(or(and(or(and(or(and(type eq SALES))))))))))))");

DSLSQLBuilder<User> sqlBuilder = new DSLSQLBuilder<>(dslQuery);

RuntimeException exception = assertThrows(RuntimeException.class, () -> sqlBuilder.build(nullsOrder));
assertEquals("join on nesting too deep", exception.getMessage());
}

@Test
void should_fail_when_join_on_expression_is_too_long() {
String longValue = "a".repeat(2050);
DSLQuery<User> dslQuery = new DSLQuery<>(null, User.class)
.joinOn("org", "(and(type eq " + longValue + "))");

DSLSQLBuilder<User> sqlBuilder = new DSLSQLBuilder<>(dslQuery);

RuntimeException exception = assertThrows(RuntimeException.class, () -> sqlBuilder.build(nullsOrder));
assertEquals("join on expression too long", exception.getMessage());
}
}
Loading