/**
* 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.horizondb.db.parser.builders;
import io.horizondb.db.HorizonDBException;
import io.horizondb.db.databases.Database;
import io.horizondb.db.databases.DatabaseManager;
import io.horizondb.db.parser.HqlBaseListener;
import io.horizondb.db.parser.HqlParser.BetweenPredicateContext;
import io.horizondb.db.parser.HqlParser.InPredicateContext;
import io.horizondb.db.parser.HqlParser.PredicateContext;
import io.horizondb.db.parser.HqlParser.SelectContext;
import io.horizondb.db.parser.HqlParser.SelectListContext;
import io.horizondb.db.parser.HqlParser.SimplePredicateContext;
import io.horizondb.db.parser.MsgBuilder;
import io.horizondb.db.series.TimeSeries;
import io.horizondb.model.core.Predicate;
import io.horizondb.model.core.Projection;
import io.horizondb.model.core.predicates.Operator;
import io.horizondb.model.protocol.Msg;
import io.horizondb.model.protocol.MsgHeader;
import io.horizondb.model.protocol.OpCode;
import io.horizondb.model.protocol.SelectPayload;
import io.horizondb.model.schema.TimeSeriesDefinition;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.misc.NotNull;
/**
* <code>Builder</code> for <code>SelectQuery</code> message instances.
*/
final class SelectMsgBuilder extends HqlBaseListener implements MsgBuilder {
/**
* The database manager.
*/
private final DatabaseManager databaseManager;
/**
* The original request header.
*/
private final MsgHeader requestHeader;
/**
* The name of the database in which the time series must be created.
*/
private String databaseName;
/**
* The time series name.
*/
private String timeSeriesName;
/**
* The record and field projectionBuilder.
*/
private ProjectionBuilder projectionBuilder;
/**
* The predicate builders.
*/
private Deque<PredicateBuilder> predicateBuilders = new LinkedList<>();
/**
* Creates a new <code>CreateTimeSeriesRequestBuilder</code> instance.
*
* @param requestHeader the original request header
* @param database the database in which the time series must be created
*/
public SelectMsgBuilder(DatabaseManager databaseManager,
MsgHeader requestHeader,
String databaseName) {
this.databaseManager = databaseManager;
this.requestHeader = requestHeader;
this.databaseName = databaseName;
this.predicateBuilders.addFirst(PredicateBuilders.noop());
}
/**
* {@inheritDoc}
*/
@Override
public void enterSelect(@NotNull SelectContext ctx) {
if (ctx.databaseName() != null) {
this.databaseName = ctx.databaseName().getText();
}
this.timeSeriesName = ctx.ID().getText();
}
/**
* {@inheritDoc}
*/
@Override
public Msg<?> build() throws IOException, HorizonDBException {
Database database = this.databaseManager.getDatabase(this.databaseName);
TimeSeries timeSeries = database.getTimeSeries(this.timeSeriesName);
TimeSeriesDefinition definition = timeSeries.getDefinition();
PredicateBuilder builder = this.predicateBuilders.poll();
Predicate predicate = builder.build(definition);
Projection projection = this.projectionBuilder.build(definition);
SelectPayload payload = new SelectPayload(this.databaseName,
this.timeSeriesName,
projection,
predicate);
return Msg.newRequestMsg(this.requestHeader, OpCode.SELECT, payload);
}
/**
* {@inheritDoc}
*/
@Override
public void enterSelectList(@NotNull SelectListContext ctx) {
this.projectionBuilder = new ProjectionBuilder(toList(ctx));
}
/**
* {@inheritDoc}
*/
@Override
public void enterPredicate(@NotNull PredicateContext ctx) {
}
/**
* {@inheritDoc}
*/
@Override
public void exitPredicate(@NotNull PredicateContext ctx) {
if (ctx.AND() != null) {
PredicateBuilder right = this.predicateBuilders.removeFirst();
PredicateBuilder left = this.predicateBuilders.removeFirst();
PredicateBuilder expression = PredicateBuilders.and(left, right);
this.predicateBuilders.addFirst(expression);
} else if (ctx.OR() != null) {
PredicateBuilder right = this.predicateBuilders.removeFirst();
PredicateBuilder left = this.predicateBuilders.removeFirst();
PredicateBuilder expression = PredicateBuilders.or(left, right);
this.predicateBuilders.addFirst(expression);
}
}
/**
* {@inheritDoc}
*/
@Override
public void enterInPredicate(@NotNull InPredicateContext ctx) {
int childCount = ctx.getChildCount();
String fieldName = ctx.getChild(0).getText();
boolean notIn = (ctx.NOT() != null);
int start = 3;
if (notIn) {
start = 4;
}
List<String> values = new ArrayList<>();
for (int i = start; i < childCount - 1; i += 2 ) {
values.add(ctx.getChild(i).getText());
}
if (notIn) {
this.predicateBuilders.addFirst(PredicateBuilders.notIn(fieldName, values));
} else {
this.predicateBuilders.addFirst(PredicateBuilders.in(fieldName, values));
}
}
/**
* {@inheritDoc}
*/
@Override
public void enterBetweenPredicate(@NotNull BetweenPredicateContext ctx) {
String fieldName = ctx.getChild(0).getText();
boolean notBetween = (ctx.NOT() != null);
if (notBetween) {
String min = ctx.getChild(3).getText();
String max = ctx.getChild(5).getText();
this.predicateBuilders.addFirst(PredicateBuilders.notBetween(fieldName, min, max));
} else {
String min = ctx.getChild(2).getText();
String max = ctx.getChild(4).getText();
this.predicateBuilders.addFirst(PredicateBuilders.between(fieldName, min, max));
}
}
/**
* {@inheritDoc}
*/
@Override
public void enterSimplePredicate(@NotNull SimplePredicateContext ctx) {
String fieldName = ctx.ID().getText();
Operator operator = Operator.fromSymbol(ctx.operator().getText());
String value = ctx.value().getText();
this.predicateBuilders.addFirst(PredicateBuilders.simplePredicate(fieldName, operator, value));
}
/**
* Extracts a list of values from the specified context.
*
* @param ctx the context from which the list must be extracted
* @return a list of values
*/
private static List<String> toList(ParserRuleContext ctx) {
if (ctx == null) {
return Collections.emptyList();
}
List<String> list = new ArrayList<>();
for (int i = 0, m = ctx.getChildCount(); i < m; i += 2) {
list.add(ctx.getChild(i).getText().trim());
}
return list;
}
}