From 08a71236a962ccd463b664c48fa06d25332eb1ea Mon Sep 17 00:00:00 2001 From: ElvinWei Date: Mon, 19 Sep 2022 16:26:54 +0800 Subject: [PATCH] [feature](statistics) Internal-query, execute SQL query statement internally in FE (#9983) Execute SQL query statements internally(in FE). Internal-query mainly used for statistics module, FE obtains statistics by SQL from BE, such as column maximum value, minimum value, etc. This is a tool module as statistics, it will not affect the original code, also will not affect the use of users. The simple usage process is as follows(the following code does no exception handling): ``` String dbName = "test"; String sql = "SELECT * FROM table0"; InternalQuery query = new InternalQuery(dbName, sql); InternalQueryResult result = query.query(); List resultRows = result.getResultRows(); for (ResultRow resultRow : resultRows) { List columns = resultRow.getColumns(); for (int i = 0; i < resultRow.getColumns().size(); i++) { resultRow.getColumnIndex(columns.get(i)); resultRow.getColumnName(i); resultRow.getColumnType(columns.get(i)); resultRow.getColumnType(i); resultRow.getColumnValue(columns.get(i)); resultRow.getColumnValue(i); } } ``` --- .../doris/statistics/util/InternalQuery.java | 217 +++++++++++++++ .../statistics/util/InternalQueryBuffer.java | 158 +++++++++++ .../statistics/util/InternalQueryResult.java | 256 ++++++++++++++++++ .../util/InternalQueryBufferTest.java | 120 ++++++++ .../util/InternalQueryResultTest.java | 130 +++++++++ 5 files changed, 881 insertions(+) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQuery.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryBuffer.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryResult.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryBufferTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryResultTest.java diff --git a/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQuery.java b/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQuery.java new file mode 100644 index 0000000000..8bbd1958d7 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQuery.java @@ -0,0 +1,217 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.statistics.util; + +import org.apache.doris.analysis.Analyzer; +import org.apache.doris.analysis.QueryStmt; +import org.apache.doris.analysis.SqlParser; +import org.apache.doris.analysis.SqlScanner; +import org.apache.doris.analysis.StatementBase; +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.cluster.ClusterNamespace; +import org.apache.doris.common.Config; +import org.apache.doris.common.DdlException; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.UserException; +import org.apache.doris.common.util.SqlParserUtils; +import org.apache.doris.planner.OriginalPlanner; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.Coordinator; +import org.apache.doris.qe.OriginStatement; +import org.apache.doris.qe.QeProcessorImpl; +import org.apache.doris.qe.RowBatch; +import org.apache.doris.statistics.util.InternalQueryResult.ResultRow; +import org.apache.doris.system.SystemInfoService; +import org.apache.doris.thrift.TQueryOptions; +import org.apache.doris.thrift.TResultBatch; +import org.apache.doris.thrift.TUniqueId; + +import com.google.common.collect.Lists; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.StringReader; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * Execute SQL query statements internally(in FE). Internal-query mainly used for statistics module, + * FE obtains statistics by SQL from BE, such as column maximum value, minimum value, etc. + * TODO(wzt): For statistics it should be better to implement a statistics sink. + **/ +public class InternalQuery { + private static final Logger LOG = LogManager.getLogger(InternalQuery.class); + + private int timeout = 0; + private final String sql; + private final String database; + + private ConnectContext context; + private Coordinator coord; + + private StatementBase stmt; + private final List resultBatches = Lists.newArrayList(); + + public InternalQuery(String database, String sql) { + this.database = database; + this.sql = sql; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Execute the query internally and return the query result. + * + * @return Result of the query statement + * @throws Exception Errors in parsing or execution + */ + public InternalQueryResult query() throws Exception { + // step1: mock connectContext + buildContext(); + + // step2: parse sql + parseSql(); + + // step3: generate plan + prepare(); + + // step4: execute and get result + execute(); + + // step5: parse result data and return + return fetchResult(); + } + + private void buildContext() { + context = new ConnectContext(); + context.setEnv(Env.getCurrentEnv()); + context.setCluster(SystemInfoService.DEFAULT_CLUSTER); + context.setCurrentUserIdentity(UserIdentity.ROOT); + context.setQualifiedUser(UserIdentity.ROOT.getQualifiedUser()); + + String fullDbName = ClusterNamespace + .getFullName(SystemInfoService.DEFAULT_CLUSTER, database); + context.setDatabase(fullDbName); + + UUID uuid = UUID.randomUUID(); + TUniqueId newQueryId = new TUniqueId(uuid.getMostSignificantBits(), + uuid.getLeastSignificantBits()); + context.setQueryId(newQueryId); + + context.setThreadLocalInfo(); + context.setStartTime(); + + // If user does not set the timeout, then use max_cbo_statistics_task_timeout_sec + timeout = timeout > 0 ? timeout : Config.max_cbo_statistics_task_timeout_sec; + context.getSessionVariable().setQueryTimeoutS(timeout); + } + + private void parseSql() throws DdlException { + SqlScanner input = new SqlScanner(new StringReader(sql), + context.getSessionVariable().getSqlMode()); + SqlParser parser = new SqlParser(input); + + try { + stmt = SqlParserUtils.getFirstStmt(parser); + stmt.setOrigStmt(new OriginStatement(sql, 0)); + } catch (Exception e) { + LOG.warn("Failed to parse the statement: {}. {}", sql, e); + throw new DdlException("Failed to parse the statement:" + sql); + } + + if (! (stmt instanceof QueryStmt)) { + throw new DdlException("Only query statements are supported:" + sql); + } + } + + private void prepare() throws UserException { + Analyzer analyzer = new Analyzer(context.getEnv(), context); + stmt.analyze(analyzer); + + OriginalPlanner originalPlanner = new OriginalPlanner(stmt.getAnalyzer()); + TQueryOptions queryOptions = new TQueryOptions(); + originalPlanner.plan(stmt, queryOptions); + + coord = new Coordinator(context, analyzer, originalPlanner); + } + + private void execute() throws Exception { + TUniqueId tUniqueId = context.queryId(); + try { + QeProcessorImpl.INSTANCE.registerQuery(tUniqueId, coord); + coord.exec(); + if (coord.getExecStatus().ok()) { + RowBatch batch; + do { + batch = coord.getNext(); + if (batch.getBatch() != null) { + resultBatches.add(batch.getBatch()); + } + } while (!batch.isEos()); + } else { + coord.cancel(); + String errMsg = coord.getExecStatus().getErrorMsg(); + ErrorReport.reportDdlException(errMsg, ErrorCode.ERR_QUERY_INTERRUPTED); + } + } finally { + QeProcessorImpl.INSTANCE.unregisterQuery(tUniqueId); + } + } + + private InternalQueryResult fetchResult() throws DdlException { + List columns = stmt.getColLabels(); + List types = stmt.getResultExprs().stream() + .map(e -> e.getType().getPrimitiveType()) + .collect(Collectors.toList()); + + InternalQueryResult result = new InternalQueryResult(columns, types); + List resultRows = result.getResultRows(); + + for (TResultBatch batch : resultBatches) { + List rows = batch.getRows(); + for (ByteBuffer buffer : rows) { + List values = Lists.newArrayList(); + InternalQueryBuffer queryBuffer = new InternalQueryBuffer(buffer.slice()); + + for (int i = 0; i < columns.size(); i++) { + String value = queryBuffer.readStringWithLength(); + values.add(value); + } + + ResultRow resultRow = new ResultRow(values); + resultRows.add(resultRow); + } + } + + return result; + } + + public void cancel() { + if (!coord.isDone()) { + coord.cancel(); + LOG.info("Internal query has been cancelled: {}", sql); + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryBuffer.java b/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryBuffer.java new file mode 100644 index 0000000000..257698e2de --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryBuffer.java @@ -0,0 +1,158 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.statistics.util; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +/** + * Parse the MySQL protocol result data returned by BE, + * only simple parsing operations are performed here (parsed as String). + * For more, @see `be/src/runtime/mysql_result_writer.cpp`. + */ +public class InternalQueryBuffer { + private static final long NULL_LENGTH = -1; + private static final byte[] EMPTY_BYTES = new byte[0]; + + private final ByteBuffer buffer; + + public InternalQueryBuffer(ByteBuffer buffer) { + this.buffer = buffer; + } + + public byte[] data() { + return buffer.array(); + } + + public int length() { + return buffer.capacity(); + } + + public int position() { + return buffer.position(); + } + + public void clear() { + buffer.clear(); + } + + private byte read() { + return buffer.get(); + } + + private int readUB2() { + int i = read() & 0xff; + i |= (read() & 0xff) << 8; + return i; + } + + private int readUB3() { + int i = read() & 0xff; + i |= (read() & 0xff) << 8; + i |= (read() & 0xff) << 16; + return i; + } + + private int readUB4() { + int i = read() & 0xff; + i |= (read() & 0xff) << 8; + i |= (read() & 0xff) << 16; + i |= (read() & 0xff) << 24; + return i; + } + + private long readLong() { + long i = read() & 0xff; + i |= (long) (read() & 0xff) << 8; + i |= (long) (read() & 0xff) << 16; + i |= (long) (read() & 0xff) << 24; + i |= (long) (read() & 0xff) << 32; + i |= (long) (read() & 0xff) << 40; + i |= (long) (read() & 0xff) << 48; + i |= (long) (read() & 0xff) << 56; + return i; + } + + /** + * The length of the data is not fixed, the length value is determined by the 1-9 bytes + * before the data, and the number of bytes occupied by the length value is not fixed, + * and the number of bytes is determined by the first byte. (@see `be/src/runtime/mysql_row_buffer.cpp`) + * + * @return Length coded binary + */ + private long readLength() { + int length = read() & 0xff; + switch (length) { + case 251: + return NULL_LENGTH; + case 252: + return readUB2(); + case 253: + return readUB3(); + case 254: + return readLong(); + default: + return length; + } + } + + public byte[] readBytesWithLength() { + int length = (int) readLength(); + if (length == NULL_LENGTH) { + return null; + } + if (length <= 0) { + return EMPTY_BYTES; + } + byte[] bytes = new byte[length]; + buffer.get(bytes); + return bytes; + } + + public String readStringWithLength() { + byte[] bytes = readBytesWithLength(); + if (bytes != null) { + return new String(bytes); + } + return null; + } + + public String readStringWithLength(String charset) + throws UnsupportedEncodingException { + byte[] bytes = readBytesWithLength(); + if (bytes != null) { + return new String(bytes, charset); + } + return null; + } + + public Integer readInt() { + String src = readStringWithLength(); + return src == null ? null : new Integer(src); + } + + public Float readFloat() { + String src = readStringWithLength(); + return src == null ? null : new Float(src); + } + + public Double readDouble() { + String src = readStringWithLength(); + return src == null ? null : new Double(src); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryResult.java b/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryResult.java new file mode 100644 index 0000000000..59a3e568ff --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/util/InternalQueryResult.java @@ -0,0 +1,256 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.statistics.util; + +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.common.DdlException; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.List; +import java.util.Map; + +/** + * Readable results of internal SQL execution, + * providing some read operations. + */ +public class InternalQueryResult { + private static List mateOfColumns; + private static List mateOfTypes; + + private final List resultRows = Lists.newArrayList(); + + public InternalQueryResult(List columns, List types) { + mateOfColumns = columns; + mateOfTypes = types; + } + + public List getResultRows() { + return resultRows; + } + + public static List getMateOfColumns() throws DdlException { + if (mateOfColumns == null) { + throw new DdlException("Failed to get the column names."); + } + return mateOfColumns; + } + + public static List getMateOfTypes() throws DdlException { + if (mateOfTypes == null) { + throw new DdlException("Failed to get the column types."); + } + return mateOfTypes; + } + + public static class ResultRow { + private final List values; + + private final Map columnNameMap = Maps.newHashMap(); + private final Map columnIndexMap = Maps.newHashMap(); + + public ResultRow(List values) throws DdlException { + this.values = values; + buildColumnNameMap(); + buildColumnIndexMap(); + } + + public List getColumns() throws DdlException { + return getMateOfColumns(); + } + + public List getTypes() throws DdlException { + return getMateOfTypes(); + } + + public List getValues() { + return values != null ? values : Lists.newArrayList(); + } + + private void buildColumnNameMap() throws DdlException { + List columns = getColumns(); + for (int i = 0; i < columns.size(); i++) { + columnNameMap.put(columns.get(i), i); + } + } + + private void buildColumnIndexMap() throws DdlException { + List columns = getColumns(); + for (int i = 0; i < columns.size(); i++) { + columnIndexMap.put(i, columns.get(i)); + } + } + + public int getColumnIndex(String columnName) { + return columnNameMap.getOrDefault(columnName, -1); + } + + public String getColumnName(int index) throws DdlException { + List columns = getColumns(); + if (columnIndexMap.containsKey(index)) { + return columnIndexMap.get(index); + } else { + throw new DdlException("Index should be between 0 and " + columns.size()); + } + } + + public PrimitiveType getColumnType(String columnName) throws DdlException { + List types = getTypes(); + int index = getColumnIndex(columnName); + if (index == -1) { + throw new DdlException("The column name does not exist."); + } + return types.get(index); + } + + public PrimitiveType getColumnType(int index) throws DdlException { + List types = getTypes(); + if (index >= 0 && index < types.size()) { + return types.get(index); + } else { + throw new DdlException("Index should be between 0 and " + types.size()); + } + } + + public Object getColumnValue(String columnName) throws DdlException { + int index = getColumnIndex(columnName); + if (index == -1) { + throw new DdlException("The column name does not exist."); + } + return values.get(index); + } + + public Object getColumnValue(int index) throws DdlException { + List columns = getColumns(); + if (index >= 0 && index < columns.size()) { + return values.get(index); + } else { + throw new DdlException("Index should be between 0 and " + columns.size()); + } + } + + public String getString(int index) throws DdlException { + List columns = getColumns(); + if (index >= 0 && index < columns.size()) { + return values.get(index); + } + throw new DdlException("Index should be between 0 and " + columns.size()); + } + + public int getInt(int index) throws DdlException { + List types = getTypes(); + if (index >= 0 && index < types.size()) { + String value = values.get(index); + PrimitiveType type = types.get(index); + switch (type) { + case BOOLEAN: + case TINYINT: + case SMALLINT: + case INT: + case BIGINT: + return new Integer(value); + default: + throw new DdlException("Unable to convert field to int: " + value); + } + } + throw new DdlException("Index should be between 0 and " + types.size()); + } + + public long getLong(int index) throws DdlException { + List types = getTypes(); + if (index >= 0 && index < types.size()) { + String value = values.get(index); + PrimitiveType type = types.get(index); + switch (type) { + case TINYINT: + case SMALLINT: + case INT: + case BIGINT: + return Long.parseLong(value); + default: + throw new DdlException("Unable to convert field to long: " + value); + } + } + throw new DdlException("Index should be between 0 and " + types.size()); + } + + public float getFloat(int index) throws DdlException { + List types = getTypes(); + if (index >= 0 && index < types.size()) { + String value = values.get(index); + PrimitiveType type = types.get(index); + if (type == PrimitiveType.FLOAT) { + return Float.parseFloat(value); + } + throw new DdlException("Unable to convert field to float: " + value); + } + throw new DdlException("Index should be between 0 and " + types.size()); + } + + public double getDouble(int index) throws DdlException { + List types = getTypes(); + if (index >= 0 && index < types.size()) { + String value = values.get(index); + PrimitiveType type = types.get(index); + if (type == PrimitiveType.DOUBLE) { + return Double.parseDouble(value); + } + throw new DdlException("Unable to convert field to long: " + value); + } + throw new DdlException("Index should be between 0 and " + types.size()); + } + + @Override + public String toString() { + try { + StringBuilder sb = new StringBuilder(); + sb.append("ResultRow{ "); + if (values != null && values.size() > 0) { + List columns = getColumns(); + for (int i = 0; i < values.size(); i++) { + sb.append(columns.get(i)); + sb.append(":"); + sb.append(values.get(i)); + sb.append(" "); + } + } + sb.append("}"); + return sb.toString(); + } catch (DdlException ignored) { + return "ResultRow{" + "values=" + values + ", columnNameMap=" + + columnNameMap + ", columnIndexMap=" + columnIndexMap + '}'; + } + } + } + + @Override + public String toString() { + if (resultRows.size() > 0) { + StringBuilder sb = new StringBuilder(); + sb.append("InternalQueryResult:\n"); + for (ResultRow resultRow : resultRows) { + sb.append(" - "); + sb.append(resultRow.toString()); + sb.append("\n"); + } + return sb.toString(); + } + return "InternalQueryResult{" + "resultRows=" + resultRows + '}'; + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryBufferTest.java b/fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryBufferTest.java new file mode 100644 index 0000000000..c1e1d889b4 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryBufferTest.java @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.statistics.util; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public class InternalQueryBufferTest { + private InternalQueryBuffer internalQueryBuffer; + + @Before + public void setUp() throws Exception { + ByteBuffer buffer = ByteBuffer.allocate(1024); + buffer.put((byte) 6); + buffer.put("field1".getBytes()); + + buffer.put((byte) 3); + buffer.put("123".getBytes()); + + buffer.put((byte) 3); + buffer.put("0.1".getBytes()); + + buffer.put((byte) 7); + buffer.put("18.2322".getBytes()); + + internalQueryBuffer = new InternalQueryBuffer(buffer); + } + + @Test + public void testData() { + byte[] result = internalQueryBuffer.data(); + Assert.assertEquals(1024, result.length); + } + + @Test + public void testLength() { + int result = internalQueryBuffer.length(); + Assert.assertEquals(1024, result); + } + + @Test + public void testPosition() { + int result = internalQueryBuffer.position(); + // (1 + 6) + (1 + 3) + (1 + 3) + (1 + 7) + Assert.assertEquals(23, result); + } + + @Test + public void testReadBytesWithLength() { + internalQueryBuffer.clear(); + byte[] result1 = internalQueryBuffer.readBytesWithLength(); + Assert.assertArrayEquals("field1".getBytes(), result1); + + byte[] result2 = internalQueryBuffer.readBytesWithLength(); + Assert.assertArrayEquals("123".getBytes(), result2); + + byte[] result3 = internalQueryBuffer.readBytesWithLength(); + Assert.assertArrayEquals("0.1".getBytes(), result3); + } + + @Test + public void testReadStringWithLength() { + internalQueryBuffer.clear(); + String result1 = internalQueryBuffer.readStringWithLength(); + Assert.assertEquals("field1", result1); + + String result2 = internalQueryBuffer.readStringWithLength(); + Assert.assertEquals("123", result2); + + String result3 = internalQueryBuffer.readStringWithLength(); + Assert.assertEquals("0.1", result3); + } + + @Test + public void testReadStringWithLengthByCharset() throws Exception { + internalQueryBuffer.clear(); + String result1 = internalQueryBuffer.readStringWithLength("UTF-8"); + Assert.assertEquals("field1", result1); + + String result2 = internalQueryBuffer.readStringWithLength("UTF-8"); + Assert.assertEquals("123", result2); + + String result3 = internalQueryBuffer.readStringWithLength("UTF-8"); + Assert.assertEquals("0.1", result3); + } + + @Test + public void testReadIntAndFloatAndDouble() { + internalQueryBuffer.clear(); + String result1 = internalQueryBuffer.readStringWithLength(); + Assert.assertEquals("field1", result1); + + int result2 = internalQueryBuffer.readInt(); + Assert.assertEquals(123, result2); + + float result3 = internalQueryBuffer.readFloat(); + Assert.assertEquals(0.1, result3, 1); + + double result4 = internalQueryBuffer.readDouble(); + Assert.assertEquals(18.2322, result4, 4); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryResultTest.java b/fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryResultTest.java new file mode 100644 index 0000000000..47532fc0fd --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/statistics/util/InternalQueryResultTest.java @@ -0,0 +1,130 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.statistics.util; + +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.common.DdlException; +import org.apache.doris.statistics.util.InternalQueryResult.ResultRow; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + + +public class InternalQueryResultTest { + private InternalQueryResult queryResult; + private InternalQueryResult.ResultRow resultRow; + + @Before + public void setUp() throws Exception { + List columns = Arrays.asList("c1", "c2", "c3", "c4", "c5"); + List types = Arrays.asList(PrimitiveType.STRING, + PrimitiveType.INT, PrimitiveType.FLOAT, + PrimitiveType.DOUBLE, PrimitiveType.BIGINT); + queryResult = new InternalQueryResult(columns, types); + resultRow = new ResultRow(Arrays.asList("s1", "1000", "0.1", "0.0001", "1000000")); + } + + @Test + public void testGetMateOfColumns() throws Exception { + Assert.assertEquals(Arrays.asList("c1", "c2", "c3", "c4", "c5"), + InternalQueryResult.getMateOfColumns()); + } + + @Test + public void testGetMateOfTypes() throws Exception { + Assert.assertEquals(Arrays.asList(PrimitiveType.STRING, PrimitiveType.INT, PrimitiveType.FLOAT, + PrimitiveType.DOUBLE, PrimitiveType.BIGINT), InternalQueryResult.getMateOfTypes()); + } + + @Test + public void testGetColumnIndex() { + Assert.assertEquals(0, resultRow.getColumnIndex("c1")); + Assert.assertEquals(1, resultRow.getColumnIndex("c2")); + Assert.assertEquals(2, resultRow.getColumnIndex("c3")); + Assert.assertEquals(3, resultRow.getColumnIndex("c4")); + Assert.assertEquals(4, resultRow.getColumnIndex("c5")); + } + + @Test + public void testGetColumnName() throws Exception { + Assert.assertEquals("c1", resultRow.getColumnName(0)); + Assert.assertEquals("c2", resultRow.getColumnName(1)); + Assert.assertEquals("c3", resultRow.getColumnName(2)); + Assert.assertEquals("c4", resultRow.getColumnName(3)); + Assert.assertEquals("c5", resultRow.getColumnName(4)); + } + + @Test + public void testGetColumnTypeWithIndex() { + try { + Assert.assertEquals(PrimitiveType.STRING, resultRow.getColumnType(0)); + Assert.assertEquals(PrimitiveType.INT, resultRow.getColumnType(1)); + Assert.assertEquals(PrimitiveType.FLOAT, resultRow.getColumnType(2)); + Assert.assertEquals(PrimitiveType.DOUBLE, resultRow.getColumnType(3)); + Assert.assertEquals(PrimitiveType.BIGINT, resultRow.getColumnType(4)); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + } + + @Test + public void testGetColumnTypeWithName() { + try { + Assert.assertEquals(PrimitiveType.STRING, resultRow.getColumnType("c1")); + Assert.assertEquals(PrimitiveType.INT, resultRow.getColumnType("c2")); + Assert.assertEquals(PrimitiveType.FLOAT, resultRow.getColumnType("c3")); + Assert.assertEquals(PrimitiveType.DOUBLE, resultRow.getColumnType("c4")); + Assert.assertEquals(PrimitiveType.BIGINT, resultRow.getColumnType("c5")); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + } + + @Test + public void testGetColumnValueWithIndex() throws Exception { + Assert.assertEquals("s1", resultRow.getColumnValue(0).toString()); + Assert.assertEquals(1000, Integer.parseInt((String) resultRow.getColumnValue(1))); + Assert.assertEquals(0.1f, Float.parseFloat((String) resultRow.getColumnValue(2)), 1); + Assert.assertEquals(0.0001, Double.parseDouble((String) resultRow.getColumnValue(3)), 4); + Assert.assertEquals(1000000, Long.parseLong((String) resultRow.getColumnValue(4))); + } + + @Test + public void testGetColumnValueWithName() throws Exception { + Assert.assertEquals("s1", resultRow.getColumnValue(0).toString()); + Assert.assertEquals(1000, Integer.parseInt((String) resultRow.getColumnValue(1))); + Assert.assertEquals(0.1f, Float.parseFloat((String) resultRow.getColumnValue(2)), 1); + Assert.assertEquals(0.0001, Double.parseDouble((String) resultRow.getColumnValue(3)), 4); + Assert.assertEquals(1000000, Long.parseLong((String) resultRow.getColumnValue(4))); + } + + @Test + public void testGetTypeValue() throws Exception { + Assert.assertEquals("s1", resultRow.getString(0)); + Assert.assertEquals(1000, resultRow.getInt(1)); + Assert.assertEquals(0.1f, resultRow.getFloat(2), 1); + Assert.assertEquals(0.0001, resultRow.getDouble(3), 4); + Assert.assertEquals(1000000, resultRow.getLong(4)); + } +}