From 6ea030e7fa4a7b03e2409ddabd624fb80938a5ab Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:59:04 +0100 Subject: [PATCH] New IR -- WIP --- .../java/io/trino/sql/newir/Attribute.java | 53 ++++ .../main/java/io/trino/sql/newir/Block.java | 129 +++++++++ .../java/io/trino/sql/newir/Constant.java | 92 ++++++ .../main/java/io/trino/sql/newir/Context.java | 62 ++++ .../io/trino/sql/newir/FieldSelection.java | 123 ++++++++ .../main/java/io/trino/sql/newir/Filter.java | 119 ++++++++ .../main/java/io/trino/sql/newir/Node.java | 28 ++ .../java/io/trino/sql/newir/Operation.java | 87 ++++++ .../main/java/io/trino/sql/newir/Output.java | 113 ++++++++ .../io/trino/sql/newir/PrinterOptions.java | 19 ++ .../main/java/io/trino/sql/newir/Program.java | 89 ++++++ .../io/trino/sql/newir/ProgramBuilder.java | 68 +++++ .../main/java/io/trino/sql/newir/Query.java | 106 +++++++ .../main/java/io/trino/sql/newir/README.md | 89 ++++++ .../main/java/io/trino/sql/newir/Region.java | 58 ++++ .../sql/newir/RelationalProgramBuilder.java | 269 ++++++++++++++++++ .../main/java/io/trino/sql/newir/Return.java | 97 +++++++ .../src/main/java/io/trino/sql/newir/Row.java | 118 ++++++++ .../trino/sql/newir/ScalarProgramBuilder.java | 80 ++++++ .../io/trino/sql/newir/TypeConstraint.java | 64 +++++ .../main/java/io/trino/sql/newir/Value.java | 28 ++ .../main/java/io/trino/sql/newir/Values.java | 143 ++++++++++ .../io/trino/sql/newir/TestPrintProgram.java | 51 ++++ .../java/io/trino/spi/type/EmptyRowType.java | 79 +++++ .../main/java/io/trino/spi/type/IrType.java | 19 ++ .../java/io/trino/spi/type/MultisetType.java | 89 ++++++ .../java/io/trino/spi/type/StandardTypes.java | 1 + .../src/main/java/io/trino/spi/type/Type.java | 3 +- .../main/java/io/trino/spi/type/VoidType.java | 26 ++ 29 files changed, 2301 insertions(+), 1 deletion(-) create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Attribute.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Block.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Constant.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Context.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/FieldSelection.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Filter.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Node.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Operation.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Output.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/PrinterOptions.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Program.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/ProgramBuilder.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Query.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/README.md create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Region.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/RelationalProgramBuilder.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Return.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Row.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/ScalarProgramBuilder.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/TypeConstraint.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Value.java create mode 100644 core/trino-main/src/main/java/io/trino/sql/newir/Values.java create mode 100644 core/trino-main/src/test/java/io/trino/sql/newir/TestPrintProgram.java create mode 100644 core/trino-spi/src/main/java/io/trino/spi/type/EmptyRowType.java create mode 100644 core/trino-spi/src/main/java/io/trino/spi/type/IrType.java create mode 100644 core/trino-spi/src/main/java/io/trino/spi/type/MultisetType.java create mode 100644 core/trino-spi/src/main/java/io/trino/spi/type/VoidType.java diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Attribute.java b/core/trino-main/src/main/java/io/trino/sql/newir/Attribute.java new file mode 100644 index 000000000000..ae1a8269d72e --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Attribute.java @@ -0,0 +1,53 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import io.trino.spi.type.IrType; + +import java.util.List; + +import static java.util.Objects.requireNonNull; + +@Immutable +public interface Attribute +{} + +record ConstantResult(T type, Object value) + implements Attribute // TODO rnn +{} + +record OutputNames(List names) + implements Attribute +{ + OutputNames(List names) + { + this.names = ImmutableList.copyOf(requireNonNull(names, "names is null")); + } +} + +record FieldName(String name) + implements Attribute +{ + FieldName + { + requireNonNull(name, "name is null"); + } +} + +record Cardinality(int cardinality) + implements Attribute +{ +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Block.java b/core/trino-main/src/main/java/io/trino/sql/newir/Block.java new file mode 100644 index 000000000000..f13fd4490e70 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Block.java @@ -0,0 +1,129 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import io.trino.spi.type.IrType; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static io.trino.sql.newir.PrinterOptions.INDENT; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +public record Block(Optional name, List arguments, List operations) + implements Node +{ + public record Argument(String name, IrType type) // TODO validate name + // TODO accept "name" at construction, and add "^"?? + implements Value + { + @Override + public Block source(Program program) + { + return program.getBlock(this); + } + } + + public Block(Optional name, List arguments, List operations) + { + this.name = requireNonNull(name, "name is null"); // TODO validate name + this.arguments = ImmutableList.copyOf(requireNonNull(arguments, "argumentNames is null")); + this.operations = ImmutableList.copyOf(requireNonNull(operations, "operations is null")); + // TODO verify list not emtpy + + // TODO verify that ends with a terminal operation + } + + @Override + public String print(int indentLevel) + { + StringBuilder builder = new StringBuilder(); + String indent = INDENT.repeat(indentLevel); + + builder.append(indent) + .append(name().orElse("")); + + if (!arguments().isEmpty()) { + builder.append(arguments().stream() + .map(argument -> argument.name() + " : " + argument.type()) + .collect(joining(", ", " (", ")"))); + } + + builder.append(operations().stream() + .map(operation -> operation.print(indentLevel + 1)) + .collect(joining("\n", "\n", ""))); + + return builder.toString(); + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty block"; + } + + public int getIndex(Argument argument) + { + int index = arguments.indexOf(argument); + checkState(index >= 0, "no chyba nie"); // TODO error + return index; + } + + public Operation getTerminalOperation() + { + return operations.getLast(); + } + + public IrType getReturnedType() + { + return getTerminalOperation().result().type(); + } + + public static class Builder + { + private final Optional name; + private final List arguments; + private final ImmutableList.Builder operations = ImmutableList.builder(); + private Optional recentOperation = Optional.empty(); + + public Builder(Optional name, List arguments) + { + this.name = name; + this.arguments = arguments; + } + + public Builder addOperation(Operation operation) + { + operations.add(operation); // TODO will throw on null. + recentOperation = Optional.of(operation); + return this; + } + + // access to the recently added operation allows the caller to insert a return operation or a navigating operation (in the future) + public Operation recentOperation() + { + return recentOperation.orElseThrow(() -> new RuntimeException("no operations added yet")); // TODO error + } + + public Block build() + { + return new Block(name, arguments, operations.build()); + } + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Constant.java b/core/trino-main/src/main/java/io/trino/sql/newir/Constant.java new file mode 100644 index 000000000000..04a1017dd709 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Constant.java @@ -0,0 +1,92 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.type.IrType; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +public final class Constant + implements Operation +{ + private static final String NAME = "constant"; + + private final Result result; + private final Set attributes; + + public Constant(String resultName, IrType type, Object value) + { + requireNonNull(resultName, "resultName is null"); + + this.result = new Result(resultName, type); + + this.attributes = ImmutableSet.of(new ConstantResult<>(type, value)); // TODO have another attr with the actual value unwrapped + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(); + } + + @Override + public List regions() + { + return ImmutableList.of(); + } + + @Override + public Set attributes() { + return attributes; + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty constant"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Constant) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() + { + return Objects.hash(result, attributes); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Context.java b/core/trino-main/src/main/java/io/trino/sql/newir/Context.java new file mode 100644 index 000000000000..3a0e077b3db6 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Context.java @@ -0,0 +1,62 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableMap; +import io.trino.sql.planner.Symbol; + +import java.util.Map; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static java.util.HashMap.newHashMap; +import static java.util.Objects.requireNonNull; + +public record Context(Block.Builder block, Map symbolMapping) +{ + public Context(Block.Builder block) + { + this(block, Map.of()); + } + + public Context(Block.Builder block, Map symbolMapping) + { + this.block = requireNonNull(block, "block is null"); + this.symbolMapping = ImmutableMap.copyOf(requireNonNull(symbolMapping, "symbolMapping is null")); + } + + public static Map argumentMapping(Block.Argument argument, Map symbolMapping) + { + return symbolMapping.entrySet().stream() + .collect(toImmutableMap( + Map.Entry::getKey, + entry -> new RowField(argument, entry.getValue()))); + } + + public static Map composedMapping(Context context, Map newMapping) + { + Map composed = newHashMap(context.symbolMapping().size() + newMapping.size()); + composed.putAll(context.symbolMapping()); + composed.putAll(newMapping); + return composed; + } + + public record RowField(Block.Argument row, String field) + { + public RowField + { + requireNonNull(row, "row is null"); + requireNonNull(field, "field is null"); + } + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/FieldSelection.java b/core/trino-main/src/main/java/io/trino/sql/newir/FieldSelection.java new file mode 100644 index 000000000000..d137e1a72015 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/FieldSelection.java @@ -0,0 +1,123 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.type.RowType; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static io.trino.spi.type.EmptyRowType.EMPTY_ROW; +import static io.trino.sql.newir.TypeConstraint.IS_RELATION_ROW; +import static java.util.Objects.requireNonNull; + +public final class FieldSelection + implements Operation +{ + private static final String NAME = "field_selection"; + + private final Result result; + private final Value input; + private final Set attributes; + + public FieldSelection(String resultName, Value input, String fieldName, Set sourceAttributes) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(input, "input is null"); + requireNonNull(sourceAttributes, "sourceAttributes is null"); + + // We compare field names case-sensitive, although in TranslationMap field references are resolved case-insensitive. + // Explanation: + // In TranslationMap, all user-provided field references by name are resolved case-insensitive and translated to field references by index. + // Operations, like this one, are created after TranslationMap, so there are no user-provided field references by name. + // At this point, the only field references by name are added programatically (the FieldSelection Operation), and they refer to RowTypes created programatically. + // Those RowTypes have lower-case unique field names which can be safely compared case-sensitive. + // When we add a Parser to create the IR from text, we should assume that the text is a printout of a valid query program, + // and thus all field references by name are case-safe. + if (!IS_RELATION_ROW.test(input.type()) || input.type().equals(EMPTY_ROW)) { // TODO check that the row is not an EmptyRow - constraint + throw new RuntimeException("throw some smart error here"); // TODO throw IR type mismatch exception + } + Optional matchingField = ((RowType) input.type()).getFields().stream() + .filter(field -> fieldName.equals(field.getName().orElseThrow())).findFirst(); + if (matchingField.isEmpty()) { + throw new RuntimeException("throw some smart error here"); // TODO throw invalid field selection + } + this.result = new Result(resultName, matchingField.orElseThrow().getType()); + + this.input = input; + + this.attributes = deriveAttributes(fieldName, sourceAttributes); + } + + private Set deriveAttributes(String fieldName, Set sourceAttributes) + { + return ImmutableSet.of(new FieldName(fieldName)); + // TODO add source attributes for the selected field + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(input); + } + + @Override + public List regions() + { + return ImmutableList.of(); + } + + @Override + public Set attributes() { + return attributes; + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty field selection"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (FieldSelection) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.input, that.input) && + Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() + { + return Objects.hash(result, input, attributes); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Filter.java b/core/trino-main/src/main/java/io/trino/sql/newir/Filter.java new file mode 100644 index 000000000000..d969ff0477bd --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Filter.java @@ -0,0 +1,119 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static io.trino.sql.newir.Region.singleBlockRegion; +import static io.trino.sql.newir.TypeConstraint.IS_RELATION; +import static java.util.Objects.requireNonNull; + +public final class Filter + implements Operation +{ + private static final String NAME = "filter"; + + private final Result result; + private final Value input; + private final Region predicate; + private final Set attributes; + + public Filter(String resultName, Value input, Block predicate, Set sourceAttributes) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(input, "input is null"); + requireNonNull(predicate, "predicate is null"); + requireNonNull(sourceAttributes, "sourceAttributes is null"); + + if (!IS_RELATION.test(input.type())) { + throw new RuntimeException("throw some smart error here"); // TODO throw IR type mismatch exception + } + + this.result = new Result(resultName, input.type()); // derives output type: same as input type + + // TODO for Operations that create their output type: do indexing from 1 for similarity with SQL indexing + + this.input = input; + + // TODO check if predicate returns boolean and takes a single Row + // TODO check if Block input type is the same as input relation RowType + this.predicate = singleBlockRegion(Optional.of("predicate"), predicate); // TODO naming convention for Regions - add special sign or validate + + this.attributes = deriveAttributes(sourceAttributes); + } + + // TODO + private Set deriveAttributes(Set sourceAttributes) + { + return ImmutableSet.of(); + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(input); + } + + @Override + public List regions() + { + return ImmutableList.of(predicate); + } + + @Override + public Set attributes() { + return attributes; + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty filter"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Filter) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.input, that.input) && + Objects.equals(this.predicate, that.predicate) && + Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() + { + return Objects.hash(result, input, predicate, attributes); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Node.java b/core/trino-main/src/main/java/io/trino/sql/newir/Node.java new file mode 100644 index 000000000000..bbc15bb27493 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Node.java @@ -0,0 +1,28 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.google.errorprone.annotations.Immutable; + +@Immutable +// no JSON serialization; using own serialized format +public sealed interface Node + permits Operation, Region, Block +{ + String print(int indentLevel); + + String prettyPrint(int indentLevel); +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Operation.java b/core/trino-main/src/main/java/io/trino/sql/newir/Operation.java new file mode 100644 index 000000000000..822ba26cb5b4 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Operation.java @@ -0,0 +1,87 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import io.trino.spi.type.IrType; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static io.trino.sql.newir.PrinterOptions.INDENT; +import static java.util.stream.Collectors.joining; + +public sealed interface Operation + extends Node + permits Constant, FieldSelection, Filter, Output, Query, Return, Row, Values +{ + // TODO need to keep the dialect name "trino" somewhere -- applies to all Operations, Types and Attributes. -- keep in Printer? Parser must know it too. + + record Result(String name, IrType type) // TODO validate name + implements Value + { + @Override + public Operation source(Program program) + { + return program.getOperation(this); + } + } + + String name(); + + Result result(); + + List arguments(); + + List regions(); + + Set attributes(); + + @Override + default String print(int indentLevel) + { + StringBuilder builder = new StringBuilder(); + String indent = INDENT.repeat(indentLevel); + + builder.append(indent) + .append(result().name()) + .append(" = ") + .append(name()) + .append(arguments().stream() + .map(Value::name) + .collect(joining(", ", "(", ")"))) + .append(" : ") + .append(arguments().stream() + .map(Value::type) + .map(IrType::toString) + .collect(joining(", ", "(", ")"))) + .append(" -> ") + .append(result().type().toString()) + .append(regions().stream() + .map(region -> region.print(indentLevel + 1)) + .collect(joining(", ", " (", ")"))); + + // do not render empty attributes list + if(!attributes().isEmpty()) { + builder.append("\n") + .append(indent) + .append(INDENT) + .append(attributes().stream() + .map(Attribute::toString) + .collect(joining(", ", "{", "}"))); + } + + return builder.toString(); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Output.java b/core/trino-main/src/main/java/io/trino/sql/newir/Output.java new file mode 100644 index 000000000000..ab41bac17424 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Output.java @@ -0,0 +1,113 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static io.trino.spi.type.VoidType.VOID; +import static io.trino.sql.newir.Region.singleBlockRegion; +import static io.trino.sql.newir.TypeConstraint.IS_RELATION; +import static java.util.Objects.requireNonNull; + +public final class Output + implements Operation +{ + private static final String NAME = "output"; + + private final Result result; + private final Value input; + private final Region fieldSelector; + private final Attribute outputNames; + + public Output(String resultName, Value input, Block fieldSelector, List outputNames) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(input, "input is null"); + requireNonNull(fieldSelector, "fieldSelector is null"); + requireNonNull(outputNames, "outputNames is null"); + + if (!IS_RELATION.test(input.type())) { + throw new RuntimeException("throw some smart error here"); // TODO throw IR type mismatch exception + } + + this.result = new Result(resultName, VOID); + + this.input = input; + + // TODO check if field selector returns Row and takes a single Row - type constraint for the Region + // TODO check if Block input type is the same as input relation RowType and not an EMPTY_ROW + this.fieldSelector = singleBlockRegion(Optional.of("outputFieldSelector"), fieldSelector); + + // TODO check if outputFieldsSelector and outputNames match in size + this.outputNames = new OutputNames(outputNames); + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(input); + } + + @Override + public List regions() + { + return ImmutableList.of(fieldSelector); + } + + @Override + public Set attributes() { + return ImmutableSet.of(outputNames); + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty output"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Output) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.input, that.input) && + Objects.equals(this.fieldSelector, that.fieldSelector) && + Objects.equals(this.outputNames, that.outputNames); + } + + @Override + public int hashCode() + { + return Objects.hash(result, input, fieldSelector, outputNames); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/PrinterOptions.java b/core/trino-main/src/main/java/io/trino/sql/newir/PrinterOptions.java new file mode 100644 index 000000000000..7f7a0d3654e1 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/PrinterOptions.java @@ -0,0 +1,19 @@ +/* + * Licensed 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 io.trino.sql.newir; + +public class PrinterOptions +{ + public static final String INDENT = " "; +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Program.java b/core/trino-main/src/main/java/io/trino/sql/newir/Program.java new file mode 100644 index 000000000000..b2f50e66c1be --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Program.java @@ -0,0 +1,89 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import io.trino.sql.newir.Block.Argument; +import io.trino.sql.newir.Operation.Result; + +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +@Immutable +public final class Program +{ + // for now, we build a Program for a single query. Later, we might support multiple statements. + // Query is a special root-level Operation to enclose all query computations in one Block. + private final Query root; + + // each Operation.Result is mapped to its returning Operation + // each Block.Argument is mapped to its declaring Block + private final Map valueMap; // TODO values in the map should be only Operation or Block, not a Region + + public Program(Query root, Map valueMap) + { + this.root = requireNonNull(root, "root is null"); + this.valueMap = ImmutableMap.copyOf(requireNonNull(valueMap, "valueMap is null")); + } + + public Operation getOperation(Result value) // jesli będzie Type w value, to tu sprawdzac spójność - czy się zgadza ze znalezionym + { + Node source = valueMap.get(value); + if (source == null) { + throw new RuntimeException("value ... not defined"); // TODO throw ValuNotDefined Exception + } + + if (source instanceof Operation operation) + { + // TODO verify the type? + /*if (!value.type().equals(operation.result().type())) { + throw new RuntimeException("type mismatch...");// TODO throw type mismatch? + }*/ + return operation; + } + + throw new RuntimeException("to nie był Operation result"); // TODO throw NotAnOperationResult exception + } + + public Block getBlock(Argument value) + { + Node source = valueMap.get(value); + if (source == null) { + throw new RuntimeException("value ... not defined"); // TODO throw ValuNotDefined Exception + } + + if (source instanceof Block block) + { + // TODO verify the type? + /*if (!value.type().equals(block.arguments().get(block.getIndex(value)).type())) { + throw new RuntimeException("type mismatch...");// TODO throw type mismatch? + }*/ + return block; + } + + throw new RuntimeException("to nie był Block argument"); // TODO throw NotABlockArgument exception + } + + public Query getRoot() + { + return root; + } + + public String print() + { + return root.print(0); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/ProgramBuilder.java b/core/trino-main/src/main/java/io/trino/sql/newir/ProgramBuilder.java new file mode 100644 index 000000000000..206e433e490b --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/ProgramBuilder.java @@ -0,0 +1,68 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import io.trino.sql.planner.plan.OutputNode; +import io.trino.sql.planner.plan.PlanNode; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.IntStream; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +public class ProgramBuilder +{ + private ProgramBuilder() {} + + public static Program buildProgram(PlanNode root) + { + checkArgument(root instanceof OutputNode, "Expected root to be an OutputNode. Actual: " + root.getClass().getSimpleName()); + + ValueNameAllocator nameAllocator = new ValueNameAllocator(); + ImmutableMap.Builder valueMapBuilder = ImmutableMap.builder(); + Block.Builder rootBlock = new Block.Builder(Optional.of("^query"), ImmutableList.of()); + + // for now, ignoring return value. Could be worth to remember it as the final terminal Operation in the Program. + root.accept(new RelationalProgramBuilder(nameAllocator, valueMapBuilder), new Context(rootBlock)); + + // verify if all values are mapped + Set allocatedValues = IntStream.range(0, nameAllocator.label).mapToObj(index -> "%" + index).collect(toImmutableSet()); + Map valueMap = valueMapBuilder.buildOrThrow(); + Set mappedValues = valueMap.keySet().stream().map(Value::name).collect(toImmutableSet()); + if (!Sets.symmetricDifference(allocatedValues, mappedValues).isEmpty()) { + throw new RuntimeException("go map them all"); // TODO error + } + + // allocating this name last to avoid stealing the "%0" label. This label won't be printed. + String resultName = nameAllocator.newName(); + + return new Program(new Query(resultName, rootBlock.build()), valueMap); + } + + public static class ValueNameAllocator + { + private int label = 0; + + public String newName() + { + return "%" + label++; + } + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Query.java b/core/trino-main/src/main/java/io/trino/sql/newir/Query.java new file mode 100644 index 000000000000..7c8c92dfe81f --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Query.java @@ -0,0 +1,106 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.type.IrType; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static io.trino.spi.type.VoidType.VOID; +import static io.trino.sql.newir.PrinterOptions.INDENT; +import static io.trino.sql.newir.Region.singleBlockRegion; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +public final class Query + implements Operation +{ + private static final String NAME = "query"; + + private final Result result; + private final Region query; + + public Query(String resultName, Block query) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(query, "query is null"); + + this.result = new Result(resultName, VOID); + + // TODO check if statement returns VOID per Output terminal operation -- QUERY could be a subclass of Statement with this check. + this.query = singleBlockRegion(Optional.of("query"), query); + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(); + } + + @Override + public List regions() + { + return ImmutableList.of(query); + } + + @Override + public Set attributes() + { + return ImmutableSet.of(); + } + + @Override + public String print(int indentLevel) + { + return query.print(indentLevel); + } + + @Override + public String prettyPrint(int indentLevel) + { + return "♡♡♡ query ♡♡♡"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Query) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.query, that.query); + } + + @Override + public int hashCode() + { + return Objects.hash(result, query); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/README.md b/core/trino-main/src/main/java/io/trino/sql/newir/README.md new file mode 100644 index 000000000000..fb0157268ca0 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/README.md @@ -0,0 +1,89 @@ +# The new IR + +The new IR for Trino is conceptually based on [MLIR](https://mlir.llvm.org/).\ +This is a prototype implementation. It will change over time. + +## Abstractions + +### Operation + +[Operation](Operation.java) is the main abstraction. It is used to represent +relational and scalar SQL operations in a uniform way. Some examples of +operations are: + +- [Constant](Constant.java): represents a constant scalar value or a constant relation, +- [FieldSelection](FieldSelection.java): selects a row field by name, +- [Filter](Filter.java): relational filter operation, +- [Output](Output.java): query output, +- [Query](Query.java): represents a SELECT statement, +- [Return](Return.java): terminal operation, +- [Row](Row.java): row constructor, +- [Values](Values.java): SQL values. + +All operations share common features: + +1. Operations return a single result, being a typed value. An operation derives its + output type based on its inputs and attributes. +2. Operations can have a list of [Regions](Region.java) containing [Blocks](Block.java) + to express nested logic as a lambda. For example, [Filter](Filter.java) has a block + with the predicate, CorrelatedJoin has a block with the subquery program. A block + has a list of operations, forming a recursive structure. +3. Operations can take arguments, being either results of another operation, + or parameters of an enclosing block. Operations can require that arguments + adhere to [TypeConstraints](TypeConstraint.java). +4. Operations can have [Attributes](Attribute.java). Attributes represent + logical properties of the operation's result, like cardinality. They can also represent + operation's properties, like join type. + +### Value + +[Value](Value.java) is another abstraction important for MLIR-style modeling. +A value is either an operation result or a block argument.\ +Values follow the +[SSA form](https://en.wikipedia.org/wiki/Static_single-assignment_form), which means +that each value is assigned exactly once.\ +Values follow the usual visibility rules: a value is visible if it was assigned +earlier in the same block, or it is an argument of some enclosing block. +Visibility across nested blocks is useful for modeling nested lambdas, like +deeply correlated subqueries.\ +In MLIR, blocks can invoke other blocks and pass values. In our initial model, +we operate on a higher abstraction level, and do not use it. + +In the current Trino IR, the PlanNodes take other PlanNodes as sources. +Using explicit values introduces indirection between the operation which +produces the value and the operation which consumes it. + +### Program + +Program represents a query plan.\ +It has one top-level [Query](Query.java) operation. +This operation has one block which contains the full query program, ending with a +terminal [Output](Output.java) operation.\ +Program also has a map which links each value to its source. The source is either +an operation returning the value or a block declaring the value as an argument. + +In the future, this model can be easily extended to represent a set of statements. + +## Conversions between old and new IRs + +Initially, the old and new IRs will coexist in Trino. We need a way to go from one +representation to the other.\ +[ProgramBuilder](ProgramBuilder.java) converts a tree of PlanNodes into a Program. +For now, it supports a small subset of PlanNodes.\ +For the conversion, it uses two visitor-based rewriters: + +- [RelationalProgramBuilder](RelationalProgramBuilder.java) rewrites PlanNodes based on [PlanVisitor](../planner/plan/PlanVisitor.java) +- [ScalarProgramBuilder](ScalarProgramBuilder.java) rewrites scalar expressions based on [IrVisitor](../ir/IrVisitor.java) + +We need the two rewriters because the old IR has different representations for scalar +and relational operations. In the new IR, all operations are represented in a uniform way. + +## Evolution of the project + +#### Phase 1: new plan representation (in progress) + +- Define the abstractions in the spirit of MLIR. +- Support programmatic creation. +- Implement serialization-deserialization using the MLIR assembly format. +- Implement the scalar and relational operations which are present in the optimized Trino plan. + Use high level of abstraction, similar to that of PlanNodes. diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Region.java b/core/trino-main/src/main/java/io/trino/sql/newir/Region.java new file mode 100644 index 000000000000..6d55deff4ab5 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Region.java @@ -0,0 +1,58 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import io.trino.spi.type.IrType; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static io.trino.sql.newir.PrinterOptions.INDENT; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +public record Region(Optional name, List blocks) + implements Node +{ + public Region(Optional name, List blocks) // TODO validate name + { + this.name = requireNonNull(name, "name is null"); + this.blocks = ImmutableList.copyOf(requireNonNull(blocks, "blocks is null")); + // TODO verify single Block // (later: verify list of blocks not empty) + } + + public static Region singleBlockRegion(Optional name, Block block) + { + return new Region(name, ImmutableList.of(block)); + } + + @Override + public String print(int indentLevel) + { + String indent = INDENT.repeat(indentLevel); + + // TODO for now skipping the region name + return blocks().stream() + .map(block -> block.print(indentLevel)) + .collect(joining("\n", "{\n", "\n" + indent + "}")); + } + + @Override + public String prettyPrint(int indentLevel) + { + return "region too but pretty"; + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/RelationalProgramBuilder.java b/core/trino-main/src/main/java/io/trino/sql/newir/RelationalProgramBuilder.java new file mode 100644 index 000000000000..d28353fbdc4f --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/RelationalProgramBuilder.java @@ -0,0 +1,269 @@ +package io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.type.EmptyRowType; +import io.trino.spi.type.IrType; +import io.trino.spi.type.MultisetType; +import io.trino.spi.type.RowType; +import io.trino.spi.type.Type; +import io.trino.sql.ir.Expression; +import io.trino.sql.planner.Symbol; +import io.trino.sql.planner.plan.FilterNode; +import io.trino.sql.planner.plan.OutputNode; +import io.trino.sql.planner.plan.PlanNode; +import io.trino.sql.planner.plan.PlanVisitor; +import io.trino.sql.planner.plan.ValuesNode; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.spi.type.EmptyRowType.EMPTY_ROW; +import static io.trino.sql.newir.Context.argumentMapping; +import static io.trino.sql.newir.Context.composedMapping; +import static io.trino.sql.newir.RelationalProgramBuilder.OperationAndMapping; +import static io.trino.sql.newir.TypeConstraint.IS_RELATION; +import static io.trino.sql.newir.TypeConstraint.IS_RELATION_ROW; +import static io.trino.sql.newir.Values.valuesWithoutFields; +import static java.util.Objects.requireNonNull; + +/** + * A rewriter transforming a tree of PlanNodes into a MLIR program based on `PlanVisitor`. + * For scalar expressions, uses another rewriter `ScalarProgramBuilder` based on `IrVisitor`. + */ +public class RelationalProgramBuilder + extends PlanVisitor +{ + private final ProgramBuilder.ValueNameAllocator nameAllocator; + private final ImmutableMap.Builder valueMap; + + public RelationalProgramBuilder(ProgramBuilder.ValueNameAllocator nameAllocator, ImmutableMap.Builder valueMap) + { + this.nameAllocator = requireNonNull(nameAllocator, "nameAllocator is null"); + this.valueMap = requireNonNull(valueMap, "valueMap is null"); + } + + @Override + protected OperationAndMapping visitPlan(PlanNode node, Context context) + { + throw new UnsupportedOperationException("The new IR does not support " + node.getClass().getSimpleName() + " yet"); + } + + @Override + public OperationAndMapping visitFilter(FilterNode node, Context context) + { + OperationAndMapping input = node.getSource().accept(this, context); + String resultName = nameAllocator.newName(); + + // model filter predicate as a lambda (Block) + Block.Argument predicateArgument = new Block.Argument( + nameAllocator.newName(), + relationRowType(input.operation().result().type())); + Block.Builder predicateBuilder = new Block.Builder(Optional.of("^predicate"), ImmutableList.of(predicateArgument)); + + node.getPredicate().accept( + new ScalarProgramBuilder(nameAllocator, valueMap), + new Context(predicateBuilder, composedMapping(context, argumentMapping(predicateArgument, input.mapping())))); + + addReturnOperation(predicateBuilder); + + Block predicate = predicateBuilder.build(); + valueMap.put(predicateArgument, predicate); // each time a name is allocated, the Value should be mapped to its source + + Filter filter = new Filter(resultName, input.operation().result(), predicate, input.operation().attributes()); + valueMap.put(filter.result(), filter); + Map outputMapping = deriveOutputMapping(relationRowType(filter.result().type()), node.getOutputSymbols()); + context.block().addOperation(filter); + return new OperationAndMapping(filter, outputMapping); + } + + @Override + public OperationAndMapping visitOutput(OutputNode node, Context context) + { + OperationAndMapping input = node.getSource().accept(this, context); + String resultName = nameAllocator.newName(); + + // model output fields selection as a lambda (Block) + Block.Argument fieldSelectorArgument = new Block.Argument( + nameAllocator.newName(), + relationRowType(input.operation().result().type())); + Block fieldSelectorBlock = fieldSelectorBlock("^outputFieldSelector", fieldSelectorArgument, input.mapping(), node.getOutputSymbols()); + valueMap.put(fieldSelectorArgument, fieldSelectorBlock); + + Output output = new Output(resultName, input.operation().result(), fieldSelectorBlock, node.getColumnNames()); + valueMap.put(output.result(), output); + context.block().addOperation(output); + return new OperationAndMapping(output, ImmutableMap.of()); // unlike OutputNode, the Output operation returns Void, not a relation + } + + @Override + public OperationAndMapping visitValues(ValuesNode node, Context context) + { + // TODO Values can be constant or correlated. If it is effectively constant, it should be folded to Constant operation + String resultName = nameAllocator.newName(); + + Values values; + if (node.getOutputSymbols().isEmpty()) { + values = valuesWithoutFields(resultName, node.getRowCount()); + } + else { + // model each component row of Values as a no-argument Block + List rows = node.getRows().orElseThrow().stream() + .map(rowExpression -> { + Block.Builder rowBlock = new Block.Builder(Optional.of("^row"), ImmutableList.of()); + rowExpression.accept( + new ScalarProgramBuilder(nameAllocator, valueMap), + new Context(rowBlock, context.symbolMapping())); + addReturnOperation(rowBlock); + return rowBlock.build(); + }) + .collect(toImmutableList()); + RowType rowType = RowType.anonymous(node.getOutputSymbols().stream() + .map(Symbol::type) + .collect(toImmutableList())); + values = new Values(resultName, rowType, rows); + } + valueMap.put(values.result(), values); + Map outputMapping = deriveOutputMapping(relationRowType(values.result().type()), node.getOutputSymbols()); + context.block().addOperation(values); + return new OperationAndMapping(values, outputMapping); + } + + /** + * A type of relation is represented as MultisetType of row type: RowType or EmptyRowType. This method extracts the element type. + */ + private static IrType relationRowType(IrType relationType) + { + checkArgument(IS_RELATION.test(relationType), "not a relation type"); // TODO error + + return ((MultisetType) relationType).getElementType(); + } + + /** + * Map each output symbol of the PlanNode to a corresponding field name in the Operations output row type. + */ + private static Map deriveOutputMapping(IrType relationRowType, List outputSymbols) + { + checkArgument(IS_RELATION_ROW.test(relationRowType), "not a relation row type"); // TODO error + + if (relationRowType.equals(EMPTY_ROW)) { + checkArgument(outputSymbols.isEmpty(), "..."); // TODO error + return ImmutableMap.of(); + } + + RowType rowType = (RowType) relationRowType; + checkArgument(rowType.getFields().size() == outputSymbols.size(), "Relation RowType does not match output symbols."); + + // Using a HashMap because it can handle duplicates. + // If a PlanNode outputs some symbol twice, we will use the first occurrence for mapping. + // As a result, the downstream references to the symbol will be mapped to the same output field, + // and the other field will be unused and eligible for pruning. + Map mapping = HashMap.newHashMap(outputSymbols.size()); + for (int i = 0; i < outputSymbols.size(); i++) { + Symbol symbol = outputSymbols.get(i); + String fieldName = rowType.getFields().get(i).getName().orElseThrow(); + mapping.putIfAbsent(symbol, fieldName); + } + return mapping; + } + + private Block fieldSelectorBlock(String blockName, Block.Argument inputRow, Map inputSymbolMapping, List selectedSymbolsList) + { + return fieldSelectorBlock( + blockName, + ImmutableList.of(inputRow), + ImmutableList.of(inputSymbolMapping), + ImmutableList.of(selectedSymbolsList)); + } + + /** + * A helper method to express input field selection as a lambda. + * Useful for operations which pass selected input fields on output, for example join, unnest. + * Also useful for selecting input columns necessary for the operation's logic, for example ordering columns, partitioning columns. + * + * @param blockName name for the result Block + * @param inputRows arguments to the lambda representing input rows from which we want to select fields + * @param inputSymbolMappings mapping symbol --> row field name for each input row + * @param selectedSymbolsLists list of symbols to select from each input row + * @return a row containing selected fields from all input rows + */ + private Block fieldSelectorBlock(String blockName, List inputRows, List> inputSymbolMappings, List> selectedSymbolsLists) + { + checkArgument(inputRows.size() == inputSymbolMappings.size(), "Inputs and input symbol mappings do not match."); + checkArgument(inputRows.size() == selectedSymbolsLists.size(), "Inputs and symbol lists do not match."); + + ImmutableList.Builder selections = ImmutableList.builder(); + + Block.Builder selectorBlock = new Block.Builder(Optional.of(blockName), inputRows); + for (int i = 0; i < inputRows.size(); i++) { + Block.Argument argument = inputRows.get(i); + Map symbolMapping = inputSymbolMappings.get(i); + List symbols = selectedSymbolsLists.get(i); + for (Symbol symbol : symbols) { + String value = nameAllocator.newName(); + FieldSelection fieldSelection = new FieldSelection(value, argument, symbolMapping.get(symbol), ImmutableSet.of()); // TODO pass appropriate row-specific input attributes through lambda arguments? + valueMap.put(fieldSelection.result(), fieldSelection); + selectorBlock.addOperation(fieldSelection); + selections.add(fieldSelection); + } + } + // build row of selected items + String rowValue = nameAllocator.newName(); + Row rowConstructor = new Row( + rowValue, + selections.build().stream().map(Operation::result).collect(toImmutableList()), + selections.build().stream().map(Operation::attributes).collect(toImmutableList())); + valueMap.put(rowConstructor.result(), rowConstructor); + selectorBlock.addOperation(rowConstructor); + + addReturnOperation(selectorBlock); + + return selectorBlock.build(); + } + + /** + * Return the value of the recent operation in the builder + */ + private void addReturnOperation(Block.Builder builder) + { + String returnValue = nameAllocator.newName(); + Operation recentOperation = builder.recentOperation(); + Return returnOperation = new Return(returnValue, recentOperation.result(), recentOperation.attributes()); + valueMap.put(returnOperation.result(), returnOperation); + builder.addOperation(returnOperation); + } + + /** + * Assigns unique lowercase names, compliant with IS_RELATION_ROW type constraint: f_1, f_2, ... + * Indexing from 1 for similarity with SQL indexing. + */ + public static RowType assignRelationRowTypeFieldNames(RowType relationRowType) + { + ImmutableList.Builder fields = ImmutableList.builder(); + for (int i = 0; i < relationRowType.getTypeParameters().size(); i++) { + fields.add(new RowType.Field( + Optional.of(String.format("f_%s", i + 1)), + relationRowType.getTypeParameters().get(i))); + } + return RowType.from(fields.build()); + } + + /** + * A result of transforming a PlanNode into an Operation. + * Maps each output symbol of the PlanNode to a corresponding field name in the Operations output RowType. + */ + public record OperationAndMapping(Operation operation, Map mapping) + { + public OperationAndMapping(Operation operation, Map mapping) + { + this.operation = requireNonNull(operation, "operation is null"); + this.mapping = ImmutableMap.copyOf(requireNonNull(mapping, "mapping is null")); + } + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Return.java b/core/trino-main/src/main/java/io/trino/sql/newir/Return.java new file mode 100644 index 000000000000..50d819e57855 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Return.java @@ -0,0 +1,97 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +public final class Return + implements Operation +{ + private static final String NAME = "return"; + + private final Result result; + private final Value input; + private final Set attributes; + + public Return(String resultName, Value input, Set sourceAttributes) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(input, "input is null"); + requireNonNull(sourceAttributes, "sourceAttributes is null"); + + this.result = new Result(resultName, input.type()); + + this.input = input; + + this.attributes = ImmutableSet.copyOf(sourceAttributes); + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(input); + } + + @Override + public List regions() + { + return ImmutableList.of(); + } + + @Override + public Set attributes() { + return attributes; + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty return"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Return) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.input, that.input) && + Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() + { + return Objects.hash(result, input, attributes); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Row.java b/core/trino-main/src/main/java/io/trino/sql/newir/Row.java new file mode 100644 index 000000000000..fc43507be4d8 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Row.java @@ -0,0 +1,118 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.type.IrType; +import io.trino.spi.type.RowType; +import io.trino.spi.type.Type; +import io.trino.spi.type.VoidType; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.util.Objects.requireNonNull; + +public final class Row + implements Operation +{ + private static final String NAME = "row"; + + private final Result result; + private final List fields; + private final Set attributes; + + public Row(String resultName, List fields, List> sourceAttributes) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(fields, "fields is null"); + requireNonNull(sourceAttributes, "sourceAttributes is null"); + + IrType resultType = RowType.anonymous( // fails if there are no fields + fields.stream().map(value -> { + if (value.type() instanceof VoidType) { + throw new RuntimeException("can't use Void"); // TODO type mismatch or other error about using Void + } + return (Type) value.type(); + }).collect(toImmutableList())); + + this.result = new Result(resultName, resultType); + + this.fields = ImmutableList.copyOf(fields); + + this.attributes = deriveAttributes(sourceAttributes); + } + + // TODO + private Set deriveAttributes(List> sourceAttributes) + { + return ImmutableSet.of(); + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() + { + return result; + } + + @Override + public List arguments() + { + return fields; + } + + @Override + public List regions() + { + return ImmutableList.of(); + } + + @Override + public Set attributes() + { + return attributes; + } + + @Override + public String prettyPrint(int indentLevel) + { + return "row :)"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Row) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.fields, that.fields) && + Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() + { + return Objects.hash(result, fields, attributes); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/ScalarProgramBuilder.java b/core/trino-main/src/main/java/io/trino/sql/newir/ScalarProgramBuilder.java new file mode 100644 index 000000000000..09a367c9442d --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/ScalarProgramBuilder.java @@ -0,0 +1,80 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.sql.ir.Expression; +import io.trino.sql.ir.IrVisitor; +import io.trino.sql.newir.ProgramBuilder.ValueNameAllocator; + +import java.util.List; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.util.Objects.requireNonNull; + +/** + * A rewriter transforming a tree of scalar Expressions into a MLIR program based on `IrVisitor`. + * It is called from `RelationalProgramBuilder` to transform predicates etc. + * Note: we don't need to pass a `RelationalProgramBuilder` for recursive calls because the + * IR Expressions don't have nested relations. + */ +public class ScalarProgramBuilder + extends IrVisitor +{ + private final ValueNameAllocator nameAllocator; + private final ImmutableMap.Builder valueMap; + + public ScalarProgramBuilder(ValueNameAllocator nameAllocator, ImmutableMap.Builder valueMap) + { + this.nameAllocator = requireNonNull(nameAllocator, "nameAllocator is null"); + this.valueMap = requireNonNull(valueMap, "valueMap is null"); + } + + @Override + protected Operation visitExpression(Expression node, Context context) + { + throw new UnsupportedOperationException("The new IR does not support " + node.getClass().getSimpleName() + " yet"); + } + + @Override + protected Operation visitConstant(io.trino.sql.ir.Constant node, Context context) + { + String resultName = nameAllocator.newName(); + Constant constant = new Constant(resultName, node.type(), node.value()); + valueMap.put(constant.result(), constant); + context.block().addOperation(constant); + return constant; + } + + @Override + protected Operation visitRow(io.trino.sql.ir.Row node, Context context) + { + // lowering alert! Unrolls the row into field assignments and a final row constructor. + ImmutableList.Builder itemsBuilder = ImmutableList.builder(); + for (Expression item : node.items()) { + itemsBuilder.add(item.accept(this, context)); + } + List items = itemsBuilder.build(); + + String resultName = nameAllocator.newName(); + Row row = new Row( + resultName, + items.stream().map(Operation::result).collect(toImmutableList()), + items.stream().map(Operation::attributes).collect(toImmutableList())); + valueMap.put(row.result(), row); + context.block().addOperation(row); + return row; + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/TypeConstraint.java b/core/trino-main/src/main/java/io/trino/sql/newir/TypeConstraint.java new file mode 100644 index 000000000000..1049540b3f9e --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/TypeConstraint.java @@ -0,0 +1,64 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import io.trino.spi.type.EmptyRowType; +import io.trino.spi.type.IrType; +import io.trino.spi.type.MultisetType; +import io.trino.spi.type.RowType; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import static java.util.Objects.requireNonNull; + +public record TypeConstraint(Predicate constraint) +{ + // Intermediate result row type. + // Row without fields is supported and represented as EmptyRowType. + // If row fields are present, they must have unique names. + public static final TypeConstraint IS_RELATION_ROW = new TypeConstraint(type -> { + if (type instanceof EmptyRowType) { + return true; + } + if (type instanceof RowType rowType) { + Set uniqueFieldNames = new HashSet<>(); + for (RowType.Field field : rowType.getFields()) { + if (field.getName().isEmpty()) { + return false; + } + if (!uniqueFieldNames.add(field.getName().orElseThrow())) { // TODO also check name format? "f_n" + return false; + } + } + return true; + } + return false; + }); + + // Intermediate result type. All fields must have unique names. + public static final TypeConstraint IS_RELATION = new TypeConstraint( + type -> type instanceof MultisetType multisetType && IS_RELATION_ROW.test(multisetType.getElementType())); + + public TypeConstraint + { + requireNonNull(constraint, "constraint is null"); + } + + public boolean test(IrType t) + { + return constraint.test(t); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Value.java b/core/trino-main/src/main/java/io/trino/sql/newir/Value.java new file mode 100644 index 000000000000..20c3459c8c16 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Value.java @@ -0,0 +1,28 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.errorprone.annotations.Immutable; +import io.trino.spi.type.IrType; + +@Immutable +public sealed interface Value + permits Operation.Result, Block.Argument +{ + String name(); + + IrType type(); + + Node source(Program program); +} diff --git a/core/trino-main/src/main/java/io/trino/sql/newir/Values.java b/core/trino-main/src/main/java/io/trino/sql/newir/Values.java new file mode 100644 index 000000000000..4ee4382188a9 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/newir/Values.java @@ -0,0 +1,143 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.type.IrType; +import io.trino.spi.type.MultisetType; +import io.trino.spi.type.RowType; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.spi.type.EmptyRowType.EMPTY_ROW; +import static io.trino.sql.newir.Region.singleBlockRegion; +import static io.trino.sql.newir.RelationalProgramBuilder.assignRelationRowTypeFieldNames; +import static java.util.Objects.requireNonNull; + +public final class Values + implements Operation +{ + private static final String NAME = "values"; + + private final Result result; + private final List rows; + private final Set attributes; + + public Values(String resultName, RowType rowType, List rows) + { + requireNonNull(resultName, "resultName is null"); + requireNonNull(rows, "rows is null"); + + // Create output type with unique field names. + // Field names of the passed RowType and of the individual rows (if present) will be ignored. + // This is consistent with the Trino behavior in StatementAnalyzer: the RelationType + // for Values has anonymous fields even if individual rows had named fields. + RowType outputType = assignRelationRowTypeFieldNames(rowType); + this.result = new Result(resultName, new MultisetType(outputType)); + + // Verify that each row matches the output type. Check field types only. + // Field names are ignored. They will be overridden by the output type. + this.rows = rows.stream() + .map(rowBlock -> { // TODO verify that Blocks have no arguments + IrType blockType = rowBlock.getReturnedType(); + if (!(blockType instanceof RowType)) { + throw new RuntimeException("miał być row type"); // TODO error + } + if (!((RowType) blockType).getTypeParameters().equals(rowType.getTypeParameters())) { + throw new RuntimeException("type mismatch"); // TODO error type mismatch + } + return singleBlockRegion(Optional.of("row"), rowBlock); // TODO sequential names? "row_1", "row_2"... + }) + .collect(toImmutableList()); + // TODO all Blocks representing rows could be combined into one Block returning a multiset + + this.attributes = ImmutableSet.of(new Cardinality(rows.size())); // TODO check constant? + } + + private Values(String resultName, int rows) + { + requireNonNull(resultName, "resultName is null"); + checkArgument(rows >= 0, "..."); // TODO error + + this.result = new Result(resultName, EMPTY_ROW); + + this.rows = ImmutableList.of(); + + this.attributes = ImmutableSet.of(new Cardinality(rows)); + } + + public static Values valuesWithoutFields(String resultName, int rows) + { + return new Values(resultName, rows); + } + + @Override + public String name() + { + return NAME; + } + + @Override + public Result result() + { + return result; + } + + @Override + public List arguments() + { + return ImmutableList.of(); + } + + @Override + public List regions() + { + return rows; + } + + @Override + public Set attributes() + { + return attributes; + } + + @Override + public String prettyPrint(int indentLevel) + { + return "pretty values"; + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (Values) obj; + return Objects.equals(this.result, that.result) && + Objects.equals(this.rows, that.rows) && + Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() + { + return Objects.hash(result, rows, attributes); + } +} diff --git a/core/trino-main/src/test/java/io/trino/sql/newir/TestPrintProgram.java b/core/trino-main/src/test/java/io/trino/sql/newir/TestPrintProgram.java new file mode 100644 index 000000000000..f33b988001a2 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/newir/TestPrintProgram.java @@ -0,0 +1,51 @@ +/* + * Licensed 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 io.trino.sql.newir; + +import com.google.common.collect.ImmutableList; +import io.trino.sql.ir.Row; +import io.trino.sql.planner.Symbol; +import io.trino.sql.planner.plan.FilterNode; +import io.trino.sql.planner.plan.OutputNode; +import io.trino.sql.planner.plan.PlanNodeId; +import io.trino.sql.planner.plan.ValuesNode; +import org.junit.jupiter.api.Test; + +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static org.assertj.core.api.Assertions.assertThat; + +class TestPrintProgram +{ + @Test + public void testProgramBuilderAndPrinter() + { + assertThat(ProgramBuilder.buildProgram( + new OutputNode( + new PlanNodeId("100"), + new FilterNode( + new PlanNodeId("101"), + new ValuesNode( + new PlanNodeId("102"), + ImmutableList.of(new Symbol(BIGINT, "a"), new Symbol(BOOLEAN, "b")), + ImmutableList.of( + new Row(ImmutableList.of(new io.trino.sql.ir.Constant(BIGINT, 3L), new io.trino.sql.ir.Constant(BOOLEAN, true))), + new Row(ImmutableList.of(new io.trino.sql.ir.Constant(BIGINT, 5L), new io.trino.sql.ir.Constant(BOOLEAN, false))))), + new io.trino.sql.ir.Constant(BOOLEAN, true)), + ImmutableList.of("col_a"), + ImmutableList.of(new Symbol(BIGINT, "a")))) + .print()) + .isEqualTo("whatever, give me the printout in the error message"); + } +} diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/EmptyRowType.java b/core/trino-spi/src/main/java/io/trino/spi/type/EmptyRowType.java new file mode 100644 index 000000000000..b6cd2e8a56a2 --- /dev/null +++ b/core/trino-spi/src/main/java/io/trino/spi/type/EmptyRowType.java @@ -0,0 +1,79 @@ +/* + * Licensed 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 io.trino.spi.type; + +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.block.BlockBuilderStatus; +import io.trino.spi.block.ByteArrayBlock; +import io.trino.spi.connector.ConnectorSession; + +public class EmptyRowType + extends AbstractType +{ + public static final EmptyRowType EMPTY_ROW = new EmptyRowType(); + private static final String BASE = "empty_row"; + + private EmptyRowType() { + super(new TypeSignature(BASE), boolean.class, ByteArrayBlock.class); // TODO + } + + @Override + public BlockBuilder createBlockBuilder(BlockBuilderStatus blockBuilderStatus, int expectedEntries, int expectedBytesPerEntry) + { + throw new UnsupportedOperationException(getClass().getName()); // TODO errors + } + + @Override + public BlockBuilder createBlockBuilder(BlockBuilderStatus blockBuilderStatus, int expectedEntries) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public Object getObjectValue(ConnectorSession session, Block block, int position) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public void appendTo(Block block, int position, BlockBuilder blockBuilder) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public int getFlatFixedSize() + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public boolean isFlatVariableWidth() + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public int getFlatVariableWidthSize(Block block, int position) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public int relocateFlatVariableWidthOffsets(byte[] fixedSizeSlice, int fixedSizeOffset, byte[] variableSizeSlice, int variableSizeOffset) + { + throw new UnsupportedOperationException(getClass().getName()); + } +} diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/IrType.java b/core/trino-spi/src/main/java/io/trino/spi/type/IrType.java new file mode 100644 index 000000000000..38c3cea1b0d2 --- /dev/null +++ b/core/trino-spi/src/main/java/io/trino/spi/type/IrType.java @@ -0,0 +1,19 @@ +/* + * Licensed 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 io.trino.spi.type; + +public sealed interface IrType + permits Type, VoidType +{ +} diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/MultisetType.java b/core/trino-spi/src/main/java/io/trino/spi/type/MultisetType.java new file mode 100644 index 000000000000..30a6e76ce896 --- /dev/null +++ b/core/trino-spi/src/main/java/io/trino/spi/type/MultisetType.java @@ -0,0 +1,89 @@ +/* + * Licensed 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 io.trino.spi.type; + +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.block.BlockBuilderStatus; +import io.trino.spi.block.MapBlock; +import io.trino.spi.block.SqlMap; +import io.trino.spi.connector.ConnectorSession; + +import static io.trino.spi.type.StandardTypes.MULTISET; + +public class MultisetType + extends AbstractType // TODO complete implementation needed to implement constant Values +{ + private final Type elementType; + + public MultisetType(Type elementType) + { + // TODO same as map type? + super(new TypeSignature(MULTISET, TypeSignatureParameter.typeParameter(elementType.getTypeSignature())), SqlMap.class, MapBlock.class); + this.elementType = elementType; + } + + public Type getElementType() + { + return elementType; + } + + @Override + public BlockBuilder createBlockBuilder(BlockBuilderStatus blockBuilderStatus, int expectedEntries, int expectedBytesPerEntry) + { + throw new UnsupportedOperationException(getClass().getName()); // TODO errors + } + + @Override + public BlockBuilder createBlockBuilder(BlockBuilderStatus blockBuilderStatus, int expectedEntries) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public Object getObjectValue(ConnectorSession session, Block block, int position) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public void appendTo(Block block, int position, BlockBuilder blockBuilder) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public int getFlatFixedSize() + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public boolean isFlatVariableWidth() + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public int getFlatVariableWidthSize(Block block, int position) + { + throw new UnsupportedOperationException(getClass().getName()); + } + + @Override + public int relocateFlatVariableWidthOffsets(byte[] fixedSizeSlice, int fixedSizeOffset, byte[] variableSizeSlice, int variableSizeOffset) + { + throw new UnsupportedOperationException(getClass().getName()); + } +} diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/StandardTypes.java b/core/trino-spi/src/main/java/io/trino/spi/type/StandardTypes.java index e39e7ba6bde9..2893e043a0a2 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/StandardTypes.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/StandardTypes.java @@ -45,6 +45,7 @@ public final class StandardTypes public static final String IPADDRESS = "ipaddress"; public static final String GEOMETRY = "Geometry"; public static final String UUID = "uuid"; + public static final String MULTISET = "multiset"; private StandardTypes() {} } diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/Type.java b/core/trino-spi/src/main/java/io/trino/spi/type/Type.java index 38350f3858b2..47d1bae7b504 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/Type.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/Type.java @@ -28,7 +28,8 @@ import static io.trino.spi.type.TypeOperatorDeclaration.NO_TYPE_OPERATOR_DECLARATION; import static java.util.Objects.requireNonNull; -public interface Type +public non-sealed interface Type + extends IrType { /** * Gets the name of this type which must be case insensitive globally unique. diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/VoidType.java b/core/trino-spi/src/main/java/io/trino/spi/type/VoidType.java new file mode 100644 index 000000000000..f95df3e543c7 --- /dev/null +++ b/core/trino-spi/src/main/java/io/trino/spi/type/VoidType.java @@ -0,0 +1,26 @@ +/* + * Licensed 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 io.trino.spi.type; + +public record VoidType() + implements IrType +{ + public static final VoidType VOID = new VoidType(); + + @Override + public String toString() + { + return "void"; + } +}