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