Skip to content

Commit

Permalink
Add OpenSearchDateType as a datatype for matching with Date/Time Open…
Browse files Browse the repository at this point in the history
…Search types

Signed-off-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
acarbonetto committed Mar 29, 2023
1 parent 31148da commit 8b2f65d
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ public enum MappingType {
Ip("ip", ExprCoreType.UNKNOWN),
GeoPoint("geo_point", ExprCoreType.UNKNOWN),
Binary("binary", ExprCoreType.UNKNOWN),
Date("date", ExprCoreType.TIMESTAMP),
Date("date", ExprCoreType.DATE),
Time("date", ExprCoreType.TIME),
Datetime("date", ExprCoreType.TIMESTAMP),
Timestamp("date", ExprCoreType.TIMESTAMP),
Object("object", ExprCoreType.STRUCT),
Nested("nested", ExprCoreType.ARRAY),
Byte("byte", ExprCoreType.BYTE),
Expand Down Expand Up @@ -102,51 +105,71 @@ public ExprType getExprType() {
* @param mappingType A mapping type.
* @return An instance or inheritor of `OpenSearchDataType`.
*/
public static OpenSearchDataType of(MappingType mappingType) {
public static OpenSearchDataType of(MappingType mappingType, Map<String, Object> innerMap) {
var res = instances.getOrDefault(mappingType.toString(), null);
if (res != null) {
return res;
if (res == null) {
res = new OpenSearchDataType(mappingType);
}
ExprCoreType exprCoreType = mappingType.getExprCoreType();
if (exprCoreType == ExprCoreType.UNKNOWN) {
switch (mappingType) {
switch (mappingType) {
case Object:
case Nested:
if (innerMap.isEmpty()) {
return res;
}
var im = innerMap.getOrDefault("properties", Map.of());
Map<String, OpenSearchDataType> properties =
parseMapping((Map<String, Object>) innerMap.getOrDefault("properties", Map.of()));
OpenSearchDataType objectDataType = res.cloneEmpty();
objectDataType.properties = properties;
return objectDataType;
case Text:
// TODO update these 2 below #1038 https://github.com/opensearch-project/sql/issues/1038
case Text: return OpenSearchTextType.of();
case GeoPoint: return OpenSearchGeoPointType.of();
case Binary: return OpenSearchBinaryType.of();
case Ip: return OpenSearchIpType.of();
default:
throw new IllegalArgumentException(mappingType.toString());
}
return OpenSearchTextType.of();
// (Map<String, Object>) innerMap.getOrDefault("fields", Map.of()));
case GeoPoint: return OpenSearchGeoPointType.of();
case Binary: return OpenSearchBinaryType.of();
case Ip: return OpenSearchIpType.of();
case Date: return OpenSearchDateType.of(
(String) innerMap.getOrDefault("format", ""));
default:
return res;
}
res = new OpenSearchDataType(mappingType);
res.exprCoreType = exprCoreType;
return res;
}

public static Map<String, OpenSearchDataType> parseMapping(Map<String, Object> indexMapping) {
Map<String, OpenSearchDataType> result = new LinkedHashMap<>();
if (indexMapping != null) {
indexMapping.forEach((k, v) -> {
var innerMap = (Map<String, Object>)v;
// by default, the type is treated as an Object if "type" is not provided
var type = ((String) innerMap
.getOrDefault(
"type",
"object"))
.replace("_", "");
if (!EnumUtils.isValidEnumIgnoreCase(OpenSearchDataType.MappingType.class, type)) {
// unknown type, e.g. `alias`
// TODO resolve alias reference
return;
}
// create OpenSearchDataType
result.put(k, OpenSearchDataType.of(
EnumUtils.getEnumIgnoreCase(OpenSearchDataType.MappingType.class, type),
innerMap)
);
});
}
return result;
}

/**
* A constructor function which builds proper `OpenSearchDataType` for given mapping `Type`.
* Designed to be called by the mapping parser only (and tests).
* @param mappingType A mapping type.
* @param properties Properties to set.
* @param fields Fields to set.
* @return An instance or inheritor of `OpenSearchDataType`.
*/
public static OpenSearchDataType of(MappingType mappingType,
Map<String, OpenSearchDataType> properties,
Map<String, OpenSearchDataType> fields) {
var res = of(mappingType);
if (!properties.isEmpty() || !fields.isEmpty()) {
// Clone to avoid changing the singleton instance.
res = res.cloneEmpty();
res.properties = ImmutableMap.copyOf(properties);
res.fields = ImmutableMap.copyOf(fields);
}
return res;
}

protected OpenSearchDataType(MappingType mappingType) {
this.mappingType = mappingType;
public static OpenSearchDataType of(MappingType mappingType) {
return of(mappingType, Map.of());
}

/**
Expand All @@ -165,11 +188,13 @@ public static OpenSearchDataType of(ExprType type) {
return new OpenSearchDataType((ExprCoreType) type);
}

protected OpenSearchDataType(ExprCoreType type) {
this.exprCoreType = type;
protected OpenSearchDataType(MappingType mappingType) {
this.mappingType = mappingType;
this.exprCoreType = mappingType.getExprCoreType();
}

protected OpenSearchDataType() {
protected OpenSearchDataType(ExprCoreType type) {
this.exprCoreType = type;
}

// For datatypes with properties (example: object and nested types)
Expand All @@ -178,11 +203,6 @@ protected OpenSearchDataType() {
@EqualsAndHashCode.Exclude
Map<String, OpenSearchDataType> properties = ImmutableMap.of();

// text could have fields
// a read-only collection
@EqualsAndHashCode.Exclude
Map<String, OpenSearchDataType> fields = ImmutableMap.of();

@Override
// Called when building TypeEnvironment and when serializing PPL response
public String typeName() {
Expand All @@ -209,16 +229,16 @@ public String legacyTypeName() {
* @return A cloned object.
*/
protected OpenSearchDataType cloneEmpty() {
var copy = new OpenSearchDataType();
copy.mappingType = mappingType;
copy.exprCoreType = exprCoreType;
return copy;
if (this.mappingType == null) {
return new OpenSearchDataType(this.exprCoreType);
}
return new OpenSearchDataType(this.mappingType);
}

/**
* Flattens mapping tree into a single layer list of objects (pairs of name-types actually),
* which don't have nested types.
* See {@link OpenSearchDataTypeTest#traverseAndFlatten() test} for example.
* See OpenSearchDataTypeTest#traverseAndFlatten() test for example.
* @param tree A list of `OpenSearchDataType`s - map between field name and its type.
* @return A list of all `OpenSearchDataType`s from given map on the same nesting level (1).
* Nested object names are prefixed by names of their host.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.opensearch.data.type;

import static org.opensearch.sql.data.type.ExprCoreType.STRING;
import static org.opensearch.sql.data.type.ExprCoreType.UNKNOWN;

import com.google.common.collect.ImmutableMap;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import lombok.EqualsAndHashCode;
import org.joda.time.DateTime;
import org.opensearch.sql.data.type.ExprType;

/**
* Of type join with relations. See
* <a href="https://opensearch.org/docs/latest/opensearch/supported-field-types/join/">doc</a>
*/
@EqualsAndHashCode(callSuper = false)
public class OpenSearchDateType extends OpenSearchDataType {

private static final OpenSearchDateType instance = new OpenSearchDateType();


// a read-only collection of relations
@EqualsAndHashCode.Exclude
DateTimeFormatter format;

private OpenSearchDateType() {
super(MappingType.Date);
exprCoreType = UNKNOWN;
}

/**
* Create a Date type which has a LinkedHashMap defining all formats
* @return A new type object.
*/
public static OpenSearchDateType of(String format) {
var res = new OpenSearchDateType();

// Initialize the format based on the given string
try {
if (format.contains("||")) {
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
for (String token: format.split("\\|\\|")) {
builder.appendPattern(token);
}
res.format = builder.toFormatter();
} else {
res.format = DateTimeFormatter.ofPattern(format);
}
} catch (IllegalArgumentException iae) {
// invalid format - skipping
// TODO: warn the user that the format is illegal in the mapping
}
return res;
}

public static OpenSearchDateType of(DateTimeFormatter format) {
var res = new OpenSearchDateType();
res.format = format;
return res;
}

public static OpenSearchDateType of() {
return OpenSearchDateType.instance;
}

@Override
public List<ExprType> getParent() {
return List.of(STRING);
}

@Override
public boolean shouldCast(ExprType other) {
return false;
}

@Override
protected OpenSearchDataType cloneEmpty() {
return OpenSearchDateType.of(this.format);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public class OpenSearchTextType extends OpenSearchDataType {

private static final OpenSearchTextType instance = new OpenSearchTextType();

// text could have fields
// a read-only collection
@EqualsAndHashCode.Exclude
Map<String, OpenSearchDataType> fields = ImmutableMap.of();

private OpenSearchTextType() {
super(MappingType.Text);
exprCoreType = UNKNOWN;
Expand All @@ -33,9 +38,18 @@ private OpenSearchTextType() {
* @param fields Fields to set for the new type.
* @return A new type object.
*/
// public static OpenSearchTextType of() {
// var res = new OpenSearchTextType();
// if (fields instanceof Map<String, OpenSearchDataType>) {
//
// }
// res.fields = ImmutableMap.copyOf(fields);
// return res;
// }

public static OpenSearchTextType of(Map<String, OpenSearchDataType> fields) {
var res = new OpenSearchTextType();
res.fields = ImmutableMap.copyOf(fields);
res.fields = fields;
return res;
}

Expand All @@ -59,7 +73,7 @@ public Map<String, OpenSearchDataType> getFields() {

@Override
protected OpenSearchDataType cloneEmpty() {
return OpenSearchTextType.of(fields);
return OpenSearchTextType.of(ImmutableMap.copyOf(this.fields));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public class IndexMapping {

@SuppressWarnings("unchecked")
public IndexMapping(MappingMetadata metaData) {
this.fieldMappings = parseMapping((Map<String, Object>) metaData.getSourceAsMap()
.getOrDefault("properties", null));
this.fieldMappings = OpenSearchDataType.parseMapping(
(Map<String, Object>) metaData.getSourceAsMap().getOrDefault("properties", null)
);
}

/**
Expand All @@ -42,27 +43,30 @@ public int size() {
return fieldMappings.size();
}

@SuppressWarnings("unchecked")
private Map<String, OpenSearchDataType> parseMapping(Map<String, Object> indexMapping) {
Map<String, OpenSearchDataType> result = new LinkedHashMap<>();
if (indexMapping != null) {
indexMapping.forEach((k, v) -> {
var innerMap = (Map<String, Object>)v;
// TODO: confirm that only `object` mappings can omit `type` field.
var type = ((String) innerMap.getOrDefault("type", "object")).replace("_", "");
if (!EnumUtils.isValidEnumIgnoreCase(OpenSearchDataType.MappingType.class, type)) {
// unknown type, e.g. `alias`
// TODO resolve alias reference
return;
}
// TODO read formats for date type
result.put(k, OpenSearchDataType.of(
EnumUtils.getEnumIgnoreCase(OpenSearchDataType.MappingType.class, type),
parseMapping((Map<String, Object>) innerMap.getOrDefault("properties", null)),
parseMapping((Map<String, Object>) innerMap.getOrDefault("fields", null))
));
});
}
return result;
}
// @SuppressWarnings("unchecked")
// private Map<String, OpenSearchDataType> parseMapping(Map<String, Object> indexMapping) {
// Map<String, OpenSearchDataType> result = new LinkedHashMap<>();
// if (indexMapping != null) {
// indexMapping.forEach((k, v) -> {
// var innerMap = (Map<String, Object>)v;
// // by default, the type is treated as an Object if "type" is not provided
// var type = ((String) innerMap
// .getOrDefault(
// "type",
// "object"))
// .replace("_", "");
// if (!EnumUtils.isValidEnumIgnoreCase(OpenSearchDataType.MappingType.class, type)) {
// // unknown type, e.g. `alias`
// // TODO resolve alias reference
// return;
// }
// // create OpenSearchDataType
// result.put(k, OpenSearchDataType.of(
// EnumUtils.getEnumIgnoreCase(OpenSearchDataType.MappingType.class, type),
// innerMap)
// );
// });
// }
// return result;
// }
}
Loading

0 comments on commit 8b2f65d

Please sign in to comment.