Skip to content

Commit

Permalink
[#442]: Support LowCardinality series datatype (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
demetribu authored Sep 23, 2023
1 parent fe79d3e commit 2c97af7
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,7 @@
import com.github.housepower.data.type.DataTypeUInt64;
import com.github.housepower.data.type.DataTypeUInt8;
import com.github.housepower.data.type.DataTypeUUID;
import com.github.housepower.data.type.complex.DataTypeArray;
import com.github.housepower.data.type.complex.DataTypeCreator;
import com.github.housepower.data.type.complex.DataTypeDateTime;
import com.github.housepower.data.type.complex.DataTypeDateTime64;
import com.github.housepower.data.type.complex.DataTypeDecimal;
import com.github.housepower.data.type.complex.DataTypeEnum16;
import com.github.housepower.data.type.complex.DataTypeEnum8;
import com.github.housepower.data.type.complex.DataTypeFixedString;
import com.github.housepower.data.type.complex.DataTypeMap;
import com.github.housepower.data.type.complex.DataTypeNothing;
import com.github.housepower.data.type.complex.DataTypeNullable;
import com.github.housepower.data.type.complex.DataTypeString;
import com.github.housepower.data.type.complex.DataTypeTuple;
import com.github.housepower.data.type.complex.*;
import com.github.housepower.misc.LRUCache;
import com.github.housepower.misc.SQLLexer;
import com.github.housepower.misc.Validate;
Expand Down Expand Up @@ -89,6 +77,8 @@ public class DataTypeFactory {
return DataTypeDateTime64.creator.createDataType(lexer, serverContext);
} else if (dataTypeName.equalsIgnoreCase("Nullable")) {
return DataTypeNullable.creator.createDataType(lexer, serverContext);
} else if (dataTypeName.equalsIgnoreCase("LowCardinality")) {
return DataTypeLowCardinality.creator.createDataType(lexer, serverContext);
} else if (dataTypeName.equalsIgnoreCase("FixedString") || dataTypeName.equals("Binary")) {
return DataTypeFixedString.creator.createDataType(lexer, serverContext);
} else if (dataTypeName.equalsIgnoreCase("Decimal")) {
Expand All @@ -98,7 +88,7 @@ public class DataTypeFactory {
} else if (dataTypeName.equalsIgnoreCase("Nothing")) {
return DataTypeNothing.CREATOR.createDataType(lexer, serverContext);
} else if (dataTypeName.equalsIgnoreCase("Map")) {
return DataTypeMap.creator.createDataType(lexer, serverContext);
return DataTypeMap.creator.createDataType(lexer, serverContext);
} else {
IDataType<?, ?> dataType = dataTypes.get(dataTypeName.toLowerCase(Locale.ROOT));
Validate.isTrue(dataType != null, "Unknown data type: " + dataTypeName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.housepower.data.type.complex;

import com.github.housepower.data.DataTypeFactory;
import com.github.housepower.data.IDataType;
import com.github.housepower.misc.SQLLexer;
import com.github.housepower.misc.Validate;
import com.github.housepower.serde.BinaryDeserializer;
import com.github.housepower.serde.BinarySerializer;

import java.io.IOException;
import java.sql.SQLException;

public class DataTypeLowCardinality implements IDataType {

public static DataTypeCreator creator = (lexer, serverContext) -> {
Validate.isTrue(lexer.character() == '(');
IDataType nestedType = DataTypeFactory.get(lexer, serverContext);
Validate.isTrue(lexer.character() == ')');
return new DataTypeLowCardinality(
"LowCardinality(" + nestedType.name() + ")", nestedType);
};

private final String name;
private final IDataType nestedDataType;

public DataTypeLowCardinality(String name, IDataType nestedDataType) {
this.name = name;
this.nestedDataType = nestedDataType;
}

@Override
public String name() {
return this.name;
}

@Override
public int sqlTypeId() {
return this.nestedDataType.sqlTypeId();
}

@Override
public Object defaultValue() {
return this.nestedDataType.defaultValue();
}

@Override
public Class javaType() {
return this.nestedDataType.javaType();
}

@Override
public Class jdbcJavaType() {
return this.nestedDataType.jdbcJavaType();
}

@Override
public boolean nullable() {
return this.nestedDataType.nullable();
}

@Override
public int getPrecision() {
return this.nestedDataType.getPrecision();
}

@Override
public int getScale() {
return this.nestedDataType.getScale();
}

@Override
public Object deserializeText(SQLLexer lexer) throws SQLException {
return this.nestedDataType.deserializeText(lexer);
}

@Override
public void serializeBinary(Object data, BinarySerializer serializer) throws SQLException, IOException {
this.nestedDataType.serializeBinary(data, serializer);
}

@Override
public void serializeBinaryBulk(Object[] data, BinarySerializer serializer) throws SQLException, IOException {
this.nestedDataType.serializeBinaryBulk(data, serializer);
}

@Override
public Object deserializeBinary(BinaryDeserializer deserializer) throws SQLException, IOException {
return this.nestedDataType.deserializeBinary(deserializer);
}

@Override
public Object[] deserializeBinaryBulk(int rows, BinaryDeserializer deserializer) throws SQLException, IOException {
Object[] data = this.nestedDataType.deserializeBinaryBulk(rows, deserializer);
return data;
}

@Override
public boolean isSigned() {
return this.nestedDataType.isSigned();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,12 @@ public class SettingKey implements Serializable {
.withDescription("Enable conditional computations")
.build();

public static SettingKey allow_suspicious_low_cardinality_types = SettingKey.builder()
.withName("allow_suspicious_low_cardinality_types")
.withType(SettingType.Int32)
.withDescription("Permits the use of LowCardinality with data types that might negatively impact performance")
.build();

public static SettingKey allow_experimental_bigint_types = SettingKey.builder()
.withName("allow_experimental_bigint_types")
.withType(SettingType.Int64)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.housepower.jdbc;

import com.github.housepower.misc.BytesHelper;
import org.junit.jupiter.api.Test;

import java.sql.*;

import static org.junit.jupiter.api.Assertions.*;

//Refer to [[https://github.com/housepower/ClickHouse-Native-JDBC/issues/442]] for more details.
public class LowCardinalityTypeTest extends AbstractITest implements BytesHelper {
@Test
public void testAllLowCardinalityTypes() throws Exception {
withStatement(statement -> {
statement.execute("DROP TABLE IF EXISTS low_cardinality_test");

StringBuilder createTableSQL = new StringBuilder();
createTableSQL.append("CREATE TABLE IF NOT EXISTS low_cardinality_test (")
.append("value_string LowCardinality(String), ")
.append("fixed_string LowCardinality(FixedString(10)), ")
.append("date_value LowCardinality(Nullable(Date)), ")
.append("datetime_value LowCardinality(Nullable(DateTime)), ")
.append("number_value LowCardinality(Nullable(Int32))) Engine=Memory()");

statement.execute(createTableSQL.toString());

String sql = "INSERT INTO low_cardinality_test " +
"(value_string, fixed_string, date_value, datetime_value, number_value) values(?, ?, ?, ?, ?);";

try (PreparedStatement pstmt = statement.getConnection().prepareStatement(sql)) {
for (int i = 0; i < 300; i++) {
pstmt.setString(1, "test");
pstmt.setString(2, "abcdefghij");
if (i % 50 == 0) {
pstmt.setNull(3, Types.DATE);
pstmt.setNull(4, Types.TIMESTAMP);
pstmt.setNull(5, Types.INTEGER);
} else {
pstmt.setDate(3, new java.sql.Date(System.currentTimeMillis()));
pstmt.setTimestamp(4, new java.sql.Timestamp(System.currentTimeMillis()));
pstmt.setInt(5, i);
}
pstmt.addBatch();
}
pstmt.executeBatch();
}

DatabaseMetaData metaData = statement.getConnection().getMetaData();
ResultSet columns = metaData.getColumns(null, "default", "low_cardinality_test", "%");
while (columns.next()) {
String columnName = columns.getString("COLUMN_NAME");
String columnType = columns.getString("TYPE_NAME");
switch (columnName) {
case "value_string":
assertEquals(columnType, "LowCardinality(String)");
break;
case "fixed_string":
assertEquals(columnType, "LowCardinality(FixedString(10))");
break;
case "date_value":
assertEquals(columnType, "LowCardinality(Nullable(Date))");
break;
case "datetime_value":
assertEquals(columnType, "LowCardinality(Nullable(DateTime))");
break;
case "number_value":
assertEquals(columnType, "LowCardinality(Nullable(Int32))");
break;
}
}

ResultSet rs = statement.executeQuery("SELECT * FROM low_cardinality_test;");
int size = 0;
while (rs.next()) {
String valueStr = rs.getString("value_string");
String fixedStr = rs.getString("fixed_string");
Date dateValue = rs.getDate("date_value");
Timestamp datetimeValue = rs.getTimestamp("datetime_value");
Integer numberValue = (Integer) rs.getObject("number_value");

assertEquals("test", valueStr);
assertEquals("abcdefghij", fixedStr);
if (size % 50 == 0) {
assertNull(dateValue);
assertNull(datetimeValue);
assertNull(numberValue);
} else {
assertNotNull(dateValue);
assertNotNull(datetimeValue);
assertTrue(numberValue >= 0 && numberValue < 300);
}

size++;
}
assertEquals(300, size);

statement.execute("DROP TABLE IF EXISTS low_cardinality_test");
}, "allow_suspicious_low_cardinality_types", "1");
}
}

0 comments on commit 2c97af7

Please sign in to comment.