/** * 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.BadHqlGrammarException; import io.horizondb.db.parser.HqlBaseListener; import io.horizondb.db.parser.HqlParser.InsertContext; import io.horizondb.db.parser.HqlParser.RecordNameContext; import io.horizondb.db.parser.MsgBuilder; import io.horizondb.db.series.TimeSeries; import io.horizondb.io.Buffer; import io.horizondb.io.buffers.Buffers; import io.horizondb.model.core.RecordUtils; import io.horizondb.model.core.records.BlockHeaderUtils; import io.horizondb.model.core.records.TimeSeriesRecord; import io.horizondb.model.protocol.InsertPayload; import io.horizondb.model.protocol.Msg; import io.horizondb.model.protocol.MsgHeader; import io.horizondb.model.protocol.OpCode; import io.horizondb.model.protocol.Payload; import io.horizondb.model.schema.TimeSeriesDefinition; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.misc.NotNull; import static java.lang.String.format; /** * <code>Builder</code> for messages requesting some data insertion. */ final class InsertMsgBuilder 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 data must be inserted. */ private String databaseName; /** * The time series in which the data must be inserted. */ private String series; /** * The type of the record that must be inserted. */ private String recordType; /** * The name of the fields in which some data must be inserted. */ private List<String> fieldNames; /** * The field values. */ private List<String> fieldValues; /** * Creates a new <code>CreateTimeSeriesRequestBuilder</code> instance. * * @param databaseManager the database manager * @param requestHeader the original request header * @param databaseName the name of the database in which the data must be inserted */ public InsertMsgBuilder(DatabaseManager databaseManager, MsgHeader requestHeader, String databaseName) { this.databaseManager = databaseManager; this.requestHeader = requestHeader; this.databaseName = databaseName; } /** * {@inheritDoc} */ @Override public void enterInsert(@NotNull InsertContext ctx) { if (ctx.databaseName() != null) { this.databaseName = ctx.databaseName().getText(); } RecordNameContext recordName = ctx.recordName(); this.series = recordName.timeSeriesName().getText(); this.recordType = recordName.ID().getText(); this.fieldNames = toList(ctx.fieldList()); this.fieldValues = toList(ctx.valueList()); } /** * 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()); } return list; } /** * {@inheritDoc} */ @Override public Msg<?> build() throws IOException, HorizonDBException { Database database = this.databaseManager.getDatabase(this.databaseName); TimeSeries timeSeries = database.getTimeSeries(this.series); TimeSeriesDefinition definition = timeSeries.getDefinition(); int recordTypeIndex = definition.getRecordTypeIndex(this.recordType); TimeSeriesRecord record = newRecord(definition, recordTypeIndex); int size = RecordUtils.computeSerializedSize(record); TimeSeriesRecord header = definition.newBlockHeader(); BlockHeaderUtils.setFirstTimestamp(header, record); BlockHeaderUtils.setLastTimestamp(header, record); BlockHeaderUtils.setCompressedBlockSize(header, size); BlockHeaderUtils.setRecordCount(header, recordTypeIndex, 1); Buffer buffer = Buffers.allocate(RecordUtils.computeSerializedSize(header) + size); RecordUtils.writeRecord(buffer, header); RecordUtils.writeRecord(buffer, record); Payload payload = new InsertPayload(this.databaseName, this.series, recordTypeIndex, buffer); return Msg.newRequestMsg(this.requestHeader, OpCode.INSERT, payload); } /** * Creates a new record from the specified payload. * * @param definition the time series definition * @param payload the payload * @return the new record * @throws BadHqlGrammarException if some values are invalid */ private TimeSeriesRecord newRecord(TimeSeriesDefinition definition, int recordIndex) throws BadHqlGrammarException { String fieldValue = null; try { TimeSeriesRecord record = definition.newRecord(recordIndex); if (this.fieldNames.isEmpty()) { for (int i = 0, m = this.fieldValues.size(); i < m ; i++) { fieldValue = this.fieldValues.get(i); record.getField(i).setValueFromString(definition.getTimeZone(), this.fieldValues.get(i)); } } else { for (int i = 0, m = this.fieldNames.size(); i < m ; i++) { String fieldName = this.fieldNames.get(i); fieldValue = this.fieldValues.get(i); int fieldIndex = definition.getFieldIndex(recordIndex, fieldName); record.getField(fieldIndex).setValueFromString(definition.getTimeZone(), this.fieldValues.get(i)); } } return record; } catch (NumberFormatException e) { throw new BadHqlGrammarException(format("The value %s cannot be converted into a number", fieldValue)); } catch (IllegalArgumentException e) { throw new BadHqlGrammarException(e.getMessage()); } } }