feat: 增加对原生sql查询支持

This commit is contained in:
zhouhao
2023-04-28 10:55:16 +08:00
parent 9253242e92
commit a670b7563c
8 changed files with 989 additions and 4 deletions

View File

@@ -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>

View File

@@ -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()

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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> {
/**

View File

@@ -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> {

View File

@@ -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() {

View File

@@ -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();
}
}