/Users/deen/code/yugabyte-db/src/yb/yql/cql/ql/test/ql-semanalyzer-test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. 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 distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include <memory> |
15 | | |
16 | | #include "yb/common/ql_type.h" |
17 | | |
18 | | #include "yb/util/result.h" |
19 | | #include "yb/util/status_log.h" |
20 | | #include "yb/util/varint.h" |
21 | | |
22 | | #include "yb/yql/cql/ql/ptree/parse_tree.h" |
23 | | #include "yb/yql/cql/ql/ptree/pt_create_table.h" |
24 | | #include "yb/yql/cql/ql/ptree/pt_expr.h" |
25 | | #include "yb/yql/cql/ql/ptree/pt_select.h" |
26 | | #include "yb/yql/cql/ql/ptree/pt_table_property.h" |
27 | | #include "yb/yql/cql/ql/test/ql-test-base.h" |
28 | | |
29 | | namespace yb { |
30 | | namespace ql { |
31 | | |
32 | | using std::make_shared; |
33 | | using std::string; |
34 | | using strings::Substitute; |
35 | | using std::dynamic_pointer_cast; |
36 | | |
37 | | class QLTestAnalyzer: public QLTestBase { |
38 | | public: |
39 | 0 | QLTestAnalyzer() : QLTestBase() { |
40 | 0 | } |
41 | | |
42 | | // TODO(omer): should also input the index_tree, in order to ensure the id found is the id of the |
43 | | // index that should be used, but I do not know how to do this yet, as index_tree has the name of |
44 | | // the index while select_tree has the id. |
45 | | void TestIndexSelection(const string& select_stmt, |
46 | | const bool use_index, |
47 | 0 | const bool covers_fully) { |
48 | 0 | ParseTree::UniPtr parse_tree; |
49 | 0 | EXPECT_OK(TestAnalyzer(select_stmt, &parse_tree)); |
50 | |
|
51 | 0 | TreeNode::SharedPtr root = parse_tree->root(); |
52 | 0 | CHECK_EQ(TreeNodeOpcode::kPTSelectStmt, root->opcode()); |
53 | 0 | auto pt_select_stmt = std::static_pointer_cast<PTSelectStmt>(root); |
54 | 0 | auto pt_child_select = pt_select_stmt->child_select(); |
55 | 0 | EXPECT_EQ(pt_child_select != nullptr, use_index) << select_stmt; |
56 | 0 | EXPECT_EQ(pt_child_select != nullptr && pt_child_select->covers_fully(), covers_fully) |
57 | 0 | << select_stmt; |
58 | 0 | } |
59 | | |
60 | | PTJsonColumnWithOperators* RetrieveJsonColumn(const ParseTree::UniPtr& parse_tree, |
61 | 0 | const DataType& expected_type) { |
62 | 0 | auto select_stmt = std::dynamic_pointer_cast<PTSelectStmt>(parse_tree->root()); |
63 | 0 | auto ptrelation2 = std::dynamic_pointer_cast<PTRelation2>(select_stmt->where_clause()); |
64 | 0 | EXPECT_EQ(expected_type, ptrelation2->op1()->ql_type()->main()); |
65 | 0 | return std::dynamic_pointer_cast<PTJsonColumnWithOperators>(ptrelation2->op1()).get(); |
66 | 0 | } |
67 | | |
68 | 0 | std::string RetrieveJsonElement(const PTJsonColumnWithOperators* const jsoncolumn, int index) { |
69 | 0 | auto element = dynamic_pointer_cast<PTJsonOperator>(jsoncolumn->operators()->element(index)); |
70 | 0 | auto text = dynamic_pointer_cast<PTConstText>(element->arg()); |
71 | 0 | return string(text->value()->c_str()); |
72 | 0 | } |
73 | | }; |
74 | | |
75 | 0 | TEST_F(QLTestAnalyzer, TestCreateTablePropertyAnalyzer) { |
76 | 0 | CreateSimulatedCluster(); |
77 | | |
78 | | // Analyze the sql statement. |
79 | 0 | ParseTree::UniPtr parse_tree; |
80 | 0 | string create_stmt = "CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY (c1)) WITH " |
81 | 0 | "default_time_to_live = 1000"; |
82 | 0 | CHECK_OK(TestAnalyzer(create_stmt, &parse_tree)); |
83 | | |
84 | | // Now verify the analysis was done correctly. |
85 | 0 | TreeNode::SharedPtr root = parse_tree->root(); |
86 | 0 | EXPECT_EQ(TreeNodeOpcode::kPTCreateTable, root->opcode()); |
87 | 0 | PTCreateTable::SharedPtr pt_create_table = std::static_pointer_cast<PTCreateTable>(root); |
88 | | |
89 | | // Verify table properties. |
90 | 0 | PTTablePropertyListNode::SharedPtr table_properties = pt_create_table->table_properties(); |
91 | 0 | EXPECT_EQ(1, table_properties->size()); |
92 | 0 | PTTableProperty::SharedPtr table_property = table_properties->element(0); |
93 | 0 | EXPECT_EQ(std::string("default_time_to_live"), table_property->lhs()->c_str()); |
94 | 0 | PTConstVarInt::SharedPtr rhs = std::static_pointer_cast<PTConstVarInt>(table_property->rhs()); |
95 | 0 | auto from_str = ASSERT_RESULT(util::VarInt::CreateFromString(rhs->Eval()->c_str())); |
96 | 0 | EXPECT_EQ(util::VarInt(1000), from_str); |
97 | 0 | } |
98 | | |
99 | 0 | TEST_F(QLTestAnalyzer, TestCreateTableAnalyze) { |
100 | 0 | CreateSimulatedCluster(); |
101 | | |
102 | | // Analyze the sql statement. |
103 | 0 | ParseTree::UniPtr parse_tree; |
104 | | |
105 | | // Duplicate hash and cluster columns. |
106 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
107 | 0 | "((c1, c1)))", &parse_tree); |
108 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
109 | 0 | "((c1), c1))", &parse_tree); |
110 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
111 | 0 | "((c1), c2, c2))", &parse_tree); |
112 | |
|
113 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
114 | 0 | "(c1)) WITH default_time_to_live = 1000 AND default_time_to_live = 2000", &parse_tree); |
115 | 0 | ANALYZE_VALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
116 | 0 | "(c1)) WITH default_time_to_live = 1000", &parse_tree); |
117 | 0 | ANALYZE_VALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
118 | 0 | "(c1))", &parse_tree); |
119 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (c1 int, c2 int, c3 int, PRIMARY KEY " |
120 | 0 | "(c1)) WITH default_time_to_live = 1000.1", &parse_tree); |
121 | 0 | } |
122 | | |
123 | 0 | TEST_F(QLTestAnalyzer, TestCreateTableWithStaticColumn) { |
124 | 0 | CreateSimulatedCluster(); |
125 | | |
126 | | // Test static column analysis. |
127 | 0 | ParseTree::UniPtr parse_tree; |
128 | 0 | ANALYZE_VALID_STMT("CREATE TABLE foo (h1 int, r1 int, s1 int static, " |
129 | 0 | "PRIMARY KEY ((h1), r1));", &parse_tree); |
130 | | // Invalid: hash column cannot be static. |
131 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (h1 int, h2 int static, r1 int, " |
132 | 0 | "PRIMARY KEY ((h1, h2), r1));", &parse_tree); |
133 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (h1 int static primary key);", &parse_tree); |
134 | | // Invalid: range column cannot be static. |
135 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (h1 int, r1 int, r2 int static, " |
136 | 0 | "PRIMARY KEY ((h1), r1, r2));", &parse_tree); |
137 | | // Invalid: no static column for table hash key column only. |
138 | 0 | ANALYZE_INVALID_STMT("CREATE TABLE foo (h1 int, h2 int, s int static, c int, " |
139 | 0 | "PRIMARY KEY ((h1, h2)));", &parse_tree); |
140 | 0 | } |
141 | | |
142 | | TEST_F(QLTestAnalyzer, TestJsonColumn) { |
143 | | CreateSimulatedCluster(); |
144 | | TestQLProcessor *processor = GetQLProcessor(); |
145 | | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, c1 jsonb, c2 int, PRIMARY KEY (h1));")); |
146 | | |
147 | | ParseTree::UniPtr parse_tree; |
148 | | |
149 | | ANALYZE_VALID_STMT("SELECT * FROM t WHERE c1->'a'->'b'->>'c' = '1'", &parse_tree); |
150 | | auto jsoncolumn = RetrieveJsonColumn(parse_tree, DataType::STRING); |
151 | | EXPECT_TRUE(jsoncolumn != nullptr); |
152 | | EXPECT_TRUE(jsoncolumn->name()->IsSimpleName()); |
153 | | EXPECT_EQ("c1", jsoncolumn->name()->first_name()); |
154 | | |
155 | | EXPECT_EQ(3, jsoncolumn->operators()->size()); |
156 | | EXPECT_EQ(JsonOperator::JSON_OBJECT, |
157 | | dynamic_pointer_cast<PTJsonOperator>( |
158 | | jsoncolumn->operators()->element(0))->json_operator()); |
159 | | EXPECT_EQ("a", RetrieveJsonElement(jsoncolumn, 0)); |
160 | | |
161 | | EXPECT_EQ(JsonOperator::JSON_OBJECT, |
162 | | dynamic_pointer_cast<PTJsonOperator>( |
163 | | jsoncolumn->operators()->element(1))->json_operator()); |
164 | | EXPECT_EQ("b", RetrieveJsonElement(jsoncolumn, 1)); |
165 | | |
166 | | EXPECT_EQ(JsonOperator::JSON_TEXT, |
167 | | dynamic_pointer_cast<PTJsonOperator>( |
168 | | jsoncolumn->operators()->element(2))->json_operator()); |
169 | | EXPECT_EQ("c", RetrieveJsonElement(jsoncolumn, 2)); |
170 | | |
171 | | ANALYZE_VALID_STMT("SELECT * FROM t WHERE c1->>'a' = '1'", &parse_tree); |
172 | | auto jsoncolumn1 = RetrieveJsonColumn(parse_tree, DataType::STRING); |
173 | | EXPECT_EQ(1, jsoncolumn1->operators()->size()); |
174 | | EXPECT_EQ(JsonOperator::JSON_TEXT, |
175 | | dynamic_pointer_cast<PTJsonOperator>( |
176 | | jsoncolumn1->operators()->element(0))->json_operator()); |
177 | | EXPECT_EQ("a", RetrieveJsonElement(jsoncolumn1, 0)); |
178 | | ANALYZE_VALID_STMT("SELECT * FROM t WHERE c1->'a' = '1'", &parse_tree); |
179 | | auto jsoncolumn2 = RetrieveJsonColumn(parse_tree, DataType::JSONB); |
180 | | EXPECT_EQ(1, jsoncolumn2->operators()->size()); |
181 | | EXPECT_EQ(JsonOperator::JSON_OBJECT, |
182 | | dynamic_pointer_cast<PTJsonOperator>( |
183 | | jsoncolumn2->operators()->element(0))->json_operator()); |
184 | | EXPECT_EQ("a", RetrieveJsonElement(jsoncolumn2, 0)); |
185 | | |
186 | | // Comparing string and integer. |
187 | | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE c1->'a'->'b'->>'c' = 1", &parse_tree); |
188 | | // Column is not json. |
189 | | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE c2->>'a' = '1'", &parse_tree); |
190 | | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE h1->'a' = '1'", &parse_tree); |
191 | | |
192 | | // Update json column. |
193 | | ANALYZE_VALID_STMT("UPDATE t SET c1->'a'->'b'->'c' = '2' WHERE h1 = 1", &parse_tree); |
194 | | ANALYZE_VALID_STMT("UPDATE t SET c1->'a' = '2' WHERE h1 = 1", &parse_tree); |
195 | | |
196 | | // Invalid updates. |
197 | | ANALYZE_INVALID_STMT("UPDATE t SET c1->>'a' = '2' WHERE h1 = 1", &parse_tree); |
198 | | ANALYZE_INVALID_STMT("UPDATE t SET c1.a.b.c = '2' WHERE h1 = 1", &parse_tree); |
199 | | } |
200 | | |
201 | 0 | TEST_F(QLTestAnalyzer, TestDmlWithStaticColumn) { |
202 | 0 | CreateSimulatedCluster(); |
203 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
204 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, h2 int, r1 int, r2 int, s1 int static, c1 int, " |
205 | 0 | "PRIMARY KEY ((h1, h2), r1, r2));")); |
206 | |
|
207 | 0 | ParseTree::UniPtr parse_tree; |
208 | | |
209 | | // Test insert with hash key only. |
210 | 0 | ANALYZE_VALID_STMT("INSERT INTO t (h1, h2, s1) VALUES (1, 1, 1);", &parse_tree); |
211 | | |
212 | | // Test update with hash key only. |
213 | 0 | ANALYZE_VALID_STMT("UPDATE t SET s1 = 1 WHERE h1 = 1 AND h2 = 1;", &parse_tree); |
214 | | |
215 | | // TODO: Test delete with hash key only. |
216 | | // ANALYZE_VALID_STMT("DELETE c1 FROM t WHERE h1 = 1 and r1 = 1;", &parse_tree); |
217 | | |
218 | | // Test select with distinct columns. |
219 | 0 | ANALYZE_VALID_STMT("SELECT DISTINCT h1, h2, s1 FROM t;", &parse_tree); |
220 | 0 | ANALYZE_VALID_STMT("SELECT DISTINCT s1 FROM t WHERE h1 = 1 AND h2 = 1;", &parse_tree); |
221 | 0 | ANALYZE_VALID_STMT("SELECT DISTINCT s1 FROM t;", &parse_tree); |
222 | | |
223 | | // Invalid: Missing a hash primary-key column. |
224 | 0 | ANALYZE_INVALID_STMT("SELECT DISTINCT h1, s1 FROM t;", &parse_tree); |
225 | 0 | ANALYZE_INVALID_STMT("SELECT DISTINCT h1, s1 FROM t WHERE h2 = 1;", &parse_tree); |
226 | | |
227 | | // Invalid: cannot select distinct with non hash primary-key column. |
228 | 0 | ANALYZE_INVALID_STMT("SELECT DISTINCT h1, h2, r1, s1 FROM t;", &parse_tree); |
229 | | |
230 | | // Invalid: cannot select distinct with non-static column. |
231 | 0 | ANALYZE_INVALID_STMT("SELECT DISTINCT h1, h2, c1 FROM t;", &parse_tree); |
232 | | |
233 | | // Invalid: cannot select distinct with non hash primary-key / non-static column. |
234 | 0 | ANALYZE_INVALID_STMT("SELECT DISTINCT * FROM t;", &parse_tree); |
235 | | |
236 | | // Invalid: cannot insert or update with partial range columns. |
237 | 0 | ANALYZE_INVALID_STMT("INSERT INTO t (h1, h2, r1, s1) VALUES (1, 1, 1, 1);", &parse_tree); |
238 | 0 | ANALYZE_INVALID_STMT("UPDATE t SET s1 = 1 WHERE h1 = 1 AND h2 = 1 AND r1 = 1;", &parse_tree); |
239 | 0 | } |
240 | | |
241 | 0 | TEST_F(QLTestAnalyzer, TestWhereClauseAnalyzer) { |
242 | 0 | CreateSimulatedCluster(); |
243 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
244 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, r1 varchar, c1 varchar, " |
245 | 0 | "PRIMARY KEY ((h1), r1));")); |
246 | |
|
247 | 0 | ParseTree::UniPtr parse_tree; |
248 | | // OR and NOT logical operator are not supported yet. |
249 | 0 | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE h1 = 1 AND r1 = 2 OR r2 = 3", &parse_tree); |
250 | 0 | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE h1 = 1 AND NOT r1 = 2", &parse_tree); |
251 | |
|
252 | 0 | CHECK_OK(processor->Run("DROP TABLE t;")); |
253 | 0 | } |
254 | | |
255 | 0 | TEST_F(QLTestAnalyzer, TestIfClauseAnalyzer) { |
256 | 0 | CreateSimulatedCluster(); |
257 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
258 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, r1 int, c1 int, " |
259 | 0 | "PRIMARY KEY ((h1), r1));")); |
260 | |
|
261 | 0 | ParseTree::UniPtr parse_tree; |
262 | | // Valid case: if not exists or if <col> = xxx. |
263 | 0 | ANALYZE_VALID_STMT("UPDATE t SET c1 = 1 WHERE h1 = 1 AND r1 = 1 IF NOT EXISTS or c1 = 0", |
264 | 0 | &parse_tree); |
265 | | |
266 | | // Invalid cases: primary key columns not allowed in if clause. |
267 | 0 | ANALYZE_INVALID_STMT("UPDATE t SET c1 = 1 WHERE h1 = 1 AND r1 = 1 IF h1 = 1", &parse_tree); |
268 | 0 | ANALYZE_INVALID_STMT("UPDATE t SET c1 = 1 WHERE h1 = 1 AND r1 = 1 IF r1 = 1", &parse_tree); |
269 | 0 | CHECK_OK(processor->Run("DROP TABLE t;")); |
270 | 0 | } |
271 | | |
272 | 0 | TEST_F(QLTestAnalyzer, TestBindVariableAnalyzer) { |
273 | 0 | CreateSimulatedCluster(); |
274 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
275 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, r1 varchar, c1 varchar, " |
276 | 0 | "PRIMARY KEY ((h1), r1));")); |
277 | | |
278 | | // Analyze the sql statement. |
279 | 0 | ParseTree::UniPtr parse_tree; |
280 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t WHERE h1 = ? AND r1 = ?;", &parse_tree); |
281 | 0 | ANALYZE_VALID_STMT("UPDATE t set c1 = :1 WHERE h1 = :2 AND r1 = :3;", &parse_tree); |
282 | 0 | ANALYZE_VALID_STMT("UPDATE t set c1 = ? WHERE h1 = ? AND r1 = ?;", &parse_tree); |
283 | 0 | ANALYZE_VALID_STMT("INSERT INTO t (h1, r1, c1) VALUES (?, ?, ?);", &parse_tree); |
284 | | |
285 | | // Bind var cannot be used in a logical boolean context. |
286 | 0 | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE NOT ?", &parse_tree); |
287 | | |
288 | | // Bind var not supported in an expression (yet). |
289 | 0 | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE h1 = (- ?)", &parse_tree); |
290 | 0 | ANALYZE_INVALID_STMT("SELECT * FROM t WHERE h1 = (- :1)", &parse_tree); |
291 | |
|
292 | 0 | CHECK_OK(processor->Run("DROP TABLE t;")); |
293 | 0 | } |
294 | | |
295 | 0 | TEST_F(QLTestAnalyzer, TestCreateIndex) { |
296 | 0 | CreateSimulatedCluster(); |
297 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
298 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, h2 text, r1 int, r2 text, c1 int, c2 text, " |
299 | 0 | "PRIMARY KEY ((h1, h2), r1, r2)) " |
300 | 0 | "with transactions = {'enabled':true};")); |
301 | | |
302 | | // Analyze the sql statement. |
303 | 0 | ParseTree::UniPtr parse_tree; |
304 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t ((r1), r2);", &parse_tree); |
305 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t ((r1, r2), h1, h2);", &parse_tree); |
306 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t ((h1, h2), r1, r2);", &parse_tree); |
307 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (h1);", &parse_tree); |
308 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (r1);", &parse_tree); |
309 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (c1);", &parse_tree); |
310 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (r2, r1) INCLUDE (c1);", &parse_tree); |
311 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (r2, r1, h1, h2) INCLUDE (c1) WITH CLUSTERING ORDER BY " |
312 | 0 | "(r1 DESC, h1 DESC, h2 ASC);", &parse_tree); |
313 | | |
314 | | // Duplicate covering columns - should succeed |
315 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t ((r1), r2) INCLUDE (r1);", &parse_tree); |
316 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t ((r1), r2) INCLUDE (r2);", &parse_tree); |
317 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (r1, r2, c1) INCLUDE (c1);", &parse_tree); |
318 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t (r1, r2) INCLUDE (c1, c1);", &parse_tree); |
319 | | |
320 | | // Duplicate primary key columns. |
321 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t (r1, r1);", &parse_tree); |
322 | | |
323 | | // Non-clustering key column in order by. |
324 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t (r2, r1) INCLUDE (c1) WITH CLUSTERING ORDER BY " |
325 | 0 | "(r2 DESC, r1 ASC);", &parse_tree); |
326 | | |
327 | | // Non-existent table. |
328 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t2 (r1, r2);", &parse_tree); |
329 | | |
330 | | // Index on system table cannot be created. |
331 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON system_schema.tables (id);", &parse_tree); |
332 | | |
333 | |
|
334 | 0 | CHECK_OK(processor->Run("CREATE TABLE t2 (h1 int, h2 text, r1 int, r2 text, c list<int>, " |
335 | 0 | "PRIMARY KEY ((h1, h2), r1, r2)) " |
336 | 0 | "with transactions = {'enabled':true};")); |
337 | | // Unsupported complex type. |
338 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t2 (c);", &parse_tree); |
339 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t2 ((r1), c);", &parse_tree); |
340 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t2 (r1, r2) INCLUDE (c);", &parse_tree); |
341 | |
|
342 | 0 | CHECK_OK(processor->Run("CREATE TABLE t3 (k int primary key, c int);")); |
343 | | |
344 | | // Index on non-transactional table. |
345 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t3 (c);", &parse_tree); |
346 | | |
347 | | // JSON secondary index on attributes. |
348 | 0 | CHECK_OK(processor->Run("CREATE TABLE t4 (h1 int, h2 text, r1 int, r2 text, j jsonb, " |
349 | 0 | "PRIMARY KEY ((h1, h2), r1, r2)) " |
350 | 0 | "with transactions = {'enabled':true};")); |
351 | | |
352 | | // Analyze the sql statement. |
353 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->>'a');", &parse_tree); |
354 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->'a'->>'b');", &parse_tree); |
355 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->'a'->'b'->>'c');", &parse_tree); |
356 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->>'a'));", &parse_tree); |
357 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->'a'->>'b'));", &parse_tree); |
358 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->'a'->'b'->>'c'));", &parse_tree); |
359 | | // Multiple JSON columns, as hash and range columns. |
360 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->>'a', j->>'x');", &parse_tree); |
361 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->>'a'), j->>'x');", &parse_tree); |
362 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->>'a', j->>'x'));", &parse_tree); |
363 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->'a'->>'b', j->'x'->>'y');", &parse_tree); |
364 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->'a'->>'b'), j->'x'->>'y');", &parse_tree); |
365 | 0 | ANALYZE_VALID_STMT("CREATE INDEX i ON t4 ((j->'a'->>'b', j->'x'->>'y'));", &parse_tree); |
366 | | // Covering column. |
367 | | // TODO: sem_context.h:215: Check failed: sem_state_ State variable is not set for the expression |
368 | | // ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->>'a') INCLUDE (j->>'x');", &parse_tree); |
369 | | // ANALYZE_VALID_STMT("CREATE INDEX i ON t4 (j->>'a') INCLUDE (j->>'x', j->>'k');", &parse_tree); |
370 | 0 | } |
371 | | |
372 | 0 | TEST_F(QLTestAnalyzer, TestCreateLocalIndex) { |
373 | 0 | CreateSimulatedCluster(); |
374 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
375 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h1 int, h2 text, r1 int, r2 text, c1 int, c2 text, " |
376 | 0 | "PRIMARY KEY ((h1, h2), r1, r2));")); |
377 | | |
378 | | // Analyze the sql statement. |
379 | | // Because transactions are not enabled, an index creation will fail iff the index is not local. |
380 | | // TODO: Mark indexes on ((h1, h2)...) as valid once local index is implemented. |
381 | 0 | ParseTree::UniPtr parse_tree; |
382 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2));", &parse_tree); |
383 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2), c2, c1);", &parse_tree); |
384 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2), r2, c1);", &parse_tree); |
385 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2), r1, r2);", &parse_tree); |
386 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2), r1, r2, c1, c2);", &parse_tree); |
387 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t (h1, h2, r1, r2);", &parse_tree); |
388 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1), h2, r1, r2);", &parse_tree); |
389 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((r1, r2), h1, h2);", &parse_tree); |
390 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h2), r1, r2);", &parse_tree); |
391 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2, r1));", &parse_tree); |
392 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, h2, r1, r2), c1, c2);", &parse_tree); |
393 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t ((h1, r1), h2);", &parse_tree); |
394 | 0 | ANALYZE_INVALID_STMT("CREATE INDEX i ON t (h1, h2);", &parse_tree); |
395 | 0 | } |
396 | | |
397 | | |
398 | 0 | TEST_F(QLTestAnalyzer, TestIndexSelection) { |
399 | 0 | CreateSimulatedCluster(); |
400 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
401 | 0 | EXPECT_OK(processor->Run("CREATE TABLE t (h1 int, h2 int, r1 int, r2 int, c1 int, c2 int, " |
402 | 0 | "PRIMARY KEY ((h1, h2), r1, r2)) " |
403 | 0 | "with transactions = {'enabled':true};")); |
404 | |
|
405 | 0 | ParseTree::UniPtr select_parse_tree, parse_tree; |
406 | | |
407 | | // Should not use index if there is no index. |
408 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND h2 = 1 AND r1 = 1", false, false); |
409 | |
|
410 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i1 ON t ((h1, h2), r1);")); |
411 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i2 ON t ((h1, h2), c2, c1);")); |
412 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i3 ON t ((h1, h2), r2) INCLUDE (c1);")); |
413 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i4 ON t ((h1, h2), r2) INCLUDE (c2);")); |
414 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i5 ON t ((h1, h2), c1);")); |
415 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i6 ON t ((c1, r2), c2);")); |
416 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i7 ON t ((h2, h1), c1) INCLUDE (c2);")); |
417 | |
|
418 | 0 | client::YBTableName table_name(YQL_DATABASE_CQL, kDefaultKeyspaceName, "t"); |
419 | 0 | processor->RemoveCachedTableDesc(table_name); |
420 | | |
421 | | // Should select from the indexed table. |
422 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND c2 = 1 AND c1 = 1", false, false); |
423 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND h2 = 1 AND r1 = 1", false, false); |
424 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND c1 = 1 AND c2 = 1", false, false); |
425 | | |
426 | | // None for i1, as the table itself is always better. |
427 | | |
428 | | // Should use i2. |
429 | 0 | TestIndexSelection("SELECT c2, r1 FROM t WHERE h1 = 1 AND h2 = 1 AND c2 = 1 AND c1 = 1", |
430 | 0 | true, true); |
431 | | // Check that fully specified beats range clauses. |
432 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND h2 = 1 AND c2 = 1 AND r1 > 0 AND r1 < 2", |
433 | 0 | true, true); |
434 | | |
435 | | // Should use i3. |
436 | 0 | TestIndexSelection("SELECT c1 FROM t WHERE h1 = 1 AND h2 = 1 AND r2 = 1", true, true); |
437 | | |
438 | | // Should use i3 as uncovered index. |
439 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND h2 = 1 AND r2 = 1", true, false); |
440 | | |
441 | | // Should use i4. |
442 | 0 | TestIndexSelection("SELECT c2, r1 FROM t WHERE h1 = 1 AND h2 = 1 AND r2 = 1", true, true); |
443 | 0 | TestIndexSelection("SELECT h2, c2, r1 FROM t WHERE h1 = 1 AND h2 = 1 AND r2 = 1 AND " |
444 | 0 | "c2 = 1 AND r1 > 0 AND r1 < 2", true, true); |
445 | | |
446 | | // Should use i5. |
447 | | // Check that local beats-non-local. |
448 | 0 | TestIndexSelection("SELECT c1 FROM t WHERE h1 = 1 AND h2 = 1 AND c1 = 1", true, true); |
449 | | |
450 | | // Should use i6. |
451 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND h2 = 1 AND r2 = 1 AND c1 = 1 AND c2 = 1", |
452 | 0 | true, true); |
453 | | |
454 | | // Should use i7. |
455 | | // Check that non-local beats local if it can perform the read. |
456 | 0 | TestIndexSelection("SELECT * FROM t WHERE h1 = 1 AND h2 = 1 AND c1 = 1", true, true); |
457 | 0 | TestIndexSelection("SELECT c2 FROM t WHERE h1 = 1 AND h2 = 1 AND r2 = 1 AND c1 = 1", true, true); |
458 | 0 | } |
459 | | |
460 | 0 | TEST_F(QLTestAnalyzer, TestIndexBasedOnJsonAttribute) { |
461 | 0 | CreateSimulatedCluster(); |
462 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
463 | 0 | EXPECT_OK(processor->Run("CREATE TABLE t (h1 int, h2 int, r1 int, r2 int, j jsonb, " |
464 | 0 | "PRIMARY KEY ((h1, h2), r1, r2)) " |
465 | 0 | "with transactions = {'enabled':true};")); |
466 | |
|
467 | 0 | EXPECT_OK(processor->Run("CREATE INDEX i1 ON t (j->>'a');")); |
468 | 0 | EXPECT_NOT_OK(processor->Run("CREATE INDEX i2 ON t (j->3);")); |
469 | 0 | } |
470 | | |
471 | 0 | TEST_F(QLTestAnalyzer, TestTruncate) { |
472 | 0 | CreateSimulatedCluster(); |
473 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
474 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h int PRIMARY KEY);")); |
475 | | |
476 | | // Analyze the TRUNCATE statement. |
477 | 0 | ParseTree::UniPtr parse_tree; |
478 | 0 | ANALYZE_VALID_STMT("TRUNCATE TABLE t;", &parse_tree); |
479 | 0 | ANALYZE_VALID_STMT(Substitute("TRUNCATE TABLE $0.t;", kDefaultKeyspaceName), &parse_tree); |
480 | | |
481 | | // No such keyspace |
482 | 0 | ANALYZE_INVALID_STMT("TRUNCATE TABLE invalid_keyspace.t;", &parse_tree); |
483 | | // No such table |
484 | 0 | ANALYZE_INVALID_STMT("TRUNCATE TABLE invalid_table;", &parse_tree); |
485 | | // Only one table can be truncated in each statement. |
486 | 0 | ANALYZE_INVALID_STMT("TRUNCATE TABLE t1, t2;", &parse_tree); |
487 | | // Invalid qualified table name. |
488 | 0 | ANALYZE_INVALID_STMT("TRUNCATE TABLE k.t.c;", &parse_tree); |
489 | 0 | } |
490 | | |
491 | 0 | TEST_F(QLTestAnalyzer, TestMisc) { |
492 | 0 | CreateSimulatedCluster(); |
493 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
494 | | |
495 | | // Analyze misc empty sql statements. |
496 | 0 | ParseTree::UniPtr parse_tree; |
497 | 0 | CHECK_OK(TestAnalyzer("", &parse_tree)); |
498 | 0 | EXPECT_TRUE(parse_tree->root() == nullptr); |
499 | 0 | CHECK_OK(TestAnalyzer(";", &parse_tree)); |
500 | 0 | EXPECT_TRUE(parse_tree->root() == nullptr); |
501 | 0 | CHECK_OK(TestAnalyzer(" ; ; ", &parse_tree)); |
502 | 0 | EXPECT_TRUE(parse_tree->root() == nullptr); |
503 | | |
504 | | // Invalid: multi-statement not supported. |
505 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h INT PRIMARY KEY, c INT);")); |
506 | 0 | ANALYZE_INVALID_STMT("SELECT * FROM t; SELECT C FROM t;", &parse_tree); |
507 | 0 | } |
508 | | |
509 | 0 | TEST_F(QLTestAnalyzer, TestOffsetLimitClause) { |
510 | 0 | CreateSimulatedCluster(); |
511 | 0 | TestQLProcessor *processor = GetQLProcessor(); |
512 | 0 | CHECK_OK(processor->Run("CREATE TABLE t (h int PRIMARY KEY);")); |
513 | | |
514 | | // Analyze the LIMIT/OFFSET clause. |
515 | 0 | ParseTree::UniPtr parse_tree; |
516 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t LIMIT 10 OFFSET 10;", &parse_tree); |
517 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t OFFSET 10;", &parse_tree); |
518 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t LIMIT 10;", &parse_tree); |
519 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t OFFSET 0;", &parse_tree); |
520 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t LIMIT 0;", &parse_tree); |
521 | 0 | ANALYZE_VALID_STMT("SELECT * FROM t OFFSET 10 LIMIT 10;", &parse_tree); |
522 | 0 | } |
523 | | |
524 | | } // namespace ql |
525 | | } // namespace yb |