Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Only keep the first element of multivalue field response. #1026

Merged
merged 11 commits into from
Feb 3, 2021
Merged
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
13 changes: 13 additions & 0 deletions docs/experiment/ppl/general/datatypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,16 @@ PPL query::
|-----------+-------------|
| 1 | Seattle |
+-----------+-------------+

Example 3: Selecting Field of Array Value
-----------------------------------------

Select deeper level for object fields of array value which returns the first element in the array. For example, because inner field ``accounts.id`` has three values instead of a tuple in this document, the first entry is returned.::

od> source = people | fields accounts, accounts.id;
fetched rows / total rows = 1/1
+------------+---------------+
| accounts | accounts.id |
|------------+---------------|
| {'id': 1} | 1 |
+------------+---------------+
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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.amazon.opendistroforelasticsearch.sql.elasticsearch.data.utils;

import java.util.Iterator;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;

/**
* Regardless the underling data format, the {@link Content} define the data in abstract manner.
* which could be parsed by ElasticsearchExprValueFactory.
* There are two major use cases:
* 1. Represent the JSON data retrieve from Elasticsearch search response.
* 2. Represent the Object data extract from the Elasticsearch aggregation response.
*/
public interface Content {

/**
* Is null value.
*/
boolean isNull();

/**
* Is number value.
*/
boolean isNumber();

/**
* Is string value.
*/
boolean isString();

/**
* Get integer value.
*/
Integer intValue();

/**
* Get long value.
*/
Long longValue();

/**
* Get short value.
*/
Short shortValue();

/**
* Get byte value.
*/
Byte byteValue();

/**
* Get float value.
*/
Float floatValue();

/**
* Get double value.
*/
Double doubleValue();

/**
* Get string value.
*/
String stringValue();

/**
* Get boolean value.
*/
Boolean booleanValue();

/**
* Get map of {@link Content} value.
*/
Iterator<Map.Entry<String, Content>> map();

/**
* Get array of {@link Content} value.
*/
Iterator<? extends Content> array();

/**
* Get geo point value.
*/
Pair<Double, Double> geoValue();

/**
* Get {@link Object} value.
*/
Object objectValue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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.amazon.opendistroforelasticsearch.sql.elasticsearch.data.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Iterators;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;

/**
* The Implementation of Content to represent {@link JsonNode}.
*/
@RequiredArgsConstructor
public class ElasticsearchJsonContent implements Content {

private final JsonNode value;

@Override
public Integer intValue() {
return value().intValue();
}

@Override
public Long longValue() {
return value().longValue();
}

@Override
public Short shortValue() {
return value().shortValue();
}

@Override
public Byte byteValue() {
return (byte) value().shortValue();
}

@Override
public Float floatValue() {
return value().floatValue();
}

@Override
public Double doubleValue() {
return value().doubleValue();
}

@Override
public String stringValue() {
return value().asText();
}

@Override
public Boolean booleanValue() {
return value().booleanValue();
}

@Override
public Iterator<Map.Entry<String, Content>> map() {
LinkedHashMap<String, Content> map = new LinkedHashMap<>();
final JsonNode mapValue = value();
mapValue
.fieldNames()
.forEachRemaining(
field -> map.put(field, new ElasticsearchJsonContent(mapValue.get(field))));
return map.entrySet().iterator();
}

@Override
public Iterator<? extends Content> array() {
return Iterators.transform(value.elements(), ElasticsearchJsonContent::new);
}

@Override
public boolean isNull() {
return value == null || value.isNull();
}

@Override
public boolean isNumber() {
return value().isNumber();
}

@Override
public boolean isString() {
return value().isTextual();
}

@Override
public Object objectValue() {
return value();
}

@Override
public Pair<Double, Double> geoValue() {
return Pair.of(value().get("lat").doubleValue(), value().get("lon").doubleValue());
}

/**
* Return the first element if is Elasticsearch Array.
* https://www.elastic.co/guide/en/elasticsearch/reference/current/array.html.
*/
private JsonNode value() {
return value.isArray() ? value.get(0) : value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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.amazon.opendistroforelasticsearch.sql.elasticsearch.data.utils;

import java.util.AbstractMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;

/**
* The Implementation of Content to represent {@link Object}.
*/
@RequiredArgsConstructor
public class ObjectContent implements Content {

private final Object value;

@Override
public Integer intValue() {
return parseNumberValue(value, Integer::valueOf, Number::intValue);
}

@Override
public Long longValue() {
return parseNumberValue(value, Long::valueOf, Number::longValue);
}

@Override
public Short shortValue() {
return parseNumberValue(value, Short::valueOf, Number::shortValue);
}

@Override
public Byte byteValue() {
return parseNumberValue(value, Byte::valueOf, Number::byteValue);
}

@Override
public Float floatValue() {
return parseNumberValue(value, Float::valueOf, Number::floatValue);
}

@Override
public Double doubleValue() {
return parseNumberValue(value, Double::valueOf, Number::doubleValue);
}

@Override
public String stringValue() {
return (String) value;
}

@Override
public Boolean booleanValue() {
return (Boolean) value;
}

@Override
public Object objectValue() {
return value;
}

@SuppressWarnings("unchecked")
@Override
public Iterator<Map.Entry<String, Content>> map() {
return ((Map<String, Object>) value).entrySet().stream()
.map(entry -> (Map.Entry<String, Content>) new AbstractMap.SimpleEntry<String, Content>(
entry.getKey(),
new ObjectContent(entry.getValue())))
.iterator();
}

@SuppressWarnings("unchecked")
@Override
public Iterator<? extends Content> array() {
return ((List<Object>) value).stream().map(ObjectContent::new).iterator();
}

@Override
public boolean isNull() {
return value == null;
}

@Override
public boolean isNumber() {
return value instanceof Number;
}

@Override
public boolean isString() {
return value instanceof String;
}

@Override
public Pair<Double, Double> geoValue() {
final String[] split = ((String) value).split(",");
return Pair.of(Double.valueOf(split[0]), Double.valueOf(split[1]));
}

private <T> T parseNumberValue(Object value, Function<String, T> stringTFunction,
Function<Number, T> numberTFunction) {
if (value instanceof String) {
return stringTFunction.apply((String) value);
} else {
return numberTFunction.apply((Number) value);
}
}
}
Loading