From 900fb0aee0b68cbb88f578f20019738b4416f436 Mon Sep 17 00:00:00 2001 From: Marcin Chrzanowski Date: Mon, 4 Nov 2019 10:27:29 +0100 Subject: Add query language interpreter code --- .../mimuw/cloudatlas/interpreter/Functions.java | 428 +++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Functions.java (limited to 'src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Functions.java') diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Functions.java b/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Functions.java new file mode 100644 index 0000000..dde84fe --- /dev/null +++ b/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Functions.java @@ -0,0 +1,428 @@ +/** + * Copyright (c) 2014, University of Warsaw + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package pl.edu.mimuw.cloudatlas.interpreter; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import pl.edu.mimuw.cloudatlas.interpreter.Result.AggregationOperation; +import pl.edu.mimuw.cloudatlas.interpreter.Result.TransformOperation; +import pl.edu.mimuw.cloudatlas.interpreter.Result.UnaryOperation; +import pl.edu.mimuw.cloudatlas.model.Type; +import pl.edu.mimuw.cloudatlas.model.Type.PrimaryType; +import pl.edu.mimuw.cloudatlas.model.TypeCollection; +import pl.edu.mimuw.cloudatlas.model.TypePrimitive; +import pl.edu.mimuw.cloudatlas.model.Value; +import pl.edu.mimuw.cloudatlas.model.ValueBoolean; +import pl.edu.mimuw.cloudatlas.model.ValueDouble; +import pl.edu.mimuw.cloudatlas.model.ValueInt; +import pl.edu.mimuw.cloudatlas.model.ValueList; +import pl.edu.mimuw.cloudatlas.model.ValueNull; +import pl.edu.mimuw.cloudatlas.model.ValueSet; +import pl.edu.mimuw.cloudatlas.model.ValueTime; + +class Functions { + private static Functions instance = null; + + private static final UnaryOperation ROUND = new UnaryOperation() { + @Override + public Value perform(Value v) { + if(v.getType().isCompatible(TypePrimitive.DOUBLE)) { + if(v.isNull()) + return new ValueDouble(null); + return new ValueDouble((double)Math.round(((ValueDouble)v).getValue())); + } + throw new IllegalArgumentException("Value must have type " + TypePrimitive.DOUBLE + "."); + } + }; + + private static final UnaryOperation FLOOR = new UnaryOperation() { + @Override + public Value perform(Value v) { + if(v.getType().isCompatible(TypePrimitive.DOUBLE)) { + if(v.isNull()) + return new ValueDouble(null); + return new ValueDouble((double)Math.floor(((ValueDouble)v).getValue())); + } + throw new IllegalArgumentException("Value must have type " + TypePrimitive.DOUBLE + "."); + } + }; + + private static final UnaryOperation CEIL = new UnaryOperation() { + @Override + public Value perform(Value v) { + // TODO + throw new UnsupportedOperationException("Not yet implemented"); + } + }; + + private static final AggregationOperation COUNT = new AggregationOperation() { + @Override + public ValueInt perform(ValueList values) { + ValueList nlist = Result.filterNullsList(values); + if(nlist.getValue() == null) { + return new ValueInt(null); + } + return new ValueInt((long)nlist.size()); + } + }; + + private static final AggregationOperation SUM = new AggregationOperation() { + @Override + public Value perform(ValueList values) { + // TODO + throw new UnsupportedOperationException("Not yet implemented"); + } + }; + + private static final AggregationOperation AVERAGE = new AggregationOperation() { + @Override + public Value perform(ValueList values) { + Type elementType = ((TypeCollection)values.getType()).getElementType(); + PrimaryType primaryType = elementType.getPrimaryType(); + + if(primaryType != PrimaryType.INT && primaryType != PrimaryType.DOUBLE && primaryType != PrimaryType.DURATION + && primaryType != PrimaryType.NULL) { + throw new IllegalArgumentException("Aggregation doesn't support type: " + elementType + "."); + } + + ValueList nlist = Result.filterNullsList(values); + if(nlist.getValue() == null || nlist.isEmpty()) { + return ValueNull.getInstance(); + } + + Value result = nlist.get(0).getDefaultValue(); + + for(Value v : nlist) { + result = result.addValue(v); + } + Value size = primaryType == PrimaryType.DOUBLE? new ValueDouble((double)nlist.size()) : new ValueInt( + (long)nlist.size()); + return result.divide(size); + } + }; + + private static final AggregationOperation AND = new AggregationOperation() { + @Override + public ValueBoolean perform(ValueList values) { // lazy + ValueList nlist = Result.filterNullsList(values); + if(nlist.getValue() == null) { + return new ValueBoolean(null); + } else if(values.isEmpty()) { + return new ValueBoolean(true); + } + for(Value v : nlist) { + if(v.getType().isCompatible(TypePrimitive.BOOLEAN)) { + if(v.isNull() || !((ValueBoolean)v).getValue()) + return new ValueBoolean(false); + } else + throw new IllegalArgumentException("Aggregation doesn't support type: " + v.getType() + "."); + } + return new ValueBoolean(true); + } + }; + + private static final AggregationOperation OR = new AggregationOperation() { + @Override + public ValueBoolean perform(ValueList values) { // lazy + // TODO + throw new UnsupportedOperationException("Not yet implemented"); + } + }; + + private static final AggregationOperation MIN = new AggregationOperation() { + @Override + public Value perform(ValueList values) { + ValueList nlist = Result.filterNullsList(values); + if(nlist.getValue() == null || nlist.isEmpty()) { + return ValueNull.getInstance(); + } + Value result = nlist.get(0); + for(Value v : nlist) { + if(((ValueBoolean)v.isLowerThan(result)).getValue()) { + result = v; + } + } + return result; + } + }; + + private static final AggregationOperation MAX = new AggregationOperation() { + @Override + public Value perform(ValueList values) { + ValueList nlist = Result.filterNullsList(values); + if(nlist.getValue() == null || nlist.isEmpty()) { + return ValueNull.getInstance(); + } + Value result = nlist.get(0); + for(Value v : nlist) { + if(((ValueBoolean)v.isLowerThan(result)).negate().and(v.isEqual(result).negate()).getValue()) { + result = v; + } + } + return result; + } + }; + + private static final TransformOperation UNFOLD = new TransformOperation() { + @Override + public ValueList perform(ValueList values) { + if(!((TypeCollection)values.getType()).getElementType().isCollection()) { + throw new IllegalArgumentException("All elements must have a collection compatible type."); + } + ValueList nlist = Result.filterNullsList(values); + if(nlist.getValue() == null) { + return new ValueList(null, + ((TypeCollection)((TypeCollection)values.getType()).getElementType()).getElementType()); + } else if(nlist.isEmpty()) { + return new ValueList( + ((TypeCollection)((TypeCollection)values.getType()).getElementType()).getElementType()); + } + List ret = new ArrayList(); + for(Value v : nlist) { + if(v.getType().getPrimaryType() == Type.PrimaryType.SET) { + ret.addAll((ValueSet)v); + } else if(v.getType().getPrimaryType() == Type.PrimaryType.LIST) { + ret.addAll((ValueList)v); + } + } + return new ValueList(ret, + ((TypeCollection)((TypeCollection)values.getType()).getElementType()).getElementType()); + } + }; + + private static final TransformOperation DISTINCT = new TransformOperation() { + @Override + public ValueList perform(ValueList values) { + if(values.isEmpty()) + return new ValueList(((TypeCollection)values.getType()).getElementType()); + List ret = new ArrayList(); + for(Value v : values) { + if(!ret.contains(v)) { + ret.add(v); + } + } + return new ValueList(ret, ((TypeCollection)values.getType()).getElementType()); + } + }; + + private static final TransformOperation SORT = new TransformOperation() { + @Override + public ValueList perform(ValueList values) { + if(values.isEmpty()) + return new ValueList(((TypeCollection)values.getType()).getElementType()); + List ret = new ArrayList(); + ret.addAll(values); + Collections.sort(ret, new Comparator() { + public int compare(Value v1, Value v2) { + if(((ValueBoolean)v1.isLowerThan(v2)).getValue()) { + return -1; + } else if(((ValueBoolean)v1.isEqual(v2)).getValue()) { + return 0; + } else { + return 1; + } + } + }); + return new ValueList(ret, ((TypeCollection)values.getType()).getElementType()); + } + }; + + private final ValueTime EPOCH; + + private Functions() { + try { + EPOCH = new ValueTime("2000/01/01 00:00:00.000"); + } catch(ParseException exception) { + throw new InternalInterpreterException("Cannot parse time when creating an EPOCH object.\n" + + exception.getMessage()); + } + } + + public static Functions getInstance() { + if(instance == null) + instance = new Functions(); + return instance; + } + + public Result evaluate(String name, List arguments) { + switch(name) { + case "round": + if(arguments.size() == 1) + return arguments.get(0).unaryOperation(ROUND); + break; + case "floor": + if(arguments.size() == 1) + return arguments.get(0).unaryOperation(FLOOR); + break; + case "ceil": + if(arguments.size() == 1) + return arguments.get(0).unaryOperation(CEIL); + break; + case "now": + if(arguments.size() == 0) + return new ResultSingle(new ValueTime(Calendar.getInstance().getTimeInMillis())); + break; + case "epoch": + if(arguments.size() == 0) + return new ResultSingle(EPOCH); + break; + case "count": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(COUNT); + break; + case "size": + if(arguments.size() == 1) + return arguments.get(0).valueSize(); + break; + case "sum": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(SUM); + break; + case "avg": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(AVERAGE); + break; + case "land": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(AND); + break; + case "lor": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(OR); + break; + case "min": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(MIN); + break; + case "max": + if(arguments.size() == 1) + return arguments.get(0).aggregationOperation(MAX); + break; + case "unfold": + if(arguments.size() == 1) + return arguments.get(0).transformOperation(UNFOLD); + break; + case "distinct": + if(arguments.size() == 1) + return arguments.get(0).transformOperation(DISTINCT); + break; + case "sort": + if(arguments.size() == 1) + return arguments.get(0).transformOperation(SORT); + break; + case "filterNulls": + if(arguments.size() == 1) + return arguments.get(0).filterNulls(); + break; + case "first": + if(arguments.size() == 2) { + Value size = arguments.get(0).getValue(); + if(size.getType().isCompatible(TypePrimitive.INTEGER) && ((ValueInt)size).getValue().intValue() >= 0) + return arguments.get(1).first(((ValueInt)size).getValue().intValue()); + throw new IllegalArgumentException("First argument must have type " + TypePrimitive.INTEGER + + " and be >= 0."); + } + break; + case "last": + if(arguments.size() == 2) { + Value size = arguments.get(0).getValue(); + if(size.getType().isCompatible(TypePrimitive.INTEGER) && ((ValueInt)size).getValue().intValue() >= 0) + return arguments.get(1).last(((ValueInt)size).getValue().intValue()); + throw new IllegalArgumentException("First argument must have type " + TypePrimitive.INTEGER + + " and be >= 0."); + } + break; + case "random": + if(arguments.size() == 2) { + Value size = arguments.get(0).getValue(); + if(size.getType().isCompatible(TypePrimitive.INTEGER) && ((ValueInt)size).getValue().intValue() >= 0) + return arguments.get(1).random(((ValueInt)size).getValue().intValue()); + throw new IllegalArgumentException("First argument must have type " + TypePrimitive.INTEGER + + " and be >= 0."); + } + break; + case "to_boolean": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.BOOLEAN); + break; + case "to_contact": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.CONTACT); + break; + case "to_double": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.DOUBLE); + break; + case "to_duration": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.DURATION); + break; + case "to_integer": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.INTEGER); + break; + case "to_string": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.STRING); + break; + case "to_time": + if(arguments.size() == 1) + return arguments.get(0).convertTo(TypePrimitive.TIME); + break; + case "to_set": + if(arguments.size() == 1) { + Type t = arguments.get(0).getType(); + if(t.isCollection()) { + Type elementType = ((TypeCollection)t).getElementType(); + return arguments.get(0).convertTo(new TypeCollection(Type.PrimaryType.SET, elementType)); + } + throw new IllegalArgumentException("First argument must be a collection."); + } + break; + case "to_list": + if(arguments.size() == 1) { + Type t = arguments.get(0).getType(); + if(t.isCollection()) { + Type elementType = ((TypeCollection)t).getElementType(); + return arguments.get(0).convertTo(new TypeCollection(Type.PrimaryType.LIST, elementType)); + } + throw new IllegalArgumentException("First argument must be a collection."); + } + break; + case "isNull": + if(arguments.size() == 1) + return arguments.get(0).isNull(); + break; + default: + throw new IllegalArgumentException("Illegal function name."); + } + throw new IllegalArgumentException("Illegal number of arguments."); + } +} -- cgit v1.2.3