/* * Licensed to 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.tablefunctions; import io.crate.analyze.WhereClause; import io.crate.data.Bucket; import io.crate.data.Row; import io.crate.data.RowN; import io.crate.metadata.*; import io.crate.metadata.table.ColumnRegistrar; import io.crate.metadata.table.StaticTableInfo; import io.crate.metadata.table.TableInfo; import io.crate.metadata.tablefunctions.TableFunctionImplementation; import io.crate.data.Input; import io.crate.types.CollectionType; import io.crate.types.DataType; import io.crate.types.DataTypes; import org.elasticsearch.cluster.service.ClusterService; import javax.annotation.Nullable; import java.util.*; public class UnnestFunction { private final static String NAME = "unnest"; private static final TableIdent TABLE_IDENT = new TableIdent(null, NAME); static class UnnestTableFunctionImplementation implements TableFunctionImplementation { private final FunctionInfo info; private UnnestTableFunctionImplementation(FunctionInfo info) { this.info = info; } @Override public FunctionInfo info() { return info; } /** * @param arguments collection of array-literals * e.g. [ [1, 2], [Marvin, Trillian] ] * @return Bucket containing the unnested rows. * [ [1, Marvin], [2, Trillian] ] */ @Override public Bucket execute(Collection<? extends Input> arguments) { final List<Object[]> values = extractValues(arguments); final int numCols = arguments.size(); final int numRows = maxLength(values); return new Bucket() { final Object[] cells = new Object[numCols]; final RowN row = new RowN(cells); @Override public int size() { return numRows; } @Override public Iterator<Row> iterator() { return new Iterator<Row>() { int currentRow = 0; @Override public boolean hasNext() { return currentRow < numRows; } @Override public Row next() { if (!hasNext()) { throw new NoSuchElementException("No more rows"); } for (int c = 0; c < numCols; c++) { Object[] columnValues = values.get(c); if (columnValues.length > currentRow) { cells[c] = columnValues[currentRow]; } else { cells[c] = null; } } currentRow++; return row; } @Override public void remove() { throw new UnsupportedOperationException("remove is not supported for " + UnnestFunction.class.getSimpleName() + "$iterator"); } }; } }; } private static List<Object[]> extractValues(Collection<? extends Input> arguments) { List<Object[]> values = new ArrayList<>(arguments.size()); for (Input argument : arguments) { Object value = argument.value(); assert value instanceof Object[] : "must be an array because unnest only accepts array arguments"; Object[] columnValues = (Object[]) value; values.add(columnValues); } return values; } @Override public TableInfo createTableInfo(ClusterService clusterService) { ColumnRegistrar columnRegistrar = new ColumnRegistrar(TABLE_IDENT, RowGranularity.DOC); for (int i = 0; i < info.ident().argumentTypes().size(); i++) { columnRegistrar.register(new ColumnIdent( "col" + (i + 1)), ((CollectionType) info.ident().argumentTypes().get(i)).innerType()); } final String localNodeId = clusterService.localNode().getId(); return new StaticTableInfo(TABLE_IDENT, columnRegistrar, Collections.emptyList()) { @Override public RowGranularity rowGranularity() { return RowGranularity.DOC; } @Override public Routing getRouting(WhereClause whereClause, @Nullable String preference) { return Routing.forTableOnSingleNode(TABLE_IDENT, localNodeId); } }; } } private static int maxLength(List<Object[]> values) { int length = 0; for (Object[] value : values) { if (value.length > length) { length = value.length; } } return length; } public static void register(TableFunctionModule module){ module.register(NAME, new BaseFunctionResolver( Signature.withLenientVarArgs(Signature.ArgMatcher.ANY_ARRAY)) { @Override public FunctionImplementation getForTypes(List<DataType> dataTypes) throws IllegalArgumentException { return new UnnestTableFunctionImplementation( new FunctionInfo(new FunctionIdent(NAME, dataTypes), DataTypes.OBJECT, FunctionInfo.Type.TABLE)); } }); } }