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}