1use datatypes::extension::json::JSON2_REMAINDER_FIELD_NAME;
16use datatypes::json::JSON2_MAX_STRUCTURED_DEPTH;
17use datatypes::schema::FulltextOptions;
18use snafu::{ResultExt, ensure};
19use sqlparser::ast::{DataType, ExactNumberInfo, ObjectName};
20use sqlparser::dialect::keywords::Keyword;
21use sqlparser::parser::Parser;
22use sqlparser::tokenizer::Token;
23
24use crate::ast::Ident;
25use crate::dialect::GreptimeDbDialect;
26use crate::error::{InvalidSqlSnafu, Result, SyntaxSnafu};
27use crate::parsers::create_parser::{INVERTED, SKIPPING};
28use crate::statements::OptionMap;
29use crate::statements::create::{Json2Options, JsonTypeHint};
30
31const JSON2_TYPE_NAME: &str = "JSON2";
32const MAX_AUTO_EXPANDED_PATHS: &str = "max_auto_expanded_paths";
33
34pub fn parse_json2_type_hint_path(path: &str) -> Result<Vec<String>> {
36 let dialect = GreptimeDbDialect {};
37 let mut parser = Parser::new(&dialect)
38 .try_with_sql(path)
39 .context(SyntaxSnafu)?;
40 let path = parse_json2_path(&mut parser)?;
41 ensure!(
42 parser.peek_token().token == Token::EOF,
43 InvalidSqlSnafu {
44 msg: format!(
45 "unexpected token '{}' in JSON2 type hint path",
46 parser.peek_token()
47 )
48 }
49 );
50 Ok(path)
51}
52
53pub(crate) fn parse_json2_type_and_options(
54 parser: &mut Parser<'_>,
55) -> Result<Option<(DataType, Option<Json2Options>)>> {
56 let token = parser.peek_token();
57 let Token::Word(word) = &token.token else {
58 return Ok(None);
59 };
60
61 if !word.value.eq_ignore_ascii_case(JSON2_TYPE_NAME) || word.quote_style.is_some() {
62 return Ok(None);
63 }
64
65 parser.next_token();
66 let data_type = DataType::Custom(ObjectName::from(vec![Ident::new(JSON2_TYPE_NAME)]), vec![]);
67 let options = if parser.consume_token(&Token::LParen) {
68 parse_json2_options(parser)?
69 } else {
70 None
71 };
72
73 Ok(Some((data_type, options)))
74}
75
76fn parse_json2_options(parser: &mut Parser<'_>) -> Result<Option<Json2Options>> {
77 if parser.consume_token(&Token::RParen) {
78 return Ok(None);
79 }
80
81 let mut max_auto_expanded_paths = None;
82 let mut type_hints = Vec::new();
83 loop {
84 let token = parser.peek_token();
85 let is_max_auto_expanded_paths = matches!(
86 &token.token,
87 Token::Word(word)
88 if word.quote_style.is_none()
89 && word.value.eq_ignore_ascii_case(MAX_AUTO_EXPANDED_PATHS)
90 );
91 if is_max_auto_expanded_paths {
92 parser.next_token();
93 ensure!(
94 max_auto_expanded_paths.is_none(),
95 InvalidSqlSnafu {
96 msg: format!("duplicated JSON2 option '{MAX_AUTO_EXPANDED_PATHS}'")
97 }
98 );
99 parser.expect_token(&Token::Eq).context(SyntaxSnafu)?;
100
101 let token = parser.next_token();
102 let Token::Number(value, _) = token.token else {
103 return InvalidSqlSnafu {
104 msg: format!(
105 "JSON2 option '{MAX_AUTO_EXPANDED_PATHS}' expects a non-negative integer"
106 ),
107 }
108 .fail();
109 };
110 max_auto_expanded_paths = Some(value.parse::<u32>().map_err(|_| {
111 InvalidSqlSnafu {
112 msg: format!(
113 "JSON2 option '{MAX_AUTO_EXPANDED_PATHS}' expects a non-negative integer"
114 ),
115 }
116 .build()
117 })?);
118 } else {
119 let hint = parse_json2_type_hint(parser)?;
120 ensure_no_path_conflict(&type_hints, &hint.path)?;
121 type_hints.push(hint);
122 }
123
124 if parser.consume_token(&Token::Comma) {
125 if parser.consume_token(&Token::RParen) {
126 break;
127 }
128 } else {
129 parser.expect_token(&Token::RParen).context(SyntaxSnafu)?;
130 break;
131 }
132 }
133
134 Ok(Some(Json2Options {
135 max_auto_expanded_paths,
136 type_hints,
137 }))
138}
139
140fn parse_json2_type_hint(parser: &mut Parser<'_>) -> Result<JsonTypeHint> {
141 let path = parse_json2_path(parser)?;
142 ensure!(
143 path.first().is_none_or(|x| x != JSON2_REMAINDER_FIELD_NAME),
144 InvalidSqlSnafu {
145 msg: format!(
146 "JSON2 type hint path cannot be rooted at reserved field '{JSON2_REMAINDER_FIELD_NAME}'"
147 )
148 }
149 );
150 ensure!(
151 path.len() <= JSON2_MAX_STRUCTURED_DEPTH,
152 InvalidSqlSnafu {
153 msg: format!(
154 "JSON2 type hint path cannot exceed {JSON2_MAX_STRUCTURED_DEPTH} segments"
155 ),
156 }
157 );
158 let data_type = parser.parse_data_type().context(SyntaxSnafu)?;
159 let data_type = validate_json2_type_hint_type(data_type)?;
160
161 let mut inverted_index = false;
162 let mut skipping_index = None;
163 let mut fulltext_index = None;
164
165 loop {
166 if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL])
167 || parser.parse_keyword(Keyword::NULL)
168 {
169 return InvalidSqlSnafu {
170 msg: "JSON2 type hint NULL/NOT NULL is not supported; hinted fields are always nullable"
171 .to_string(),
172 }
173 .fail();
174 } else if parser.parse_keyword(Keyword::DEFAULT) {
175 return InvalidSqlSnafu {
176 msg: "JSON2 type hint DEFAULT is not supported".to_string(),
177 }
178 .fail();
179 } else if let Token::Word(word) = parser.peek_token().token
180 && word.value.eq_ignore_ascii_case(INVERTED)
181 {
182 parser.next_token();
183 ensure!(
184 parser.parse_keyword(Keyword::INDEX),
185 InvalidSqlSnafu {
186 msg: format!(
187 "expect INDEX after INVERTED keyword for JSON2 type hint '{}'",
188 path.join(".")
189 )
190 }
191 );
192 ensure!(
193 !inverted_index,
194 InvalidSqlSnafu {
195 msg: format!(
196 "duplicated INVERTED INDEX option for JSON2 type hint '{}'",
197 path.join(".")
198 )
199 }
200 );
201 ensure!(
202 !path
203 .iter()
204 .any(|segment| segment == JSON2_REMAINDER_FIELD_NAME),
205 InvalidSqlSnafu {
206 msg: format!(
207 "JSON2 indexed type hint path cannot contain reserved field '{JSON2_REMAINDER_FIELD_NAME}'"
208 )
209 }
210 );
211 inverted_index = true;
212 } else if let Token::Word(word) = parser.peek_token().token
213 && word.value.eq_ignore_ascii_case(SKIPPING)
214 {
215 parser.next_token();
216 ensure!(
217 parser.parse_keyword(Keyword::INDEX),
218 InvalidSqlSnafu {
219 msg: "expect INDEX after SKIPPING keyword",
220 }
221 );
222 ensure!(
223 skipping_index.is_none(),
224 InvalidSqlSnafu {
225 msg: "duplicated SKIPPING INDEX option for JSON2 type hint",
226 }
227 );
228 let options = parser
229 .parse_options(Keyword::WITH)
230 .context(SyntaxSnafu)?
231 .into_iter()
232 .map(crate::util::parse_option_string)
233 .collect::<Result<Vec<_>>>()?;
234 for (key, _) in &options {
235 ensure!(
236 crate::parsers::utils::validate_column_skipping_index_create_option(key),
237 InvalidSqlSnafu {
238 msg: format!("invalid SKIPPING INDEX option: {key}"),
239 }
240 );
241 }
242 skipping_index = Some(
243 datatypes::schema::SkippingIndexOptions::try_from(
244 OptionMap::new(options).into_map(),
245 )
246 .context(crate::error::SetSkippingIndexOptionSnafu)?,
247 );
248 } else if parser.parse_keyword(Keyword::FULLTEXT) {
249 ensure!(
250 matches!(data_type, DataType::String(None)),
251 InvalidSqlSnafu {
252 msg: "JSON2 FULLTEXT index only supports string type hints",
253 }
254 );
255 ensure!(
256 parser.parse_keyword(Keyword::INDEX),
257 InvalidSqlSnafu {
258 msg: "expect INDEX after FULLTEXT keyword",
259 }
260 );
261 ensure!(
262 fulltext_index.is_none(),
263 InvalidSqlSnafu {
264 msg: "duplicated FULLTEXT INDEX option for JSON2 type hint",
265 }
266 );
267 let options = parser
268 .parse_options(Keyword::WITH)
269 .context(SyntaxSnafu)?
270 .into_iter()
271 .map(crate::util::parse_option_string)
272 .collect::<Result<Vec<_>>>()?;
273 for (key, _) in &options {
274 ensure!(
275 crate::parsers::utils::validate_column_fulltext_create_option(key),
276 InvalidSqlSnafu {
277 msg: format!("invalid FULLTEXT INDEX option: {key}")
278 }
279 );
280 }
281 fulltext_index = Some(
282 FulltextOptions::try_from(OptionMap::new(options).into_map())
283 .context(crate::error::SetFulltextOptionSnafu)?,
284 );
285 } else if matches!(parser.peek_token().token, Token::Comma | Token::RParen) {
286 break;
287 } else {
288 return parser
289 .expected("JSON2 type hint option", parser.peek_token())
290 .context(SyntaxSnafu);
291 }
292 }
293
294 Ok(JsonTypeHint {
295 path,
296 data_type,
297 inverted_index,
298 skipping_index,
299 fulltext_index,
300 })
301}
302
303fn parse_json2_path(parser: &mut Parser<'_>) -> Result<Vec<String>> {
304 let first = parser.parse_identifier().context(SyntaxSnafu)?;
305 let mut path = vec![first.value];
306
307 while parser.consume_token(&Token::Period) {
308 let segment = parser.parse_identifier().context(SyntaxSnafu)?;
309 path.push(segment.value);
310 }
311
312 ensure!(
313 !path.iter().any(|segment| segment.is_empty()),
314 InvalidSqlSnafu {
315 msg: "JSON2 type hint path segment cannot be empty".to_string(),
316 }
317 );
318
319 Ok(path)
320}
321
322fn validate_json2_type_hint_type(data_type: DataType) -> Result<DataType> {
323 match data_type {
324 DataType::Int64
325 | DataType::UInt64
326 | DataType::Float64
327 | DataType::String(None)
328 | DataType::BigInt(None)
329 | DataType::BigIntUnsigned(None)
330 | DataType::Double(ExactNumberInfo::None)
331 | DataType::Boolean => Ok(data_type),
332 _ => InvalidSqlSnafu {
333 msg: format!(
334 "unsupported JSON2 type hint data type: {data_type}; supported types: STRING, BIGINT, BIGINT UNSIGNED, DOUBLE, BOOLEAN; supported aliases: INT64, UINT64, FLOAT64"
335 ),
336 }
337 .fail(),
338 }
339}
340
341fn ensure_no_path_conflict(hints: &[JsonTypeHint], path: &[String]) -> Result<()> {
342 for hint in hints {
343 ensure!(
344 hint.path != path,
345 InvalidSqlSnafu {
346 msg: format!("duplicated JSON2 type hint path '{}'", path.join("."))
347 }
348 );
349 ensure!(
350 !hint.path.starts_with(path) && !path.starts_with(&hint.path),
351 InvalidSqlSnafu {
352 msg: format!(
353 "JSON2 type hint path '{}' conflicts with '{}'",
354 path.join("."),
355 hint.path.join(".")
356 )
357 }
358 );
359 }
360 Ok(())
361}
362
363#[cfg(test)]
364mod tests {
365 use sqlparser::ast::{DataType, ExactNumberInfo};
366
367 use super::parse_json2_type_hint_path;
368 use crate::dialect::GreptimeDbDialect;
369 use crate::parser::{ParseOptions, ParserContext};
370 use crate::statements::create::Column;
371 use crate::statements::statement::Statement;
372
373 fn parse_json2_column(sql: &str) -> Column {
374 let Statement::CreateTable(mut create_table) =
375 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
376 .unwrap()
377 .remove(0)
378 else {
379 unreachable!()
380 };
381
382 create_table.columns.remove(0)
383 }
384
385 #[test]
386 fn test_json2_fulltext_index() {
387 use datatypes::schema::FulltextBackend;
388 for (options, backend) in [
389 ("", FulltextBackend::Bloom),
390 (
391 "WITH (backend='tantivy', analyzer='Chinese', case_sensitive='true')",
392 FulltextBackend::Tantivy,
393 ),
394 (
395 "WITH (backend='bloom', granularity='8', false_positive_rate='0.05')",
396 FulltextBackend::Bloom,
397 ),
398 ] {
399 let sql = format!(
400 "CREATE TABLE logs (attrs JSON2 (message STRING FULLTEXT INDEX {options} SKIPPING INDEX INVERTED INDEX), ts TIMESTAMP TIME INDEX)"
401 );
402 let column = parse_json2_column(&sql);
403 let options = column.extensions.json2_options.unwrap();
404 let settings = options.build_json_settings().unwrap();
405 let hint = &settings.type_hints()[0];
406 assert_eq!(hint.fulltext_index.as_ref().unwrap().backend, backend);
407 assert!(hint.fulltext_index.as_ref().unwrap().enable);
408 assert!(hint.inverted_index);
409 assert!(hint.skipping_index.is_some());
410 let rebuilt = parse_json2_column(&format!(
411 "CREATE TABLE logs (attrs JSON2 {options}, ts TIMESTAMP TIME INDEX)"
412 ));
413 assert_eq!(
414 settings,
415 rebuilt
416 .extensions
417 .json2_options
418 .unwrap()
419 .build_json_settings()
420 .unwrap()
421 );
422 }
423 for hint in [
424 "message BIGINT FULLTEXT INDEX",
425 "message STRING FULLTEXT INDEX FULLTEXT INDEX",
426 "message STRING FULLTEXT",
427 "message STRING FULLTEXT INDEX WITH (analyzer='unknown')",
428 "message STRING FULLTEXT INDEX WITH (backend='unknown')",
429 "message STRING FULLTEXT INDEX WITH (granularity='0')",
430 "message STRING FULLTEXT INDEX WITH (false_positive_rate='0')",
431 "message STRING FULLTEXT INDEX WITH (unknown='1')",
432 ] {
433 let sql = format!("CREATE TABLE logs (attrs JSON2 ({hint}), ts TIMESTAMP TIME INDEX)");
434 let parsed = ParserContext::create_with_dialect(
435 &sql,
436 &GreptimeDbDialect {},
437 ParseOptions::default(),
438 );
439 assert!(parsed.is_err(), "{sql}");
440 }
441 }
442
443 #[test]
444 fn test_json2_skipping_index() {
445 let column = parse_json2_column(
446 "CREATE TABLE logs (attrs JSON2 (service.name STRING SKIPPING INDEX WITH (granularity='8', false_positive_rate='0.05') INVERTED INDEX, status BIGINT SKIPPING INDEX), ts TIMESTAMP TIME INDEX)",
447 );
448 let options = column.extensions.json2_options.as_ref().unwrap();
449 let settings = options.build_json_settings().unwrap();
450 let hint = &settings.type_hints()[0];
451 assert!(hint.inverted_index);
452 assert_eq!(hint.skipping_index.as_ref().unwrap().granularity, 8);
453 assert_eq!(
454 hint.skipping_index.as_ref().unwrap().false_positive_rate(),
455 0.05
456 );
457 assert_eq!(
458 settings.type_hints()[1].skipping_index,
459 Some(datatypes::schema::SkippingIndexOptions::default())
460 );
461 let rebuilt = parse_json2_column(&format!(
462 "CREATE TABLE logs (attrs JSON2 {options}, ts TIMESTAMP TIME INDEX)"
463 ));
464 assert_eq!(
465 settings,
466 rebuilt
467 .extensions
468 .json2_options
469 .unwrap()
470 .build_json_settings()
471 .unwrap()
472 );
473 for option in [
474 "SKIPPING INDEX SKIPPING INDEX",
475 "SKIPPING",
476 "SKIPPING INDEX WITH (granularity='0')",
477 "SKIPPING INDEX WITH (false_positive_rate='0')",
478 "SKIPPING INDEX WITH (type='unknown')",
479 "SKIPPING INDEX WITH (unknown='1')",
480 ] {
481 let sql = format!(
482 "CREATE TABLE logs (attrs JSON2 (a STRING {option}), ts TIMESTAMP TIME INDEX)"
483 );
484 assert!(
485 ParserContext::create_with_dialect(
486 &sql,
487 &GreptimeDbDialect {},
488 ParseOptions::default()
489 )
490 .is_err(),
491 "{sql}"
492 );
493 }
494 }
495
496 #[test]
497 fn test_parse_json2_type_hint_path() {
498 assert_eq!(
499 parse_json2_type_hint_path(r#"attrs."http.status_code""#).unwrap(),
500 vec!["attrs", "http.status_code"]
501 );
502 assert!(parse_json2_type_hint_path("user.id trailing").is_err());
503 }
504
505 #[test]
506 fn test_parse_json2_type_hints() {
507 let column = parse_json2_column(
508 r#"
509CREATE TABLE traces (
510 log_json_data JSON2 (
511 "service.name" STRING INVERTED INDEX,
512 http.method STRING,
513 status_code BIGINT,
514 comment STRING,
515 ),
516 ts TIMESTAMP TIME INDEX,
517)"#,
518 );
519
520 assert!(matches!(
521 column.column_def.data_type,
522 DataType::Custom(_, _)
523 ));
524 let hints = column.extensions.json2_options.unwrap().type_hints;
525 assert_eq!(hints.len(), 4);
526
527 assert_eq!(hints[0].path, vec!["service.name"]);
528 assert_eq!(hints[0].data_type, DataType::String(None));
529 assert!(hints[0].inverted_index);
530
531 assert_eq!(hints[1].path, vec!["http", "method"]);
532 assert_eq!(hints[1].data_type, DataType::String(None));
533 assert!(!hints[1].inverted_index);
534
535 assert_eq!(hints[2].path, vec!["status_code"]);
536 assert_eq!(hints[2].data_type, DataType::BigInt(None));
537
538 assert_eq!(hints[3].path, vec!["comment"]);
539 assert_eq!(hints[3].data_type, DataType::String(None));
540 }
541
542 #[test]
543 fn test_parse_json2_max_auto_expanded_paths() {
544 let column = parse_json2_column(
545 r#"
546CREATE TABLE traces (
547 log_json_data JSON2 (
548 http.method STRING,
549 max_auto_expanded_paths = 0
550 ),
551 ts TIMESTAMP TIME INDEX,
552)"#,
553 );
554
555 let options = column.extensions.json2_options.unwrap();
556 assert_eq!(options.max_auto_expanded_paths, Some(0));
557 assert_eq!(options.type_hints.len(), 1);
558
559 let empty = parse_json2_column(
560 r#"
561CREATE TABLE traces (
562 log_json_data JSON2 (),
563 ts TIMESTAMP TIME INDEX,
564)"#,
565 );
566 assert!(empty.extensions.json2_options.is_none());
567
568 let quoted = parse_json2_column(
569 r#"
570CREATE TABLE traces (
571 log_json_data JSON2 (
572 "max_auto_expanded_paths" STRING,
573 nested."!__remainder__!" STRING
574 ),
575 ts TIMESTAMP TIME INDEX,
576)"#,
577 );
578 let options = quoted.extensions.json2_options.unwrap();
579 assert_eq!(options.max_auto_expanded_paths, None);
580 assert_eq!(options.type_hints.len(), 2);
581 }
582
583 #[test]
584 fn test_parse_json2_max_auto_expanded_paths_rejects_invalid_options() {
585 for options in [
586 "max_auto_expanded_paths = 0, max_auto_expanded_paths = 1",
587 "max_auto_expanded_paths = -1",
588 "max_auto_expanded_paths = 1.5",
589 "max_auto_expanded_paths = 4294967296",
590 r#""!__remainder__!".value STRING"#,
591 ] {
592 let sql = format!(
593 "CREATE TABLE traces (log_json_data JSON2 ({options}), ts TIMESTAMP TIME INDEX)"
594 );
595 assert!(
596 ParserContext::create_with_dialect(
597 &sql,
598 &GreptimeDbDialect {},
599 ParseOptions::default()
600 )
601 .is_err(),
602 "{options}"
603 );
604 }
605 }
606
607 #[test]
608 fn test_parse_json2_type_hint_defaults_to_nullable() {
609 let column = parse_json2_column(
610 r#"
611CREATE TABLE traces (
612 log_json_data JSON2 (http.method STRING),
613 ts TIMESTAMP TIME INDEX,
614)"#,
615 );
616
617 let hints = column.extensions.json2_options.unwrap().type_hints;
618 assert_eq!(hints.len(), 1);
619 assert_eq!(hints[0].data_type, DataType::String(None));
620 }
621
622 #[test]
623 fn test_parse_json2_type_hint_quoted_path_segments() {
624 let column = parse_json2_column(
625 r#"
626CREATE TABLE traces (
627 log_json_data JSON2 (
628 "a".b STRING,
629 "x"."y" STRING,
630 "a.b"."c" STRING,
631 a."b.c" STRING
632 ),
633 ts TIMESTAMP TIME INDEX,
634)"#,
635 );
636
637 let hints = column.extensions.json2_options.unwrap().type_hints;
638 assert_eq!(hints.len(), 4);
639 assert_eq!(hints[0].path, vec!["a", "b"]);
640 assert_eq!(hints[1].path, vec!["x", "y"]);
641 assert_eq!(hints[2].path, vec!["a.b", "c"]);
642 assert_eq!(hints[3].path, vec!["a", "b.c"]);
643 }
644
645 #[test]
646 fn test_parse_json2_type_hint_supported_types() {
647 for (sql_type, expected) in [
648 ("STRING", DataType::String(None)),
649 ("BIGINT", DataType::BigInt(None)),
650 ("BIGINT UNSIGNED", DataType::BigIntUnsigned(None)),
651 ("DOUBLE", DataType::Double(ExactNumberInfo::None)),
652 ("BOOLEAN", DataType::Boolean),
653 ("INT64", DataType::Int64),
654 ("UINT64", DataType::UInt64),
655 ("FLOAT64", DataType::Float64),
656 ] {
657 for sql_type in [sql_type.to_string(), sql_type.to_lowercase()] {
658 let column = parse_json2_column(&format!(
659 "CREATE TABLE traces (j JSON2 (value {sql_type}), ts TIMESTAMP TIME INDEX)"
660 ));
661 let options = column.extensions.json2_options.unwrap();
662 let settings = options.build_json_settings().unwrap();
663 assert_eq!(settings.type_hints().len(), 1);
664 let hints = options.type_hints;
665 assert_eq!(hints[0].data_type, expected);
666 }
667 }
668 }
669
670 #[test]
671 fn test_parse_json2_type_hint_rejects_unsupported_types() {
672 for sql_type in [
673 "INT2",
674 "INT4",
675 "INT8",
676 "INT16",
677 "INT32",
678 "TINYINT",
679 "SMALLINT",
680 "INT",
681 "INTEGER",
682 "UINT8",
683 "UINT16",
684 "UINT32",
685 "TINYINT UNSIGNED",
686 "SMALLINT UNSIGNED",
687 "INT UNSIGNED",
688 "FLOAT",
689 "REAL",
690 "FLOAT4",
691 "FLOAT8",
692 "FLOAT32",
693 "BOOL",
694 "TEXT",
695 "VARCHAR(10)",
696 "CHAR(10)",
697 "TIMESTAMP",
698 "DECIMAL(10, 2)",
699 "STRING(10)",
700 "BIGINT(10)",
701 ] {
702 for sql in [
703 format!(
704 "CREATE TABLE traces (j JSON2 (value {sql_type}), ts TIMESTAMP TIME INDEX)"
705 ),
706 format!("ALTER TABLE traces MODIFY COLUMN j JSON2 (value {sql_type})"),
707 ] {
708 let err = ParserContext::create_with_dialect(
709 &sql,
710 &GreptimeDbDialect {},
711 ParseOptions::default(),
712 )
713 .unwrap_err();
714 assert!(
715 err.to_string().contains(
716 "supported types: STRING, BIGINT, BIGINT UNSIGNED, DOUBLE, BOOLEAN; supported aliases: INT64, UINT64, FLOAT64"
717 ),
718 "{sql}: {err}"
719 );
720 }
721 }
722 }
723
724 #[test]
725 fn test_parse_json2_type_hint_rejects_default() {
726 for default in ["-5", "abs(-1)"] {
727 let sql = format!(
728 "CREATE TABLE traces (log_json_data JSON2 (status_code BIGINT DEFAULT {default}), ts TIMESTAMP TIME INDEX)"
729 );
730 let err = ParserContext::create_with_dialect(
731 &sql,
732 &GreptimeDbDialect {},
733 ParseOptions::default(),
734 )
735 .unwrap_err();
736 assert!(err.to_string().contains("DEFAULT is not supported"));
737 }
738 }
739
740 #[test]
741 fn test_parse_json2_type_hint_rejects_duplicate_path() {
742 let result = ParserContext::create_with_dialect(
743 r#"
744CREATE TABLE traces (
745 log_json_data JSON2 (a.b STRING, a.b BIGINT),
746 ts TIMESTAMP TIME INDEX,
747)"#,
748 &GreptimeDbDialect {},
749 ParseOptions::default(),
750 );
751
752 assert!(result.is_err());
753 assert!(result.unwrap_err().to_string().contains("duplicated"));
754 }
755
756 #[test]
757 fn test_parse_json2_type_hint_rejects_parent_child_path() {
758 let result = ParserContext::create_with_dialect(
759 r#"
760CREATE TABLE traces (
761 log_json_data JSON2 (a STRING, a.b BIGINT),
762 ts TIMESTAMP TIME INDEX,
763)"#,
764 &GreptimeDbDialect {},
765 ParseOptions::default(),
766 );
767
768 assert!(result.is_err());
769 assert!(result.unwrap_err().to_string().contains("conflicts"));
770 }
771
772 #[test]
773 fn test_parse_json2_type_hint_rejects_nullability() {
774 for sql in [
775 r#"
776CREATE TABLE traces (
777 log_json_data JSON2 (a STRING NULL NULL),
778 ts TIMESTAMP TIME INDEX,
779)"#,
780 r#"
781CREATE TABLE traces (
782 log_json_data JSON2 (a STRING NOT NULL NOT NULL),
783 ts TIMESTAMP TIME INDEX,
784)"#,
785 r#"
786CREATE TABLE traces (
787 log_json_data JSON2 (a STRING NOT NULL NULL),
788 ts TIMESTAMP TIME INDEX,
789)"#,
790 r#"
791CREATE TABLE traces (
792 log_json_data JSON2 (a STRING NULL NOT NULL),
793 ts TIMESTAMP TIME INDEX,
794)"#,
795 ] {
796 let result = ParserContext::create_with_dialect(
797 sql,
798 &GreptimeDbDialect {},
799 ParseOptions::default(),
800 );
801
802 assert!(result.is_err());
803 assert!(
804 result
805 .unwrap_err()
806 .to_string()
807 .contains("NULL/NOT NULL is not supported")
808 );
809 }
810 }
811}