[Code] basic property related classes supports create, query, read, write, etc. (#6153)
Provides basic property related classes supports create, query, read, write, etc.
Currently, Doris FE mostly uses `if` statement to check properties in SQL. There is a lot of redundancy in the code.
The `PropertySet` class can be used in the analysis phase of `Statement`. The validation and correctness of the input properties are automatic verified. It can simplify the code and improve the readability of the code.
Usage:
1. Create a custom class that implements `SchemaGroup` interface.
2. Define the properties to be used. If it's a required parameter, there is no need to set the default value.
3. According the the requirements, in the logic called `readFromStrMap` and other functions to check and obtain parameters.
Demo:
Class definition
```
public class FileFormat implements PropertySchema.SchemaGroup {
public static final PropertySchema<FileFormat.Type> FILE_FORMAT_TYPE =
new PropertySchema.EnumProperty<>("type", FileFormat.Type.class).setDefauleValue(FileFormat.Type.CSV);
public static final PropertySchema<String> RECORD_DELIMITER =
new PropertySchema.StringProperty("record_delimiter").setDefauleValue("\n");
public static final PropertySchema<String> FIELD_DELIMITER =
new PropertySchema.StringProperty("field_delimiter").setDefauleValue("|");
public static final PropertySchema<Integer> SKIP_HEADER =
new PropertySchema.IntProperty("skip_header", true).setMin(0).setDefauleValue(0);
private static final FileFormat INSTANCE = new FileFormat();
private ImmutableMap<String, PropertySchema> schemas = PropertySchema.createSchemas(
FILE_FORMAT_TYPE,
RECORD_DELIMITER,
FIELD_DELIMITER,
SKIP_HEADER);
public ImmutableMap<String, PropertySchema> getSchemas() {
return schemas;
}
public static FileFormat get() {
return INSTANCE;
}
}
```
Usage
```
public class CreateXXXStmt extends DdlStmt {
private PropertiesSet<FileFormat> analyzedFileFormat = PropertiesSet.empty(FileFormat.get());
private final Map<String, String> fileFormatOptions;
...
public void analyze(Analyzer analyzer) throws UserException {
...
if (fileFormatOptions != null) {
try {
analyzedFileFormat = PropertiesSet.readFromStrMap(FileFormat.get(), fileFormatOptions);
} catch (IllegalArgumentException e) {
...
}
}
// 1. Get property value
String recordDelimiter = analyzedFileFormat.get(FileFormat.RECORD_DELIMITER)
// 2. Check the validity of parameters
PropertiesSet.verifyKey(FileFormat.get(), fileFormatOptions);
...
}
}
```
This commit is contained in:
@ -0,0 +1,179 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.doris.common.property;
|
||||
|
||||
import org.apache.doris.common.io.Text;
|
||||
import org.apache.doris.thrift.TPropertyVal;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PropertiesSet<T extends PropertySchema.SchemaGroup> {
|
||||
private static final Map<PropertySchema.SchemaGroup, PropertiesSet> emptyInstances = new HashMap<>();
|
||||
|
||||
private final T schemaGroup;
|
||||
private final Map<String, Object> properties;
|
||||
private List<PropertySchema> modifiedSchemas;
|
||||
|
||||
private PropertiesSet(T schemaGroup, Map<String, Object> properties) {
|
||||
this.schemaGroup = schemaGroup;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <U> U get(PropertySchema<U> prop) throws NoSuchElementException {
|
||||
return properties.containsKey(prop.getName()) ?
|
||||
(U) properties.get(prop.getName()) : prop.getDefaultValue().get();
|
||||
}
|
||||
|
||||
public List<PropertySchema> getModifiedSchemas() {
|
||||
if (modifiedSchemas == null) {
|
||||
synchronized (this) {
|
||||
modifiedSchemas = properties.keySet().stream()
|
||||
.map(key -> schemaGroup.getSchemas().get(key))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
return modifiedSchemas;
|
||||
}
|
||||
|
||||
private static <TSchemaGroup extends PropertySchema.SchemaGroup, TRaw> void checkRequiredKey(
|
||||
TSchemaGroup schemaGroup, Map<String, TRaw> rawProperties) throws NoSuchElementException {
|
||||
List<String> requiredKey = schemaGroup.getSchemas().values().stream()
|
||||
.filter(propertySchema -> !propertySchema.getDefaultValue().isPresent())
|
||||
.map(PropertySchema::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<String> missingKeys = requiredKey.stream()
|
||||
.filter(key -> !rawProperties.containsKey(key))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!missingKeys.isEmpty()) {
|
||||
throw new NoSuchElementException("Missing " + missingKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public static <TSchemaGroup extends PropertySchema.SchemaGroup> void verifyKey(
|
||||
TSchemaGroup schemaGroup, List<String> rawProperties)
|
||||
throws IllegalArgumentException {
|
||||
rawProperties.forEach(entry -> {
|
||||
if (!schemaGroup.getSchemas().containsKey(entry.toLowerCase())) {
|
||||
throw new IllegalArgumentException("Invalid property " + entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, TPropertyVal> writeToThrift() {
|
||||
Map<String, TPropertyVal> ret = new HashMap<>(properties.size());
|
||||
properties.forEach((key, value) -> {
|
||||
TPropertyVal out = new TPropertyVal();
|
||||
schemaGroup.getSchemas().get(key).write(value, out);
|
||||
ret.put(key, out);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void writeToData(DataOutput out) throws IOException {
|
||||
out.writeInt(properties.size());
|
||||
for (Map.Entry<String, Object> entry : properties.entrySet()) {
|
||||
Text.writeString(out, entry.getKey());
|
||||
schemaGroup.getSchemas().get(entry.getKey()).write(entry.getValue(), out);
|
||||
}
|
||||
}
|
||||
|
||||
private interface ReadLambda<TParsed, TRaw> {
|
||||
TParsed accept(PropertySchema schema, TRaw raw);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <TSchemaGroup extends PropertySchema.SchemaGroup, TParsed, TRaw> PropertiesSet<TSchemaGroup> read(
|
||||
TSchemaGroup schemaGroup, Map<String, TRaw> rawProperties, ReadLambda<TParsed, TRaw> reader)
|
||||
throws IllegalArgumentException, NoSuchElementException {
|
||||
checkRequiredKey(schemaGroup, rawProperties);
|
||||
Map<String, Object> properties = new HashMap<>(rawProperties.size());
|
||||
|
||||
rawProperties.forEach((key, value) -> {
|
||||
String entryKey = key.toLowerCase();
|
||||
if (!schemaGroup.getSchemas().containsKey(entryKey)) {
|
||||
throw new IllegalArgumentException("Invalid property " + key);
|
||||
}
|
||||
PropertySchema schema = schemaGroup.getSchemas().get(entryKey);
|
||||
properties.put(entryKey, reader.accept(schema, value));
|
||||
});
|
||||
|
||||
return new PropertiesSet(schemaGroup, properties);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <TSchemaGroup extends PropertySchema.SchemaGroup> PropertiesSet<TSchemaGroup> empty(
|
||||
TSchemaGroup schemaGroup) {
|
||||
if (!emptyInstances.containsKey(schemaGroup)) {
|
||||
synchronized (PropertiesSet.class) {
|
||||
if (!emptyInstances.containsKey(schemaGroup)) {
|
||||
emptyInstances.put(schemaGroup, new PropertiesSet(schemaGroup, Collections.emptyMap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyInstances.get(schemaGroup);
|
||||
}
|
||||
|
||||
public static <TSchemaGroup extends PropertySchema.SchemaGroup> PropertiesSet<TSchemaGroup> readFromStrMap(
|
||||
TSchemaGroup schemaGroup, Map<String, String> rawProperties)
|
||||
throws IllegalArgumentException {
|
||||
|
||||
return read(schemaGroup, rawProperties, PropertySchema::read);
|
||||
}
|
||||
|
||||
public static <TSchemaGroup extends PropertySchema.SchemaGroup> PropertiesSet<TSchemaGroup> readFromThrift(
|
||||
TSchemaGroup schemaGroup, Map<String, TPropertyVal> rawProperties)
|
||||
throws IllegalArgumentException {
|
||||
|
||||
return read(schemaGroup, rawProperties, PropertySchema::read);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <TSchemaGroup extends PropertySchema.SchemaGroup> PropertiesSet<TSchemaGroup> readFromData(
|
||||
TSchemaGroup schemaGroup, DataInput input)
|
||||
throws IllegalArgumentException, IOException {
|
||||
int size = input.readInt();
|
||||
Map<String, Object> properties = new HashMap<>(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
String key = Text.readString(input).toLowerCase();
|
||||
Object val = schemaGroup.getSchemas().get(key).read(input);
|
||||
properties.put(key, val);
|
||||
}
|
||||
|
||||
return new PropertiesSet(schemaGroup, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return properties.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,399 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.doris.common.property;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.apache.doris.common.io.Text;
|
||||
import org.apache.doris.thrift.TPropertyVal;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings({"unchecked","rawtypes"})
|
||||
public abstract class PropertySchema<T> {
|
||||
private final String name;
|
||||
private final boolean required;
|
||||
private Optional<T> defaultValue = Optional.empty();
|
||||
private Optional<T> maxValue = Optional.empty();
|
||||
private Optional<T> minValue = Optional.empty();
|
||||
|
||||
protected PropertySchema(String name) {
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
public PropertySchema(String name, boolean required) {
|
||||
this.name = name;
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public static ImmutableMap<String, PropertySchema> createSchemas(PropertySchema ... schemas) {
|
||||
ImmutableMap.Builder builder = ImmutableMap.builder();
|
||||
Arrays.stream(schemas).forEach(s -> builder.put(s.getName(), s));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public interface SchemaGroup {
|
||||
ImmutableMap<String, PropertySchema> getSchemas();
|
||||
}
|
||||
|
||||
public static final class StringProperty extends ComparableProperty<String> {
|
||||
StringProperty(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
StringProperty(String name, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(String rawVal) throws IllegalArgumentException {
|
||||
verifyRange(rawVal);
|
||||
return rawVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(TPropertyVal tVal) throws IllegalArgumentException {
|
||||
verifyRange(tVal.getStrVal());
|
||||
return tVal.getStrVal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(DataInput input) throws IOException {
|
||||
return Text.readString(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String val, TPropertyVal out) {
|
||||
out.setStrVal(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String val, DataOutput out) throws IOException {
|
||||
Text.writeString(out, val);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class IntProperty extends ComparableProperty<Integer> {
|
||||
IntProperty(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
IntProperty(String name, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer read(String rawVal) {
|
||||
try {
|
||||
Integer val = Integer.parseInt(rawVal);
|
||||
verifyRange(val);
|
||||
return val;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(String.format("Invalid integer %s: %s", rawVal, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer read(TPropertyVal tVal) throws IllegalArgumentException {
|
||||
verifyRange(tVal.getIntVal());
|
||||
return tVal.getIntVal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer read(DataInput input) throws IOException {
|
||||
return input.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Integer val, TPropertyVal out) {
|
||||
out.setIntVal(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Integer val, DataOutput out) throws IOException {
|
||||
out.writeInt(val);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class LongProperty extends ComparableProperty<Long> {
|
||||
LongProperty(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
LongProperty(String name, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long read(String rawVal) {
|
||||
try {
|
||||
Long val = Long.parseLong(rawVal);
|
||||
verifyRange(val);
|
||||
return val;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(String.format("Invalid long %s: %s", rawVal, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long read(TPropertyVal tVal) throws IllegalArgumentException {
|
||||
verifyRange(tVal.getLongVal());
|
||||
return tVal.getLongVal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long read(DataInput input) throws IOException {
|
||||
return input.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Long val, TPropertyVal out) {
|
||||
out.setLongVal(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Long val, DataOutput out) throws IOException {
|
||||
out.writeLong(val);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BooleanProperty extends ComparableProperty<Boolean> {
|
||||
BooleanProperty(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
BooleanProperty(String name, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean read(String rawVal) {
|
||||
if (rawVal == null ||
|
||||
(!rawVal.equalsIgnoreCase("true") && !rawVal.equalsIgnoreCase("false"))) {
|
||||
throw new IllegalArgumentException(String.format("Invalid boolean : %s, use true or false", rawVal));
|
||||
}
|
||||
|
||||
try {
|
||||
return Boolean.parseBoolean(rawVal);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(String.format("Invalid boolean %s: %s", rawVal, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean read(TPropertyVal tVal) throws IllegalArgumentException {
|
||||
return tVal.isBoolVal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean read(DataInput input) throws IOException {
|
||||
return input.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Boolean val, TPropertyVal out) {
|
||||
out.setBoolVal(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Boolean val, DataOutput out) throws IOException {
|
||||
out.writeBoolean(val);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DateProperty extends PropertySchema<Date> {
|
||||
SimpleDateFormat dateFormat;
|
||||
|
||||
DateProperty(String name, SimpleDateFormat dateFormat) {
|
||||
super(name);
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
DateProperty(String name, SimpleDateFormat dateFormat, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date read(String rawVal) throws IllegalArgumentException {
|
||||
if (rawVal == null) {
|
||||
throw new IllegalArgumentException("Invalid time format, time param can not is null");
|
||||
}
|
||||
return readTimeFormat(rawVal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date read(TPropertyVal tVal) throws IllegalArgumentException {
|
||||
return readTimeFormat(tVal.getStrVal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date read(DataInput input) throws IOException {
|
||||
return readTimeFormat(Text.readString(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Date val, TPropertyVal out) {
|
||||
out.setStrVal(writeTimeFormat(val));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Date val, DataOutput out) throws IOException {
|
||||
Text.writeString(out, writeTimeFormat(val));
|
||||
}
|
||||
|
||||
public Date readTimeFormat(String timeStr) throws IllegalArgumentException {
|
||||
try {
|
||||
return this.dateFormat.parse(timeStr);
|
||||
} catch (ParseException e) {
|
||||
throw new IllegalArgumentException("Invalid time format, time param need "
|
||||
+ "to be " + this.dateFormat.toPattern());
|
||||
}
|
||||
}
|
||||
|
||||
public String writeTimeFormat(Date timeDate) throws IllegalArgumentException {
|
||||
return this.dateFormat.format(timeDate.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EnumProperty<T extends Enum<T>> extends PropertySchema<T> {
|
||||
private final Class<T> enumClass;
|
||||
|
||||
EnumProperty(String name, Class<T> enumClass) {
|
||||
super(name);
|
||||
this.enumClass = enumClass;
|
||||
}
|
||||
|
||||
EnumProperty(String name, Class<T> enumClass, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
this.enumClass = enumClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(String rawVal) {
|
||||
if (rawVal == null || rawVal.length() == 0) {
|
||||
throw new IllegalArgumentException(formatError(rawVal));
|
||||
}
|
||||
try {
|
||||
return T.valueOf(enumClass, rawVal.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(formatError(rawVal));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(TPropertyVal tVal) throws IllegalArgumentException {
|
||||
return T.valueOf(enumClass, tVal.getStrVal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(DataInput input) throws IOException {
|
||||
return T.valueOf(enumClass, Text.readString(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T val, TPropertyVal out) {
|
||||
out.setStrVal(val.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T val, DataOutput out) throws IOException {
|
||||
Text.writeString(out, val.name());
|
||||
}
|
||||
|
||||
private String formatError(String rawVal) {
|
||||
String enumsStr = Arrays.stream(enumClass.getEnumConstants())
|
||||
.map(Enum::toString)
|
||||
.reduce((sa, sb) -> sa + ", " + sb)
|
||||
.orElse("");
|
||||
return String.format("Expected values are [%s], while [%s] provided", enumsStr, rawVal);
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class ComparableProperty<T extends Comparable> extends PropertySchema<T> {
|
||||
protected ComparableProperty(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected ComparableProperty(String name, boolean isRequired) {
|
||||
super(name, isRequired);
|
||||
}
|
||||
|
||||
protected void verifyRange(T val) throws IllegalArgumentException {
|
||||
if (getMinValue().isPresent() && (val == null || getMinValue().get().compareTo(val) > 0)) {
|
||||
throw new IllegalArgumentException(val + " should not be less than " + getMinValue().get());
|
||||
}
|
||||
|
||||
if (getMaxValue().isPresent() && (val == null || getMaxValue().get().compareTo(val) < 0)) {
|
||||
throw new IllegalArgumentException(val + " should not be greater than " + getMaxValue().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PropertySchema<T> setDefauleValue(T val) {
|
||||
this.defaultValue = Optional.of(val);
|
||||
return this;
|
||||
}
|
||||
|
||||
PropertySchema<T> setMin(T min) {
|
||||
this.minValue = Optional.of(min);
|
||||
return this;
|
||||
}
|
||||
|
||||
PropertySchema<T> setMax(T max) {
|
||||
this.maxValue = Optional.of(max);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public Optional<T> getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public Optional<T> getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
public Optional<T> getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
public abstract T read(String rawVal) throws IllegalArgumentException;
|
||||
|
||||
public abstract T read(TPropertyVal tVal) throws IllegalArgumentException;
|
||||
|
||||
public abstract T read(DataInput input) throws IOException;
|
||||
|
||||
public abstract void write(T val, TPropertyVal out);
|
||||
|
||||
public abstract void write(T val, DataOutput out) throws IOException;
|
||||
}
|
||||
|
||||
@ -0,0 +1,163 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.doris.common.property;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.apache.doris.thrift.TPropertyVal;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PropertiesSetTest {
|
||||
@Test
|
||||
public void testReadFromStr() {
|
||||
Map<String, String> raw = new HashMap<>();
|
||||
raw.put("skip_header", "0");
|
||||
PropertiesSet<FileFormat> properties = PropertiesSet.readFromStrMap(FileFormat.get(), raw);
|
||||
Assert.assertEquals(FileFormat.Type.CSV, properties.get(FileFormat.FILE_FORMAT_TYPE));
|
||||
Assert.assertEquals("\n", properties.get(FileFormat.RECORD_DELIMITER));
|
||||
Assert.assertEquals(1, properties.writeToThrift().size());
|
||||
|
||||
properties = PropertiesSet.readFromStrMap(FileFormat.get(), rawVariableProps());
|
||||
verifyVariableProps(properties);
|
||||
Assert.assertEquals(3, properties.writeToThrift().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThriftSerde() {
|
||||
PropertiesSet<FileFormat> properties = PropertiesSet.readFromStrMap(FileFormat.get(), rawVariableProps());
|
||||
Map<String, TPropertyVal> thriftMap = properties.writeToThrift();
|
||||
Assert.assertEquals("JSON", thriftMap.get(FileFormat.FILE_FORMAT_TYPE.getName()).strVal);
|
||||
Assert.assertEquals("\r", thriftMap.get(FileFormat.RECORD_DELIMITER.getName()).strVal);
|
||||
Assert.assertEquals(3, thriftMap.get(FileFormat.SKIP_HEADER.getName()).intVal);
|
||||
|
||||
properties = PropertiesSet.readFromThrift(FileFormat.get(), thriftMap);
|
||||
verifyVariableProps(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataOutputSerde() throws Exception {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertiesSet<FileFormat> properties = PropertiesSet.readFromStrMap(FileFormat.get(), rawVariableProps());
|
||||
properties.writeToData(output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
|
||||
properties = PropertiesSet.readFromData(FileFormat.get(), input);
|
||||
verifyVariableProps(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
PropertiesSet<FileFormat> p1 = PropertiesSet.empty(FileFormat.get());
|
||||
PropertiesSet<FileFormat> p2 = PropertiesSet.empty(FileFormat.get());
|
||||
|
||||
Assert.assertEquals(p1, p2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedSchemas() {
|
||||
PropertiesSet<FileFormat> properties = PropertiesSet.readFromStrMap(FileFormat.get(), rawVariableProps());
|
||||
Assert.assertEquals(3, properties.getModifiedSchemas().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
PropertiesSet<FileFormat> properties = PropertiesSet.readFromStrMap(FileFormat.get(), rawVariableProps());
|
||||
String str = properties.toString();
|
||||
|
||||
Assert.assertTrue(str.contains(FileFormat.FILE_FORMAT_TYPE.getName()));
|
||||
Assert.assertTrue(str.contains("JSON"));
|
||||
Assert.assertTrue(str.contains(FileFormat.RECORD_DELIMITER.getName()));
|
||||
Assert.assertTrue(str.contains("\r"));
|
||||
Assert.assertTrue(str.contains(FileFormat.SKIP_HEADER.getName()));
|
||||
Assert.assertTrue(str.contains("3"));
|
||||
}
|
||||
|
||||
private Map<String, String> rawVariableProps() {
|
||||
Map<String, String> raw = new HashMap<>();
|
||||
raw.put(FileFormat.FILE_FORMAT_TYPE.getName(), "Json");
|
||||
raw.put(FileFormat.RECORD_DELIMITER.getName(), "\r");
|
||||
raw.put(FileFormat.SKIP_HEADER.getName(), "3");
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckRequiredOpts() {
|
||||
try {
|
||||
PropertiesSet.readFromStrMap(FileFormat.get(), Maps.newHashMap());
|
||||
fail("Expected an NoSuchElementException to be thrown");
|
||||
} catch (NoSuchElementException e) {
|
||||
Assert.assertTrue(e.getMessage().contains("Missing"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void verifyVariableProps(PropertiesSet properties) {
|
||||
Assert.assertEquals(FileFormat.Type.JSON, properties.get(FileFormat.FILE_FORMAT_TYPE));
|
||||
Assert.assertEquals("\r", properties.get(FileFormat.RECORD_DELIMITER));
|
||||
Assert.assertEquals(3, properties.get(FileFormat.SKIP_HEADER));
|
||||
}
|
||||
|
||||
private static class FileFormat implements PropertySchema.SchemaGroup {
|
||||
public static PropertySchema<Type> FILE_FORMAT_TYPE =
|
||||
new PropertySchema.EnumProperty<>("type", Type.class)
|
||||
.setDefauleValue(Type.CSV);
|
||||
public static PropertySchema<String> RECORD_DELIMITER =
|
||||
new PropertySchema.StringProperty("record_delimiter").setDefauleValue("\n");
|
||||
public static PropertySchema<String> FIELD_DELIMITER =
|
||||
new PropertySchema.StringProperty("field_delimiter").setDefauleValue("|");
|
||||
public static PropertySchema<Integer> SKIP_HEADER =
|
||||
new PropertySchema.IntProperty("skip_header", true).setMin(0);
|
||||
|
||||
private static final FileFormat INSTANCE = new FileFormat();
|
||||
|
||||
private final ImmutableMap<String, PropertySchema> schemas = PropertySchema.createSchemas(
|
||||
FILE_FORMAT_TYPE,
|
||||
RECORD_DELIMITER,
|
||||
FIELD_DELIMITER,
|
||||
SKIP_HEADER);
|
||||
|
||||
public ImmutableMap<String, PropertySchema> getSchemas() {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
public static FileFormat get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
CSV, JSON, ORC, PARQUET
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,297 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.doris.common.property;
|
||||
|
||||
import org.apache.doris.thrift.TPropertyVal;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class PropertySchemaTest {
|
||||
@Rule
|
||||
public ExpectedException exceptionRule = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testStringPropNormal() throws Exception {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertySchema.StringProperty prop = new PropertySchema.StringProperty("key");
|
||||
prop.write("val", output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
Assert.assertEquals("val", prop.read(input));
|
||||
|
||||
TPropertyVal tProp = new TPropertyVal();
|
||||
prop.write("val", tProp);
|
||||
Assert.assertEquals("val", prop.read(tProp));
|
||||
|
||||
prop.setMin("b");
|
||||
Assert.assertEquals("c", prop.read("c"));
|
||||
Assert.assertEquals("b", prop.read("b"));
|
||||
|
||||
prop.setMax("x");
|
||||
Assert.assertEquals("w", prop.read("w"));
|
||||
Assert.assertEquals("x", prop.read("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringPropMinExceeded() {
|
||||
PropertySchema.StringProperty prop = new PropertySchema.StringProperty("key");
|
||||
prop.setMin("b");
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("should not be less than"));
|
||||
prop.read("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringPropMinNull() {
|
||||
PropertySchema.StringProperty prop = new PropertySchema.StringProperty("key");
|
||||
prop.setMin("b");
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("should not be less than"));
|
||||
prop.read((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringPropMaxExceeded() {
|
||||
PropertySchema.StringProperty prop = new PropertySchema.StringProperty("key");
|
||||
prop.setMax("b");
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("should not be greater than"));
|
||||
prop.read("c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringPropMaxNull() {
|
||||
PropertySchema.StringProperty prop = new PropertySchema.StringProperty("key");
|
||||
prop.setMax("b");
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("should not be greater than"));
|
||||
prop.read((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntPropNormal() throws Exception {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertySchema.IntProperty prop = new PropertySchema.IntProperty("key");
|
||||
prop.write(5, output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
Assert.assertEquals(Integer.valueOf(5), prop.read(input));
|
||||
|
||||
TPropertyVal tProp = new TPropertyVal();
|
||||
prop.write(6, tProp);
|
||||
Assert.assertEquals(Integer.valueOf(6), prop.read(tProp));
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(7), prop.read("7"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntPropInvalidString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid integer"));
|
||||
|
||||
PropertySchema.IntProperty prop = new PropertySchema.IntProperty("key");
|
||||
prop.read("23j");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntPropNullString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid integer"));
|
||||
|
||||
PropertySchema.IntProperty prop = new PropertySchema.IntProperty("key");
|
||||
prop.read((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumPropNormal() throws Exception {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertySchema.EnumProperty<Color> prop = new PropertySchema.EnumProperty<>("key", Color.class);
|
||||
prop.write(Color.RED, output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
Assert.assertEquals(Color.RED, prop.read(input));
|
||||
|
||||
TPropertyVal tProp = new TPropertyVal();
|
||||
prop.write(Color.GREEN, tProp);
|
||||
Assert.assertEquals(Color.GREEN, prop.read(tProp));
|
||||
|
||||
Assert.assertEquals(Color.BLUE, prop.read("BLUE"));
|
||||
Assert.assertEquals(Color.BLUE, prop.read("blue"));
|
||||
Assert.assertEquals(Color.BLUE, prop.read("Blue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumPropInvalidString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString(
|
||||
"Expected values are [RED, GREEN, BLUE], while [invalid] provided"));
|
||||
|
||||
PropertySchema.EnumProperty<Color> prop = new PropertySchema.EnumProperty<>("key", Color.class);
|
||||
prop.read("invalid");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumPropNullString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString(
|
||||
"Expected values are [RED, GREEN, BLUE], while [null] provided"));
|
||||
|
||||
PropertySchema.EnumProperty<Color> prop = new PropertySchema.EnumProperty<>("key", Color.class);
|
||||
prop.read((String) null);
|
||||
}
|
||||
|
||||
private enum Color {
|
||||
RED, GREEN, BLUE
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongPropNormal() throws Exception {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertySchema.LongProperty prop = new PropertySchema.LongProperty("key");
|
||||
prop.write(5L, output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
Assert.assertEquals(Long.valueOf(5), prop.read(input));
|
||||
|
||||
TPropertyVal tProp = new TPropertyVal();
|
||||
prop.write(6L, tProp);
|
||||
Assert.assertEquals(Long.valueOf(6), prop.read(tProp));
|
||||
|
||||
Assert.assertEquals(Long.valueOf(7), prop.read("7"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongPropInvalidString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid long"));
|
||||
|
||||
PropertySchema.LongProperty prop = new PropertySchema.LongProperty("key");
|
||||
prop.read("23j");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongPropNullString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid long"));
|
||||
|
||||
PropertySchema.LongProperty prop = new PropertySchema.LongProperty("key");
|
||||
prop.read((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatePropNormal() throws Exception {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertySchema.DateProperty prop =
|
||||
new PropertySchema.DateProperty("key", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
prop.write(dateFormat.parse("2021-06-30 20:34:51"), output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
Assert.assertEquals(1625056491000L, prop.read(input).getTime());
|
||||
|
||||
TPropertyVal tProp = new TPropertyVal();
|
||||
prop.write(new Date(1625056491000L), tProp);
|
||||
Assert.assertEquals(1625056491000L, prop.read(tProp).getTime());
|
||||
|
||||
Assert.assertEquals(1625056491000L, prop.read("2021-06-30 20:34:51").getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatePropInvalidString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid time format"));
|
||||
|
||||
PropertySchema.DateProperty prop = new PropertySchema.DateProperty("key", new SimpleDateFormat("yyyy-MM-dd "
|
||||
+ "HH:mm:ss"));
|
||||
prop.read("2021-06-30");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatePropNullString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid time format"));
|
||||
|
||||
PropertySchema.DateProperty prop = new PropertySchema.DateProperty("key", new SimpleDateFormat("yyyy-MM-dd "
|
||||
+ "HH:mm:ss"));
|
||||
prop.read((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanPropNormal() throws Exception {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutput output = new DataOutputStream(outStream);
|
||||
|
||||
PropertySchema.BooleanProperty prop = new PropertySchema.BooleanProperty("key");
|
||||
prop.write(true, output);
|
||||
|
||||
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
|
||||
DataInput input = new DataInputStream(inStream);
|
||||
Assert.assertEquals(true, prop.read(input));
|
||||
|
||||
TPropertyVal tProp = new TPropertyVal();
|
||||
prop.write(true, tProp);
|
||||
Assert.assertEquals(true, prop.read(tProp));
|
||||
|
||||
Assert.assertEquals(true, prop.read("true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanPropInvalidString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid boolean"));
|
||||
|
||||
PropertySchema.BooleanProperty prop = new PropertySchema.BooleanProperty("key");
|
||||
prop.read("233");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanPropNullString() {
|
||||
exceptionRule.expect(IllegalArgumentException.class);
|
||||
exceptionRule.expectMessage(Matchers.containsString("Invalid boolean"));
|
||||
|
||||
PropertySchema.BooleanProperty prop = new PropertySchema.BooleanProperty("key");
|
||||
prop.read((String) null);
|
||||
}
|
||||
}
|
||||
@ -687,6 +687,13 @@ struct TFrontendPingFrontendResult {
|
||||
6: required string version
|
||||
}
|
||||
|
||||
struct TPropertyVal {
|
||||
1: optional string strVal
|
||||
2: optional i32 intVal
|
||||
3: optional i64 longVal
|
||||
4: optional bool boolVal
|
||||
}
|
||||
|
||||
service FrontendService {
|
||||
TGetDbsResult getDbNames(1:TGetDbsParams params)
|
||||
TGetTablesResult getTableNames(1:TGetTablesParams params)
|
||||
|
||||
Reference in New Issue
Block a user