/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.operation.udf; import io.crate.analyze.FunctionArgumentDefinition; import io.crate.exceptions.UnhandledServerException; import io.crate.types.*; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; public class UserDefinedFunctionMetaData implements Streamable, ToXContent { private String name; private String schema; private List<FunctionArgumentDefinition> arguments; DataType returnType; private List<DataType> argumentTypes; String language; String definition; String specificName; public UserDefinedFunctionMetaData(String schema, String name, List<FunctionArgumentDefinition> arguments, DataType returnType, String language, String definition) { this.schema = schema; this.name = name; this.arguments = arguments; this.returnType = returnType; this.language = language; this.definition = definition; this.argumentTypes = argumentTypesFrom(arguments); this.specificName = specificName(name, argumentTypes); } private UserDefinedFunctionMetaData() { } public static UserDefinedFunctionMetaData fromStream(StreamInput in) throws IOException { UserDefinedFunctionMetaData udfMetaData = new UserDefinedFunctionMetaData(); udfMetaData.readFrom(in); return udfMetaData; } public String schema() { return schema; } public String name() { return name; } public DataType returnType() { return returnType; } public String language() { return language; } public String definition() { return definition; } public List<FunctionArgumentDefinition> arguments() { return arguments; } public List<DataType> argumentTypes() { return argumentTypes; } public String specificName() { return specificName; } boolean sameSignature(String schema, String name, List<DataType> types) { return this.schema().equals(schema) && this.name().equals(name) && this.argumentTypes().equals(types); } @Override public void readFrom(StreamInput in) throws IOException { schema = in.readString(); name = in.readString(); int numArguments = in.readVInt(); arguments = new ArrayList<>(numArguments); for (int i = 0; i < numArguments; i++) { arguments.add(FunctionArgumentDefinition.fromStream(in)); } argumentTypes = argumentTypesFrom(arguments()); returnType = DataTypes.fromStream(in); language = in.readString(); definition = in.readString(); specificName = specificName(name, argumentTypes); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(schema); out.writeString(name); out.writeVInt(arguments.size()); for (FunctionArgumentDefinition argument : arguments) { argument.writeTo(out); } DataTypes.toStream(returnType, out); out.writeString(language); out.writeString(definition); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field("schema", schema); builder.field("name", name); builder.startArray("arguments"); for (FunctionArgumentDefinition argument : arguments) { argument.toXContent(builder, params); } builder.endArray(); builder.field("return_type"); DataTypeXContent.toXContent(returnType, builder, params); builder.field("language", language); builder.field("definition", definition); builder.endObject(); return builder; } static UserDefinedFunctionMetaData fromXContent(XContentParser parser) throws IOException { XContentParser.Token token; String schema = null; String name = null; List<FunctionArgumentDefinition> arguments = new ArrayList<>(); DataType returnType = null; String language = null; String definition = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { if ("schema".equals(parser.currentName())) { schema = parseStringField(parser); } else if ("name".equals(parser.currentName())) { name = parseStringField(parser); } else if ("language".equals(parser.currentName())) { language = parseStringField(parser); } else if ("definition".equals(parser.currentName())) { definition = parseStringField(parser); } else if ("arguments".equals(parser.currentName())) { if (parser.nextToken() != XContentParser.Token.START_ARRAY) { throw new UnhandledServerException("failed to parse function"); } while (parser.nextToken() != XContentParser.Token.END_ARRAY) { arguments.add(FunctionArgumentDefinition.fromXContent(parser)); } } else if ("return_type".equals(parser.currentName())) { returnType = DataTypeXContent.fromXContent(parser); } else { throw new UnhandledServerException("failed to parse function"); } } else { throw new UnhandledServerException("failed to parse function"); } } return new UserDefinedFunctionMetaData(schema, name, arguments, returnType, language, definition); } private static String parseStringField(XContentParser parser) throws IOException { if (parser.nextToken() != XContentParser.Token.VALUE_STRING && parser.currentToken() != XContentParser.Token.VALUE_NULL) { throw new UnhandledServerException("failed to parse function"); } return parser.textOrNull(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserDefinedFunctionMetaData that = (UserDefinedFunctionMetaData) o; return Objects.equals(schema, that.schema) && Objects.equals(name, that.name) && Objects.equals(arguments, that.arguments) && Objects.equals(returnType, that.returnType) && Objects.equals(language, that.language) && Objects.equals(definition, that.definition); } @Override public int hashCode() { return Objects.hash(schema, name, arguments, returnType, definition, language); } public static class DataTypeXContent { public static XContentBuilder toXContent(DataType type, XContentBuilder builder, Params params) throws IOException { builder.startObject().field("id", type.id()); if (type instanceof CollectionType) { builder.field("inner_type"); toXContent(((CollectionType) type).innerType(), builder, params); } builder.endObject(); return builder; } public static DataType fromXContent(XContentParser parser) throws IOException { XContentParser.Token token; DataType type = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.VALUE_NUMBER) { int id = parser.intValue(); if (id == ArrayType.ID || id == SetType.ID) { if (parser.nextToken() != XContentParser.Token.FIELD_NAME || !"inner_type".equals(parser.currentName())) { throw new IllegalStateException("Can't parse DataType form XContent"); } DataType innerType = fromXContent(parser); if (id == ArrayType.ID) { type = new ArrayType(innerType); } else { type = new SetType(innerType); } } else { type = DataTypes.fromId(id); } } } return type; } } static List<DataType> argumentTypesFrom(List<FunctionArgumentDefinition> arguments) { return arguments.stream().map(FunctionArgumentDefinition::type).collect(toList()); } static String specificName(String name, List<DataType> types) { return String.format(Locale.ENGLISH, "%s(%s)", name, types.stream().map(DataType::getName).collect(Collectors.joining(", "))); } }