001package com.mybatisflex.core.dialect.impl;
002
003import com.mybatisflex.core.dialect.KeywordWrap;
004import com.mybatisflex.core.dialect.LimitOffsetProcessor;
005import com.mybatisflex.core.dialect.OperateType;
006import com.mybatisflex.core.exception.FlexExceptions;
007import com.mybatisflex.core.exception.locale.LocalizedFormats;
008import com.mybatisflex.core.query.CPI;
009import com.mybatisflex.core.query.QueryTable;
010import com.mybatisflex.core.query.QueryWrapper;
011import com.mybatisflex.core.row.Row;
012import com.mybatisflex.core.row.RowCPI;
013import com.mybatisflex.core.table.TableInfo;
014import com.mybatisflex.core.update.RawValue;
015import com.mybatisflex.core.util.ArrayUtil;
016import com.mybatisflex.core.util.CollectionUtil;
017import com.mybatisflex.core.util.StringUtil;
018
019import java.util.List;
020import java.util.Map;
021import java.util.Set;
022import java.util.StringJoiner;
023
024import static com.mybatisflex.core.constant.SqlConsts.*;
025
026/**
027 * @author: 老唐
028 * @date: 2024-07-20 11:36
029 * @version: 1.0
030 */
031public class ClickhouseDialectImpl extends CommonsDialectImpl {
032    public static final String ALTER_TABLE = " ALTER TABLE ";
033    public static final String CK_DELETE = " DELETE ";
034    public static final String CK_UPDATE = " UPDATE ";
035
036    public ClickhouseDialectImpl(KeywordWrap keywordWrap, LimitOffsetProcessor limitOffsetProcessor) {
037        super(keywordWrap, limitOffsetProcessor);
038    }
039
040    /**
041     * 根据主键更新
042     *
043     * @param schema
044     * @param tableName
045     * @param row
046     * @return
047     */
048    @Override
049    public String forUpdateById(String schema, String tableName, Row row) {
050        //eg: ALTER TABLE test  UPDATE USERNAME = ? , AGE = ?  WHERE CUSERID = ?
051        String table = getRealTable(tableName, OperateType.UPDATE);
052        StringBuilder sql = new StringBuilder();
053        Set<String> modifyAttrs = RowCPI.getModifyAttrs(row);
054        Map<String, RawValue> rawValueMap = RowCPI.getRawValueMap(row);
055        String[] primaryKeys = RowCPI.obtainsPrimaryKeyStrings(row);
056
057        sql.append(ALTER_TABLE);
058        if (StringUtil.isNotBlank(schema)) {
059            sql.append(wrap(getRealSchema(schema, table, OperateType.UPDATE))).append(REFERENCE);
060        }
061        sql.append(wrap(table)).append(CK_UPDATE);
062        int index = 0;
063        for (Map.Entry<String, Object> e : row.entrySet()) {
064            String colName = e.getKey();
065            if (modifyAttrs.contains(colName) && !ArrayUtil.contains(primaryKeys, colName)) {
066                if (index > 0) {
067                    sql.append(DELIMITER);
068                }
069                sql.append(wrap(colName));
070
071                if (rawValueMap.containsKey(colName)) {
072                    sql.append(EQUALS).append(rawValueMap.get(colName).toSql(this));
073                } else {
074                    sql.append(EQUALS_PLACEHOLDER);
075                }
076
077                index++;
078            }
079        }
080        sql.append(WHERE);
081        for (int i = 0; i < primaryKeys.length; i++) {
082            if (i > 0) {
083                sql.append(AND);
084            }
085            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
086        }
087        prepareAuth(schema, table, sql, OperateType.UPDATE);
088        return sql.toString();
089    }
090
091    /**
092     * 根据主键删除
093     *
094     * @param schema
095     * @param tableName
096     * @param primaryKeys
097     * @return
098     */
099    @Override
100    public String forDeleteById(String schema, String tableName, String[] primaryKeys) {
101        //eg: ALTER TABLE test  DELETE WHERE CUSERID = ?
102        String table = getRealTable(tableName, OperateType.DELETE);
103        StringBuilder sql = new StringBuilder();
104
105        sql.append(ALTER_TABLE);
106        if (StringUtil.isNotBlank(schema)) {
107            sql.append(wrap(getRealSchema(schema, table, OperateType.DELETE))).append(REFERENCE);
108        }
109        sql.append(wrap(table));
110        sql.append(CK_DELETE);
111        sql.append(WHERE);
112        for (int i = 0; i < primaryKeys.length; i++) {
113            if (i > 0) {
114                sql.append(AND);
115            }
116            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
117        }
118        prepareAuth(schema, table, sql, OperateType.DELETE);
119        return sql.toString();
120    }
121
122    /**
123     * 根据查询更新
124     *
125     * @param queryWrapper
126     * @param row
127     * @return
128     */
129    @Override
130    public String forUpdateByQuery(QueryWrapper queryWrapper, Row row) {
131        //eg: ALTER TABLE test  UPDATE USERNAME = ? , AGE = ?  WHERE CUSERID = ?
132        prepareAuth(queryWrapper, OperateType.UPDATE);
133        StringBuilder sql = new StringBuilder();
134
135        Set<String> modifyAttrs = RowCPI.getModifyAttrs(row);
136        Map<String, RawValue> rawValueMap = RowCPI.getRawValueMap(row);
137
138        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
139        if (queryTables == null || queryTables.size() != 1) {
140            throw FlexExceptions.wrap(LocalizedFormats.UPDATE_ONLY_SUPPORT_1_TABLE);
141        }
142        sql.append(ALTER_TABLE);
143        // fix: support schema
144        QueryTable queryTable = queryTables.get(0);
145        sql.append(queryTable.toSql(this, OperateType.UPDATE)).append(CK_UPDATE);
146        int index = 0;
147        for (String modifyAttr : modifyAttrs) {
148            if (index > 0) {
149                sql.append(DELIMITER);
150            }
151
152            sql.append(wrap(modifyAttr));
153
154            if (rawValueMap.containsKey(modifyAttr)) {
155                sql.append(EQUALS).append(rawValueMap.get(modifyAttr).toSql(this));
156            } else {
157                sql.append(EQUALS_PLACEHOLDER);
158            }
159
160            index++;
161        }
162
163        buildJoinSql(sql, queryWrapper, queryTables, OperateType.UPDATE);
164        buildWhereSql(sql, queryWrapper, queryTables, false);
165        buildGroupBySql(sql, queryWrapper, queryTables);
166        buildHavingSql(sql, queryWrapper, queryTables);
167
168        // ignore orderBy and limit
169        buildOrderBySql(sql, queryWrapper, queryTables);
170
171        Long limitRows = CPI.getLimitRows(queryWrapper);
172        Long limitOffset = CPI.getLimitOffset(queryWrapper);
173        if (limitRows != null || limitOffset != null) {
174            sql = buildLimitOffsetSql(sql, queryWrapper, limitRows, limitOffset);
175        }
176        return sql.toString();
177    }
178
179    /**
180     * 根据主键批量删除
181     *
182     * @param schema
183     * @param tableName
184     * @param primaryKeys
185     * @param ids
186     * @return
187     */
188    @Override
189    public String forDeleteBatchByIds(String schema, String tableName, String[] primaryKeys, Object[] ids) {
190        //eg: ALTER TABLE test  DELETE WHERE CUSERID = ?
191        String table = getRealTable(tableName, OperateType.DELETE);
192        StringBuilder sql = new StringBuilder();
193        sql.append(ALTER_TABLE);
194        if (StringUtil.isNotBlank(schema)) {
195            sql.append(wrap(getRealSchema(schema, table, OperateType.DELETE))).append(REFERENCE);
196        }
197        sql.append(wrap(table));
198        sql.append(CK_DELETE);
199        sql.append(WHERE);
200
201        // 多主键的场景
202        if (primaryKeys.length > 1) {
203            for (int i = 0; i < ids.length / primaryKeys.length; i++) {
204                if (i > 0) {
205                    sql.append(OR);
206                }
207                sql.append(BRACKET_LEFT);
208                for (int j = 0; j < primaryKeys.length; j++) {
209                    if (j > 0) {
210                        sql.append(AND);
211                    }
212                    sql.append(wrap(primaryKeys[j])).append(EQUALS_PLACEHOLDER);
213                }
214                sql.append(BRACKET_RIGHT);
215            }
216        }
217        // 单主键
218        else {
219            for (int i = 0; i < ids.length; i++) {
220                if (i > 0) {
221                    sql.append(OR);
222                }
223                sql.append(wrap(primaryKeys[0])).append(EQUALS_PLACEHOLDER);
224            }
225        }
226        prepareAuth(schema, table, sql, OperateType.DELETE);
227        return sql.toString();
228    }
229
230    /**
231     * 实体 根据主键批量删除及逻辑删除
232     *
233     * @param tableInfo
234     * @param primaryValues
235     * @return
236     */
237    @Override
238    public String forDeleteEntityBatchByIds(TableInfo tableInfo, Object[] primaryValues) {
239        //eg: ALTER TABLE test  UPDATE DR = ?  WHERE CUSERID = ?
240        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
241        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
242
243        // 正常删除
244        if (StringUtil.isBlank(logicDeleteColumn)) {
245            String deleteSQL = forDeleteBatchByIds(tableInfo.getSchema(), tableInfo.getTableName(), tableInfo.getPrimaryColumns(), primaryValues);
246
247            // 多租户
248            if (ArrayUtil.isNotEmpty(tenantIdArgs)) {
249                deleteSQL = deleteSQL.replace(WHERE, WHERE + BRACKET_LEFT) + BRACKET_RIGHT;
250                deleteSQL = tableInfo.buildTenantCondition(deleteSQL, tenantIdArgs, this);
251            }
252            return deleteSQL;
253        }
254
255        StringBuilder sql = new StringBuilder();
256        sql.append(ALTER_TABLE);
257        sql.append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE));
258        sql.append(CK_UPDATE).append(buildLogicDeletedSet(logicDeleteColumn, tableInfo));
259        sql.append(WHERE);
260        sql.append(BRACKET_LEFT);
261
262        String[] primaryKeys = tableInfo.getPrimaryColumns();
263
264        // 多主键的场景
265        if (primaryKeys.length > 1) {
266            for (int i = 0; i < primaryValues.length / primaryKeys.length; i++) {
267                if (i > 0) {
268                    sql.append(OR);
269                }
270                sql.append(BRACKET_LEFT);
271                for (int j = 0; j < primaryKeys.length; j++) {
272                    if (j > 0) {
273                        sql.append(AND);
274                    }
275                    sql.append(wrap(primaryKeys[j])).append(EQUALS_PLACEHOLDER);
276                }
277                sql.append(BRACKET_RIGHT);
278            }
279        }
280        // 单主键
281        else {
282            for (int i = 0; i < primaryValues.length; i++) {
283                if (i > 0) {
284                    sql.append(OR);
285                }
286                sql.append(wrap(primaryKeys[0])).append(EQUALS_PLACEHOLDER);
287            }
288        }
289
290        sql.append(BRACKET_RIGHT).append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
291
292        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
293        prepareAuth(tableInfo, sql, OperateType.DELETE);
294        return sql.toString();
295    }
296
297    @Override
298    public String buildDeleteSql(QueryWrapper queryWrapper) {
299        //eg: ALTER TABLE test  DELETE WHERE CUSERID = ?
300        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
301        List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
302        List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
303        // delete with join
304        if (joinTables != null && !joinTables.isEmpty()) {
305            throw new IllegalArgumentException("Delete query not support join sql ");
306        }
307        // ignore selectColumns
308        StringBuilder sqlBuilder = new StringBuilder(ALTER_TABLE);
309        String hint = CPI.getHint(queryWrapper);
310        if (StringUtil.isNotBlank(hint)) {
311            sqlBuilder.append(BLANK).append(hint).deleteCharAt(sqlBuilder.length() - 1);
312        }
313
314        sqlBuilder.append(StringUtil.join(DELIMITER, queryTables, queryTable -> queryTable.toSql(this, OperateType.DELETE)));
315        sqlBuilder.append(CK_DELETE);
316
317        buildWhereSql(sqlBuilder, queryWrapper, allTables, false);
318        buildGroupBySql(sqlBuilder, queryWrapper, allTables);
319        buildHavingSql(sqlBuilder, queryWrapper, allTables);
320
321        // ignore orderBy and limit
322        buildOrderBySql(sqlBuilder, queryWrapper, allTables);
323
324        Long limitRows = CPI.getLimitRows(queryWrapper);
325        Long limitOffset = CPI.getLimitOffset(queryWrapper);
326        if (limitRows != null || limitOffset != null) {
327            sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
328        }
329
330        List<String> endFragments = CPI.getEndFragments(queryWrapper);
331        if (CollectionUtil.isNotEmpty(endFragments)) {
332            for (String endFragment : endFragments) {
333                sqlBuilder.append(BLANK).append(endFragment);
334            }
335        }
336
337        return sqlBuilder.toString();
338    }
339
340    @Override
341    public String forDeleteEntityBatchByQuery(TableInfo tableInfo, QueryWrapper queryWrapper) {
342        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
343
344        // 正常删除
345        if (StringUtil.isBlank(logicDeleteColumn)) {
346            return forDeleteByQuery(queryWrapper);
347        }
348
349
350        prepareAuth(queryWrapper, OperateType.DELETE);
351        // 逻辑删除
352        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
353        List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
354        List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
355
356        // ignore selectColumns
357        //eg: ALTER TABLE test  UPDATE DR = ?  WHERE CUSERID = ?
358        StringBuilder sqlBuilder = new StringBuilder(ALTER_TABLE).append(forHint(CPI.getHint(queryWrapper)));
359        sqlBuilder.append(tableInfo.getWrapSchemaAndTableName(this, OperateType.DELETE));
360        sqlBuilder.append(CK_UPDATE).append(buildLogicDeletedSet(logicDeleteColumn, tableInfo));
361
362
363        buildJoinSql(sqlBuilder, queryWrapper, allTables, OperateType.DELETE);
364        buildWhereSql(sqlBuilder, queryWrapper, allTables, false);
365        buildGroupBySql(sqlBuilder, queryWrapper, allTables);
366        buildHavingSql(sqlBuilder, queryWrapper, allTables);
367
368        // ignore orderBy and limit
369        // buildOrderBySql(sqlBuilder, queryWrapper)
370        // buildLimitSql(sqlBuilder, queryWrapper)
371
372        return sqlBuilder.toString();
373    }
374
375
376    @Override
377    public String forUpdateEntity(TableInfo tableInfo, Object entity, boolean ignoreNulls) {
378        //eg: ALTER TABLE test  UPDATE AGE = ?  WHERE CUSERID = ?
379        StringBuilder sql = new StringBuilder();
380
381        Set<String> updateColumns = tableInfo.obtainUpdateColumns(entity, ignoreNulls, false);
382        Map<String, RawValue> rawValueMap = tableInfo.obtainUpdateRawValueMap(entity);
383        String[] primaryKeys = tableInfo.getPrimaryColumns();
384
385        sql.append(ALTER_TABLE)
386                .append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE))
387                .append(CK_UPDATE);
388
389        StringJoiner stringJoiner = new StringJoiner(DELIMITER);
390
391        for (String updateColumn : updateColumns) {
392            if (rawValueMap.containsKey(updateColumn)) {
393                stringJoiner.add(wrap(updateColumn) + EQUALS + rawValueMap.get(updateColumn).toSql(this));
394            } else {
395                stringJoiner.add(wrap(updateColumn) + EQUALS_PLACEHOLDER);
396            }
397        }
398
399        Map<String, String> onUpdateColumns = tableInfo.getOnUpdateColumns();
400        if (onUpdateColumns != null && !onUpdateColumns.isEmpty()) {
401            onUpdateColumns.forEach((column, value) -> stringJoiner.add(wrap(column) + EQUALS + value));
402        }
403
404        // 乐观锁字段
405        String versionColumn = tableInfo.getVersionColumn();
406        if (StringUtil.isNotBlank(versionColumn)) {
407            stringJoiner.add(wrap(versionColumn) + EQUALS + wrap(versionColumn) + " + 1 ");
408        }
409
410        sql.append(stringJoiner);
411
412        sql.append(WHERE);
413        for (int i = 0; i < primaryKeys.length; i++) {
414            if (i > 0) {
415                sql.append(AND);
416            }
417            sql.append(wrap(primaryKeys[i])).append(EQUALS_PLACEHOLDER);
418        }
419
420        // 逻辑删除条件,已删除的数据不能被修改
421        String logicDeleteColumn = tableInfo.getLogicDeleteColumnOrSkip();
422        if (StringUtil.isNotBlank(logicDeleteColumn)) {
423            sql.append(AND).append(buildLogicNormalCondition(logicDeleteColumn, tableInfo));
424        }
425
426
427        // 租户ID字段
428        Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
429        tableInfo.buildTenantCondition(sql, tenantIdArgs, this);
430
431        // 乐观锁条件
432        if (StringUtil.isNotBlank(versionColumn)) {
433            Object versionValue = tableInfo.buildColumnSqlArg(entity, versionColumn);
434            if (versionValue == null) {
435                throw FlexExceptions.wrap(LocalizedFormats.ENTITY_VERSION_NULL, entity);
436            }
437            sql.append(AND).append(wrap(versionColumn)).append(EQUALS).append(versionValue);
438        }
439
440        prepareAuth(tableInfo, sql, OperateType.UPDATE);
441        return sql.toString();
442    }
443
444    @Override
445    public String forUpdateEntityByQuery(TableInfo tableInfo, Object entity, boolean ignoreNulls, QueryWrapper queryWrapper) {
446        //eg: ALTER TABLE test  UPDATE DR = ?  WHERE CUSERID = ?
447        prepareAuth(queryWrapper, OperateType.UPDATE);
448        StringBuilder sqlBuilder = new StringBuilder();
449
450        Set<String> updateColumns = tableInfo.obtainUpdateColumns(entity, ignoreNulls, true);
451        Map<String, RawValue> rawValueMap = tableInfo.obtainUpdateRawValueMap(entity);
452
453        sqlBuilder.append(ALTER_TABLE).append(forHint(CPI.getHint(queryWrapper)));
454        sqlBuilder.append(tableInfo.getWrapSchemaAndTableName(this, OperateType.UPDATE));
455
456        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
457        buildJoinSql(sqlBuilder, queryWrapper, queryTables, OperateType.UPDATE);
458
459        sqlBuilder.append(CK_UPDATE);
460
461        StringJoiner stringJoiner = new StringJoiner(DELIMITER);
462
463        for (String modifyAttr : updateColumns) {
464            if (rawValueMap.containsKey(modifyAttr)) {
465                stringJoiner.add(wrap(modifyAttr) + EQUALS + rawValueMap.get(modifyAttr).toSql(this));
466            } else {
467                stringJoiner.add(wrap(modifyAttr) + EQUALS_PLACEHOLDER);
468            }
469        }
470
471
472        Map<String, String> onUpdateColumns = tableInfo.getOnUpdateColumns();
473        if (onUpdateColumns != null && !onUpdateColumns.isEmpty()) {
474            onUpdateColumns.forEach((column, value) -> stringJoiner.add(wrap(column) + EQUALS + value));
475        }
476
477        // 乐观锁字段
478        String versionColumn = tableInfo.getVersionColumn();
479        if (StringUtil.isNotBlank(versionColumn)) {
480            stringJoiner.add(wrap(versionColumn) + EQUALS + wrap(versionColumn) + " + 1 ");
481        }
482
483        sqlBuilder.append(stringJoiner);
484
485
486        buildWhereSql(sqlBuilder, queryWrapper, queryTables, false);
487        buildGroupBySql(sqlBuilder, queryWrapper, queryTables);
488        buildHavingSql(sqlBuilder, queryWrapper, queryTables);
489
490        // ignore orderBy and limit
491        buildOrderBySql(sqlBuilder, queryWrapper, queryTables);
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
500        List<String> endFragments = CPI.getEndFragments(queryWrapper);
501        if (CollectionUtil.isNotEmpty(endFragments)) {
502            for (String endFragment : endFragments) {
503                sqlBuilder.append(BLANK).append(endFragment);
504            }
505        }
506
507        return sqlBuilder.toString();
508    }
509}