Skip to content

Commit

Permalink
Merge pull request #1871 from ClickHouse/clientv2_fix_column_to_gette…
Browse files Browse the repository at this point in the history
…r_matching

added matching strategy option
  • Loading branch information
chernser authored Oct 18, 2024
2 parents 1704cab + bda4611 commit 5dc8e67
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 11 deletions.
42 changes: 31 additions & 11 deletions client-v2/src/main/java/com/clickhouse/client/api/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import com.clickhouse.client.api.internal.SettingsConverter;
import com.clickhouse.client.api.internal.TableSchemaParser;
import com.clickhouse.client.api.internal.ValidationUtils;
import com.clickhouse.client.api.metadata.ColumnToMethodMatchingStrategy;
import com.clickhouse.client.api.metadata.DefaultColumnToMethodMatchingStrategy;
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.metrics.ClientMetrics;
import com.clickhouse.client.api.metrics.OperationMetrics;
Expand Down Expand Up @@ -146,8 +148,10 @@ public class Client implements AutoCloseable {
private Map<String, TableSchema> tableSchemaCache = new ConcurrentHashMap<>();
private Map<String, Boolean> tableSchemaHasDefaults = new ConcurrentHashMap<>();

private final ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy;

private Client(Set<String> endpoints, Map<String,String> configuration, boolean useNewImplementation,
ExecutorService sharedOperationExecutor) {
ExecutorService sharedOperationExecutor, ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy) {
this.endpoints = endpoints;
this.configuration = configuration;
this.endpoints.forEach(endpoint -> {
Expand All @@ -170,6 +174,7 @@ private Client(Set<String> endpoints, Map<String,String> configuration, boolean
this.oldClient = ClientV1AdaptorHelper.createClient(configuration);
LOG.info("Using old http client implementation");
}
this.columnToMethodMatchingStrategy = columnToMethodMatchingStrategy;
}

/**
Expand Down Expand Up @@ -211,6 +216,7 @@ public static class Builder {
private boolean useNewImplementation = true;

private ExecutorService sharedOperationExecutor = null;
private ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy;

public Builder() {
this.endpoints = new HashSet<>();
Expand Down Expand Up @@ -857,6 +863,18 @@ public Builder serverSetting(String name, Collection<String> values) {
return this;
}

/**
* Sets column to method matching strategy. It is used while registering POJO serializers and deserializers.
* Default is {@link DefaultColumnToMethodMatchingStrategy}.
*
* @param strategy - matching strategy
* @return same instance of the builder
*/
public Builder columnToMethodMatchingStrategy(ColumnToMethodMatchingStrategy strategy) {
this.columnToMethodMatchingStrategy = strategy;
return this;
}

public Client build() {
setDefaults();

Expand Down Expand Up @@ -914,7 +932,7 @@ public Client build() {
throw new IllegalArgumentException("Nor server timezone nor specific timezone is set");
}

return new Client(this.endpoints, this.configuration, this.useNewImplementation, this.sharedOperationExecutor);
return new Client(this.endpoints, this.configuration, this.useNewImplementation, this.sharedOperationExecutor, this.columnToMethodMatchingStrategy);
}

private static final int DEFAULT_NETWORK_BUFFER_SIZE = 300_000;
Expand Down Expand Up @@ -986,6 +1004,10 @@ private void setDefaults() {
if (!configuration.containsKey("client_allow_binary_reader_to_reuse_buffers")) {
allowBinaryReaderToReuseBuffers(false);
}

if (columnToMethodMatchingStrategy == null) {
columnToMethodMatchingStrategy = DefaultColumnToMethodMatchingStrategy.INSTANCE;
}
}
}

Expand Down Expand Up @@ -1041,19 +1063,17 @@ public synchronized void register(Class<?> clazz, TableSchema schema) {
}
tableSchemaCache.put(schemaKey, schema);

ColumnToMethodMatchingStrategy matchingStrategy = columnToMethodMatchingStrategy;

//Create a new POJOSerializer with static .serialize(object, columns) methods
Map<String, Method> classGetters = new HashMap<>();
Map<String, Method> classSetters = new HashMap<>();
for (Method method : clazz.getMethods()) {//Clean up the method names
String methodName = method.getName();
if (methodName.startsWith("get") || methodName.startsWith("has")) {
methodName = methodName.substring(3).toLowerCase();
classGetters.put(methodName, method);
} else if (methodName.startsWith("is")) {
methodName = methodName.substring(2).toLowerCase();
if (matchingStrategy.isGetter(method.getName())) {
String methodName = matchingStrategy.normalizeMethodName(method.getName());
classGetters.put(methodName, method);
} else if (methodName.startsWith("set")) {
methodName = methodName.substring(3).toLowerCase();
} else if (matchingStrategy.isSetter(method.getName())) {
String methodName = matchingStrategy.normalizeMethodName(method.getName());
classSetters.put(methodName, method);
}
}
Expand All @@ -1063,7 +1083,7 @@ public synchronized void register(Class<?> clazz, TableSchema schema) {
boolean defaultsSupport = schema.hasDefaults();
tableSchemaHasDefaults.put(schemaKey, defaultsSupport);
for (ClickHouseColumn column : schema.getColumns()) {
String propertyName = column.getColumnName().toLowerCase().replace("_", "").replace(".", "");
String propertyName = columnToMethodMatchingStrategy.normalizeColumnName(column.getColumnName());
Method getterMethod = classGetters.get(propertyName);
if (getterMethod != null) {
schemaSerializers.put(column.getColumnName(), (obj, stream) -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.clickhouse.client.api.metadata;


/**
* Strategy to match column names to method names.
*/
public interface ColumnToMethodMatchingStrategy {

/**
* Normalizes method name to match column name.
* @param methodName original method name
* @return normalized method name
*/
String normalizeMethodName(String methodName);

/**
* Checks if the method is a setter.
* @param methodName original (not normalized) method name
* @return true if the method is a setter
*/
boolean isSetter(String methodName);

/**
* Checks if the method is a getter.
* @param methodName original (not normalized) method name
* @return true if the method is a getter
*/
boolean isGetter(String methodName);

/**
* Normalizes column name to match method name.
* @param columnName original column name
* @return normalized column name
*/
String normalizeColumnName(String columnName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.clickhouse.client.api.metadata;


import java.util.regex.Pattern;

/**
* Default implementation of {@link ColumnToMethodMatchingStrategy} takes the following rules:
* <ul>
* <li>Method name is normalized by removing prefixes like "get", "set", "is", "has".</li>
* <li>Column name is normalized by removing special characters like "-", "_", ".".</li>
* <li>Normalized method name and column name are compared case-insensitively.</li>
* </ul>
*
*
*/
public class DefaultColumnToMethodMatchingStrategy implements ColumnToMethodMatchingStrategy {

public static final DefaultColumnToMethodMatchingStrategy INSTANCE = new DefaultColumnToMethodMatchingStrategy();

private final Pattern getterPattern;
private final Pattern setterPattern;

private final Pattern methodReplacePattern;

private final Pattern columnReplacePattern;


public DefaultColumnToMethodMatchingStrategy() {
this("^(get|is|has).+", "^(set).+", "^(get|set|is|has)|_", "[-_.]");
}

public DefaultColumnToMethodMatchingStrategy(String getterPatternRegEx, String setterPaternRegEx, String methodReplacePatternRegEx, String columnReplacePatternRegEx) {
this.getterPattern = Pattern.compile(getterPatternRegEx);
this.setterPattern = Pattern.compile(setterPaternRegEx);
this.methodReplacePattern = Pattern.compile(methodReplacePatternRegEx);
this.columnReplacePattern = Pattern.compile(columnReplacePatternRegEx);
}

@Override
public String normalizeMethodName(String methodName) {
return methodReplacePattern.matcher(methodName).replaceAll("").toLowerCase();
}

@Override
public boolean isSetter(String methodName) {
return setterPattern.matcher(methodName).matches();
}

@Override
public boolean isGetter(String methodName) {
return getterPattern.matcher(methodName).matches();
}

@Override
public String normalizeColumnName(String columnName) {
return columnReplacePattern.matcher(columnName).replaceAll("").toLowerCase();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
import com.clickhouse.client.ClickHouseRequest;
import com.clickhouse.client.ClickHouseResponse;
import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.metadata.DefaultColumnToMethodMatchingStrategy;
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.data.ClickHouseColumn;
import com.clickhouse.data.ClickHouseRecord;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.Iterator;
Expand Down Expand Up @@ -82,4 +84,28 @@ private void prepareDataSet(String tableName) {
Assert.fail("Failed to prepare data set", e);
}
}

@Test(groups = {"integration"}, dataProvider = "testMatchingNormalizationData")
public void testDefaultColumnToMethodMatchingStrategy(String methodName, String columnName) {
methodName = DefaultColumnToMethodMatchingStrategy.INSTANCE.normalizeMethodName(methodName);
columnName = DefaultColumnToMethodMatchingStrategy.INSTANCE.normalizeColumnName(columnName);
Assert.assertEquals(methodName, columnName, "Method name: " + methodName + " Column name: " + columnName);
}

@DataProvider(name = "testMatchingNormalizationData")
public Object[][] testMatchingNormalizationData() {
return new Object[][]{
{"getLastName", "LastName"},
{"getLastName", "last_name"},
{"getLastName", "last.name"},
{"setLastName", "last.name"},
{"isLastUpdate", "last_update"},
{"hasMore", "more"},
{"getFIRST_NAME", "first_name"},
{"setUPDATED_ON", "updated.ON"},
{"getNUM_OF_TRIES", "num_of_tries"},
{"gethas_more", "has_more"},

};
}
}

0 comments on commit 5dc8e67

Please sign in to comment.