001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.dialect.impl;
017
018import com.mybatisflex.core.dialect.IDialect;
019import com.mybatisflex.core.dialect.KeywordWrap;
020import com.mybatisflex.core.dialect.LimitOffsetProcessor;
021import com.mybatisflex.core.dialect.OperateType;
022import com.mybatisflex.core.exception.FlexAssert;
023import com.mybatisflex.core.exception.FlexExceptions;
024import com.mybatisflex.core.exception.locale.LocalizedFormats;
025import com.mybatisflex.core.logicdelete.LogicDeleteManager;
026import com.mybatisflex.core.query.CPI;
027import com.mybatisflex.core.query.Join;
028import com.mybatisflex.core.query.QueryColumn;
029import com.mybatisflex.core.query.QueryCondition;
030import com.mybatisflex.core.query.QueryOrderBy;
031import com.mybatisflex.core.query.QueryTable;
032import com.mybatisflex.core.query.QueryWrapper;
033import com.mybatisflex.core.query.UnionWrapper;
034import com.mybatisflex.core.query.With;
035import com.mybatisflex.core.row.Row;
036import com.mybatisflex.core.row.RowCPI;
037import com.mybatisflex.core.table.TableInfo;
038import com.mybatisflex.core.update.RawValue;
039import com.mybatisflex.core.util.ArrayUtil;
040import com.mybatisflex.core.util.CollectionUtil;
041import com.mybatisflex.core.util.SqlUtil;
042import com.mybatisflex.core.util.StringUtil;
043
044import java.util.*;
045import java.util.function.Function;
046import java.util.stream.Collectors;
047import java.util.stream.IntStream;
048
049import static com.mybatisflex.core.constant.SqlConsts.AND;
050import static com.mybatisflex.core.constant.SqlConsts.ASTERISK;
051import static com.mybatisflex.core.constant.SqlConsts.BLANK;
052import static com.mybatisflex.core.constant.SqlConsts.BRACKET_LEFT;
053import static com.mybatisflex.core.constant.SqlConsts.BRACKET_RIGHT;
054import static com.mybatisflex.core.constant.SqlConsts.DELETE;
055import static com.mybatisflex.core.constant.SqlConsts.DELETE_FROM;
056import static com.mybatisflex.core.constant.SqlConsts.DELIMITER;
057import static com.mybatisflex.core.constant.SqlConsts.DUAL;
058import static com.mybatisflex.core.constant.SqlConsts.EMPTY;
059import static com.mybatisflex.core.constant.SqlConsts.EQUALS;
060import static com.mybatisflex.core.constant.SqlConsts.EQUALS_PLACEHOLDER;
061import static com.mybatisflex.core.constant.SqlConsts.FROM;
062import static com.mybatisflex.core.constant.SqlConsts.GROUP_BY;
063import static com.mybatisflex.core.constant.SqlConsts.HAVING;
064import static com.mybatisflex.core.constant.SqlConsts.HINT_END;
065import static com.mybatisflex.core.constant.SqlConsts.HINT_START;
066import static com.mybatisflex.core.constant.SqlConsts.INSERT_INTO;
067import static com.mybatisflex.core.constant.SqlConsts.OR;
068import static com.mybatisflex.core.constant.SqlConsts.ORDER_BY;
069import static com.mybatisflex.core.constant.SqlConsts.PLACEHOLDER;
070import static com.mybatisflex.core.constant.SqlConsts.REFERENCE;
071import static com.mybatisflex.core.constant.SqlConsts.SELECT;
072import static com.mybatisflex.core.constant.SqlConsts.SELECT_ALL_FROM;
073import static com.mybatisflex.core.constant.SqlConsts.SEMICOLON;
074import static com.mybatisflex.core.constant.SqlConsts.SET;
075import static com.mybatisflex.core.constant.SqlConsts.UPDATE;
076import static com.mybatisflex.core.constant.SqlConsts.VALUES;
077import static com.mybatisflex.core.constant.SqlConsts.WHERE;
078
079/**
080 * 通用的方言设计,其他方言可以继承于当前 CommonsDialectImpl
081 * 创建或获取方言请参考 {@link com.mybatisflex.core.dialect.DialectFactory}
082 */
083public class CommonsDialectImpl implements IDialect {
084
085    protected KeywordWrap keywordWrap = KeywordWrap.BACK_QUOTE;
086    private LimitOffsetProcessor limitOffsetProcessor = LimitOffsetProcessor.MYSQL;
087
088    public CommonsDialectImpl() {
089    }
090
091    public CommonsDialectImpl(LimitOffsetProcessor limitOffsetProcessor) {
092        this.limitOffsetProcessor = limitOffsetProcessor;
093    }
094
095    public CommonsDialectImpl(KeywordWrap keywordWrap, LimitOffsetProcessor limitOffsetProcessor) {
096        this.keywordWrap = keywordWrap;
097        this.limitOffsetProcessor = limitOffsetProcessor;
098    }
099
100    @Override
101    public String wrap(String keyword) {
102        return ASTERISK.equals(keyword) || DUAL.equalsIgnoreCase(StringUtil.tryTrim(keyword)) ?
103            keyword : keywordWrap.wrap(keyword);
104    }
105
106    @Override
107    public String wrapColumnAlias(String keyword) {
108//        return ASTERISK.equals(keyword) ? keyword : keywordWrap.getPrefix() + keyword + keywordWrap.getSuffix();
109        return ASTERISK.equals(keyword) ? keyword : keywordWrap.wrap(keyword);
110    }
111
112    @Override
113    public String forHint(String hintString) {
114        return StringUtil.hasText(hintString) ? HINT_START + hintString + HINT_END : EMPTY;
115    }
116
117    @Override
118    public String forInsertRow(String schema, String tableName, Row row) {
119        StringBuilder fields = new StringBuilder();
120        StringBuilder paramsOrPlaceholder = new StringBuilder();
121
122        // 插入数据时,可能包含主键
123        Set<String> modifyAttrs = RowCPI.getInsertAttrs(row);
124        int index = 0;
125        for (String attr : modifyAttrs) {
126            fields.append(wrap(attr));
127
128            Object value = row.get(attr);
129            if (value instanceof RawValue) {
130                paramsOrPlaceholder.append(((RawValue) value).toSql(this));
131            } else {
132                paramsOrPlaceholder.append(PLACEHOLDER);
133            }
134            if (index != modifyAttrs.size() - 1) {
135                fields.append(DELIMITER);
136                paramsOrPlaceholder.append(DELIMITER);
137            }
138            index++;
139        }
140
141        String table = getRealTable(tableName, OperateType.INSERT);
142        StringBuilder sql = new StringBuilder();
143        sql.append(INSERT_INTO);
144        if (StringUtil.hasText(schema)) {
145            sql.append(wrap(getRealSchema(schema, table, OperateType.INSERT))).append(REFERENCE);
146        }
147        sql.append(wrap(table));
148        sql.append(BRACKET_LEFT).append(fields).append(BRACKET_RIGHT);
149        sql.append(VALUES).append(BRACKET_LEFT).append(paramsOrPlaceholder).append(BRACKET_RIGHT);
150        return sql.toString();
151    }
152
153
154    @Override
155    public String forInsertBatchWithFirstRowColumns(String schema, String tableName, List<Row> rows) {
156        StringBuilder fields = new StringBuilder();
157        StringBuilder questions = new StringBuilder();
158
159        Row firstRow = rows.get(0);
160        Set<String> attrs = RowCPI.getInsertAttrs(firstRow);
161        int index = 0;
162        for (String column : attrs) {
163            fields.append(wrap(column));
164            if (index != attrs.size() - 1) {
165                fields.append(DELIMITER);
166            }
167            index++;
168        }
169
170        for (int i = 0; i < rows.size(); i++) {
171            questions.append(SqlUtil.buildSqlParamPlaceholder(attrs.size()));
172            if (i != rows.size() - 1) {
173                questions.append(DELIMITER);
174            }
175        }
176
177        String table = getRealTable(tableName, OperateType.INSERT);
178        StringBuilder sql = new StringBuilder();
179        sql.append(INSERT_INTO);
180        if (StringUtil.hasText(schema)) {
181            sql.append(wrap(getRealSchema(schema, table, OperateType.INSERT))).append(REFERENCE);
182        }
183        sql.append(wrap(table));
184        sql.append(BLANK).append(BRACKET_LEFT)
185            .append(fields)
186            .append(BRACKET_RIGHT).append(BLANK);
187        sql.append(VALUES).append(questions);
188        return sql.toString();
189    }
190
191
192    @Override
193    public String forDeleteById(String schema, String tableName, String[] primaryKeys) {
194        assertPrimaryKeysNotEmpty(primaryKeys);
195        String table = getRealTable(tableName, OperateType.DELETE);
196        StringBuilder sql = new StringBuilder();
197        sql.append(DELETE_FROM);
198        if (StringUtil.hasText(schema)) {
199            sql.append(wrap(getRealSchema(schema, table, OperateType.DELETE))).append(REFERENCE);
200        }
201        sql.append(wrap(table));
202        sql.append(WHERE);
203        for (int i = 0; i < primaryKeys.length; i++) {
204            if (i > 0) {
205                sql.append(AND);
206            }
207            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
208        }
209        prepareAuth(schema, table, sql, OperateType.DELETE);
210        return sql.toString();
211    }
212
213
214    @Override
215    public String forDeleteBatchByIds(String schema, String tableName, String[] primaryKeys, Object[] ids) {
216        assertPrimaryKeysNotEmpty(primaryKeys);
217        String table = getRealTable(tableName, OperateType.DELETE);
218        StringBuilder sql = new StringBuilder();
219        sql.append(DELETE_FROM);
220        if (StringUtil.hasText(schema)) {
221            sql.append(wrap(getRealSchema(schema, table, OperateType.DELETE))).append(REFERENCE);
222        }
223
224        sql.append(wrap(table));
225        sql.append(WHERE);
226
227        // 多主键的场景
228        if (primaryKeys.length > 1) {
229            for (int i = 0; i < ids.length / primaryKeys.length; i++) {
230                if (i > 0) {
231                    sql.append(OR);
232                }
233                sql.append(BRACKET_LEFT);
234                for (int j = 0; j < primaryKeys.length; j++) {
235                    if (j > 0) {
236                        sql.append(AND);
237                    }
238                    sql.append(wrap(primaryKeys[j])).append(EQUALS_PLACEHOLDER);
239                }
240                sql.append(BRACKET_RIGHT);
241            }
242        }
243        // 单主键
244        else {
245            for (int i = 0; i < ids.length; i++) {
246                if (i > 0) {
247                    sql.append(OR);
248                }
249                sql.append(wrap(primaryKeys[0])).append(EQUALS_PLACEHOLDER);
250            }
251        }
252        prepareAuth(schema, table, sql, OperateType.DELETE);
253        return sql.toString();
254    }
255
256    @Override
257    public String forDeleteByQuery(QueryWrapper queryWrapper) {
258        prepareAuth(queryWrapper, OperateType.DELETE);
259        return buildDeleteSql(queryWrapper);
260    }
261
262    @Override
263    public String forUpdateById(String schema, String tableName, Row row) {
264        String table = getRealTable(tableName, OperateType.UPDATE);
265        StringBuilder sql = new StringBuilder();
266        Set<String> modifyAttrs = RowCPI.getModifyAttrs(row);
267        Map<String, RawValue> rawValueMap = RowCPI.getRawValueMap(row);
268        String[] primaryKeys = RowCPI.obtainsPrimaryKeyStrings(row);
269        assertPrimaryKeysNotEmpty(primaryKeys);
270
271        sql.append(UPDATE);
272        if (StringUtil.hasText(schema)) {
273            sql.append(wrap(getRealSchema(schema, table, OperateType.UPDATE))).append(REFERENCE);
274        }
275
276        sql.append(wrap(table)).append(SET);
277        int index = 0;
278        for (Map.Entry<String, Object> e : row.entrySet()) {
279            String colName = e.getKey();
280            if (modifyAttrs.contains(colName) && !ArrayUtil.contains(primaryKeys, colName)) {
281                if (index > 0) {
282                    sql.append(DELIMITER);
283                }
284                sql.append(wrap(colName));
285
286                if (rawValueMap.containsKey(colName)) {
287                    sql.append(EQUALS).append(rawValueMap.get(colName).toSql(this));
288                } else {
289                    sql.append(EQUALS_PLACEHOLDER);
290                }
291
292                index++;
293            }
294        }
295        sql.append(WHERE);
296        for (int i = 0; i < primaryKeys.length; i++) {
297            if (i > 0) {
298                sql.append(AND);
299            }
300            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
301        }
302        prepareAuth(schema, table, sql, OperateType.UPDATE);
303        return sql.toString();
304    }
305
306    @Override
307    public String forUpdateByQuery(QueryWrapper queryWrapper, Row row) {
308        prepareAuth(queryWrapper, OperateType.UPDATE);
309        StringBuilder sqlBuilder = new StringBuilder();
310
311        Set<String> modifyAttrs = RowCPI.getModifyAttrs(row);
312        Map<String, RawValue> rawValueMap = RowCPI.getRawValueMap(row);
313
314        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
315        if (queryTables == null || queryTables.size() != 1) {
316            throw FlexExceptions.wrap(LocalizedFormats.UPDATE_ONLY_SUPPORT_1_TABLE);
317        }
318
319        // fix: support schema
320        QueryTable queryTable = queryTables.get(0);
321        sqlBuilder.append(UPDATE).append(queryTable.toSql(this, OperateType.UPDATE)).append(SET);
322        int index = 0;
323        for (String modifyAttr : modifyAttrs) {
324            if (index > 0) {
325                sqlBuilder.append(DELIMITER);
326            }
327
328            sqlBuilder.append(wrap(modifyAttr));
329
330            if (rawValueMap.containsKey(modifyAttr)) {
331                sqlBuilder.append(EQUALS).append(rawValueMap.get(modifyAttr).toSql(this));
332            } else {
333                sqlBuilder.append(EQUALS_PLACEHOLDER);
334            }
335
336            index++;
337        }
338
339        buildJoinSql(sqlBuilder, queryWrapper, queryTables, OperateType.UPDATE);
340        buildWhereSql(sqlBuilder, queryWrapper, queryTables, false);
341        buildGroupBySql(sqlBuilder, queryWrapper, queryTables);
342        buildHavingSql(sqlBuilder, queryWrapper, queryTables);
343
344        // ignore orderBy and limit
345        buildOrderBySql(sqlBuilder, queryWrapper, queryTables);
346
347        Long limitRows = CPI.getLimitRows(queryWrapper);
348        Long limitOffset = CPI.getLimitOffset(queryWrapper);
349        if (limitRows != null || limitOffset != null) {
350            sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
351        }
352
353        return sqlBuilder.toString();
354    }
355
356    @Override
357    public String forUpdateBatchById(String schema, String tableName, List<Row> rows) {
358        if (rows.size() == 1) {
359            return forUpdateById(schema, tableName, rows.get(0));
360        }
361        StringBuilder sql = new StringBuilder();
362        for (Row row : rows) {
363            sql.append(forUpdateById(schema, tableName, row)).append(SEMICOLON).append(BLANK);
364        }
365        return sql.toString();
366    }
367
368
369    @Override
370    public String forSelectOneById(String schema, String tableName, String[] primaryKeys, Object[] primaryValues) {
371        assertPrimaryKeysNotEmpty(primaryKeys);
372        String table = getRealTable(tableName, OperateType.SELECT);
373        StringBuilder sql = new StringBuilder(SELECT_ALL_FROM);
374        if (StringUtil.hasText(schema)) {
375            sql.append(wrap(getRealSchema(schema, table, OperateType.SELECT))).append(REFERENCE);
376        }
377        sql.append(wrap(table)).append(WHERE);
378        for (int i = 0; i < primaryKeys.length; i++) {
379            if (i > 0) {
380                sql.append(AND);
381            }
382            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
383        }
384        prepareAuth(schema, table, sql, OperateType.SELECT);
385        return sql.toString();
386    }
387
388    @Override
389    public String forSelectByQuery(QueryWrapper queryWrapper) {
390        prepareAuth(queryWrapper, OperateType.SELECT);
391        return buildSelectSql(queryWrapper);
392    }
393
394
395    ////////////build query sql///////
396    @Override
397    public String buildSelectSql(QueryWrapper queryWrapper) {
398        return buildSelectSql(queryWrapper, Collections.emptyList());
399    }
400
401    @Override
402    public String buildSelectSql(QueryWrapper queryWrapper, List<QueryTable> contextTables) {
403        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
404
405        List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
406        List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
407        allTables = CollectionUtil.merge(allTables, contextTables);
408
409        List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
410
411        // 多个表,非 SELECT * 时,需要处理重名字段
412        if (allTables.size() > 1 && selectColumns != null && selectColumns.size() > 1) {
413            IntStream.range(0, selectColumns.size())
414                .boxed()
415                // 生成 索引-字段值 对应关系
416                .collect(Collectors.toMap(Function.identity(), selectColumns::get))
417                .entrySet()
418                .stream()
419                // 需要处理别名的情况
420                .filter(e -> StringUtil.hasText(e.getValue().getName()))
421                .filter(e -> StringUtil.noText(e.getValue().getAlias()))
422                .filter(e -> !"*".equals(e.getValue().getName()))
423                // 将相同字段对象放在一个集合里
424                .collect(Collectors.groupingBy(e -> e.getValue().getName(),
425                    Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)))
426                .values()
427                .stream()
428                // 过滤出来重名的字段
429                .filter(e -> e.size() > 1)
430                // 合并所有需要加别名的字段
431                .flatMap(Collection::stream)
432                // 过滤出来可以添加别名的列
433                .filter(e -> e.getValue().getTable() != null)
434                .filter(e -> StringUtil.hasText(e.getValue().getTable().getName()))
435                // 添加别名并放回原集合索引位置
436                .forEach(e -> selectColumns.set(e.getKey(),
437                    e.getValue().as(e.getValue().getTable().getName() + "$" + e.getValue().getName())));
438        }
439
440        StringBuilder sqlBuilder = new StringBuilder();
441        With with = CPI.getWith(queryWrapper);
442        if (with != null) {
443            sqlBuilder.append(with.toSql(this));
444        }
445
446        buildSelectColumnSql(sqlBuilder, allTables, selectColumns, CPI.getHint(queryWrapper));
447
448
449        if(CollectionUtil.isNotEmpty(queryTables)) {
450            sqlBuilder.append(FROM).append(StringUtil.join(DELIMITER, queryTables, queryTable -> queryTable.toSql(this, OperateType.SELECT)));
451        }
452
453        buildJoinSql(sqlBuilder, queryWrapper, allTables, OperateType.SELECT);
454        buildWhereSql(sqlBuilder, queryWrapper, allTables, true);
455        buildGroupBySql(sqlBuilder, queryWrapper, allTables);
456        buildHavingSql(sqlBuilder, queryWrapper, allTables);
457        buildOrderBySql(sqlBuilder, queryWrapper, allTables);
458
459        List<UnionWrapper> unions = CPI.getUnions(queryWrapper);
460        if (CollectionUtil.isNotEmpty(unions)) {
461            sqlBuilder.insert(0, BRACKET_LEFT).append(BRACKET_RIGHT);
462            for (UnionWrapper unionWrapper : unions) {
463                unionWrapper.buildSql(sqlBuilder, this);
464            }
465        }
466
467        Long limitRows = CPI.getLimitRows(queryWrapper);
468        Long limitOffset = CPI.getLimitOffset(queryWrapper);
469        if (limitRows != null || limitOffset != null) {
470            sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
471        }
472
473        List<String> endFragments = CPI.getEndFragments(queryWrapper);
474        if (CollectionUtil.isNotEmpty(endFragments)) {
475            for (String endFragment : endFragments) {
476                sqlBuilder.append(BLANK).append(endFragment);
477            }
478        }
479
480        return sqlBuilder.toString();
481    }
482
483    @Override
484    public String buildNoSelectSql(QueryWrapper queryWrapper) {
485        StringBuilder sqlBuilder = new StringBuilder();
486
487        buildJoinSql(sqlBuilder, queryWrapper, Collections.EMPTY_LIST, OperateType.SELECT);
488        buildWhereSql(sqlBuilder, queryWrapper, Collections.EMPTY_LIST, true);
489        buildGroupBySql(sqlBuilder, queryWrapper, Collections.EMPTY_LIST);
490        buildHavingSql(sqlBuilder, queryWrapper, Collections.EMPTY_LIST);
491        buildOrderBySql(sqlBuilder, queryWrapper, Collections.EMPTY_LIST);
492
493        List<UnionWrapper> unions = CPI.getUnions(queryWrapper);
494        if (CollectionUtil.isNotEmpty(unions)) {
495            if (sqlBuilder.length() > 0) {
496                sqlBuilder.insert(0, BRACKET_LEFT).append(BRACKET_RIGHT);
497            }
498            for (UnionWrapper unionWrapper : unions) {
499                unionWrapper.buildSql(sqlBuilder, this);
500            }
501        }
502
503        Long limitRows = CPI.getLimitRows(queryWrapper);
504        Long limitOffset = CPI.getLimitOffset(queryWrapper);
505        if (limitRows != null || limitOffset != null) {
506            sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
507        }
508
509        List<String> endFragments = CPI.getEndFragments(queryWrapper);
510        if (CollectionUtil.isNotEmpty(endFragments)) {
511            for (String endFragment : endFragments) {
512                sqlBuilder.append(BLANK).append(endFragment);
513            }
514        }
515
516        return sqlBuilder.toString();
517    }
518
519    private void buildSelectColumnSql(StringBuilder sqlBuilder, List<QueryTable> queryTables, List<QueryColumn> selectColumns, String hint) {
520        sqlBuilder.append(SELECT);
521        sqlBuilder.append(forHint(hint));
522        if (selectColumns == null || selectColumns.isEmpty()) {
523            sqlBuilder.append(ASTERISK);
524        } else {
525            int index = 0;
526            for (QueryColumn selectColumn : selectColumns) {
527                String selectColumnSql = CPI.toSelectSql(selectColumn, queryTables, this);
528                sqlBuilder.append(selectColumnSql);
529                if (index != selectColumns.size() - 1) {
530                    sqlBuilder.append(DELIMITER);
531                }
532                index++;
533            }
534        }
535    }
536
537
538    @Override
539    public String buildDeleteSql(QueryWrapper queryWrapper) {
540        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
541        List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
542        List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
543
544        // ignore selectColumns
545        StringBuilder sqlBuilder = new StringBuilder(DELETE);
546        String hint = CPI.getHint(queryWrapper);
547        if (StringUtil.hasText(hint)) {
548            sqlBuilder.append(BLANK).append(hint).deleteCharAt(sqlBuilder.length() - 1);
549        }
550
551        // delete with join
552        if (joinTables != null && !joinTables.isEmpty()) {
553            if (queryTables == null || queryTables.isEmpty()) {
554                throw new IllegalArgumentException("Delete with join sql must designate the from table.");
555            } else if (queryTables.size() != 1) {
556                throw new IllegalArgumentException("Delete with join sql must has 1 table only. but current has " + queryTables.size());
557            }
558            QueryTable queryTable = queryTables.get(0);
559            String table = getRealTable(queryTable.getName(), OperateType.DELETE);
560            if (StringUtil.hasText(queryTable.getSchema())) {
561                sqlBuilder.append(wrap(getRealSchema(queryTable.getSchema(), table, OperateType.DELETE))).append(REFERENCE);
562            }
563            sqlBuilder.append(BLANK).append(wrap(getRealTable(table, OperateType.DELETE)));
564        }
565
566
567        sqlBuilder.append(FROM).append(StringUtil.join(DELIMITER, queryTables, queryTable -> queryTable.toSql(this, OperateType.DELETE)));
568
569        buildJoinSql(sqlBuilder, queryWrapper, allTables, OperateType.DELETE);
570        buildWhereSql(sqlBuilder, queryWrapper, allTables, false);
571        buildGroupBySql(sqlBuilder, queryWrapper, allTables);
572        buildHavingSql(sqlBuilder, queryWrapper, allTables);
573
574        // ignore orderBy and limit
575        buildOrderBySql(sqlBuilder, queryWrapper, allTables);
576
577        Long limitRows = CPI.getLimitRows(queryWrapper);
578        Long limitOffset = CPI.getLimitOffset(queryWrapper);
579        if (limitRows != null || limitOffset != null) {
580            sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
581        }
582
583        List<String> endFragments = CPI.getEndFragments(queryWrapper);
584        if (CollectionUtil.isNotEmpty(endFragments)) {
585            for (String endFragment : endFragments) {
586                sqlBuilder.append(BLANK).append(endFragment);
587            }
588        }
589
590        return sqlBuilder.toString();
591    }
592
593
594    @Override
595    public String buildWhereConditionSql(QueryWrapper queryWrapper) {
596        QueryCondition whereQueryCondition = CPI.getWhereQueryCondition(queryWrapper);
597        return whereQueryCondition != null ? whereQueryCondition.toSql(CPI.getQueryTables(queryWrapper), this) : EMPTY;
598    }
599
600
601    @Override
602    public String forInsertEntity(TableInfo tableInfo, Object entity, boolean ignoreNulls) {
603        StringBuilder sql = new StringBuilder();
604        sql.append(INSERT_INTO).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.INSERT));
605
606        String[] insertColumns = tableInfo.obtainInsertColumns(entity, ignoreNulls);
607        Map<String, String> onInsertColumns = tableInfo.getOnInsertColumns();
608
609        Map<String, RawValue> rawValueMap = tableInfo.obtainUpdateRawValueMap(entity);
610
611        StringJoiner sqlFields = new StringJoiner(DELIMITER);
612        StringJoiner sqlValues = new StringJoiner(DELIMITER);
613
614        for (String insertColumn : insertColumns) {
615            sqlFields.add(wrap(insertColumn));
616            if (rawValueMap.containsKey(insertColumn)) {
617                sqlValues.add(rawValueMap.remove(insertColumn).toSql(this));
618            } else if (onInsertColumns != null && onInsertColumns.containsKey(insertColumn)) {
619                sqlValues.add(onInsertColumns.get(insertColumn));
620            } else {
621                sqlValues.add(PLACEHOLDER);
622            }
623        }
624
625        rawValueMap.forEach((k, v) -> {
626            sqlFields.add(wrap(k));
627            sqlValues.add(v.toSql(this));
628        });
629
630        return sql.append(BRACKET_LEFT).append(sqlFields).append(BRACKET_RIGHT)
631            .append(VALUES)
632            .append(BRACKET_LEFT).append(sqlValues).append(BRACKET_RIGHT)
633            .toString();
634    }
635
636
637    @Override
638    public String forInsertEntityWithPk(TableInfo tableInfo, Object entity, boolean ignoreNulls) {
639
640        StringBuilder sql = new StringBuilder();
641        sql.append(INSERT_INTO).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.INSERT));
642
643        String[] insertColumns = tableInfo.obtainInsertColumnsWithPk(entity, ignoreNulls);
644        Map<String, String> onInsertColumns = tableInfo.getOnInsertColumns();
645
646        StringJoiner sqlFields = new StringJoiner(DELIMITER);
647        StringJoiner sqlValues = new StringJoiner(DELIMITER);
648
649        for (String insertColumn : insertColumns) {
650            sqlFields.add(wrap(insertColumn));
651            if (onInsertColumns != null && onInsertColumns.containsKey(insertColumn)) {
652                sqlValues.add(onInsertColumns.get(insertColumn));
653            } else {
654                sqlValues.add(PLACEHOLDER);
655            }
656        }
657
658        return sql.append(BRACKET_LEFT).append(sqlFields).append(BRACKET_RIGHT)
659            .append(VALUES)
660            .append(BRACKET_LEFT).append(sqlValues).append(BRACKET_RIGHT)
661            .toString();
662    }
663
664
665    @Override
666    public String forInsertEntityBatch(TableInfo tableInfo, Collection<?> entities) {
667        StringBuilder sql = new StringBuilder();
668        sql.append(INSERT_INTO).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.INSERT));
669        String[] insertColumns = tableInfo.obtainInsertColumns(null, false);
670        String[] warpedInsertColumns = new String[insertColumns.length];
671        for (int i = 0; i < insertColumns.length; i++) {
672            warpedInsertColumns[i] = wrap(insertColumns[i]);
673        }
674        sql.append(BRACKET_LEFT)
675            .append(StringUtil.join(DELIMITER, warpedInsertColumns))
676            .append(BRACKET_RIGHT);
677        sql.append(VALUES);
678
679        Map<String, String> onInsertColumns = tableInfo.getOnInsertColumns();
680        for (int i = 0; i < entities.size(); i++) {
681            StringJoiner stringJoiner = new StringJoiner(DELIMITER, BRACKET_LEFT, BRACKET_RIGHT);
682            for (String insertColumn : insertColumns) {
683                if (onInsertColumns != null && onInsertColumns.containsKey(insertColumn)) {
684                    // 直接读取 onInsert 配置的值,而不用 "?" 代替
685                    stringJoiner.add(onInsertColumns.get(insertColumn));
686                } else {
687                    stringJoiner.add(PLACEHOLDER);
688                }
689            }
690            sql.append(stringJoiner);
691            if (i != entities.size() - 1) {
692                sql.append(DELIMITER);
693            }
694        }
695
696        return sql.toString();
697    }
698
699    @Override
700    public String forDeleteEntityById(TableInfo tableInfo) {
701        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
702        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
703
704        String[] primaryKeys = tableInfo.getPrimaryColumns();
705        assertPrimaryKeysNotEmpty(primaryKeys);
706
707        // 正常删除
708        if (StringUtil.noText(logicDeleteColumn)) {
709            String deleteByIdSql = forDeleteById(tableInfo.getSchema(), tableInfo.getTableName(), primaryKeys);
710            return tableInfo.buildTenantCondition(deleteByIdSql, tenantIdArgs, this);
711        }
712
713        // 逻辑删除
714        StringBuilder sql = new StringBuilder();
715        sql.append(UPDATE).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE));
716        sql.append(SET).append(buildLogicDeletedSet(logicDeleteColumn, tableInfo));
717        sql.append(WHERE);
718        for (int i = 0; i < primaryKeys.length; i++) {
719            if (i > 0) {
720                sql.append(AND);
721            }
722            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
723        }
724
725        sql.append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
726
727        // 租户ID
728        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
729        prepareAuth(tableInfo, sql, OperateType.DELETE);
730        return sql.toString();
731    }
732
733
734    @Override
735    public String forDeleteEntityBatchByIds(TableInfo tableInfo, Object[] primaryValues) {
736        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
737        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
738
739        String[] primaryKeys = tableInfo.getPrimaryColumns();
740        assertPrimaryKeysNotEmpty(primaryKeys);
741
742        // 正常删除
743        if (StringUtil.noText(logicDeleteColumn)) {
744            String deleteSQL = forDeleteBatchByIds(tableInfo.getSchema(), tableInfo.getTableName(), primaryKeys, primaryValues);
745
746            // 多租户
747            if (ArrayUtil.isNotEmpty(tenantIdArgs)) {
748                deleteSQL = deleteSQL.replace(WHERE, WHERE + BRACKET_LEFT) + BRACKET_RIGHT;
749                deleteSQL = tableInfo.buildTenantCondition(deleteSQL, tenantIdArgs, this);
750            }
751            return deleteSQL;
752        }
753
754        StringBuilder sql = new StringBuilder();
755        sql.append(UPDATE);
756        sql.append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE));
757        sql.append(SET).append(buildLogicDeletedSet(logicDeleteColumn, tableInfo));
758        sql.append(WHERE);
759        sql.append(BRACKET_LEFT);
760
761        // 多主键的场景
762        if (primaryKeys.length > 1) {
763            for (int i = 0; i < primaryValues.length / primaryKeys.length; i++) {
764                if (i > 0) {
765                    sql.append(OR);
766                }
767                sql.append(BRACKET_LEFT);
768                for (int j = 0; j < primaryKeys.length; j++) {
769                    if (j > 0) {
770                        sql.append(AND);
771                    }
772                    sql.append(wrap(primaryKeys[j])).append(EQUALS_PLACEHOLDER);
773                }
774                sql.append(BRACKET_RIGHT);
775            }
776        }
777        // 单主键
778        else {
779            for (int i = 0; i < primaryValues.length; i++) {
780                if (i > 0) {
781                    sql.append(OR);
782                }
783                sql.append(wrap(primaryKeys[0])).append(EQUALS_PLACEHOLDER);
784            }
785        }
786
787        sql.append(BRACKET_RIGHT).append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
788
789        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
790        prepareAuth(tableInfo, sql, OperateType.DELETE);
791        return sql.toString();
792    }
793
794    @Override
795    public String forDeleteEntityBatchByQuery(TableInfo tableInfo, QueryWrapper queryWrapper) {
796
797        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
798
799        // 正常删除
800        if (StringUtil.noText(logicDeleteColumn)) {
801            return forDeleteByQuery(queryWrapper);
802        }
803
804
805        prepareAuth(queryWrapper, OperateType.DELETE);
806        // 逻辑删除
807        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
808        List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
809        List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
810
811        // ignore selectColumns
812        StringBuilder sqlBuilder = new StringBuilder(UPDATE).append(forHint(CPI.getHint(queryWrapper)));
813        sqlBuilder.append(tableInfo.getWrapSchemaAndTableName(this, OperateType.DELETE));
814        sqlBuilder.append(SET).append(buildLogicDeletedSet(logicDeleteColumn, tableInfo));
815
816
817        buildJoinSql(sqlBuilder, queryWrapper, allTables, OperateType.DELETE);
818        buildWhereSql(sqlBuilder, queryWrapper, allTables, false);
819        buildGroupBySql(sqlBuilder, queryWrapper, allTables);
820        buildHavingSql(sqlBuilder, queryWrapper, allTables);
821
822        // ignore orderBy and limit
823        // buildOrderBySql(sqlBuilder, queryWrapper)
824        // buildLimitSql(sqlBuilder, queryWrapper)
825
826        return sqlBuilder.toString();
827    }
828
829
830    @Override
831    public String forUpdateEntity(TableInfo tableInfo, Object entity, boolean ignoreNulls) {
832        StringBuilder sql = new StringBuilder();
833
834        Set<String> updateColumns = tableInfo.obtainUpdateColumns(entity, ignoreNulls, false);
835        Map<String, RawValue> rawValueMap = tableInfo.obtainUpdateRawValueMap(entity);
836        String[] primaryKeys = tableInfo.getPrimaryColumns();
837        assertPrimaryKeysNotEmpty(primaryKeys);
838
839        sql.append(UPDATE).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE)).append(SET);
840
841        StringJoiner stringJoiner = new StringJoiner(DELIMITER);
842
843        for (String updateColumn : updateColumns) {
844            if (rawValueMap.containsKey(updateColumn)) {
845                stringJoiner.add(wrap(updateColumn) + EQUALS + rawValueMap.get(updateColumn).toSql(this));
846            } else {
847                stringJoiner.add(wrap(updateColumn) + EQUALS_PLACEHOLDER);
848            }
849        }
850
851        Map<String, String> onUpdateColumns = tableInfo.getOnUpdateColumns();
852        if (onUpdateColumns != null && !onUpdateColumns.isEmpty()) {
853            onUpdateColumns.forEach((column, value) -> stringJoiner.add(wrap(column) + EQUALS + value));
854        }
855
856        // 乐观锁字段
857        String versionColumn = tableInfo.getVersionColumn();
858        if (StringUtil.hasText(tableInfo.getOptimisticLockColumnOrSkip())) {
859            stringJoiner.add(wrap(versionColumn) + EQUALS + wrap(versionColumn) + " + 1 ");
860        }
861
862        sql.append(stringJoiner);
863
864        sql.append(WHERE);
865        for (int i = 0; i < primaryKeys.length; i++) {
866            if (i > 0) {
867                sql.append(AND);
868            }
869            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
870        }
871
872        // 逻辑删除条件,已删除的数据不能被修改
873        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
874        if (StringUtil.hasText(logicDeleteColumn)) {
875            sql.append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
876        }
877
878
879        // 租户ID字段
880        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
881        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
882
883        // 乐观锁条件
884        if (StringUtil.hasText(tableInfo.getOptimisticLockColumnOrSkip())) {
885            Object versionValue = tableInfo.buildColumnSqlArg(entity, versionColumn);
886            if (versionValue == null) {
887                throw FlexExceptions.wrap(LocalizedFormats.ENTITY_VERSION_NULL, entity);
888            }
889            sql.append(AND).append(wrap(versionColumn)).append(EQUALS).append(versionValue);
890        }
891
892        prepareAuth(tableInfo, sql, OperateType.UPDATE);
893        return sql.toString();
894    }
895
896    @Override
897    public String forUpdateEntityByQuery(TableInfo tableInfo, Object entity, boolean ignoreNulls, QueryWrapper queryWrapper) {
898        prepareAuth(queryWrapper, OperateType.UPDATE);
899        StringBuilder sqlBuilder = new StringBuilder();
900
901        Set<String> updateColumns = tableInfo.obtainUpdateColumns(entity, ignoreNulls, true);
902        Map<String, RawValue> rawValueMap = tableInfo.obtainUpdateRawValueMap(entity);
903
904        sqlBuilder.append(UPDATE).append(forHint(CPI.getHint(queryWrapper)));
905        sqlBuilder.append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE));
906
907        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
908        List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
909        List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
910        buildJoinSql(sqlBuilder, queryWrapper, allTables, OperateType.UPDATE);
911
912
913        sqlBuilder.append(SET);
914
915        StringJoiner stringJoiner = new StringJoiner(DELIMITER);
916
917        for (String modifyAttr : updateColumns) {
918            if (rawValueMap.containsKey(modifyAttr)) {
919                stringJoiner.add(wrap(modifyAttr) + EQUALS + rawValueMap.get(modifyAttr).toSql(this));
920            } else {
921                stringJoiner.add(wrap(modifyAttr) + EQUALS_PLACEHOLDER);
922            }
923        }
924
925
926        Map<String, String> onUpdateColumns = tableInfo.getOnUpdateColumns();
927        if (onUpdateColumns != null && !onUpdateColumns.isEmpty()) {
928            onUpdateColumns.forEach((column, value) -> stringJoiner.add(wrap(column) + EQUALS + value));
929        }
930
931        // 乐观锁字段
932        String versionColumn = tableInfo.getVersionColumn();
933        if (StringUtil.hasText(tableInfo.getOptimisticLockColumnOrSkip())) {
934            stringJoiner.add(wrap(versionColumn) + EQUALS + wrap(versionColumn) + " + 1 ");
935        }
936
937        sqlBuilder.append(stringJoiner);
938
939
940        buildWhereSql(sqlBuilder, queryWrapper, queryTables, false);
941        buildGroupBySql(sqlBuilder, queryWrapper, queryTables);
942        buildHavingSql(sqlBuilder, queryWrapper, queryTables);
943
944        // ignore orderBy and limit
945        buildOrderBySql(sqlBuilder, queryWrapper, queryTables);
946
947        Long limitRows = CPI.getLimitRows(queryWrapper);
948        Long limitOffset = CPI.getLimitOffset(queryWrapper);
949        if (limitRows != null || limitOffset != null) {
950            sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
951        }
952
953
954        List<String> endFragments = CPI.getEndFragments(queryWrapper);
955        if (CollectionUtil.isNotEmpty(endFragments)) {
956            for (String endFragment : endFragments) {
957                sqlBuilder.append(BLANK).append(endFragment);
958            }
959        }
960
961        return sqlBuilder.toString();
962    }
963
964
965    @Override
966    public String forSelectOneEntityById(TableInfo tableInfo) {
967        StringBuilder sql = new StringBuilder();
968        buildSelectColumnSql(sql, null, null, null);
969        sql.append(FROM).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.SELECT));
970        sql.append(WHERE);
971        String[] pKeys = tableInfo.getPrimaryColumns();
972        assertPrimaryKeysNotEmpty(pKeys);
973
974        for (int i = 0; i < pKeys.length; i++) {
975            if (i > 0) {
976                sql.append(AND);
977            }
978            sql.append(wrap(pKeys[i])).append(EQUALS_PLACEHOLDER);
979        }
980
981        // 逻辑删除的情况下,需要添加逻辑删除的条件
982        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
983        if (StringUtil.hasText(logicDeleteColumn)) {
984            sql.append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
985        }
986
987        // 多租户
988        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
989        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
990        prepareAuth(tableInfo, sql, OperateType.SELECT);
991        return sql.toString();
992    }
993
994
995    @Override
996    public String forSelectEntityListByIds(TableInfo tableInfo, Object[] primaryValues) {
997        StringBuilder sql = new StringBuilder();
998        buildSelectColumnSql(sql, null, tableInfo.getDefaultQueryColumn(), null);
999        sql.append(FROM).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.SELECT));
1000        sql.append(WHERE);
1001        String[] primaryKeys = tableInfo.getPrimaryColumns();
1002        assertPrimaryKeysNotEmpty(primaryKeys);
1003
1004        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
1005        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
1006        if (StringUtil.hasText(logicDeleteColumn) || ArrayUtil.isNotEmpty(tenantIdArgs)) {
1007            sql.append(BRACKET_LEFT);
1008        }
1009
1010        // 多主键的场景
1011        if (primaryKeys.length > 1) {
1012            for (int i = 0; i < primaryValues.length / primaryKeys.length; i++) {
1013                if (i > 0) {
1014                    sql.append(OR);
1015                }
1016                sql.append(BRACKET_LEFT);
1017                for (int j = 0; j < primaryKeys.length; j++) {
1018                    if (j > 0) {
1019                        sql.append(AND);
1020                    }
1021                    sql.append(wrap(primaryKeys[j])).append(EQUALS_PLACEHOLDER);
1022                }
1023                sql.append(BRACKET_RIGHT);
1024            }
1025        }
1026        // 单主键
1027        else {
1028            for (int i = 0; i < primaryValues.length; i++) {
1029                if (i > 0) {
1030                    sql.append(OR);
1031                }
1032                sql.append(wrap(primaryKeys[0])).append(EQUALS_PLACEHOLDER);
1033            }
1034        }
1035
1036        if (StringUtil.hasText(logicDeleteColumn) || ArrayUtil.isNotEmpty(tenantIdArgs)) {
1037            sql.append(BRACKET_RIGHT);
1038        }
1039
1040
1041        if (StringUtil.hasText(logicDeleteColumn)) {
1042            sql.append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
1043        }
1044
1045        // 多租户
1046        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
1047        prepareAuth(tableInfo, sql, OperateType.SELECT);
1048        return sql.toString();
1049    }
1050
1051
1052    protected boolean buildJoinSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables, OperateType operateType) {
1053        List<Join> joins = CPI.getJoins(queryWrapper);
1054        boolean joinSuccess = false;
1055        if (joins != null && !joins.isEmpty()) {
1056            for (Join join : joins) {
1057                if (!join.checkEffective()) {
1058                    continue;
1059                }
1060                sqlBuilder.append(join.toSql(queryTables, this, operateType));
1061                joinSuccess = true;
1062            }
1063        }
1064        return joinSuccess;
1065    }
1066
1067
1068    protected void buildWhereSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables, boolean allowNoCondition) {
1069        QueryCondition whereQueryCondition = CPI.getWhereQueryCondition(queryWrapper);
1070        if (whereQueryCondition != null) {
1071            String whereSql = whereQueryCondition.toSql(queryTables, this);
1072            if (StringUtil.hasText(whereSql)) {
1073                sqlBuilder.append(WHERE).append(whereSql);
1074            } else if (!allowNoCondition) {
1075                throw FlexExceptions.wrap(LocalizedFormats.UPDATE_OR_DELETE_NOT_ALLOW);
1076            }
1077        } else {
1078            // whereQueryCondition == null
1079            if (!allowNoCondition) {
1080                throw FlexExceptions.wrap(LocalizedFormats.UPDATE_OR_DELETE_NOT_ALLOW);
1081            }
1082        }
1083    }
1084
1085
1086    protected void buildGroupBySql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
1087        List<QueryColumn> groupByColumns = CPI.getGroupByColumns(queryWrapper);
1088        if (groupByColumns != null && !groupByColumns.isEmpty()) {
1089            sqlBuilder.append(GROUP_BY);
1090            int index = 0;
1091            for (QueryColumn groupByColumn : groupByColumns) {
1092                String groupBy = CPI.toConditionSql(groupByColumn, queryTables, this);
1093                sqlBuilder.append(groupBy);
1094                if (index != groupByColumns.size() - 1) {
1095                    sqlBuilder.append(DELIMITER);
1096                }
1097                index++;
1098            }
1099        }
1100    }
1101
1102
1103    protected void buildHavingSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
1104        QueryCondition havingQueryCondition = CPI.getHavingQueryCondition(queryWrapper);
1105        if (havingQueryCondition != null) {
1106            String havingSql = havingQueryCondition.toSql(queryTables, this);
1107            if (StringUtil.hasText(havingSql)) {
1108                sqlBuilder.append(HAVING).append(havingSql);
1109            }
1110        }
1111    }
1112
1113
1114    protected void buildOrderBySql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
1115        List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
1116        if (orderBys != null && !orderBys.isEmpty()) {
1117            sqlBuilder.append(ORDER_BY);
1118            int index = 0;
1119            for (QueryOrderBy orderBy : orderBys) {
1120                sqlBuilder.append(orderBy.toSql(queryTables, this));
1121                if (index != orderBys.size() - 1) {
1122                    sqlBuilder.append(DELIMITER);
1123                }
1124                index++;
1125            }
1126        }
1127    }
1128
1129
1130    /**
1131     * 构建 limit 和 offset 的参数
1132     */
1133    protected StringBuilder buildLimitOffsetSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, Long limitRows, Long limitOffset) {
1134        return limitOffsetProcessor.process(this, sqlBuilder, queryWrapper, limitRows, limitOffset);
1135    }
1136
1137
1138    protected String buildLogicNormalCondition(String logicColumn, TableInfo tableInfo) {
1139        return LogicDeleteManager.getProcessor().buildLogicNormalCondition(logicColumn, tableInfo, this);
1140    }
1141
1142
1143    protected String buildLogicDeletedSet(String logicColumn, TableInfo tableInfo) {
1144        return LogicDeleteManager.getProcessor().buildLogicDeletedSet(logicColumn, tableInfo, this);
1145    }
1146
1147    /**
1148     * 断言主键非空
1149     *
1150     * @param primaryKeys 主键
1151     */
1152    protected void assertPrimaryKeysNotEmpty(String[] primaryKeys) {
1153        if (Objects.isNull(primaryKeys) || primaryKeys.length == 0 || Arrays.stream(primaryKeys).allMatch(String::isEmpty)) {
1154            throw FlexExceptions.wrap("primary key not recognized! Please check the @com.mybatisflex.annotation.Id annotation");
1155        }
1156    }
1157}