1mod flight;
16mod network;
17
18use api::v1::QueryRequest;
19use api::v1::greptime_request::Request;
20use api::v1::query_request::Query;
21use common_query::OutputData;
22use common_recordbatch::RecordBatches;
23use frontend::instance::Instance;
24use servers::query_handler::grpc::GrpcQueryHandler;
25use session::context::QueryContext;
26
27#[allow(unused)]
28async fn query_and_expect(instance: &Instance, sql: &str, expected: &str) {
29 let request = Request::Query(QueryRequest {
30 query: Some(Query::Sql(sql.to_string())),
31 });
32 let output = GrpcQueryHandler::do_query(instance, request, QueryContext::arc())
33 .await
34 .unwrap();
35 let OutputData::Stream(stream) = output.data else {
36 unreachable!()
37 };
38 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
39 let actual = recordbatches.pretty_print().unwrap();
40 assert_eq!(actual, expected, "actual: {}", actual);
41}
42
43#[cfg(test)]
44mod test {
45 use std::collections::HashMap;
46 use std::sync::Arc;
47
48 use api::v1::column::Values;
49 use api::v1::column_data_type_extension::TypeExt;
50 use api::v1::ddl_request::Expr as DdlExpr;
51 use api::v1::greptime_request::Request;
52 use api::v1::query_request::Query;
53 use api::v1::region::QueryRequest as RegionQueryRequest;
54 use api::v1::{
55 AddColumn, AddColumns, AlterTableExpr, Column, ColumnDataType, ColumnDataTypeExtension,
56 ColumnDef, CreateDatabaseExpr, CreateTableExpr, DdlRequest, DeleteRequest, DeleteRequests,
57 DropTableExpr, InsertRequest, InsertRequests, QueryRequest, SemanticType,
58 VectorTypeExtension, alter_table_expr,
59 };
60 use client::OutputData;
61 use common_catalog::consts::MITO_ENGINE;
62 use common_meta::rpc::router::region_distribution;
63 use common_query::Output;
64 use common_recordbatch::RecordBatches;
65 use frontend::instance::Instance;
66 use query::parser::QueryLanguageParser;
67 use query::query_engine::DefaultSerializer;
68 use rstest::rstest;
69 use rstest_reuse::apply;
70 use servers::query_handler::grpc::GrpcQueryHandler;
71 use session::context::{QueryContext, QueryContextBuilder};
72 use store_api::mito_engine_options::TWCS_TIME_WINDOW;
73 use store_api::storage::RegionId;
74 use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
75
76 use super::*;
77 use crate::standalone::GreptimeDbStandaloneBuilder;
78 use crate::test_util::execute_sql_and_expect;
79 use crate::tests;
80 use crate::tests::MockDistributedInstance;
81 use crate::tests::test_util::{MockInstance, both_instances_cases, distributed, standalone};
82
83 #[tokio::test(flavor = "multi_thread")]
84 async fn test_distributed_handle_ddl_request() {
85 common_telemetry::init_default_ut_logging();
86 let instance =
87 tests::create_distributed_instance("test_distributed_handle_ddl_request").await;
88
89 test_handle_ddl_request(instance.frontend().as_ref()).await;
90
91 verify_table_is_dropped(&instance).await;
92 }
93
94 #[tokio::test(flavor = "multi_thread")]
95 async fn test_standalone_handle_ddl_request() {
96 let standalone = GreptimeDbStandaloneBuilder::new("test_standalone_handle_ddl_request")
97 .build()
98 .await;
99 let instance = standalone.fe_instance();
100
101 test_handle_ddl_request(instance.as_ref()).await;
102 }
103
104 #[tokio::test(flavor = "multi_thread")]
105 async fn test_distributed_handle_multi_ddl_request() {
106 common_telemetry::init_default_ut_logging();
107 let instance =
108 tests::create_distributed_instance("test_distributed_handle_multi_ddl_request").await;
109
110 test_handle_multi_ddl_request(instance.frontend().as_ref()).await;
111
112 verify_table_is_dropped(&instance).await;
113 }
114
115 #[tokio::test(flavor = "multi_thread")]
116 async fn test_standalone_handle_multi_ddl_request() {
117 let standalone =
118 GreptimeDbStandaloneBuilder::new("test_standalone_handle_multi_ddl_request")
119 .build()
120 .await;
121 let instance = standalone.fe_instance();
122
123 test_handle_multi_ddl_request(instance.as_ref()).await;
124 }
125
126 async fn query(instance: &Instance, request: Request) -> Output {
127 GrpcQueryHandler::do_query(instance, request, QueryContext::arc())
128 .await
129 .unwrap()
130 }
131
132 async fn test_handle_multi_ddl_request(instance: &Instance) {
133 let request = Request::Ddl(DdlRequest {
134 expr: Some(DdlExpr::CreateDatabase(CreateDatabaseExpr {
135 catalog_name: "greptime".to_string(),
136 schema_name: "database_created_through_grpc".to_string(),
137 create_if_not_exists: true,
138 options: Default::default(),
139 })),
140 });
141 let output = query(instance, request).await;
142 assert!(matches!(output.data, OutputData::AffectedRows(1)));
143
144 let request = Request::Ddl(DdlRequest {
145 expr: Some(DdlExpr::CreateTable(CreateTableExpr {
146 catalog_name: "greptime".to_string(),
147 schema_name: "database_created_through_grpc".to_string(),
148 table_name: "table_created_through_grpc".to_string(),
149 column_defs: vec![
150 ColumnDef {
151 name: "a".to_string(),
152 data_type: ColumnDataType::String as _,
153 is_nullable: true,
154 default_constraint: vec![],
155 semantic_type: SemanticType::Field as i32,
156 ..Default::default()
157 },
158 ColumnDef {
159 name: "ts".to_string(),
160 data_type: ColumnDataType::TimestampMillisecond as _,
161 is_nullable: false,
162 default_constraint: vec![],
163 semantic_type: SemanticType::Timestamp as i32,
164 ..Default::default()
165 },
166 ],
167 time_index: "ts".to_string(),
168 engine: MITO_ENGINE.to_string(),
169 ..Default::default()
170 })),
171 });
172 let output = query(instance, request).await;
173 assert!(matches!(output.data, OutputData::AffectedRows(0)));
174
175 let request = Request::Ddl(DdlRequest {
176 expr: Some(DdlExpr::AlterTable(AlterTableExpr {
177 catalog_name: "greptime".to_string(),
178 schema_name: "database_created_through_grpc".to_string(),
179 table_name: "table_created_through_grpc".to_string(),
180 kind: Some(alter_table_expr::Kind::AddColumns(AddColumns {
181 add_columns: vec![
182 AddColumn {
183 column_def: Some(ColumnDef {
184 name: "b".to_string(),
185 data_type: ColumnDataType::Int32 as _,
186 is_nullable: true,
187 default_constraint: vec![],
188 semantic_type: SemanticType::Field as i32,
189 ..Default::default()
190 }),
191 location: None,
192 add_if_not_exists: true,
193 },
194 AddColumn {
195 column_def: Some(ColumnDef {
196 name: "a".to_string(),
197 data_type: ColumnDataType::String as _,
198 is_nullable: true,
199 default_constraint: vec![],
200 semantic_type: SemanticType::Field as i32,
201 ..Default::default()
202 }),
203 location: None,
204 add_if_not_exists: true,
205 },
206 ],
207 })),
208 })),
209 });
210 let output = query(instance, request).await;
211 assert!(matches!(output.data, OutputData::AffectedRows(0)));
212
213 let request = Request::Ddl(DdlRequest {
214 expr: Some(DdlExpr::AlterTable(AlterTableExpr {
215 catalog_name: "greptime".to_string(),
216 schema_name: "database_created_through_grpc".to_string(),
217 table_name: "table_created_through_grpc".to_string(),
218 kind: Some(alter_table_expr::Kind::AddColumns(AddColumns {
219 add_columns: vec![
220 AddColumn {
221 column_def: Some(ColumnDef {
222 name: "c".to_string(),
223 data_type: ColumnDataType::Int32 as _,
224 is_nullable: true,
225 default_constraint: vec![],
226 semantic_type: SemanticType::Field as i32,
227 ..Default::default()
228 }),
229 location: None,
230 add_if_not_exists: true,
231 },
232 AddColumn {
233 column_def: Some(ColumnDef {
234 name: "d".to_string(),
235 data_type: ColumnDataType::Int32 as _,
236 is_nullable: true,
237 default_constraint: vec![],
238 semantic_type: SemanticType::Field as i32,
239 ..Default::default()
240 }),
241 location: None,
242 add_if_not_exists: true,
243 },
244 ],
245 })),
246 })),
247 });
248 let output = query(instance, request).await;
249 assert!(matches!(output.data, OutputData::AffectedRows(0)));
250
251 let request = Request::Query(QueryRequest {
252 query: Some(Query::Sql("INSERT INTO database_created_through_grpc.table_created_through_grpc (a, b, c, d, ts) VALUES ('s', 1, 1, 1, 1672816466000)".to_string()))
253 });
254 let output = query(instance, request).await;
255 assert!(matches!(output.data, OutputData::AffectedRows(1)));
256
257 let sql = "SELECT ts, a, b FROM database_created_through_grpc.table_created_through_grpc";
258 let expected = "\
259+---------------------+---+---+
260| ts | a | b |
261+---------------------+---+---+
262| 2023-01-04T07:14:26 | s | 1 |
263+---------------------+---+---+";
264 query_and_expect(instance, sql, expected).await;
265
266 let request = Request::Ddl(DdlRequest {
267 expr: Some(DdlExpr::DropTable(DropTableExpr {
268 catalog_name: "greptime".to_string(),
269 schema_name: "database_created_through_grpc".to_string(),
270 table_name: "table_created_through_grpc".to_string(),
271 ..Default::default()
272 })),
273 });
274 let output = query(instance, request).await;
275 assert!(matches!(output.data, OutputData::AffectedRows(0)));
276 }
277
278 async fn test_handle_ddl_request(instance: &Instance) {
279 let request = Request::Ddl(DdlRequest {
280 expr: Some(DdlExpr::CreateDatabase(CreateDatabaseExpr {
281 catalog_name: "greptime".to_string(),
282 schema_name: "database_created_through_grpc".to_string(),
283 create_if_not_exists: true,
284 options: Default::default(),
285 })),
286 });
287 let output = query(instance, request).await;
288 assert!(matches!(output.data, OutputData::AffectedRows(1)));
289
290 let request = Request::Ddl(DdlRequest {
291 expr: Some(DdlExpr::CreateTable(CreateTableExpr {
292 catalog_name: "greptime".to_string(),
293 schema_name: "database_created_through_grpc".to_string(),
294 table_name: "table_created_through_grpc".to_string(),
295 column_defs: vec![
296 ColumnDef {
297 name: "a".to_string(),
298 data_type: ColumnDataType::String as _,
299 is_nullable: true,
300 default_constraint: vec![],
301 semantic_type: SemanticType::Field as i32,
302 ..Default::default()
303 },
304 ColumnDef {
305 name: "ts".to_string(),
306 data_type: ColumnDataType::TimestampMillisecond as _,
307 is_nullable: false,
308 default_constraint: vec![],
309 semantic_type: SemanticType::Timestamp as i32,
310 ..Default::default()
311 },
312 ],
313 time_index: "ts".to_string(),
314 engine: MITO_ENGINE.to_string(),
315 ..Default::default()
316 })),
317 });
318 let output = query(instance, request).await;
319 assert!(matches!(output.data, OutputData::AffectedRows(0)));
320
321 let request = Request::Ddl(DdlRequest {
322 expr: Some(DdlExpr::AlterTable(AlterTableExpr {
323 catalog_name: "greptime".to_string(),
324 schema_name: "database_created_through_grpc".to_string(),
325 table_name: "table_created_through_grpc".to_string(),
326 kind: Some(alter_table_expr::Kind::AddColumns(AddColumns {
327 add_columns: vec![AddColumn {
328 column_def: Some(ColumnDef {
329 name: "b".to_string(),
330 data_type: ColumnDataType::Int32 as _,
331 is_nullable: true,
332 default_constraint: vec![],
333 semantic_type: SemanticType::Field as i32,
334 ..Default::default()
335 }),
336 location: None,
337 add_if_not_exists: false,
338 }],
339 })),
340 })),
341 });
342 let output = query(instance, request).await;
343 assert!(matches!(output.data, OutputData::AffectedRows(0)));
344
345 let request = Request::Query(QueryRequest {
346 query: Some(Query::Sql("INSERT INTO database_created_through_grpc.table_created_through_grpc (a, b, ts) VALUES ('s', 1, 1672816466000)".to_string()))
347 });
348 let output = query(instance, request).await;
349 assert!(matches!(output.data, OutputData::AffectedRows(1)));
350
351 let sql = "SELECT ts, a, b FROM database_created_through_grpc.table_created_through_grpc";
352 let expected = "\
353+---------------------+---+---+
354| ts | a | b |
355+---------------------+---+---+
356| 2023-01-04T07:14:26 | s | 1 |
357+---------------------+---+---+";
358 query_and_expect(instance, sql, expected).await;
359
360 let request = Request::Ddl(DdlRequest {
361 expr: Some(DdlExpr::DropTable(DropTableExpr {
362 catalog_name: "greptime".to_string(),
363 schema_name: "database_created_through_grpc".to_string(),
364 table_name: "table_created_through_grpc".to_string(),
365 ..Default::default()
366 })),
367 });
368 let output = query(instance, request).await;
369 assert!(matches!(output.data, OutputData::AffectedRows(0)));
370 }
371
372 async fn verify_table_is_dropped(instance: &MockDistributedInstance) {
373 assert!(
374 instance
375 .frontend()
376 .catalog_manager()
377 .table(
378 "greptime",
379 "database_created_through_grpc",
380 "table_created_through_grpc",
381 None,
382 )
383 .await
384 .unwrap()
385 .is_none()
386 );
387 }
388
389 #[tokio::test(flavor = "multi_thread")]
390 async fn test_distributed_insert_delete_and_query() {
391 common_telemetry::init_default_ut_logging();
392
393 let instance =
394 tests::create_distributed_instance("test_distributed_insert_delete_and_query").await;
395 let frontend = instance.frontend();
396 let frontend = frontend.as_ref();
397
398 let table_name = "my_dist_table";
399 let sql = format!(
400 r"
401CREATE TABLE {table_name} (
402 a INT,
403 b STRING,
404 c JSON,
405 d VECTOR(3),
406 ts TIMESTAMP,
407 TIME INDEX (ts),
408 PRIMARY KEY (a, b, c)
409) PARTITION ON COLUMNS(a) (
410 a < 10,
411 a >= 10 AND a < 20,
412 a >= 20 AND a < 50,
413 a >= 50
414)"
415 );
416 create_table(frontend, sql).await;
417
418 test_insert_delete_and_query_on_existing_table(frontend, table_name).await;
419
420 verify_data_distribution(
421 &instance,
422 table_name,
423 HashMap::from([
424 (
425 0u32,
426 "\
427+---------------------+---+-------------------+
428| ts | a | b |
429+---------------------+---+-------------------+
430| 2023-01-01T07:26:12 | 1 | ts: 1672557972000 |
431| 2023-01-01T07:26:15 | 4 | ts: 1672557975000 |
432| 2023-01-01T07:26:16 | 5 | ts: 1672557976000 |
433| 2023-01-01T07:26:17 | | ts: 1672557977000 |
434+---------------------+---+-------------------+",
435 ),
436 (
437 1u32,
438 "\
439+---------------------+----+-------------------+
440| ts | a | b |
441+---------------------+----+-------------------+
442| 2023-01-01T07:26:18 | 11 | ts: 1672557978000 |
443+---------------------+----+-------------------+",
444 ),
445 (
446 2u32,
447 "\
448+---------------------+----+-------------------+
449| ts | a | b |
450+---------------------+----+-------------------+
451| 2023-01-01T07:26:20 | 20 | ts: 1672557980000 |
452| 2023-01-01T07:26:21 | 21 | ts: 1672557981000 |
453| 2023-01-01T07:26:23 | 23 | ts: 1672557983000 |
454+---------------------+----+-------------------+",
455 ),
456 (
457 3u32,
458 "\
459+---------------------+----+-------------------+
460| ts | a | b |
461+---------------------+----+-------------------+
462| 2023-01-01T07:26:24 | 50 | ts: 1672557984000 |
463| 2023-01-01T07:26:25 | 51 | ts: 1672557985000 |
464+---------------------+----+-------------------+",
465 ),
466 ]),
467 )
468 .await;
469
470 test_insert_delete_and_query_on_auto_created_table(frontend).await;
471
472 verify_data_distribution(
474 &instance,
475 "auto_created_table",
476 HashMap::from([(
477 0u32,
478 "\
479+---------------------+---+---+
480| ts | a | b |
481+---------------------+---+---+
482| 2023-01-01T07:26:16 | | |
483| 2023-01-01T07:26:17 | 6 | |
484| 2023-01-01T07:26:18 | | x |
485| 2023-01-01T07:26:20 | | z |
486+---------------------+---+---+",
487 )]),
488 )
489 .await;
490 }
491
492 #[tokio::test(flavor = "multi_thread")]
493 async fn test_standalone_insert_and_query() {
494 common_telemetry::init_default_ut_logging();
495 let standalone = GreptimeDbStandaloneBuilder::new("test_standalone_insert_and_query")
496 .build()
497 .await;
498 let instance = standalone.fe_instance();
499
500 let table_name = "my_table";
501 let sql = format!(
502 "CREATE TABLE {table_name} (a INT, b STRING, c JSON, ts TIMESTAMP, TIME INDEX (ts), PRIMARY KEY (a, b, c))"
503 );
504 create_table(instance, sql).await;
505
506 test_insert_delete_and_query_on_existing_table(instance, table_name).await;
507
508 test_insert_delete_and_query_on_auto_created_table(instance).await
509 }
510
511 async fn create_table(frontend: &Instance, sql: String) {
512 let request = Request::Query(QueryRequest {
513 query: Some(Query::Sql(sql)),
514 });
515 let output = query(frontend, request).await;
516 assert!(matches!(output.data, OutputData::AffectedRows(0)));
517 }
518
519 async fn test_insert_delete_and_query_on_existing_table(instance: &Instance, table_name: &str) {
520 let timestamp_millisecond_values = vec![
521 1672557972000,
522 1672557973000,
523 1672557974000,
524 1672557975000,
525 1672557976000,
526 1672557977000,
527 1672557978000,
528 1672557979000,
529 1672557980000,
530 1672557981000,
531 1672557982000,
532 1672557983000,
533 1672557984000,
534 1672557985000,
535 1672557986000,
536 1672557987000,
537 ];
538 let json_strings = vec![
539 r#"{ "id": 1, "name": "Alice", "age": 30, "active": true }"#.to_string(),
540 r#"{ "id": 2, "name": "Bob", "balance": 1234.56, "active": false }"#.to_string(),
541 r#"{ "id": 3, "tags": ["rust", "testing", "json"], "age": 28 }"#.to_string(),
542 r#"{ "id": 4, "metadata": { "created_at": "2024-10-30T12:00:00Z", "status": "inactive" } }"#.to_string(),
543 r#"{ "id": 5, "name": null, "phone": "+1234567890" }"#.to_string(),
544 r#"{ "id": 6, "height": 5.9, "weight": 72.5, "active": true }"#.to_string(),
545 r#"{ "id": 7, "languages": ["English", "Spanish"], "age": 29 }"#.to_string(),
546 r#"{ "id": 8, "contact": { "email": "hank@example.com", "phone": "+0987654321" } }"#.to_string(),
547 r#"{ "id": 9, "preferences": { "notifications": true, "theme": "dark" } }"#.to_string(),
548 r#"{ "id": 10, "scores": [88, 92, 76], "active": false }"#.to_string(),
549 r#"{ "id": 11, "birthday": "1996-07-20", "location": { "city": "New York", "zip": "10001" } }"#.to_string(),
550 r#"{ "id": 12, "subscription": { "type": "premium", "expires": "2025-01-01" } }"#.to_string(),
551 r#"{ "id": 13, "settings": { "volume": 0.8, "brightness": 0.6 }, "active": true }"#.to_string(),
552 r#"{ "id": 14, "notes": ["first note", "second note"], "priority": 1 }"#.to_string(),
553 r#"{ "id": 15, "transactions": [{ "amount": 500, "date": "2024-01-01" }, { "amount": -200, "date": "2024-02-01" }] }"#.to_string(),
554 r#"{ "id": 16, "transactions": [{ "amount": 500, "date": "2024-01-01" }] }"#.to_string(),
555 ];
556 let vector_values = [
557 [1.0f32, 2.0, 3.0],
558 [4.0, 5.0, 6.0],
559 [7.0, 8.0, 9.0],
560 [10.0, 11.0, 12.0],
561 [13.0, 14.0, 15.0],
562 [16.0, 17.0, 18.0],
563 [19.0, 20.0, 21.0],
564 [22.0, 23.0, 24.0],
565 [25.0, 26.0, 27.0],
566 [28.0, 29.0, 30.0],
567 [31.0, 32.0, 33.0],
568 [34.0, 35.0, 36.0],
569 [37.0, 38.0, 39.0],
570 [40.0, 41.0, 42.0],
571 [43.0, 44.0, 45.0],
572 [46.0, 47.0, 48.0],
573 ]
574 .iter()
575 .map(|x| x.iter().flat_map(|&f| f.to_le_bytes()).collect::<Vec<u8>>())
576 .collect::<Vec<_>>();
577
578 let insert = InsertRequest {
579 table_name: table_name.to_string(),
580 columns: vec![
581 Column {
582 column_name: "a".to_string(),
583 values: Some(Values {
584 i32_values: vec![1, 2, 3, 4, 5, 11, 12, 20, 21, 22, 23, 50, 51, 52, 53],
585 ..Default::default()
586 }),
587 null_mask: vec![32, 0],
588 semantic_type: SemanticType::Tag as i32,
589 datatype: ColumnDataType::Int32 as i32,
590 ..Default::default()
591 },
592 Column {
593 column_name: "b".to_string(),
594 values: Some(Values {
595 string_values: timestamp_millisecond_values
596 .iter()
597 .map(|x| format!("ts: {x}"))
598 .collect(),
599 ..Default::default()
600 }),
601 semantic_type: SemanticType::Tag as i32,
602 datatype: ColumnDataType::String as i32,
603 ..Default::default()
604 },
605 Column {
606 column_name: "c".to_string(),
607 values: Some(Values {
608 string_values: json_strings,
609 ..Default::default()
610 }),
611 semantic_type: SemanticType::Tag as i32,
612 datatype: ColumnDataType::Json as i32,
613 ..Default::default()
614 },
615 Column {
616 column_name: "d".to_string(),
617 values: Some(Values {
618 binary_values: vector_values.clone(),
619 ..Default::default()
620 }),
621 semantic_type: SemanticType::Field as i32,
622 datatype: ColumnDataType::Vector as i32,
623 datatype_extension: Some(ColumnDataTypeExtension {
624 type_ext: Some(TypeExt::VectorType(VectorTypeExtension { dim: 3 })),
625 }),
626 ..Default::default()
627 },
628 Column {
629 column_name: "ts".to_string(),
630 values: Some(Values {
631 timestamp_millisecond_values,
632 ..Default::default()
633 }),
634 semantic_type: SemanticType::Timestamp as i32,
635 datatype: ColumnDataType::TimestampMillisecond as i32,
636 ..Default::default()
637 },
638 ],
639 row_count: 16,
640 };
641 let output = query(
642 instance,
643 Request::Inserts(InsertRequests {
644 inserts: vec![insert],
645 }),
646 )
647 .await;
648 assert!(matches!(output.data, OutputData::AffectedRows(16)));
649
650 let request = Request::Query(QueryRequest {
651 query: Some(Query::Sql(format!(
652 "SELECT ts, a, b, json_to_string(c) as c, d FROM {table_name} ORDER BY ts"
653 ))),
654 });
655 let output = query(instance, request.clone()).await;
656 let OutputData::Stream(stream) = output.data else {
657 unreachable!()
658 };
659 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
660 let expected = r#"+---------------------+----+-------------------+---------------------------------------------------------------------------------------------------+--------------------------+
661| ts | a | b | c | d |
662+---------------------+----+-------------------+---------------------------------------------------------------------------------------------------+--------------------------+
663| 2023-01-01T07:26:12 | 1 | ts: 1672557972000 | {"active":true,"age":30,"id":1,"name":"Alice"} | 0000803f0000004000004040 |
664| 2023-01-01T07:26:13 | 2 | ts: 1672557973000 | {"active":false,"balance":1234.56,"id":2,"name":"Bob"} | 000080400000a0400000c040 |
665| 2023-01-01T07:26:14 | 3 | ts: 1672557974000 | {"age":28,"id":3,"tags":["rust","testing","json"]} | 0000e0400000004100001041 |
666| 2023-01-01T07:26:15 | 4 | ts: 1672557975000 | {"id":4,"metadata":{"created_at":"2024-10-30T12:00:00Z","status":"inactive"}} | 000020410000304100004041 |
667| 2023-01-01T07:26:16 | 5 | ts: 1672557976000 | {"id":5,"name":null,"phone":"+1234567890"} | 000050410000604100007041 |
668| 2023-01-01T07:26:17 | | ts: 1672557977000 | {"active":true,"height":5.9,"id":6,"weight":72.5} | 000080410000884100009041 |
669| 2023-01-01T07:26:18 | 11 | ts: 1672557978000 | {"age":29,"id":7,"languages":["English","Spanish"]} | 000098410000a0410000a841 |
670| 2023-01-01T07:26:19 | 12 | ts: 1672557979000 | {"contact":{"email":"hank@example.com","phone":"+0987654321"},"id":8} | 0000b0410000b8410000c041 |
671| 2023-01-01T07:26:20 | 20 | ts: 1672557980000 | {"id":9,"preferences":{"notifications":true,"theme":"dark"}} | 0000c8410000d0410000d841 |
672| 2023-01-01T07:26:21 | 21 | ts: 1672557981000 | {"active":false,"id":10,"scores":[88,92,76]} | 0000e0410000e8410000f041 |
673| 2023-01-01T07:26:22 | 22 | ts: 1672557982000 | {"birthday":"1996-07-20","id":11,"location":{"city":"New York","zip":"10001"}} | 0000f8410000004200000442 |
674| 2023-01-01T07:26:23 | 23 | ts: 1672557983000 | {"id":12,"subscription":{"expires":"2025-01-01","type":"premium"}} | 0000084200000c4200001042 |
675| 2023-01-01T07:26:24 | 50 | ts: 1672557984000 | {"active":true,"id":13,"settings":{"brightness":0.6,"volume":0.8}} | 000014420000184200001c42 |
676| 2023-01-01T07:26:25 | 51 | ts: 1672557985000 | {"id":14,"notes":["first note","second note"],"priority":1} | 000020420000244200002842 |
677| 2023-01-01T07:26:26 | 52 | ts: 1672557986000 | {"id":15,"transactions":[{"amount":500,"date":"2024-01-01"},{"amount":-200,"date":"2024-02-01"}]} | 00002c420000304200003442 |
678| 2023-01-01T07:26:27 | 53 | ts: 1672557987000 | {"id":16,"transactions":[{"amount":500,"date":"2024-01-01"}]} | 0000384200003c4200004042 |
679+---------------------+----+-------------------+---------------------------------------------------------------------------------------------------+--------------------------+"#;
680 similar_asserts::assert_eq!(recordbatches.pretty_print().unwrap(), expected);
681
682 let hex_repr_of_vector_values = vector_values.iter().map(hex::encode).collect::<Vec<_>>();
684 assert_eq!(
685 hex_repr_of_vector_values,
686 vec![
687 "0000803f0000004000004040",
688 "000080400000a0400000c040",
689 "0000e0400000004100001041",
690 "000020410000304100004041",
691 "000050410000604100007041",
692 "000080410000884100009041",
693 "000098410000a0410000a841",
694 "0000b0410000b8410000c041",
695 "0000c8410000d0410000d841",
696 "0000e0410000e8410000f041",
697 "0000f8410000004200000442",
698 "0000084200000c4200001042",
699 "000014420000184200001c42",
700 "000020420000244200002842",
701 "00002c420000304200003442",
702 "0000384200003c4200004042",
703 ]
704 );
705
706 let new_grpc_delete_request = |a, b, c, d, ts, row_count| DeleteRequest {
707 table_name: table_name.to_string(),
708 key_columns: vec![
709 Column {
710 column_name: "a".to_string(),
711 semantic_type: SemanticType::Tag as i32,
712 values: Some(Values {
713 i32_values: a,
714 ..Default::default()
715 }),
716 datatype: ColumnDataType::Int32 as i32,
717 ..Default::default()
718 },
719 Column {
720 column_name: "b".to_string(),
721 semantic_type: SemanticType::Tag as i32,
722 values: Some(Values {
723 string_values: b,
724 ..Default::default()
725 }),
726 datatype: ColumnDataType::String as i32,
727 ..Default::default()
728 },
729 Column {
730 column_name: "c".to_string(),
731 values: Some(Values {
732 string_values: c,
733 ..Default::default()
734 }),
735 semantic_type: SemanticType::Tag as i32,
736 datatype: ColumnDataType::Json as i32,
737 ..Default::default()
738 },
739 Column {
740 column_name: "d".to_string(),
741 values: Some(Values {
742 binary_values: d,
743 ..Default::default()
744 }),
745 semantic_type: SemanticType::Field as i32,
746 datatype: ColumnDataType::Vector as i32,
747 datatype_extension: Some(ColumnDataTypeExtension {
748 type_ext: Some(TypeExt::VectorType(VectorTypeExtension { dim: 3 })),
749 }),
750 ..Default::default()
751 },
752 Column {
753 column_name: "ts".to_string(),
754 semantic_type: SemanticType::Timestamp as i32,
755 values: Some(Values {
756 timestamp_millisecond_values: ts,
757 ..Default::default()
758 }),
759 datatype: ColumnDataType::TimestampMillisecond as i32,
760 ..Default::default()
761 },
762 ],
763 row_count,
764 };
765 let delete1 = new_grpc_delete_request(
766 vec![2, 12, 22, 52],
767 vec![
768 "ts: 1672557973000".to_string(),
769 "ts: 1672557979000".to_string(),
770 "ts: 1672557982000".to_string(),
771 "ts: 1672557986000".to_string(),
772 ],
773 vec![
774 r#"{ "id": 2, "name": "Bob", "balance": 1234.56, "active": false }"#.to_string(),
775 r#"{ "id": 8, "contact": { "email": "hank@example.com", "phone": "+0987654321" } }"#.to_string(),
776 r#"{ "id": 11, "birthday": "1996-07-20", "location": { "city": "New York", "zip": "10001" } }"#.to_string(),
777 r#"{ "id": 15, "transactions": [{ "amount": 500, "date": "2024-01-01" }, { "amount": -200, "date": "2024-02-01" }] }"#.to_string(),
778 ],
779 vec![
780 [4.0f32, 5.0, 6.0].iter().flat_map(|f| f.to_le_bytes()).collect::<Vec<u8>>(),
781 [22.0f32, 23.0, 24.0].iter().flat_map(|f| f.to_le_bytes()).collect::<Vec<u8>>(),
782 [31.0f32, 32.0, 33.0].iter().flat_map(|f| f.to_le_bytes()).collect::<Vec<u8>>(),
783 [43.0f32, 44.0, 45.0].iter().flat_map(|f| f.to_le_bytes()).collect::<Vec<u8>>(),
784 ],
785 vec![1672557973000, 1672557979000, 1672557982000, 1672557986000],
786 4,
787 );
788 let delete2 = new_grpc_delete_request(
789 vec![3, 53],
790 vec![
791 "ts: 1672557974000".to_string(),
792 "ts: 1672557987000".to_string(),
793 ],
794 vec![
795 r#"{ "id": 3, "tags": ["rust", "testing", "json"], "age": 28 }"#.to_string(),
796 r#"{ "id": 16, "transactions": [{ "amount": 500, "date": "2024-01-01" }] }"#
797 .to_string(),
798 ],
799 vec![
800 [7.0f32, 8.0, 9.0]
801 .iter()
802 .flat_map(|f| f.to_le_bytes())
803 .collect::<Vec<u8>>(),
804 [46.0f32, 47.0, 48.0]
805 .iter()
806 .flat_map(|f| f.to_le_bytes())
807 .collect::<Vec<u8>>(),
808 ],
809 vec![1672557974000, 1672557987000],
810 2,
811 );
812 let output = query(
813 instance,
814 Request::Deletes(DeleteRequests {
815 deletes: vec![delete1, delete2],
816 }),
817 )
818 .await;
819 assert!(matches!(output.data, OutputData::AffectedRows(6)));
820
821 let output = query(instance, request).await;
822 let OutputData::Stream(stream) = output.data else {
823 unreachable!()
824 };
825 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
826 let expected = r#"+---------------------+----+-------------------+-------------------------------------------------------------------------------+--------------------------+
827| ts | a | b | c | d |
828+---------------------+----+-------------------+-------------------------------------------------------------------------------+--------------------------+
829| 2023-01-01T07:26:12 | 1 | ts: 1672557972000 | {"active":true,"age":30,"id":1,"name":"Alice"} | 0000803f0000004000004040 |
830| 2023-01-01T07:26:15 | 4 | ts: 1672557975000 | {"id":4,"metadata":{"created_at":"2024-10-30T12:00:00Z","status":"inactive"}} | 000020410000304100004041 |
831| 2023-01-01T07:26:16 | 5 | ts: 1672557976000 | {"id":5,"name":null,"phone":"+1234567890"} | 000050410000604100007041 |
832| 2023-01-01T07:26:17 | | ts: 1672557977000 | {"active":true,"height":5.9,"id":6,"weight":72.5} | 000080410000884100009041 |
833| 2023-01-01T07:26:18 | 11 | ts: 1672557978000 | {"age":29,"id":7,"languages":["English","Spanish"]} | 000098410000a0410000a841 |
834| 2023-01-01T07:26:20 | 20 | ts: 1672557980000 | {"id":9,"preferences":{"notifications":true,"theme":"dark"}} | 0000c8410000d0410000d841 |
835| 2023-01-01T07:26:21 | 21 | ts: 1672557981000 | {"active":false,"id":10,"scores":[88,92,76]} | 0000e0410000e8410000f041 |
836| 2023-01-01T07:26:23 | 23 | ts: 1672557983000 | {"id":12,"subscription":{"expires":"2025-01-01","type":"premium"}} | 0000084200000c4200001042 |
837| 2023-01-01T07:26:24 | 50 | ts: 1672557984000 | {"active":true,"id":13,"settings":{"brightness":0.6,"volume":0.8}} | 000014420000184200001c42 |
838| 2023-01-01T07:26:25 | 51 | ts: 1672557985000 | {"id":14,"notes":["first note","second note"],"priority":1} | 000020420000244200002842 |
839+---------------------+----+-------------------+-------------------------------------------------------------------------------+--------------------------+"#;
840 similar_asserts::assert_eq!(recordbatches.pretty_print().unwrap(), expected);
841 }
842
843 async fn verify_data_distribution(
844 instance: &MockDistributedInstance,
845 table_name: &str,
846 expected_distribution: HashMap<u32, &str>,
847 ) {
848 let table = instance
849 .frontend()
850 .catalog_manager()
851 .table("greptime", "public", table_name, None)
852 .await
853 .unwrap()
854 .unwrap();
855 let table_id = table.table_info().ident.table_id;
856 let table_route_value = instance
857 .table_metadata_manager()
858 .table_route_manager()
859 .table_route_storage()
860 .get(table_id)
861 .await
862 .unwrap()
863 .unwrap();
864
865 let region_to_dn_map = region_distribution(
866 table_route_value
867 .region_routes()
868 .expect("physical table route"),
869 )
870 .iter()
871 .map(|(k, v)| (v.leader_regions[0], *k))
872 .collect::<HashMap<u32, u64>>();
873 assert!(region_to_dn_map.len() <= instance.datanodes().len());
874
875 let stmt = QueryLanguageParser::parse_sql(
876 &format!("SELECT ts, a, b FROM {table_name} ORDER BY ts"),
877 &QueryContext::arc(),
878 )
879 .unwrap();
880 let plan = instance
881 .frontend()
882 .statement_executor()
883 .plan(&stmt, QueryContext::arc())
884 .await
885 .unwrap();
886 let plan = DFLogicalSubstraitConvertor
887 .encode(&plan, DefaultSerializer)
888 .unwrap();
889
890 for (region, dn) in region_to_dn_map.iter() {
891 let region_server = instance.datanodes().get(dn).unwrap().region_server();
892
893 let region_id = RegionId::new(table_id, *region);
894
895 let stream = region_server
896 .handle_remote_read(
897 RegionQueryRequest {
898 region_id: region_id.as_u64(),
899 plan: plan.to_vec(),
900 ..Default::default()
901 },
902 QueryContext::arc(),
903 )
904 .await
905 .unwrap();
906
907 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
908 let actual = recordbatches.pretty_print().unwrap();
909
910 let expected = expected_distribution.get(region).unwrap();
911 assert_eq!(&actual, expected);
912 }
913 }
914
915 async fn test_insert_delete_and_query_on_auto_created_table(instance: &Instance) {
916 let insert = InsertRequest {
917 table_name: "auto_created_table".to_string(),
918 columns: vec![
919 Column {
920 column_name: "a".to_string(),
921 values: Some(Values {
922 i32_values: vec![4, 6],
923 ..Default::default()
924 }),
925 null_mask: vec![2],
926 semantic_type: SemanticType::Field as i32,
927 datatype: ColumnDataType::Int32 as i32,
928 ..Default::default()
929 },
930 Column {
931 column_name: "c".to_string(),
932 values: Some(Values {
933 string_values: vec![
934 r#"{ "id": 1, "name": "Alice", "age": 30, "active": true }"#
935 .to_string(),
936 r#"{ "id": 2, "name": "Bob", "balance": 1234.56, "active": false }"#
937 .to_string(),
938 ],
939 ..Default::default()
940 }),
941 null_mask: vec![2],
942 semantic_type: SemanticType::Field as i32,
943 datatype: ColumnDataType::Json as i32,
944 ..Default::default()
945 },
946 Column {
947 column_name: "ts".to_string(),
948 values: Some(Values {
949 timestamp_millisecond_values: vec![
950 1672557975000,
951 1672557976000,
952 1672557977000,
953 ],
954 ..Default::default()
955 }),
956 semantic_type: SemanticType::Timestamp as i32,
957 datatype: ColumnDataType::TimestampMillisecond as i32,
958 ..Default::default()
959 },
960 ],
961 row_count: 3,
962 };
963
964 let request = Request::Inserts(InsertRequests {
966 inserts: vec![insert],
967 });
968 let output = query(instance, request).await;
969 assert!(matches!(output.data, OutputData::AffectedRows(3)));
970
971 let insert = InsertRequest {
972 table_name: "auto_created_table".to_string(),
973 columns: vec![
974 Column {
975 column_name: "b".to_string(),
976 values: Some(Values {
977 string_values: vec!["x".to_string(), "z".to_string()],
978 ..Default::default()
979 }),
980 null_mask: vec![2],
981 semantic_type: SemanticType::Field as i32,
982 datatype: ColumnDataType::String as i32,
983 ..Default::default()
984 },
985 Column {
986 column_name: "ts".to_string(),
987 values: Some(Values {
988 timestamp_millisecond_values: vec![
989 1672557978000,
990 1672557979000,
991 1672557980000,
992 ],
993 ..Default::default()
994 }),
995 semantic_type: SemanticType::Timestamp as i32,
996 datatype: ColumnDataType::TimestampMillisecond as i32,
997 ..Default::default()
998 },
999 ],
1000 row_count: 3,
1001 };
1002
1003 let request = Request::Inserts(InsertRequests {
1005 inserts: vec![insert],
1006 });
1007 let output = query(instance, request).await;
1008 assert!(matches!(output.data, OutputData::AffectedRows(3)));
1009
1010 let request = Request::Query(QueryRequest {
1011 query: Some(Query::Sql(
1012 "SELECT ts, a, b, json_to_string(c) as c FROM auto_created_table order by ts"
1013 .to_string(),
1014 )),
1015 });
1016 let output = query(instance, request.clone()).await;
1017 let OutputData::Stream(stream) = output.data else {
1018 unreachable!()
1019 };
1020 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
1021 let expected = r#"+---------------------+---+---+--------------------------------------------------------+
1022| ts | a | b | c |
1023+---------------------+---+---+--------------------------------------------------------+
1024| 2023-01-01T07:26:15 | 4 | | {"active":true,"age":30,"id":1,"name":"Alice"} |
1025| 2023-01-01T07:26:16 | | | |
1026| 2023-01-01T07:26:17 | 6 | | {"active":false,"balance":1234.56,"id":2,"name":"Bob"} |
1027| 2023-01-01T07:26:18 | | x | |
1028| 2023-01-01T07:26:19 | | | |
1029| 2023-01-01T07:26:20 | | z | |
1030+---------------------+---+---+--------------------------------------------------------+"#;
1031 similar_asserts::assert_eq!(recordbatches.pretty_print().unwrap(), expected);
1032
1033 let delete = DeleteRequest {
1034 table_name: "auto_created_table".to_string(),
1035 key_columns: vec![Column {
1036 column_name: "ts".to_string(),
1037 values: Some(Values {
1038 timestamp_millisecond_values: vec![1672557975000, 1672557979000],
1039 ..Default::default()
1040 }),
1041 semantic_type: SemanticType::Timestamp as i32,
1042 datatype: ColumnDataType::TimestampMillisecond as i32,
1043 ..Default::default()
1044 }],
1045 row_count: 2,
1046 };
1047
1048 let output = query(
1049 instance,
1050 Request::Deletes(DeleteRequests {
1051 deletes: vec![delete],
1052 }),
1053 )
1054 .await;
1055 assert!(matches!(output.data, OutputData::AffectedRows(2)));
1056
1057 let output = query(instance, request).await;
1058 let OutputData::Stream(stream) = output.data else {
1059 unreachable!()
1060 };
1061 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
1062 let expected = r#"+---------------------+---+---+--------------------------------------------------------+
1063| ts | a | b | c |
1064+---------------------+---+---+--------------------------------------------------------+
1065| 2023-01-01T07:26:16 | | | |
1066| 2023-01-01T07:26:17 | 6 | | {"active":false,"balance":1234.56,"id":2,"name":"Bob"} |
1067| 2023-01-01T07:26:18 | | x | |
1068| 2023-01-01T07:26:20 | | z | |
1069+---------------------+---+---+--------------------------------------------------------+"#;
1070 similar_asserts::assert_eq!(recordbatches.pretty_print().unwrap(), expected);
1071 }
1072
1073 #[tokio::test(flavor = "multi_thread")]
1074 async fn test_promql_query() {
1075 let standalone = GreptimeDbStandaloneBuilder::new("test_standalone_promql_query")
1076 .build()
1077 .await;
1078 let instance = standalone.fe_instance();
1079
1080 let table_name = "my_table";
1081 let sql = format!(
1082 "CREATE TABLE {table_name} (h string, a double, ts TIMESTAMP, TIME INDEX (ts), PRIMARY KEY(h))"
1083 );
1084 create_table(instance, sql).await;
1085
1086 let insert = InsertRequest {
1087 table_name: table_name.to_string(),
1088 columns: vec![
1089 Column {
1090 column_name: "h".to_string(),
1091 values: Some(Values {
1092 string_values: vec![
1093 "t".to_string(),
1094 "t".to_string(),
1095 "t".to_string(),
1096 "t".to_string(),
1097 "t".to_string(),
1098 "t".to_string(),
1099 "t".to_string(),
1100 "t".to_string(),
1101 ],
1102 ..Default::default()
1103 }),
1104 semantic_type: SemanticType::Tag as i32,
1105 datatype: ColumnDataType::String as i32,
1106 ..Default::default()
1107 },
1108 Column {
1109 column_name: "a".to_string(),
1110 values: Some(Values {
1111 f64_values: vec![1f64, 11f64, 20f64, 22f64, 50f64, 55f64, 99f64],
1112 ..Default::default()
1113 }),
1114 null_mask: vec![4],
1115 semantic_type: SemanticType::Field as i32,
1116 datatype: ColumnDataType::Float64 as i32,
1117 ..Default::default()
1118 },
1119 Column {
1120 column_name: "ts".to_string(),
1121 values: Some(Values {
1122 timestamp_millisecond_values: vec![
1123 1672557972000,
1124 1672557973000,
1125 1672557974000,
1126 1672557975000,
1127 1672557976000,
1128 1672557977000,
1129 1672557978000,
1130 1672557979000,
1131 ],
1132 ..Default::default()
1133 }),
1134 semantic_type: SemanticType::Timestamp as i32,
1135 datatype: ColumnDataType::TimestampMillisecond as i32,
1136 ..Default::default()
1137 },
1138 ],
1139 row_count: 8,
1140 };
1141
1142 let request = Request::Inserts(InsertRequests {
1143 inserts: vec![insert],
1144 });
1145 let output = query(instance, request).await;
1146 assert!(matches!(output.data, OutputData::AffectedRows(8)));
1147
1148 let request = Request::Query(QueryRequest {
1149 query: Some(Query::PromRangeQuery(api::v1::PromRangeQuery {
1150 query: "my_table".to_owned(),
1151 start: "1672557973".to_owned(),
1152 end: "1672557978".to_owned(),
1153 step: "1s".to_owned(),
1154 lookback: "5m".to_string(),
1155 })),
1156 });
1157 let output = query(instance, request).await;
1158 let OutputData::Stream(stream) = output.data else {
1159 unreachable!()
1160 };
1161 let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
1162 let expected = "\
1163+---+------+---------------------+
1164| h | a | ts |
1165+---+------+---------------------+
1166| t | 11.0 | 2023-01-01T07:26:13 |
1167| t | | 2023-01-01T07:26:14 |
1168| t | 20.0 | 2023-01-01T07:26:15 |
1169| t | 22.0 | 2023-01-01T07:26:16 |
1170| t | 50.0 | 2023-01-01T07:26:17 |
1171| t | 55.0 | 2023-01-01T07:26:18 |
1172+---+------+---------------------+";
1173 assert_eq!(recordbatches.pretty_print().unwrap(), expected);
1174 }
1175
1176 #[apply(both_instances_cases)]
1177 async fn test_extra_external_table_options(instance: Arc<dyn MockInstance>) {
1178 common_telemetry::init_default_ut_logging();
1179 let frontend = instance.frontend();
1180 let instance = frontend.as_ref();
1181
1182 let insert = InsertRequest {
1183 table_name: "auto_created_table".to_string(),
1184 columns: vec![
1185 Column {
1186 column_name: "a".to_string(),
1187 values: Some(Values {
1188 i32_values: vec![4, 6],
1189 ..Default::default()
1190 }),
1191 null_mask: vec![2],
1192 semantic_type: SemanticType::Field as i32,
1193 datatype: ColumnDataType::Int32 as i32,
1194 ..Default::default()
1195 },
1196 Column {
1197 column_name: "c".to_string(),
1198 values: Some(Values {
1199 string_values: vec![
1200 r#"{ "id": 1, "name": "Alice", "age": 30, "active": true }"#
1201 .to_string(),
1202 r#"{ "id": 2, "name": "Bob", "balance": 1234.56, "active": false }"#
1203 .to_string(),
1204 ],
1205 ..Default::default()
1206 }),
1207 null_mask: vec![2],
1208 semantic_type: SemanticType::Field as i32,
1209 datatype: ColumnDataType::Json as i32,
1210 ..Default::default()
1211 },
1212 Column {
1213 column_name: "ts".to_string(),
1214 values: Some(Values {
1215 timestamp_millisecond_values: vec![
1216 1672557975000,
1217 1672557976000,
1218 1672557977000,
1219 ],
1220 ..Default::default()
1221 }),
1222 semantic_type: SemanticType::Timestamp as i32,
1223 datatype: ColumnDataType::TimestampMillisecond as i32,
1224 ..Default::default()
1225 },
1226 ],
1227 row_count: 3,
1228 };
1229 let request = Request::Inserts(InsertRequests {
1230 inserts: vec![insert],
1231 });
1232
1233 let ctx = Arc::new(
1234 QueryContextBuilder::default()
1235 .set_extension(TWCS_TIME_WINDOW.to_string(), "1d".to_string())
1236 .build(),
1237 );
1238 let output = GrpcQueryHandler::do_query(instance, request, ctx)
1239 .await
1240 .unwrap();
1241 assert!(matches!(output.data, OutputData::AffectedRows(3)));
1242
1243 let sql = "show create table auto_created_table";
1244 let expected = r#"+--------------------+---------------------------------------------------+
1245| Table | Create Table |
1246+--------------------+---------------------------------------------------+
1247| auto_created_table | CREATE TABLE IF NOT EXISTS "auto_created_table" ( |
1248| | "a" INT NULL, |
1249| | "c" JSON NULL, |
1250| | "ts" TIMESTAMP(3) NOT NULL, |
1251| | TIME INDEX ("ts") |
1252| | ) |
1253| | |
1254| | ENGINE=mito |
1255| | WITH( |
1256| | 'comment' = 'Created on insertion', |
1257| | 'compaction.twcs.time_window' = '1d', |
1258| | 'compaction.type' = 'twcs' |
1259| | ) |
1260+--------------------+---------------------------------------------------+"#;
1261 execute_sql_and_expect(&frontend, sql, expected).await;
1262 }
1263}