Skip to content

Commit e5dacc0

Browse files
Add Greatest and Least Comparison functions
1 parent 2eea334 commit e5dacc0

File tree

17 files changed

+1074
-0
lines changed

17 files changed

+1074
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package org.apache.iotdb.relational.it.query.old.query;
16+
17+
import org.apache.iotdb.it.env.EnvFactory;
18+
import org.apache.iotdb.it.framework.IoTDBTestRunner;
19+
import org.apache.iotdb.itbase.category.TableClusterIT;
20+
import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
21+
import org.apache.iotdb.itbase.env.BaseEnv;
22+
23+
import org.junit.AfterClass;
24+
import org.junit.BeforeClass;
25+
import org.junit.Test;
26+
import org.junit.experimental.categories.Category;
27+
import org.junit.runner.RunWith;
28+
29+
import java.sql.Connection;
30+
import java.sql.Statement;
31+
32+
import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail;
33+
import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
34+
35+
@RunWith(IoTDBTestRunner.class)
36+
@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
37+
public class IoTDBGreatestLeastTableIT {
38+
39+
private static final String DATABASE_NAME = "db";
40+
41+
private static final String[] SQLs =
42+
new String[] {
43+
"CREATE DATABASE " + DATABASE_NAME,
44+
"USE " + DATABASE_NAME,
45+
"CREATE TABLE boolean_table(device_id STRING TAG, bool1 BOOLEAN FIELD, bool2 BOOLEAN FIELD)",
46+
"CREATE TABLE number_table(device_id STRING TAG, int1 INT32 FIELD, int2 INT32 FIELD, long1 INT64 FIELD, long2 INT64 FIELD, float1 FLOAT FIELD, float2 FLOAT FIELD, double1 DOUBLE FIELD, double2 DOUBLE FIELD)",
47+
"CREATE TABLE string_table(device_id STRING TAG, string1 STRING FIELD, string2 STRING FIELD, text1 TEXT FIELD, text2 TEXT FIELD)",
48+
"CREATE TABLE mix_type_table(device_id STRING TAG, s1 INT32 FIELD, s2 INT64 FIELD, s3 FLOAT FIELD, s4 DOUBLE FIELD, s5 BOOLEAN FIELD, s6 STRING FIELD, s7 TEXT FIELD)",
49+
"CREATE TABLE null_table(device_id STRING TAG, string1 STRING FIELD, string2 STRING FIELD, int1 INT32 FIELD, int2 INT32 FIELD, double1 DOUBLE FIELD, double2 DOUBLE FIELD, timestamp1 TIMESTAMP FIELD, timestamp2 TIMESTAMP FIELD)",
50+
"CREATE TABLE any_null_table(device_id STRING TAG, string1 STRING FIELD, string2 STRING FIELD, int1 INT32 FIELD, int2 INT32 FIELD, double1 DOUBLE FIELD, double2 DOUBLE FIELD, timestamp1 TIMESTAMP FIELD, timestamp2 TIMESTAMP FIELD)",
51+
// normal case
52+
"INSERT INTO number_table(time, device_id, int1, int2, long1, long2, float1, float2, double1, double2) VALUES (10, 'd1', 1000000, 2000000, 1000000, 2000000, 10.1, 20.2, 10.1, 20.2)",
53+
"INSERT INTO string_table(time, device_id, string1, string2, text1, text2) VALUES(10, 'd1', 'aaa', 'bbb', 'aaa', 'bbb')",
54+
"INSERT INTO boolean_table(time, device_id, bool1, bool2) VALUES(10, 'd1', true, false)",
55+
"INSERT INTO mix_type_table(time, device_id, s1, s2, s3, s4, s5, s6, s7) VALUES(10, 'd1', 1, 1, 1.0, 1.0, true, 'a', 'a')",
56+
"INSERT INTO null_table(time, device_id) VALUES(10, 'd1')",
57+
"INSERT INTO any_null_table(time, device_id, string2, int2, double2, timestamp2) VALUES(10, 'd1', 'test', 10, 10.0, 10)",
58+
};
59+
60+
@BeforeClass
61+
public static void setUp() throws Exception {
62+
EnvFactory.getEnv().initClusterEnvironment();
63+
insertData();
64+
}
65+
66+
protected static void insertData() {
67+
try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
68+
Statement statement = connection.createStatement()) {
69+
70+
for (String sql : SQLs) {
71+
statement.execute(sql);
72+
}
73+
} catch (Exception e) {
74+
e.printStackTrace();
75+
}
76+
}
77+
78+
@AfterClass
79+
public static void tearDown() throws Exception {
80+
EnvFactory.getEnv().cleanClusterEnvironment();
81+
}
82+
83+
@Test
84+
public void testNumberTypeGreatestFunction() {
85+
86+
tableResultSetEqualTest(
87+
"SELECT GREATEST(int1, int2) FROM number_table",
88+
new String[] {"_col0"},
89+
new String[] {"2000000,"},
90+
DATABASE_NAME);
91+
92+
tableResultSetEqualTest(
93+
"SELECT GREATEST(long1, long2) FROM number_table",
94+
new String[] {"_col0"},
95+
new String[] {"2000000,"},
96+
DATABASE_NAME);
97+
98+
tableResultSetEqualTest(
99+
"SELECT GREATEST(float1, float2) FROM number_table",
100+
new String[] {"_col0"},
101+
new String[] {"20.2,"},
102+
DATABASE_NAME);
103+
104+
tableResultSetEqualTest(
105+
"SELECT GREATEST(double1, double2) FROM number_table",
106+
new String[] {"_col0"},
107+
new String[] {"20.2,"},
108+
DATABASE_NAME);
109+
}
110+
111+
@Test
112+
public void testNumberTypeLeastFunction() {
113+
tableResultSetEqualTest(
114+
"SELECT LEAST(int1, int2) FROM number_table",
115+
new String[] {"_col0"},
116+
new String[] {"1000000,"},
117+
DATABASE_NAME);
118+
119+
tableResultSetEqualTest(
120+
"SELECT LEAST(long1, long2) FROM number_table",
121+
new String[] {"_col0"},
122+
new String[] {"1000000,"},
123+
DATABASE_NAME);
124+
125+
tableResultSetEqualTest(
126+
"SELECT LEAST(float1, float2) FROM number_table",
127+
new String[] {"_col0"},
128+
new String[] {"10.1,"},
129+
DATABASE_NAME);
130+
131+
tableResultSetEqualTest(
132+
"SELECT LEAST(double1, double2) FROM number_table",
133+
new String[] {"_col0"},
134+
new String[] {"10.1,"},
135+
DATABASE_NAME);
136+
}
137+
138+
@Test
139+
public void testStringTypeGreatestFunction() {
140+
tableResultSetEqualTest(
141+
"SELECT GREATEST(string1, string2) FROM string_table",
142+
new String[] {"_col0"},
143+
new String[] {"bbb,"},
144+
DATABASE_NAME);
145+
146+
tableResultSetEqualTest(
147+
"SELECT GREATEST(text1, text2) FROM string_table",
148+
new String[] {"_col0"},
149+
new String[] {"bbb,"},
150+
DATABASE_NAME);
151+
}
152+
153+
@Test
154+
public void testStringTypeLeastFunction() {
155+
tableResultSetEqualTest(
156+
"SELECT LEAST(string1, string2) FROM string_table",
157+
new String[] {"_col0"},
158+
new String[] {"aaa,"},
159+
DATABASE_NAME);
160+
161+
tableResultSetEqualTest(
162+
"SELECT LEAST(text1, text2) FROM string_table",
163+
new String[] {"_col0"},
164+
new String[] {"aaa,"},
165+
DATABASE_NAME);
166+
}
167+
168+
@Test
169+
public void testBooleanTypeGreatestFunction() {
170+
tableResultSetEqualTest(
171+
"SELECT GREATEST(bool1, bool2) FROM boolean_table",
172+
new String[] {"_col0"},
173+
new String[] {"true,"},
174+
DATABASE_NAME);
175+
}
176+
177+
@Test
178+
public void testBooleanTypeLeastFunction() {
179+
tableResultSetEqualTest(
180+
"SELECT LEAST(bool1, bool2) FROM boolean_table",
181+
new String[] {"_col0"},
182+
new String[] {"false,"},
183+
DATABASE_NAME);
184+
}
185+
186+
@Test
187+
public void testAllNullValue() {
188+
tableResultSetEqualTest(
189+
"SELECT GREATEST(string1, string2) FROM null_table",
190+
new String[] {"_col0"},
191+
new String[] {"null,"},
192+
DATABASE_NAME);
193+
194+
tableResultSetEqualTest(
195+
"SELECT LEAST(string1, string2) FROM null_table",
196+
new String[] {"_col0"},
197+
new String[] {"null,"},
198+
DATABASE_NAME);
199+
200+
tableResultSetEqualTest(
201+
"SELECT GREATEST(int1, int2) FROM null_table",
202+
new String[] {"_col0"},
203+
new String[] {"null,"},
204+
DATABASE_NAME);
205+
206+
tableResultSetEqualTest(
207+
"SELECT LEAST(int1, int2) FROM null_table",
208+
new String[] {"_col0"},
209+
new String[] {"null,"},
210+
DATABASE_NAME);
211+
212+
tableResultSetEqualTest(
213+
"SELECT GREATEST(double1, double2) FROM null_table",
214+
new String[] {"_col0"},
215+
new String[] {"null,"},
216+
DATABASE_NAME);
217+
218+
tableResultSetEqualTest(
219+
"SELECT LEAST(double1, double2) FROM null_table",
220+
new String[] {"_col0"},
221+
new String[] {"null,"},
222+
DATABASE_NAME);
223+
224+
tableResultSetEqualTest(
225+
"SELECT GREATEST(timestamp1, timestamp2) FROM null_table",
226+
new String[] {"_col0"},
227+
new String[] {"null,"},
228+
DATABASE_NAME);
229+
230+
tableResultSetEqualTest(
231+
"SELECT LEAST(timestamp1, timestamp2) FROM null_table",
232+
new String[] {"_col0"},
233+
new String[] {"null,"},
234+
DATABASE_NAME);
235+
}
236+
237+
@Test
238+
public void testAnyNullValue() {
239+
tableResultSetEqualTest(
240+
"SELECT GREATEST(string1, string2) FROM any_null_table",
241+
new String[] {"_col0"},
242+
new String[] {"test,"},
243+
DATABASE_NAME);
244+
245+
tableResultSetEqualTest(
246+
"SELECT LEAST(string1, string2) FROM any_null_table",
247+
new String[] {"_col0"},
248+
new String[] {"test,"},
249+
DATABASE_NAME);
250+
251+
tableResultSetEqualTest(
252+
"SELECT GREATEST(int1, int2) FROM any_null_table",
253+
new String[] {"_col0"},
254+
new String[] {"10,"},
255+
DATABASE_NAME);
256+
257+
tableResultSetEqualTest(
258+
"SELECT LEAST(int1, int2) FROM any_null_table",
259+
new String[] {"_col0"},
260+
new String[] {"10,"},
261+
DATABASE_NAME);
262+
263+
tableResultSetEqualTest(
264+
"SELECT GREATEST(double1, double2) FROM any_null_table",
265+
new String[] {"_col0"},
266+
new String[] {"10.0,"},
267+
DATABASE_NAME);
268+
269+
tableResultSetEqualTest(
270+
"SELECT LEAST(double1, double2) FROM any_null_table",
271+
new String[] {"_col0"},
272+
new String[] {"10.0,"},
273+
DATABASE_NAME);
274+
275+
tableResultSetEqualTest(
276+
"SELECT GREATEST(timestamp1, timestamp2) FROM any_null_table",
277+
new String[] {"_col0"},
278+
new String[] {"1970-01-01T00:00:00.010Z,"},
279+
DATABASE_NAME);
280+
281+
tableResultSetEqualTest(
282+
"SELECT LEAST(timestamp1, timestamp2) FROM any_null_table",
283+
new String[] {"_col0"},
284+
new String[] {"1970-01-01T00:00:00.010Z,"},
285+
DATABASE_NAME);
286+
}
287+
288+
@Test
289+
public void testAnomalies() {
290+
// do not support different type
291+
for (int i = 1; i <= 7; i++) {
292+
for (int j = i + 1; j <= 7; j++) {
293+
tableAssertTestFail(
294+
String.format("SELECT LEAST(s%d, s%d) FROM mix_type_table", i, j),
295+
"701: Scalar function least must have at least two arguments, and all type must be the same.",
296+
DATABASE_NAME);
297+
298+
tableAssertTestFail(
299+
String.format("SELECT GREATEST(s%d, s%d) FROM mix_type_table", i, j),
300+
"701: Scalar function greatest must have at least two arguments, and all type must be the same.",
301+
DATABASE_NAME);
302+
}
303+
}
304+
}
305+
}

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import org.apache.iotdb.db.queryengine.transformation.dag.column.leaf.LeafColumnTransformer;
8787
import org.apache.iotdb.db.queryengine.transformation.dag.column.leaf.NullColumnTransformer;
8888
import org.apache.iotdb.db.queryengine.transformation.dag.column.leaf.TimeColumnTransformer;
89+
import org.apache.iotdb.db.queryengine.transformation.dag.column.multi.AbstractGreatestLeastColumnTransformer;
8990
import org.apache.iotdb.db.queryengine.transformation.dag.column.multi.CoalesceColumnTransformer;
9091
import org.apache.iotdb.db.queryengine.transformation.dag.column.multi.InBinaryMultiColumnTransformer;
9192
import org.apache.iotdb.db.queryengine.transformation.dag.column.multi.InBooleanMultiColumnTransformer;
@@ -977,6 +978,20 @@ private ColumnTransformer getFunctionColumnTransformer(
977978
}
978979
return new FormatColumnTransformer(
979980
STRING, columnTransformers, context.sessionInfo.getZoneId());
981+
} else if (TableBuiltinScalarFunction.GREATEST
982+
.getFunctionName()
983+
.equalsIgnoreCase(functionName)) {
984+
List<ColumnTransformer> columnTransformers =
985+
children.stream().map(child -> this.process(child, context)).collect(Collectors.toList());
986+
Type returnType = columnTransformers.get(0).getType();
987+
return AbstractGreatestLeastColumnTransformer.getGreatestColumnTransformer(
988+
returnType, columnTransformers);
989+
} else if (TableBuiltinScalarFunction.LEAST.getFunctionName().equalsIgnoreCase(functionName)) {
990+
List<ColumnTransformer> columnTransformers =
991+
children.stream().map(child -> this.process(child, context)).collect(Collectors.toList());
992+
Type returnType = columnTransformers.get(0).getType();
993+
return AbstractGreatestLeastColumnTransformer.getLeastColumnTransformer(
994+
returnType, columnTransformers);
980995
} else {
981996
// user defined function
982997
if (TableUDFUtils.isScalarFunction(functionName)) {

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,15 @@ && isIntegerNumber(argumentTypes.get(2)))) {
541541
+ " must have at least two arguments, and first argument must be char type.");
542542
}
543543
return STRING;
544+
} else if (TableBuiltinScalarFunction.GREATEST.getFunctionName().equalsIgnoreCase(functionName)
545+
|| TableBuiltinScalarFunction.LEAST.getFunctionName().equalsIgnoreCase(functionName)) {
546+
if (argumentTypes.size() < 2 || !areAllTypesSameAndComparable(argumentTypes)) {
547+
throw new SemanticException(
548+
"Scalar function "
549+
+ functionName.toLowerCase(Locale.ENGLISH)
550+
+ " must have at least two arguments, and all type must be the same.");
551+
}
552+
return argumentTypes.get(0);
544553
}
545554

546555
// builtin aggregation function
@@ -884,6 +893,17 @@ public static boolean isTwoTypeComparable(List<? extends Type> argumentTypes) {
884893
|| ((isNumericType(left) || isCharType(left)) && isUnknownType(right));
885894
}
886895

896+
public static boolean areAllTypesSameAndComparable(List<? extends Type> argumentTypes) {
897+
if (argumentTypes == null || argumentTypes.isEmpty()) {
898+
return true;
899+
}
900+
Type firstType = argumentTypes.get(0);
901+
if (!firstType.isComparable()) {
902+
return false;
903+
}
904+
return argumentTypes.stream().allMatch(type -> type.equals(firstType));
905+
}
906+
887907
public static boolean isArithmeticType(Type type) {
888908
return INT32.equals(type)
889909
|| INT64.equals(type)

0 commit comments

Comments
 (0)