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