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