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