All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.questdb.cutlass.http.client.ser.JsonToTableSerializer Maven / Gradle / Ivy

There is a newer version: 8.2.1
Show newest version
/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2024 QuestDB
 *
 *  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.questdb.cutlass.http.client.ser;

import io.questdb.cairo.CairoEngine;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.TableWriter;
import io.questdb.cairo.TableWriterAPI;
import io.questdb.cairo.security.AllowAllSecurityContext;
import io.questdb.cairo.vm.Vm;
import io.questdb.cairo.vm.api.MemoryMARW;
import io.questdb.cutlass.json.JsonException;
import io.questdb.cutlass.json.JsonLexer;
import io.questdb.cutlass.json.JsonParser;
import io.questdb.griffin.SqlException;
import io.questdb.griffin.model.CreateTableModel;
import io.questdb.griffin.model.ExpressionNode;
import io.questdb.std.*;
import io.questdb.std.datetime.microtime.TimestampFormatUtils;
import io.questdb.std.datetime.millitime.DateFormatUtils;
import io.questdb.std.str.Path;
import io.questdb.std.str.Utf8StringSink;

import static io.questdb.cairo.ColumnType.*;

public class JsonToTableSerializer implements JsonParser, Mutable, QuietCloseable {
    private static final int STATE_DATA_ARRAY = 4;
    private static final int STATE_DATA_CELL = 10;
    private static final int STATE_DATA_ROW = 6;
    private static final int STATE_IGNORE_RECURSIVE = 12;
    private static final int STATE_MAIN_ATTR_NAMES = 1;
    private static final int STATE_METADATA_ARRAY = 3;
    private static final int STATE_METADATA_ATTR_COLUMN_NAME = 8;
    private static final int STATE_METADATA_ATTR_COLUMN_TYPE = 9;
    private static final int STATE_METADATA_ATTR_NAMES = 7;
    private static final int STATE_METADATA_OBJ = 5;
    private static final int STATE_METADATA_TIMESTAMP = 11;
    private static final int STATE_QUERY_TEXT = 2;
    private static final int STATE_START = 0;
    private final CreateTableModel createTableModel = CreateTableModel.FACTORY.newInstance();
    private final CairoEngine engine;
    private final JsonLexer lexer = new JsonLexer(1024, 1024);
    private final MemoryMARW mem = Vm.getMARWInstance();
    private final ExpressionNode partitionBy = ExpressionNode.FACTORY.newInstance();
    private final Path path = new Path();
    private final ExpressionNode tableName = ExpressionNode.FACTORY.newInstance();
    private final ExpressionNode timestampName = ExpressionNode.FACTORY.newInstance();
    private final Utf8StringSink utf8Sink = new Utf8StringSink();
    private int columnIndex;
    private String columnName;
    private int ignoreDepth = 0;
    private int ignoreReturnState;
    private TableWriter.Row row;
    private int state = STATE_START;
    private TableWriterAPI writer;

    public JsonToTableSerializer(CairoEngine engine) {
        this.engine = engine;
        // wire up the model
        createTableModel.setName(tableName);
        createTableModel.setTimestamp(timestampName);
        createTableModel.setPartitionBy(partitionBy);
    }

    @Override
    public void clear() {
        this.state = STATE_START;
        columnIndex = 0;
        columnName = null;
        lexer.clear();
        createTableModel.clear();
        createTableModel.setName(tableName);
        createTableModel.setTimestamp(timestampName);
        createTableModel.setPartitionBy(partitionBy);
    }

    @Override
    public void close() {
        Misc.free(path);
        Misc.free(mem);
        writer = Misc.free(writer);
    }

    @Override
    public void onEvent(int code, CharSequence tag, int position) throws JsonException {
        switch (code) {
            case JsonLexer.EVT_OBJ_START:
                switch (state) {
                    case STATE_START:
                        // expecting root object
                        state = STATE_MAIN_ATTR_NAMES;
                        // expect main attributes
                        // "query"
                        // "columns"
                        // "dataset"
                        break;
                    case STATE_METADATA_OBJ:
                        // expect metadata attribute
                        state = STATE_METADATA_ATTR_NAMES;
                        // expect:
                        // "name"
                        // "type"
                        break;
                    case STATE_IGNORE_RECURSIVE: // ignore
                        ignoreDepth++;
                        break;
                    default:
                        assert false;
                }
                break;
            case JsonLexer.EVT_OBJ_END:
                switch (state) {
                    case STATE_MAIN_ATTR_NAMES: // the end
                        state = STATE_START;
                        break;
                    case STATE_METADATA_ATTR_NAMES: // column definition end
                        state = STATE_METADATA_OBJ;
                        break;
                    case STATE_IGNORE_RECURSIVE: // ignore
                        ignoreDepth--;
                        break;
                    default:
                        assert false;
                }
                break;
            case JsonLexer.EVT_NAME:
                switch (state) {
                    case STATE_MAIN_ATTR_NAMES: // expecting any main attribute name
                        if (Chars.equals(tag, "query")) {
                            state = STATE_QUERY_TEXT; // expect query text
                        } else if (Chars.equals(tag, "columns")) {
                            state = STATE_METADATA_ARRAY; // expect metadata array
                        } else if (Chars.equals(tag, "dataset")) {
                            state = STATE_DATA_ARRAY; // expect data array
                        } else if (Chars.equals(tag, "timestamp")) {
                            state = STATE_METADATA_TIMESTAMP; // expect timestamp index
                        } else {
                            // ignore nested
                            state = STATE_IGNORE_RECURSIVE;
                            ignoreReturnState = STATE_MAIN_ATTR_NAMES;
                            ignoreDepth = 1;
                        }
                        break;
                    case STATE_METADATA_ATTR_NAMES: // metadata attribute
                        if (Chars.equals(tag, "name")) {
                            // column name
                            state = STATE_METADATA_ATTR_COLUMN_NAME;
                        } else if (Chars.equals(tag, "type")) {
                            // column type
                            state = STATE_METADATA_ATTR_COLUMN_TYPE;
                        } else {
                            assert false;
                        }
                        break;
                    case STATE_IGNORE_RECURSIVE: // ignore
                        ignoreDepth++;
                        break;
                    default:
                        assert false;
                }
                break;
            case JsonLexer.EVT_VALUE:
                switch (state) {
                    case STATE_QUERY_TEXT: // query text
                        // expect main attribute name
                        state = STATE_MAIN_ATTR_NAMES;
                        break;
                    case STATE_METADATA_ARRAY: // columns array
                        createTableModel.clear();
                        // going deeper
                        break;
                    case STATE_METADATA_ATTR_COLUMN_NAME: // column name
                        this.columnName = Chars.toString(tag);
                        // back to metadata attribute name
                        state = STATE_METADATA_ATTR_NAMES;
                        break;
                    case STATE_METADATA_ATTR_COLUMN_TYPE: // column type
                        try {
                            createTableModel.addColumn(columnName, ColumnType.typeOf(tag), 128);
                            state = STATE_METADATA_ATTR_NAMES;
                        } catch (SqlException e) {
                            throw new JsonException().put(e.getFlyweightMessage());
                        }
                        break;
                    case 11: // timestamp value
                        try {
                            int timestampIndex = Numbers.parseInt(tag);
                            if (timestampIndex != -1) {
                                timestampName.token = createTableModel.getColumnName(timestampIndex);
                                createTableModel.setTimestamp(timestampName);
                            } else {
                                createTableModel.setTimestamp(null);
                            }
                        } catch (NumericException e) {
                            throw JsonException.$(position, "invalid timestamp index: ").put(tag);
                        }
                        createTable();
                        // back to main attributes
                        state = STATE_MAIN_ATTR_NAMES;
                        break;
                    case STATE_IGNORE_RECURSIVE: // ignoring
                        if (--ignoreDepth == 0) {
                            state = ignoreReturnState;
                        }
                        break;
                    default:
                        break;
                }
                break;
            case JsonLexer.EVT_ARRAY_START:
                switch (state) {
                    case STATE_METADATA_ARRAY: // metadata array
                        state = STATE_METADATA_OBJ; // expect metadata object
                        break;
                    case STATE_DATA_ARRAY: // data array
                        state = STATE_DATA_ROW; // expect metadata row
                        break;
                    case STATE_DATA_ROW: // data row array (nested into data array)
                        state = STATE_DATA_CELL;
                        // start processing from column 0
                        columnIndex = 0;
                        // timestamp value is not yet determined because
                        // we are parsing JSON in streaming mode

                        // value parser will later overwrite timestamp value by index
                        row = writer.newRowDeferTimestamp();
                        break;
                    case STATE_IGNORE_RECURSIVE: // ignore
                        ignoreDepth++;
                        break;
                    default:
                        assert false;
                }
                break;
            case JsonLexer.EVT_ARRAY_VALUE:
                if (state == STATE_DATA_CELL) {
                    try {
                        switch (createTableModel.getColumnType(columnIndex)) {
                            case BOOLEAN:
                                row.putBool(columnIndex, (tag.charAt(0) | 32) == 't');
                                break;
                            case BYTE:
                                row.putByte(columnIndex, (byte) Numbers.parseInt(tag));
                                break;
                            case SHORT:
                                row.putShort(columnIndex, (short) Numbers.parseInt(tag));
                                break;
                            case CHAR:
                                row.putChar(columnIndex, tag.length() > 0 ? tag.charAt(0) : 0);
                                break;
                            case INT:
                                row.putInt(columnIndex, Numbers.parseInt(tag));
                                break;
                            case LONG:
                                row.putLong(columnIndex, Numbers.parseLong(tag));
                                break;
                            case DATE:
                                row.putDate(columnIndex, DateFormatUtils.parseUTCDate(tag));
                                break;
                            case TIMESTAMP:
                                row.putTimestamp(columnIndex, TimestampFormatUtils.parseUTCTimestamp(tag));
                                break;
                            case FLOAT:
                                row.putFloat(columnIndex, Numbers.parseFloat(tag));
                                break;
                            case DOUBLE:
                                row.putDouble(columnIndex, Numbers.parseDouble(tag));
                                break;
                            case STRING:
                                row.putStr(columnIndex, tag);
                                break;
                            case VARCHAR:
                                utf8Sink.clear();
                                utf8Sink.put(tag);
                                row.putVarchar(columnIndex, utf8Sink);
                                break;
                            case SYMBOL:
                                row.putSym(columnIndex, tag);
                                break;
                            case LONG256:
                                row.putLong256(columnIndex, tag);
                                break;
                            case GEOBYTE:
                            case GEOSHORT:
                            case GEOINT:
                            case GEOLONG:
                                row.putGeoStr(columnIndex, tag);
                                break;
                            case BINARY:
                                break;
                            case UUID:
                                row.putUuid(columnIndex, tag);
                                break;
                            default:
                                assert false;
                        }
                        columnIndex++;
                    } catch (NumericException e) {
                        row.cancel();
                        throw JsonException.$(position, "could not parse value [value=").put(tag).put(", type=").put(ColumnType.nameOf(createTableModel.getColumnType(columnIndex))).put(']');
                    }
                } else {
                    throw JsonException.$(position, "unexpected array value");
                }
                break;
            case JsonLexer.EVT_ARRAY_END:
                switch (state) {
                    case STATE_METADATA_OBJ: // metadata array end
                        // back to main attributes:
                        state = STATE_MAIN_ATTR_NAMES;
                        break;
                    case STATE_DATA_ROW: // data array end
                        state = STATE_MAIN_ATTR_NAMES;
                        writer.commit();
                        break;
                    case STATE_DATA_CELL: // data row array end
                        row.append();
                        state = STATE_DATA_ROW; // expect data row array
                        break;
                    case STATE_IGNORE_RECURSIVE: // ignore
                        ignoreDepth--;
                        break;
                    default:
                        assert false;
                }
                break;
        }
    }

    public void parse(long lo, long hi) throws JsonException {
        lexer.parse(lo, hi, this);
    }

    private void createTable() {
        tableName.token = "hello123";
        createTableModel.setWalEnabled(true);
        partitionBy.token = "HOUR";
        writer = engine.getWalWriter(engine.createTable(AllowAllSecurityContext.INSTANCE, mem, path, false, createTableModel, false));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy