com.speedment.runtime.join.internal.component.stream.sql.JoinSqlUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tool-deploy Show documentation
Show all versions of tool-deploy Show documentation
A Speedment bundle that shades all dependencies into one jar. This is
useful when deploying an application on a server.
The newest version!
/*
*
* Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
*
* 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 com.speedment.runtime.join.internal.component.stream.sql;
import static com.speedment.common.invariant.IntRangeUtil.requireNonNegative;
import com.speedment.runtime.config.Column;
import com.speedment.runtime.config.Dbms;
import com.speedment.runtime.config.Project;
import com.speedment.runtime.config.Table;
import com.speedment.runtime.config.identifier.TableIdentifier;
import com.speedment.runtime.config.util.DocumentDbUtil;
import com.speedment.runtime.core.component.DbmsHandlerComponent;
import com.speedment.runtime.core.component.SqlAdapter;
import com.speedment.runtime.core.db.AsynchronousQueryResult;
import com.speedment.runtime.core.db.DatabaseNamingConvention;
import com.speedment.runtime.core.db.FieldPredicateView;
import com.speedment.runtime.core.db.SqlFunction;
import com.speedment.runtime.core.db.SqlPredicateFragment;
import com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil;
import com.speedment.runtime.core.stream.parallel.ParallelStrategy;
import com.speedment.runtime.field.Field;
import com.speedment.runtime.field.predicate.CombinedPredicate;
import com.speedment.runtime.field.predicate.FieldPredicate;
import com.speedment.runtime.field.predicate.PredicateType;
import com.speedment.runtime.field.trait.HasComparableOperators;
import com.speedment.runtime.join.internal.component.stream.SqlAdapterMapper;
import com.speedment.runtime.join.stage.JoinOperator;
import com.speedment.runtime.join.stage.JoinType;
import com.speedment.runtime.join.stage.Stage;
import com.speedment.runtime.typemapper.TypeMapper;
import java.sql.ResultSet;
import java.util.*;
import static com.speedment.runtime.join.JoinComponent.MAX_DEGREE;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
*
* @author Per Minborg
*/
final class JoinSqlUtil {
private JoinSqlUtil() {}
static Dbms requireSameDbms(
final Project project,
final List> stages
) {
requireNonNull(project);
requireNonNull(stages);
final Dbms dbms = DocumentDbUtil.referencedDbms(project, stages.get(0).identifier());
final List failingDbmses = new ArrayList<>();
for (int i = 1; i < stages.size(); i++) {
final Dbms otherDbms = DocumentDbUtil.referencedDbms(project, stages.get(i).identifier());
if (!DocumentDbUtil.isSame(dbms, otherDbms)) {
failingDbmses.add(otherDbms);
}
}
if (!failingDbmses.isEmpty()) {
throw new IllegalStateException(
"The first database in this join is " + dbms.toString()
+ " but there are other databases in the same join which is illegal: "
+ failingDbmses.toString()
);
}
return dbms;
}
static SqlFunction resultSetMapper(
final Project project,
final TableIdentifier identifier,
final List> stages,
final int stageIndex,
final SqlAdapterMapper sqlAdapterMapper
) {
requireNonNull(project);
requireNonNull(identifier);
requireNonNull(stages);
requireNonNegative(stageIndex);
requireNonNull(sqlAdapterMapper);
@SuppressWarnings("unchecked")
final Stage stage = (Stage) stages.get(stageIndex);
int offset = 0;
for (int i = 0; i < stageIndex; i++) {
final Stage otherStage = stages.get(i);
final Table table = DocumentDbUtil.referencedTable(project, otherStage.identifier());
offset += table.columns()
.filter(Column::isEnabled)
.count();
}
final Table table = DocumentDbUtil.referencedTable(project, stage.identifier());
int nullOffset = -1;
// Check if this stage renders an on-field to be nullable
if (stage.joinType().isPresent()) {
if (stage.joinType().orElseThrow(() -> newNoSuchElementException(stage)).isNullableSelf()) {
nullOffset = findNullOffset(table, stage, stage.field().orElseThrow(() -> new NoSuchElementException("field is missing in stage" + stage)));
}
}
// Check if another (RIGHT JOIN) stage renders an on-field in this stage to be nullable
// No use to check if we already know
if (nullOffset == -1) {
final TableIdentifier thisId = stage.identifier();
for (int i = 0; i < stages.size(); i++) {
if (stageIndex == i) {
// Ignore this stage
continue;
}
final Stage otherStage = stages.get(i);
if (otherStage.joinType().isPresent()) {
if (otherStage.joinType().orElseThrow(() -> newNoSuchElementException(otherStage)).isNullableOther()) {
final HasComparableOperators otherStageForeignField = otherStage.foreignField().orElseThrow(() -> new NoSuchElementException("Foreign fiels is missing in other stage " + otherStage));
final TableIdentifier referencedId = otherStageForeignField.identifier().asTableIdentifier();
if (thisId.equals(referencedId)) {
nullOffset = findNullOffset(table, otherStage, otherStageForeignField);
// If we have a between operation where there is another field pointed, my
// belief is that we can safely ignore that because both fields will be null and we
// only need to detect one
}
}
}
}
}
if (nullOffset >= 0) {
return new NullAwareSqlAdapter<>(sqlAdapterMapper.apply(identifier), nullOffset).entityMapper(offset);
} else {
return sqlAdapterMapper.apply(identifier).entityMapper(offset);
}
}
private static int findNullOffset(
final Table table,
final Stage stage,
final HasComparableOperators field
) {
int result = -1;
final String onColumnId = field
.identifier()
.getColumnId();
final List columns = table.columns()
.filter(Column::isEnabled)
.collect(toList());
for (int j = 0; j < columns.size(); j++) {
final String columnId = columns.get(j).getId();
if (columnId.equals(onColumnId)) {
// Compose a null detecting entity mapper
result = j;
break;
}
}
if (result == -1) {
throw new IllegalStateException(
"Unable to locate column " + onColumnId + " in table " + table.getId()
+ " for stage " + stage.toString()
+ " Columns: " + columns.stream().map(Column::getId).collect(joining(", "))
);
}
return result;
}
private static class NullAwareSqlAdapter implements SqlAdapter {
private final SqlAdapter inner;
private final int nullableColumnOffset;
private NullAwareSqlAdapter(SqlAdapter inner, int nullableColumnOffset) {
this.inner = requireNonNull(inner);
this.nullableColumnOffset = requireNonNegative(nullableColumnOffset);
}
@Override
public TableIdentifier identifier() {
return inner.identifier();
}
@Override
public SqlFunction entityMapper() {
return entityMapper(0);
}
@Override
public SqlFunction entityMapper(int offset) {
return rs -> {
final Object value = rs.getObject(1 + offset + nullableColumnOffset);
// We must check rs.wasNull() becaues the joined field might be null
// even though it is non-nullable like int, long etc.
if (value == null || rs.wasNull()) {
return null;
} else {
return inner.entityMapper(offset).apply(rs);
}
};
}
}
static Stream stream(
final DbmsHandlerComponent dbmsHandlerComponent,
final Project project,
final List> stages,
final SqlFunction rsMapper,
final boolean allowStreamIteratorAndSpliterator
) {
requireNonNull(project);
requireNonNull(dbmsHandlerComponent);
requireNonNull(stages);
requireNonNull(rsMapper);
final SqlInfo sqlInfo = new SqlInfo(dbmsHandlerComponent, project, stages);
final List sqlStages = sqlInfo.sqlStages();
final StringBuilder sb = new StringBuilder();
sb.append("SELECT ");
sb.append(
sqlStages.stream()
.map(SqlStage::sqlColumnList)
.collect(joining(", "))
);
final SqlStage firstSqlStage = sqlStages.get(0);
sb.append(" FROM ").append(firstSqlStage.sqlTableReference()).append(" ");
for (int i = 1; i < sqlStages.size(); i++) {
final String joinSql = renderJoin(sqlInfo.namingConvention(), sqlStages, stages, i);
sb.append(joinSql);
}
final StringBuilder predicateSql = new StringBuilder();
final List