mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-06-20 22:36:12 +08:00
feat: 增加对原生sql查询支持
This commit is contained in:
@@ -143,6 +143,12 @@
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>4.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,16 +1,26 @@
|
||||
package org.hswebframework.web.crud.query;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.hswebframework.ezorm.core.*;
|
||||
import org.hswebframework.ezorm.core.dsl.Query;
|
||||
import org.hswebframework.ezorm.core.param.Term;
|
||||
import org.hswebframework.ezorm.rdb.executor.SqlRequest;
|
||||
import org.hswebframework.ezorm.rdb.executor.SqlRequests;
|
||||
import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSqlExecutor;
|
||||
import org.hswebframework.ezorm.rdb.executor.wrapper.ColumnWrapperContext;
|
||||
import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrapper;
|
||||
import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrappers;
|
||||
import org.hswebframework.ezorm.rdb.mapping.defaults.record.DefaultRecord;
|
||||
import org.hswebframework.ezorm.rdb.mapping.defaults.record.Record;
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBFeatureType;
|
||||
import org.hswebframework.ezorm.rdb.metadata.TableOrViewMetadata;
|
||||
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.Paginator;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.NativeSql;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.PrepareSqlFragments;
|
||||
import org.hswebframework.ezorm.rdb.operator.dml.Join;
|
||||
import org.hswebframework.ezorm.rdb.operator.dml.JoinType;
|
||||
import org.hswebframework.ezorm.rdb.operator.dml.QueryOperator;
|
||||
@@ -19,7 +29,9 @@ import org.hswebframework.ezorm.rdb.operator.dml.query.Selects;
|
||||
import org.hswebframework.ezorm.rdb.operator.dml.query.SortOrder;
|
||||
import org.hswebframework.web.api.crud.entity.PagerResult;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -28,8 +40,9 @@ import javax.persistence.Table;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrappers.column;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class DefaultQueryHelper implements QueryHelper {
|
||||
@@ -38,6 +51,27 @@ public class DefaultQueryHelper implements QueryHelper {
|
||||
|
||||
private final Map<Class<?>, Table> nameMapping = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, QueryAnalyzer> analyzerCaches = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
static final ResultWrapper<Integer, ?> countWrapper = ResultWrappers.column("_total", i -> ((Number) i).intValue());
|
||||
|
||||
@Override
|
||||
public QueryAnalyzer analysis(String selectSql) {
|
||||
return analyzerCaches.computeIfAbsent(selectSql, sql -> new QueryAnalyzerImpl(database, sql));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeQuerySpec<Record> select(String sql, Object... args) {
|
||||
return new NativeQuerySpecImpl<>(this, sql, args, DefaultRecord::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> NativeQuerySpec<T> select(String sql,
|
||||
Supplier<T> newInstance,
|
||||
Object... args) {
|
||||
return new NativeQuerySpecImpl<>(this, sql, args, map -> FastBeanCopier.copy(map, newInstance));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> SelectColumnMapperSpec<R> select(Class<R> resultType) {
|
||||
return new QuerySpec<>(resultType, this);
|
||||
@@ -87,6 +121,94 @@ public class DefaultQueryHelper implements QueryHelper {
|
||||
return arr;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
static class NativeQuerySpecImpl<R> implements NativeQuerySpec<R> {
|
||||
private final DefaultQueryHelper parent;
|
||||
private final String sql;
|
||||
private final Object[] args;
|
||||
|
||||
private final Function<Map<String, Object>, R> mapper;
|
||||
|
||||
private QueryParamEntity param;
|
||||
|
||||
private SqlRequest createQuerySql() {
|
||||
// if (param == null) {
|
||||
// return SqlRequests.prepare(sql, args);
|
||||
// }
|
||||
return parent.analysis(sql).inject(param, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecuteSpec<R> where(QueryParamEntity param) {
|
||||
this.param = param;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<R> fetch() {
|
||||
return parent
|
||||
.database
|
||||
.sql()
|
||||
.reactive()
|
||||
.select(createQuerySql(), ResultWrappers.map())
|
||||
.map(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PagerResult<R>> fetchPaged() {
|
||||
if (param == null) {
|
||||
return fetchPaged(0, 25);
|
||||
}
|
||||
return fetchPaged(param.getPageIndex(), param.getPageSize());
|
||||
}
|
||||
|
||||
private SqlRequest createPagingSql(SqlRequest request, int pageIndex, int pageSize) {
|
||||
PrepareSqlFragments sql = PrepareSqlFragments.of(request.getSql(), request.getParameters());
|
||||
|
||||
Paginator paginator = parent
|
||||
.database
|
||||
.getMetadata()
|
||||
.getCurrentSchema()
|
||||
.findFeatureNow(RDBFeatureType.paginator.getId());
|
||||
|
||||
return paginator.doPaging(sql, pageIndex, pageSize).toRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PagerResult<R>> fetchPaged(int pageIndex, int pageSize) {
|
||||
SqlRequest listSql = createQuerySql();
|
||||
|
||||
SqlRequest countSql = SqlRequests.prepare(
|
||||
"select count(1) as _total from (" + listSql.getSql() + ") t",
|
||||
listSql.getParameters()
|
||||
);
|
||||
ReactiveSqlExecutor sqlExecutor = parent.database.sql().reactive();
|
||||
|
||||
QueryParamEntity param = this.param == null ? new QueryParamEntity().doPaging(pageIndex, pageSize) : this.param;
|
||||
|
||||
if (param.getTotal() != null) {
|
||||
return sqlExecutor
|
||||
.select(createPagingSql(listSql, pageIndex, pageSize), ResultWrappers.map()).map(mapper)
|
||||
.collectList()
|
||||
.map(list -> PagerResult.of(param.getTotal(), list, param));
|
||||
}
|
||||
|
||||
return sqlExecutor
|
||||
.select(countSql, countWrapper)
|
||||
.single(0)
|
||||
.flatMap(total -> {
|
||||
if (total == 0) {
|
||||
return Mono.just(PagerResult.of(0, new ArrayList<>(), param));
|
||||
} else {
|
||||
return sqlExecutor
|
||||
.select(createPagingSql(listSql, param.getPageIndex(), param.getPageSize()), ResultWrappers.map())
|
||||
.map(mapper)
|
||||
.collectList()
|
||||
.map(list -> PagerResult.of(total, list, param));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class ColumnMapping<R> {
|
||||
final QuerySpec<R> parent;
|
||||
@@ -353,9 +475,8 @@ public class DefaultQueryHelper implements QueryHelper {
|
||||
? Mono.just(param.getTotal())
|
||||
: query.clone()
|
||||
.select(Selects.count1().as("_total"))
|
||||
.fetch(column("_total", Number.class::cast))
|
||||
.fetch(countWrapper)
|
||||
.reactive()
|
||||
.map(Number::intValue)
|
||||
.single(0);
|
||||
|
||||
Mono<List<R>> results = createQuery()
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.hswebframework.web.crud.query;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.ezorm.rdb.executor.SqlRequest;
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
|
||||
import org.hswebframework.ezorm.rdb.metadata.TableOrViewMetadata;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface QueryAnalyzer {
|
||||
|
||||
String nativeSql();
|
||||
|
||||
SqlRequest inject(QueryParamEntity entity,Object... args);
|
||||
|
||||
Select select();
|
||||
|
||||
List<Join> joins();
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
class Join {
|
||||
|
||||
final String alias;
|
||||
final Type type;
|
||||
final Table table;
|
||||
|
||||
// final List<Term> on;
|
||||
|
||||
enum Type {
|
||||
left, right, inner
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
class Select {
|
||||
final Map<String, Column> columns;
|
||||
final Table table;
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
class Table {
|
||||
final String alias;
|
||||
|
||||
final TableOrViewMetadata metadata;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
class Column {
|
||||
String name;
|
||||
String alias;
|
||||
String owner;
|
||||
RDBColumnMetadata metadata;
|
||||
}
|
||||
|
||||
class SelectTable extends Table {
|
||||
final Map<String, Column> columns;
|
||||
|
||||
public SelectTable(String alias,
|
||||
Map<String, Column> columns,
|
||||
TableOrViewMetadata metadata) {
|
||||
super(alias, metadata);
|
||||
this.columns = columns;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
package org.hswebframework.web.crud.query;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import net.sf.jsqlparser.statement.values.ValuesStatement;
|
||||
import org.hswebframework.ezorm.core.meta.FeatureSupportedMetadata;
|
||||
import org.hswebframework.ezorm.core.param.Term;
|
||||
import org.hswebframework.ezorm.rdb.executor.SqlRequest;
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBSchemaMetadata;
|
||||
import org.hswebframework.ezorm.rdb.metadata.dialect.Dialect;
|
||||
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.AbstractTermsFragmentBuilder;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.EmptySqlFragments;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.PrepareSqlFragments;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static net.sf.jsqlparser.statement.select.PlainSelect.getStringList;
|
||||
import static net.sf.jsqlparser.statement.select.PlainSelect.orderByToString;
|
||||
import static org.hswebframework.ezorm.rdb.operator.builder.fragments.TermFragmentBuilder.createFeatureId;
|
||||
|
||||
|
||||
class QueryAnalyzerImpl implements FromItemVisitor, SelectItemVisitor, SelectVisitor, QueryAnalyzer {
|
||||
|
||||
private final DatabaseOperator database;
|
||||
|
||||
private String sql;
|
||||
|
||||
private SelectBody parsed;
|
||||
|
||||
private QueryAnalyzer.Select select;
|
||||
|
||||
private final Map<String, QueryAnalyzer.Join> joins = new LinkedHashMap<>();
|
||||
|
||||
private QueryInjector injector;
|
||||
|
||||
@Override
|
||||
public String nativeSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlRequest inject(QueryParamEntity entity, Object... args) {
|
||||
if (injector == null) {
|
||||
initInjector();
|
||||
}
|
||||
return injector.inject(entity, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Select select() {
|
||||
return select;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Join> joins() {
|
||||
return new ArrayList<>(joins.values());
|
||||
}
|
||||
|
||||
QueryAnalyzerImpl(DatabaseOperator database, String sql) {
|
||||
this(database, parse(sql));
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static SelectBody parse(String sql) {
|
||||
return ((net.sf.jsqlparser.statement.select.Select) CCJSqlParserUtil.parse(sql)).getSelectBody();
|
||||
}
|
||||
|
||||
QueryAnalyzerImpl(DatabaseOperator database, SelectBody selectBody) {
|
||||
this.database = database;
|
||||
if (null != selectBody) {
|
||||
this.parsed = selectBody;
|
||||
selectBody.accept(this);
|
||||
} else {
|
||||
this.parsed = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String parsePlainName(String name) {
|
||||
if (null == name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name.startsWith(database.getMetadata().getDialect().getQuoteStart())
|
||||
&& name.endsWith(database.getMetadata().getDialect().getQuoteEnd())) {
|
||||
return name.substring(1, name.length() - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(net.sf.jsqlparser.schema.Table tableName) {
|
||||
String schema = parsePlainName(tableName.getSchemaName());
|
||||
RDBSchemaMetadata schemaMetadata;
|
||||
if (schema != null) {
|
||||
schemaMetadata = database
|
||||
.getMetadata()
|
||||
.getSchema(schema)
|
||||
.orElseThrow(() -> new IllegalStateException("schema " + schema + " not initialized"));
|
||||
} else {
|
||||
schemaMetadata = database.getMetadata().getCurrentSchema();
|
||||
}
|
||||
|
||||
String alias = tableName.getAlias() == null ? tableName.getName() : tableName.getAlias().getName();
|
||||
|
||||
QueryAnalyzer.Table table = new QueryAnalyzer.Table(
|
||||
parsePlainName(alias),
|
||||
schemaMetadata
|
||||
.getTableOrView(parsePlainName(tableName.getName()), false)
|
||||
.orElseThrow(() -> {
|
||||
throw new IllegalStateException("table or view " + tableName.getName() + " not found in " + schemaMetadata.getName());
|
||||
})
|
||||
);
|
||||
|
||||
select = new QueryAnalyzer.Select(new LinkedHashMap<>(), table);
|
||||
|
||||
}
|
||||
|
||||
// select * from ( select a,b,c from table ) t
|
||||
@Override
|
||||
public void visit(SubSelect subSelect) {
|
||||
SelectBody body = subSelect.getSelectBody();
|
||||
QueryAnalyzerImpl sub = new QueryAnalyzerImpl(database, body);
|
||||
String alias = subSelect.getAlias() == null ? null : subSelect.getAlias().getName();
|
||||
|
||||
Map<String, Column> columnMap = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Column> entry : sub.select.columns.entrySet()) {
|
||||
Column val = entry.getValue();
|
||||
|
||||
columnMap.put(entry.getKey(),
|
||||
new Column(val.alias, val.getAlias(), val.owner, val.metadata));
|
||||
}
|
||||
|
||||
select = new QueryAnalyzer.Select(
|
||||
new LinkedHashMap<>(),
|
||||
new QueryAnalyzer.SelectTable(
|
||||
parsePlainName(alias),
|
||||
columnMap,
|
||||
sub.select.table.metadata
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SubJoin subjoin) {
|
||||
for (net.sf.jsqlparser.statement.select.Join join : subjoin.getJoinList()) {
|
||||
join.getRightItem().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LateralSubSelect lateralSubSelect) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ValuesList valuesList) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TableFunction tableFunction) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ParenthesisFromItem aThis) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AllColumns allColumns) {
|
||||
putSelectColumns(select.table, select.columns);
|
||||
|
||||
for (QueryAnalyzer.Join value : new HashSet<>(joins.values())) {
|
||||
putSelectColumns(value.table, select.columns);
|
||||
}
|
||||
}
|
||||
|
||||
private void putSelectColumns(String prefix, QueryAnalyzer.Table table, Map<String, QueryAnalyzer.Column> container) {
|
||||
|
||||
if (table instanceof QueryAnalyzer.SelectTable) {
|
||||
QueryAnalyzer.SelectTable selectTable = ((QueryAnalyzer.SelectTable) table);
|
||||
|
||||
for (QueryAnalyzer.Column column : selectTable.columns.values()) {
|
||||
container.put(column.getAlias(),
|
||||
new QueryAnalyzer.Column(
|
||||
column.name,
|
||||
column.getAlias(),
|
||||
table.alias,
|
||||
column.metadata
|
||||
));
|
||||
}
|
||||
} else {
|
||||
for (RDBColumnMetadata column : table.metadata.getColumns()) {
|
||||
container.put(column.getAlias(),
|
||||
new QueryAnalyzer.Column(
|
||||
column.getName(),
|
||||
column.getAlias(),
|
||||
table.alias,
|
||||
column
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void putSelectColumns(QueryAnalyzer.Table table, Map<String, QueryAnalyzer.Column> container) {
|
||||
putSelectColumns(null, table, container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AllTableColumns allTableColumns) {
|
||||
net.sf.jsqlparser.schema.Table table = allTableColumns.getTable();
|
||||
|
||||
QueryAnalyzer.Join join = joins.get(parsePlainName(table.getName()));
|
||||
|
||||
if (join == null) {
|
||||
throw new IllegalStateException("table " + table.getName() + " not found in join");
|
||||
}
|
||||
putSelectColumns(join.table, select.columns);
|
||||
}
|
||||
|
||||
private QueryAnalyzer.Table getTable(net.sf.jsqlparser.schema.Table table) {
|
||||
QueryAnalyzer.Table meta;
|
||||
if (null == table) {
|
||||
return select.table;
|
||||
}
|
||||
String tableName = parsePlainName(table.getName());
|
||||
|
||||
if (Objects.equals(tableName, select.table.alias)) {
|
||||
meta = select.table;
|
||||
} else {
|
||||
QueryAnalyzer.Join join = joins.get(tableName);
|
||||
if (join == null) {
|
||||
throw new IllegalStateException("table " + table + " not found in from or join");
|
||||
}
|
||||
meta = join.table;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
||||
static class ExpressionColumn extends Column {
|
||||
|
||||
private final SelectItem expr;
|
||||
|
||||
public ExpressionColumn(String alias, String owner, RDBColumnMetadata metadata, SelectItem expr) {
|
||||
super(alias, alias, owner, metadata);
|
||||
this.expr = expr;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SelectExpressionItem selectExpressionItem) {
|
||||
Expression expr = selectExpressionItem.getExpression();
|
||||
Alias alias = selectExpressionItem.getAlias();
|
||||
|
||||
if (!(expr instanceof net.sf.jsqlparser.schema.Column)) {
|
||||
String aliasName = alias == null ? expr.toString() : alias.getName();
|
||||
select.columns.put(aliasName, new ExpressionColumn(aliasName, null, null, selectExpressionItem));
|
||||
|
||||
return;
|
||||
}
|
||||
net.sf.jsqlparser.schema.Column column = ((net.sf.jsqlparser.schema.Column) expr);
|
||||
|
||||
String columnName = parsePlainName(column.getColumnName());
|
||||
|
||||
QueryAnalyzer.Table table = getTable(column.getTable());
|
||||
|
||||
String aliasName = alias == null ? columnName : alias.getName();
|
||||
|
||||
RDBColumnMetadata metadata = table
|
||||
.getMetadata()
|
||||
.getColumn(columnName)
|
||||
.orElse(null);
|
||||
|
||||
if (metadata == null) {
|
||||
if (table instanceof QueryAnalyzer.SelectTable) {
|
||||
Column c = ((SelectTable) table).columns.get(columnName);
|
||||
if (null != c) {
|
||||
metadata = c.metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata == null) {
|
||||
throw new IllegalStateException("column [" + column.getColumnName() + "] not found in " + table.metadata.getName());
|
||||
}
|
||||
|
||||
select.columns.put(aliasName, new QueryAnalyzer.Column(metadata.getName(), aliasName, table.alias, metadata));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(PlainSelect select) {
|
||||
|
||||
FromItem from = select.getFromItem();
|
||||
|
||||
from.accept(this);
|
||||
|
||||
List<net.sf.jsqlparser.statement.select.Join> joinList = select.getJoins();
|
||||
|
||||
if (joinList != null) {
|
||||
for (net.sf.jsqlparser.statement.select.Join join : joinList) {
|
||||
FromItem fromItem = join.getRightItem();
|
||||
QueryAnalyzerImpl joinAn = new QueryAnalyzerImpl(database, (SelectBody) null);
|
||||
fromItem.accept(joinAn);
|
||||
|
||||
Join.Type type;
|
||||
if (join.isLeft()) {
|
||||
type = Join.Type.left;
|
||||
} else if (join.isRight()) {
|
||||
type = Join.Type.right;
|
||||
} else if (join.isInner()) {
|
||||
type = Join.Type.inner;
|
||||
} else {
|
||||
type = null;
|
||||
}
|
||||
joins.put(joinAn.select.table.alias, new Join(joinAn.select.table.alias, type, joinAn.select.table));
|
||||
}
|
||||
}
|
||||
|
||||
for (SelectItem selectItem : select.getSelectItems()) {
|
||||
selectItem.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SetOperationList setOpList) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(WithItem withItem) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ValuesStatement aThis) {
|
||||
|
||||
}
|
||||
|
||||
private void initInjector() {
|
||||
SimpleQueryInjector injector = new SimpleQueryInjector();
|
||||
parsed.accept(injector);
|
||||
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
static class QueryAnalyzerTermsFragmentBuilder extends AbstractTermsFragmentBuilder<QueryAnalyzerImpl> {
|
||||
|
||||
@Override
|
||||
public SqlFragments createTermFragments(QueryAnalyzerImpl parameter, List<Term> terms) {
|
||||
return super.createTermFragments(parameter, terms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlFragments createTermFragments(QueryAnalyzerImpl parameter, Term term) {
|
||||
String column = term.getColumn();
|
||||
String alias;
|
||||
|
||||
Table table;
|
||||
String columnName = column;
|
||||
|
||||
if (column.contains(".")) {
|
||||
String[] split = column.split("[.]");
|
||||
alias = split[0];
|
||||
columnName = split[1];
|
||||
if (Objects.equals(parameter.select.table.alias, alias)) {
|
||||
table = parameter.select.table;
|
||||
} else {
|
||||
QueryAnalyzer.Join join = parameter.joins.get(alias);
|
||||
if (null != join) {
|
||||
table = join.table;
|
||||
} else {
|
||||
throw new IllegalArgumentException("undefined column [" + column + "]");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
table = parameter.select.table;
|
||||
alias = parameter.select.table.alias;
|
||||
}
|
||||
|
||||
if (table instanceof SelectTable) {
|
||||
SelectTable sTable = ((SelectTable) table);
|
||||
Column c = sTable.columns.get(columnName);
|
||||
if (c == null) {
|
||||
return EmptySqlFragments.INSTANCE;
|
||||
}
|
||||
FeatureSupportedMetadata metadata = c.metadata;
|
||||
if (c.metadata == null) {
|
||||
metadata = table.metadata;
|
||||
}
|
||||
return metadata
|
||||
.findFeature(createFeatureId(term.getTermType()))
|
||||
.map(feature -> feature.createFragments(sTable.alias + "." + c.name, c.metadata, term))
|
||||
.orElse(EmptySqlFragments.INSTANCE);
|
||||
}
|
||||
|
||||
return table
|
||||
.metadata
|
||||
.getColumn(columnName)
|
||||
.flatMap(metadata -> metadata
|
||||
.findFeature(createFeatureId(term.getTermType()))
|
||||
.map(feature -> feature.createFragments(metadata.getFullName(alias), metadata, term)))
|
||||
.orElse(EmptySqlFragments.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static QueryAnalyzerTermsFragmentBuilder TERMS_BUILDER = new QueryAnalyzerTermsFragmentBuilder();
|
||||
|
||||
class SimpleQueryInjector implements QueryInjector, SelectVisitor {
|
||||
private String prefix;
|
||||
private String where;
|
||||
private String suffix;
|
||||
|
||||
public SimpleQueryInjector() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(PlainSelect plainSelect) {
|
||||
|
||||
StringBuilder prefix = new StringBuilder();
|
||||
StringBuilder suffix = new StringBuilder();
|
||||
|
||||
prefix.append("SELECT ");
|
||||
|
||||
|
||||
int idx = 0;
|
||||
|
||||
if (plainSelect.getDistinct() != null) {
|
||||
prefix.append(plainSelect.getDistinct());
|
||||
}
|
||||
Dialect dialect = database.getMetadata().getDialect();
|
||||
|
||||
for (Map.Entry<String, Column> entry : select.columns.entrySet()) {
|
||||
if (idx++ > 0) {
|
||||
prefix.append(",");
|
||||
}
|
||||
Column column = entry.getValue();
|
||||
if (column instanceof ExpressionColumn) {
|
||||
prefix.append(((ExpressionColumn) column).expr);
|
||||
continue;
|
||||
}
|
||||
// RDBColumnMetadata column=entry.getValue().metadata;
|
||||
boolean sameTable = Objects.equals(column.owner, select.table.alias);
|
||||
|
||||
String columnName = column.owner + "." + dialect.quote(column.name);
|
||||
|
||||
prefix.append(columnName)
|
||||
.append(" as ")
|
||||
.append(sameTable
|
||||
? dialect.quote(column.alias, false)
|
||||
: dialect.quote(column.owner + "." + column.alias, false));
|
||||
}
|
||||
|
||||
// prefix.append(getStringList(plainSelect.getSelectItems()));
|
||||
|
||||
if (null != plainSelect.getFromItem()) {
|
||||
prefix.append(" FROM ");
|
||||
|
||||
prefix.append(plainSelect.getFromItem());
|
||||
|
||||
if (plainSelect.getJoins() != null) {
|
||||
for (net.sf.jsqlparser.statement.select.Join join : plainSelect.getJoins()) {
|
||||
if (join.isSimple()) {
|
||||
prefix.append(", ").append(join);
|
||||
} else {
|
||||
prefix.append(" ").append(join);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null != plainSelect.getGroupBy()) {
|
||||
suffix.append(' ').append(plainSelect.getGroupBy());
|
||||
}
|
||||
|
||||
suffix.append(' ');
|
||||
if (null != plainSelect.getHaving()) {
|
||||
suffix.append(" HAVING ").append(plainSelect.getHaving());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (null != plainSelect.getWhere()) {
|
||||
where = plainSelect.getWhere().toString();
|
||||
}
|
||||
|
||||
if (plainSelect.getOrderByElements() != null) {
|
||||
suffix.append(orderByToString(plainSelect.isOracleSiblings(), plainSelect.getOrderByElements()));
|
||||
}
|
||||
|
||||
if (plainSelect.getLimit() != null) {
|
||||
suffix.append(plainSelect.getLimit());
|
||||
}
|
||||
if (plainSelect.getOffset() != null) {
|
||||
suffix.append(plainSelect.getOffset());
|
||||
}
|
||||
|
||||
|
||||
this.prefix = prefix.toString();
|
||||
this.suffix = suffix.toString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SetOperationList setOpList) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(WithItem withItem) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ValuesStatement aThis) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlRequest inject(QueryParamEntity param, Object... args) {
|
||||
PrepareSqlFragments sql = PrepareSqlFragments.of(prefix, args);
|
||||
|
||||
SqlFragments fragments = TERMS_BUILDER.createTermFragments(QueryAnalyzerImpl.this, param.getTerms());
|
||||
|
||||
SqlRequest condition = fragments.toRequest();
|
||||
|
||||
if (condition.isNotEmpty() || StringUtils.hasText(where)) {
|
||||
sql.addSql(" WHERE ");
|
||||
}
|
||||
if (StringUtils.hasText(where)) {
|
||||
sql.addSql("(");
|
||||
sql.addSql(where);
|
||||
sql.addSql(")");
|
||||
}
|
||||
if (condition.isNotEmpty()) {
|
||||
if (StringUtils.hasText(where)) {
|
||||
sql.addSql("AND");
|
||||
}
|
||||
sql.addSql("(");
|
||||
sql.addFragments(fragments);
|
||||
sql.addSql(")");
|
||||
}
|
||||
|
||||
sql.addSql(suffix);
|
||||
|
||||
return sql.toRequest();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface QueryInjector {
|
||||
|
||||
SqlRequest inject(QueryParamEntity param, Object... args);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.hswebframework.web.crud.query;
|
||||
|
||||
import org.hswebframework.ezorm.core.Conditional;
|
||||
import org.hswebframework.ezorm.core.dsl.Query;
|
||||
import org.hswebframework.ezorm.rdb.mapping.defaults.record.Record;
|
||||
import org.hswebframework.ezorm.rdb.operator.dml.FunctionColumn;
|
||||
import org.hswebframework.ezorm.rdb.operator.dml.query.SortOrder;
|
||||
import org.hswebframework.web.api.crud.entity.PagerResult;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
@@ -11,6 +14,7 @@ import java.io.Serializable;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 使用DSL方式链式调用来构建复杂查询
|
||||
@@ -28,7 +32,7 @@ import java.util.function.Function;
|
||||
* .from(A.class)
|
||||
* .leftJoin(B.class,spec-> spec.is(A::id, B::id))
|
||||
* .where(dsl->dsl.like(B::getName,'zhang%'))
|
||||
* .fetch()
|
||||
* .fetch();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -37,6 +41,70 @@ import java.util.function.Function;
|
||||
*/
|
||||
public interface QueryHelper {
|
||||
|
||||
/**
|
||||
* 基于SQL创建分析器
|
||||
*
|
||||
* @param selectSql SQL
|
||||
* @return QueryAnalyzer
|
||||
*/
|
||||
QueryAnalyzer analysis(String selectSql);
|
||||
|
||||
/**
|
||||
* 逻辑和{@link QueryHelper#select(String, Object...)}相同,将查询结果转换为指定的实体类
|
||||
*
|
||||
* @param sql SQL
|
||||
* @param newInstance 实体类实例化方法
|
||||
* @param args 参数
|
||||
* @param <T> 实体类型
|
||||
* @return NativeQuerySpec
|
||||
*/
|
||||
<T> NativeQuerySpec<T> select(String sql,
|
||||
Supplier<T> newInstance,
|
||||
Object... args);
|
||||
|
||||
/**
|
||||
* 创建原生SQL查询器
|
||||
* <p>
|
||||
* 预编译参数仅支持<code>?</code>,如果要使用模版,请使用{@link org.hswebframework.ezorm.rdb.executor.SqlRequests#template(String, Object)}
|
||||
* 构造sql以及参数
|
||||
* <pre>{@code
|
||||
*
|
||||
* Flux<Record> records = helper.select("select * from table where type = ?",type)
|
||||
* //注入动态查询条件
|
||||
* .where(param)
|
||||
* //执行查询
|
||||
* .fetch();
|
||||
* }</pre>
|
||||
* <p>
|
||||
* join逻辑:
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* helper.select("select t1.id,t2.* from table t1"+
|
||||
* " left join table2 t2 on t1.id = t2.id") ...
|
||||
*
|
||||
* 将返回结构:
|
||||
* [
|
||||
* {"id":"t1.id的值",
|
||||
* "t2":{
|
||||
* "c1":"t2的字段",
|
||||
* ...
|
||||
* }}
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* ⚠️注意:避免动态拼接SQL语句,应该使用预编译参数或者动态注入动态条件来进行条件处理
|
||||
*
|
||||
* @param sql SQL查询语句
|
||||
* @param args 预编译参数
|
||||
* @return 查询构造器
|
||||
*/
|
||||
NativeQuerySpec<Record> select(String sql, Object... args);
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个查询构造器
|
||||
*
|
||||
@@ -58,6 +126,41 @@ public interface QueryHelper {
|
||||
Consumer<ColumnMapperSpec<R, ?>> mapperSpec);
|
||||
|
||||
|
||||
interface NativeQuerySpec<T> extends ExecuteSpec<T> {
|
||||
|
||||
/**
|
||||
* 以DSL方式构造查询条件
|
||||
* <pre>{@code
|
||||
* helper
|
||||
* .select("select * from table t")
|
||||
* .where(dsl->dsl.is("type","device"))
|
||||
* }</pre>
|
||||
*
|
||||
* @param dsl DSL
|
||||
* @return this
|
||||
*/
|
||||
default ExecuteSpec<T> where(Consumer<Query<?, QueryParamEntity>> dsl) {
|
||||
Query<?, QueryParamEntity> query = QueryParamEntity.newQuery();
|
||||
dsl.accept(query);
|
||||
return where(query.getParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定动态查询条件,通常用于前端动态传入查询条件
|
||||
* <pre>{@code
|
||||
* helper
|
||||
* .select("select * from table t")
|
||||
* .where(param)
|
||||
* .fetch()
|
||||
* }</pre>
|
||||
*
|
||||
* @param param DSL
|
||||
* @return this
|
||||
*/
|
||||
ExecuteSpec<T> where(QueryParamEntity param);
|
||||
|
||||
}
|
||||
|
||||
interface SelectSpec<R> {
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,8 @@ import java.util.function.Function;
|
||||
* @see GenericReactiveCrudService
|
||||
* @see GenericReactiveTreeSupportCrudService
|
||||
* @see EnableCacheReactiveCrudService
|
||||
* @see org.hswebframework.web.crud.query.QueryHelper
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface ReactiveCrudService<E, K> {
|
||||
|
||||
|
||||
@@ -29,6 +29,39 @@ class DefaultQueryHelperTest {
|
||||
private DatabaseOperator database;
|
||||
|
||||
|
||||
@Test
|
||||
public void testNative() {
|
||||
database.dml()
|
||||
.insert("s_test_event")
|
||||
.value("id", "helper_testNative")
|
||||
.value("name", "Ename2")
|
||||
.execute()
|
||||
.sync();
|
||||
|
||||
database.dml()
|
||||
.insert("s_test")
|
||||
.value("id", "helper_testNative")
|
||||
.value("name", "main2")
|
||||
.value("age", 20)
|
||||
.execute()
|
||||
.sync();
|
||||
|
||||
DefaultQueryHelper helper = new DefaultQueryHelper(database);
|
||||
|
||||
helper.select("select e.*,t.id as \"id\" from s_test t " +
|
||||
"left join s_test_event e on e.id = t.id" +
|
||||
" where t.age = ? order by t.age desc", 20)
|
||||
.where(dsl -> dsl
|
||||
.is("e.id", "helper_testNative")
|
||||
.is("t.age", 20))
|
||||
.fetch()
|
||||
.doOnNext(v -> System.out.println(JSON.toJSONString(v, SerializerFeature.PrettyFormat)))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.hswebframework.web.crud.query;
|
||||
|
||||
import org.hswebframework.ezorm.rdb.executor.SqlRequest;
|
||||
import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrappers;
|
||||
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
class QueryAnalyzerImplTest {
|
||||
@Autowired
|
||||
private DatabaseOperator database;
|
||||
|
||||
|
||||
@Test
|
||||
void testInject() {
|
||||
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
|
||||
"select count(distinct time) t2, \"name\" n from \"s_test\" t");
|
||||
SqlRequest request = analyzer.inject(
|
||||
QueryParamEntity
|
||||
.newQuery()
|
||||
.and("name", "123")
|
||||
.getParam());
|
||||
|
||||
System.out.println(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
|
||||
"select name n from s_test t");
|
||||
|
||||
assertNotNull(analyzer.select().table.alias, "t");
|
||||
assertNotNull(analyzer.select().table.metadata.getName(), "s_test");
|
||||
|
||||
assertNotNull(analyzer.select().columns.get("n"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSub() {
|
||||
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
|
||||
"select * from ( select distinct(name) as n from s_test ) t");
|
||||
|
||||
assertEquals(analyzer.select().table.alias, "t");
|
||||
|
||||
assertNotNull(analyzer.select().getColumns().get("n"));
|
||||
|
||||
SqlRequest request = analyzer
|
||||
.inject(QueryParamEntity
|
||||
.newQuery()
|
||||
.where("n", "123")
|
||||
.getParam());
|
||||
|
||||
System.out.println(request);
|
||||
|
||||
database.sql()
|
||||
.reactive()
|
||||
.select(request, ResultWrappers.map())
|
||||
.as(StepVerifier::create)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user