Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/sqlancer/clickhouse/ClickHouseErrors.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static List<String> getExpectedExpressionErrors() {

"Unknown element '", "Cannot parse infinity.",
"Cannot parse number with a sign character but without any numeric character",
"Cannot parse number without any digits",
"Cannot parse number with multiple sign (+/-) characters or intermediate sign character",
"Cannot parse string", "Cannot read floating point value",
"Cyclic aliases: default expression and column type are incompatible", "Directory for table data",
Expand Down
38 changes: 36 additions & 2 deletions src/sqlancer/clickhouse/ClickHouseSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ public static class ClickHouseTable

private final String engine;
private final String samplingKey;
private final boolean hasUnfinishedMutations;

public ClickHouseTable(String tableName, List<ClickHouseColumn> columns, List<TableIndex> indexes,
boolean isView) {
Expand All @@ -448,9 +449,15 @@ public ClickHouseTable(String tableName, List<ClickHouseColumn> columns, List<Ta

public ClickHouseTable(String tableName, List<ClickHouseColumn> columns, List<TableIndex> indexes,
boolean isView, String engine, String samplingKey) {
this(tableName, columns, indexes, isView, engine, samplingKey, false);
}

public ClickHouseTable(String tableName, List<ClickHouseColumn> columns, List<TableIndex> indexes,
boolean isView, String engine, String samplingKey, boolean hasUnfinishedMutations) {
super(tableName, columns, indexes, isView);
this.engine = engine == null ? "" : engine;
this.samplingKey = samplingKey == null ? "" : samplingKey;
this.hasUnfinishedMutations = hasUnfinishedMutations;
}

public String getEngine() {
Expand All @@ -471,22 +478,34 @@ public boolean supportsFinal() {
|| engine.equals("VersionedCollapsingMergeTree");
}

public boolean hasUnfinishedMutations() {
return hasUnfinishedMutations;
}

/*
* A table with an unfinished mutation is not stable either. The typical one is an `ALTER ... MODIFY COLUMN`
* whose conversion cannot succeed on the stored values (`String` -> `UInt64` over `''`): the new type is
* committed to the metadata before the mutation runs, the mutation keeps failing, and every read of the column
* converts the old parts on the fly and throws `CANNOT_PARSE_NUMBER`, `ATTEMPT_TO_READ_AFTER_EOF` or
* `CANNOT_PARSE_TEXT` "while reading from part". That is the server working as designed, not a finding.
*/
public boolean isStableForRepeatedReads() {
return !supportsFinal();
return !supportsFinal() && !hasUnfinishedMutations;
}
}

public static ClickHouseSchema fromConnection(SQLConnection con, String databaseName) throws SQLException {
List<ClickHouseTable> databaseTables = new ArrayList<>();
List<String> tableNames = getTableNames(con);
java.util.Map<String, TableMeta> metaByName = getTableMeta(con, databaseName);
java.util.Set<String> tablesWithUnfinishedMutations = getTablesWithUnfinishedMutations(con, databaseName);
for (String tableName : tableNames) {
List<ClickHouseColumn> databaseColumns = getTableColumns(con, tableName);
List<TableIndex> indexes = Collections.emptyList();
boolean isView = matchesViewName(tableName);
TableMeta meta = metaByName.getOrDefault(tableName, TableMeta.EMPTY);
ClickHouseTable t = new ClickHouseTable(tableName, databaseColumns, indexes, isView, meta.engine,
meta.samplingKey);
meta.samplingKey, tablesWithUnfinishedMutations.contains(tableName));
for (ClickHouseColumn c : databaseColumns) {
c.setTable(t);
}
Expand Down Expand Up @@ -522,6 +541,21 @@ private static java.util.Map<String, TableMeta> getTableMeta(SQLConnection con,
return meta;
}

private static java.util.Set<String> getTablesWithUnfinishedMutations(SQLConnection con, String databaseName)
throws SQLException {
java.util.Set<String> tables = new java.util.HashSet<>();
try (Statement s = con.createStatement()) {
String q = "SELECT DISTINCT table FROM system.mutations WHERE database = '"
+ databaseName.replace("'", "''") + "' AND NOT is_done";
try (ResultSet rs = s.executeQuery(q)) {
while (rs.next()) {
tables.add(rs.getString(1));
}
}
}
return tables;
}

private static List<String> getTableNames(SQLConnection con) throws SQLException {
List<String> tableNames = new ArrayList<>();
try (Statement s = con.createStatement()) {
Expand Down
Loading