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