1use datatypes::extension::json::JSON2_REMAINDER_FIELD_NAME;
16use datatypes::json::JSON2_MAX_STRUCTURED_DEPTH;
17use snafu::{ResultExt, ensure};
18use sqlparser::ast::{DataType, ExactNumberInfo, ObjectName};
19use sqlparser::dialect::keywords::Keyword;
20use sqlparser::parser::Parser;
21use sqlparser::tokenizer::Token;
22
23use crate::ast::Ident;
24use crate::dialect::GreptimeDbDialect;
25use crate::error::{InvalidSqlSnafu, Result, SyntaxSnafu};
26use crate::parsers::create_parser::{INVERTED, SKIPPING};
27use crate::statements::create::{Json2Options, JsonTypeHint};
28use crate::statements::transform::type_alias::get_type_by_alias;
29
30const JSON2_TYPE_NAME: &str = "JSON2";
31const MAX_AUTO_EXPANDED_PATHS: &str = "max_auto_expanded_paths";
32
33pub fn parse_json2_type_hint_path(path: &str) -> Result<Vec<String>> {
35 let dialect = GreptimeDbDialect {};
36 let mut parser = Parser::new(&dialect)
37 .try_with_sql(path)
38 .context(SyntaxSnafu)?;
39 let path = parse_json2_path(&mut parser)?;
40 ensure!(
41 parser.peek_token().token == Token::EOF,
42 InvalidSqlSnafu {
43 msg: format!(
44 "unexpected token '{}' in JSON2 type hint path",
45 parser.peek_token()
46 )
47 }
48 );
49 Ok(path)
50}
51
52pub(crate) fn parse_json2_type_and_options(
53 parser: &mut Parser<'_>,
54) -> Result<Option<(DataType, Option<Json2Options>)>> {
55 let token = parser.peek_token();
56 let Token::Word(word) = &token.token else {
57 return Ok(None);
58 };
59
60 if !word.value.eq_ignore_ascii_case(JSON2_TYPE_NAME) || word.quote_style.is_some() {
61 return Ok(None);
62 }
63
64 parser.next_token();
65 let data_type = DataType::Custom(ObjectName::from(vec![Ident::new(JSON2_TYPE_NAME)]), vec![]);
66 let options = if parser.consume_token(&Token::LParen) {
67 parse_json2_options(parser)?
68 } else {
69 None
70 };
71
72 Ok(Some((data_type, options)))
73}
74
75fn parse_json2_options(parser: &mut Parser<'_>) -> Result<Option<Json2Options>> {
76 if parser.consume_token(&Token::RParen) {
77 return Ok(None);
78 }
79
80 let mut max_auto_expanded_paths = None;
81 let mut type_hints = Vec::new();
82 loop {
83 let token = parser.peek_token();
84 let is_max_auto_expanded_paths = matches!(
85 &token.token,
86 Token::Word(word)
87 if word.quote_style.is_none()
88 && word.value.eq_ignore_ascii_case(MAX_AUTO_EXPANDED_PATHS)
89 );
90 if is_max_auto_expanded_paths {
91 parser.next_token();
92 ensure!(
93 max_auto_expanded_paths.is_none(),
94 InvalidSqlSnafu {
95 msg: format!("duplicated JSON2 option '{MAX_AUTO_EXPANDED_PATHS}'")
96 }
97 );
98 parser.expect_token(&Token::Eq).context(SyntaxSnafu)?;
99
100 let token = parser.next_token();
101 let Token::Number(value, _) = token.token else {
102 return InvalidSqlSnafu {
103 msg: format!(
104 "JSON2 option '{MAX_AUTO_EXPANDED_PATHS}' expects a non-negative integer"
105 ),
106 }
107 .fail();
108 };
109 max_auto_expanded_paths = Some(value.parse::<u32>().map_err(|_| {
110 InvalidSqlSnafu {
111 msg: format!(
112 "JSON2 option '{MAX_AUTO_EXPANDED_PATHS}' expects a non-negative integer"
113 ),
114 }
115 .build()
116 })?);
117 } else {
118 let hint = parse_json2_type_hint(parser)?;
119 ensure_no_path_conflict(&type_hints, &hint.path)?;
120 type_hints.push(hint);
121 }
122
123 if parser.consume_token(&Token::Comma) {
124 if parser.consume_token(&Token::RParen) {
125 break;
126 }
127 } else {
128 parser.expect_token(&Token::RParen).context(SyntaxSnafu)?;
129 break;
130 }
131 }
132
133 Ok(Some(Json2Options {
134 max_auto_expanded_paths,
135 type_hints,
136 }))
137}
138
139fn parse_json2_type_hint(parser: &mut Parser<'_>) -> Result<JsonTypeHint> {
140 let path = parse_json2_path(parser)?;
141 ensure!(
142 path.first().is_none_or(|x| x != JSON2_REMAINDER_FIELD_NAME),
143 InvalidSqlSnafu {
144 msg: format!(
145 "JSON2 type hint path cannot be rooted at reserved field '{JSON2_REMAINDER_FIELD_NAME}'"
146 )
147 }
148 );
149 ensure!(
150 path.len() <= JSON2_MAX_STRUCTURED_DEPTH,
151 InvalidSqlSnafu {
152 msg: format!(
153 "JSON2 type hint path cannot exceed {JSON2_MAX_STRUCTURED_DEPTH} segments"
154 ),
155 }
156 );
157 let data_type = parser.parse_data_type().context(SyntaxSnafu)?;
158 let data_type = normalize_json2_type_hint_type(data_type)?;
159
160 let mut inverted_index = false;
161
162 loop {
163 if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL])
164 || parser.parse_keyword(Keyword::NULL)
165 {
166 return InvalidSqlSnafu {
167 msg: "JSON2 type hint NULL/NOT NULL is not supported; hinted fields are always nullable"
168 .to_string(),
169 }
170 .fail();
171 } else if parser.parse_keyword(Keyword::DEFAULT) {
172 return InvalidSqlSnafu {
173 msg: "JSON2 type hint DEFAULT is not supported".to_string(),
174 }
175 .fail();
176 } else if let Token::Word(word) = parser.peek_token().token
177 && word.value.eq_ignore_ascii_case(INVERTED)
178 {
179 parser.next_token();
180 ensure!(
181 parser.parse_keyword(Keyword::INDEX),
182 InvalidSqlSnafu {
183 msg: format!(
184 "expect INDEX after INVERTED keyword for JSON2 type hint '{}'",
185 path.join(".")
186 )
187 }
188 );
189 ensure!(
190 !inverted_index,
191 InvalidSqlSnafu {
192 msg: format!(
193 "duplicated INVERTED INDEX option for JSON2 type hint '{}'",
194 path.join(".")
195 )
196 }
197 );
198 inverted_index = true;
199 } else if let Token::Word(word) = parser.peek_token().token
200 && word.value.eq_ignore_ascii_case(SKIPPING)
201 {
202 return InvalidSqlSnafu {
203 msg: "JSON2 type hint SKIPPING INDEX is not supported yet".to_string(),
204 }
205 .fail();
206 } else if matches!(parser.peek_token().token, Token::Comma | Token::RParen) {
207 break;
208 } else {
209 return parser
210 .expected("JSON2 type hint option", parser.peek_token())
211 .context(SyntaxSnafu);
212 }
213 }
214
215 Ok(JsonTypeHint {
216 path,
217 data_type,
218 inverted_index,
219 })
220}
221
222fn parse_json2_path(parser: &mut Parser<'_>) -> Result<Vec<String>> {
223 let first = parser.parse_identifier().context(SyntaxSnafu)?;
224 let mut path = vec![first.value];
225
226 while parser.consume_token(&Token::Period) {
227 let segment = parser.parse_identifier().context(SyntaxSnafu)?;
228 path.push(segment.value);
229 }
230
231 ensure!(
232 !path.iter().any(|segment| segment.is_empty()),
233 InvalidSqlSnafu {
234 msg: "JSON2 type hint path segment cannot be empty".to_string(),
235 }
236 );
237
238 Ok(path)
239}
240
241fn normalize_json2_type_hint_type(data_type: DataType) -> Result<DataType> {
242 let data_type = get_type_by_alias(&data_type).unwrap_or(data_type);
243 let normalized = match data_type {
244 DataType::String(_) | DataType::Text | DataType::Varchar(_) | DataType::Char(_) => {
245 DataType::String(None)
246 }
247 DataType::TinyInt(_)
248 | DataType::SmallInt(_)
249 | DataType::Int(_)
250 | DataType::Integer(_)
251 | DataType::BigInt(_) => DataType::BigInt(None),
252 DataType::TinyIntUnsigned(_)
253 | DataType::SmallIntUnsigned(_)
254 | DataType::IntUnsigned(_)
255 | DataType::UnsignedInteger
256 | DataType::BigIntUnsigned(_) => DataType::BigIntUnsigned(None),
257 DataType::Float(_) | DataType::Real | DataType::Double(_) => {
258 DataType::Double(ExactNumberInfo::None)
259 }
260 DataType::Boolean => DataType::Boolean,
261 _ => {
262 return InvalidSqlSnafu {
263 msg: format!("unsupported JSON2 type hint data type: {data_type}"),
264 }
265 .fail();
266 }
267 };
268
269 Ok(normalized)
270}
271
272fn ensure_no_path_conflict(hints: &[JsonTypeHint], path: &[String]) -> Result<()> {
273 for hint in hints {
274 ensure!(
275 hint.path != path,
276 InvalidSqlSnafu {
277 msg: format!("duplicated JSON2 type hint path '{}'", path.join("."))
278 }
279 );
280 ensure!(
281 !hint.path.starts_with(path) && !path.starts_with(&hint.path),
282 InvalidSqlSnafu {
283 msg: format!(
284 "JSON2 type hint path '{}' conflicts with '{}'",
285 path.join("."),
286 hint.path.join(".")
287 )
288 }
289 );
290 }
291 Ok(())
292}
293
294#[cfg(test)]
295mod tests {
296 use sqlparser::ast::{DataType, ExactNumberInfo};
297
298 use super::parse_json2_type_hint_path;
299 use crate::dialect::GreptimeDbDialect;
300 use crate::parser::{ParseOptions, ParserContext};
301 use crate::statements::create::Column;
302 use crate::statements::statement::Statement;
303
304 fn parse_json2_column(sql: &str) -> Column {
305 let Statement::CreateTable(mut create_table) =
306 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
307 .unwrap()
308 .remove(0)
309 else {
310 unreachable!()
311 };
312
313 create_table.columns.remove(0)
314 }
315
316 #[test]
317 fn test_parse_json2_type_hint_path() {
318 assert_eq!(
319 parse_json2_type_hint_path(r#"attrs."http.status_code""#).unwrap(),
320 vec!["attrs", "http.status_code"]
321 );
322 assert!(parse_json2_type_hint_path("user.id trailing").is_err());
323 }
324
325 #[test]
326 fn test_parse_json2_type_hints() {
327 let column = parse_json2_column(
328 r#"
329CREATE TABLE traces (
330 log_json_data JSON2 (
331 "service.name" STRING INVERTED INDEX,
332 http.method STRING,
333 status_code INT64,
334 comment STRING,
335 ),
336 ts TIMESTAMP TIME INDEX,
337)"#,
338 );
339
340 assert!(matches!(
341 column.column_def.data_type,
342 DataType::Custom(_, _)
343 ));
344 let hints = column.extensions.json2_options.unwrap().type_hints;
345 assert_eq!(hints.len(), 4);
346
347 assert_eq!(hints[0].path, vec!["service.name"]);
348 assert_eq!(hints[0].data_type, DataType::String(None));
349 assert!(hints[0].inverted_index);
350
351 assert_eq!(hints[1].path, vec!["http", "method"]);
352 assert_eq!(hints[1].data_type, DataType::String(None));
353 assert!(!hints[1].inverted_index);
354
355 assert_eq!(hints[2].path, vec!["status_code"]);
356 assert_eq!(hints[2].data_type, DataType::BigInt(None));
357
358 assert_eq!(hints[3].path, vec!["comment"]);
359 assert_eq!(hints[3].data_type, DataType::String(None));
360 }
361
362 #[test]
363 fn test_parse_json2_max_auto_expanded_paths() {
364 let column = parse_json2_column(
365 r#"
366CREATE TABLE traces (
367 log_json_data JSON2 (
368 http.method STRING,
369 max_auto_expanded_paths = 0
370 ),
371 ts TIMESTAMP TIME INDEX,
372)"#,
373 );
374
375 let options = column.extensions.json2_options.unwrap();
376 assert_eq!(options.max_auto_expanded_paths, Some(0));
377 assert_eq!(options.type_hints.len(), 1);
378
379 let empty = parse_json2_column(
380 r#"
381CREATE TABLE traces (
382 log_json_data JSON2 (),
383 ts TIMESTAMP TIME INDEX,
384)"#,
385 );
386 assert!(empty.extensions.json2_options.is_none());
387
388 let quoted = parse_json2_column(
389 r#"
390CREATE TABLE traces (
391 log_json_data JSON2 (
392 "max_auto_expanded_paths" STRING,
393 nested."!__remainder__!" STRING
394 ),
395 ts TIMESTAMP TIME INDEX,
396)"#,
397 );
398 let options = quoted.extensions.json2_options.unwrap();
399 assert_eq!(options.max_auto_expanded_paths, None);
400 assert_eq!(options.type_hints.len(), 2);
401 }
402
403 #[test]
404 fn test_parse_json2_max_auto_expanded_paths_rejects_invalid_options() {
405 for options in [
406 "max_auto_expanded_paths = 0, max_auto_expanded_paths = 1",
407 "max_auto_expanded_paths = -1",
408 "max_auto_expanded_paths = 1.5",
409 "max_auto_expanded_paths = 4294967296",
410 r#""!__remainder__!".value STRING"#,
411 ] {
412 let sql = format!(
413 "CREATE TABLE traces (log_json_data JSON2 ({options}), ts TIMESTAMP TIME INDEX)"
414 );
415 assert!(
416 ParserContext::create_with_dialect(
417 &sql,
418 &GreptimeDbDialect {},
419 ParseOptions::default()
420 )
421 .is_err(),
422 "{options}"
423 );
424 }
425 }
426
427 #[test]
428 fn test_parse_json2_type_hint_defaults_to_nullable() {
429 let column = parse_json2_column(
430 r#"
431CREATE TABLE traces (
432 log_json_data JSON2 (http.method STRING),
433 ts TIMESTAMP TIME INDEX,
434)"#,
435 );
436
437 let hints = column.extensions.json2_options.unwrap().type_hints;
438 assert_eq!(hints.len(), 1);
439 assert_eq!(hints[0].data_type, DataType::String(None));
440 }
441
442 #[test]
443 fn test_parse_json2_type_hint_quoted_path_segments() {
444 let column = parse_json2_column(
445 r#"
446CREATE TABLE traces (
447 log_json_data JSON2 (
448 "a".b STRING,
449 "x"."y" STRING,
450 "a.b"."c" STRING,
451 a."b.c" STRING
452 ),
453 ts TIMESTAMP TIME INDEX,
454)"#,
455 );
456
457 let hints = column.extensions.json2_options.unwrap().type_hints;
458 assert_eq!(hints.len(), 4);
459 assert_eq!(hints[0].path, vec!["a", "b"]);
460 assert_eq!(hints[1].path, vec!["x", "y"]);
461 assert_eq!(hints[2].path, vec!["a.b", "c"]);
462 assert_eq!(hints[3].path, vec!["a", "b.c"]);
463 }
464
465 #[test]
466 fn test_parse_json2_type_hint_normalizes_numeric_types() {
467 let column = parse_json2_column(
468 r#"
469CREATE TABLE traces (
470 log_json_data JSON2 (
471 tinyint_value TINYINT,
472 smallint_value SMALLINT,
473 int_value INT,
474 integer_value INTEGER,
475 bigint_value BIGINT,
476 int64_value INT64,
477 tinyuint_value TINYINT UNSIGNED,
478 smalluint_value SMALLINT UNSIGNED,
479 uint_value INT UNSIGNED,
480 uint64_value UINT64,
481 float_value FLOAT,
482 real_value REAL,
483 double_value DOUBLE,
484 float64_value FLOAT64
485 ),
486 ts TIMESTAMP TIME INDEX,
487)"#,
488 );
489
490 let hints = column.extensions.json2_options.unwrap().type_hints;
491 assert_eq!(hints.len(), 14);
492 for hint in hints.iter().take(6) {
493 assert_eq!(hint.data_type, DataType::BigInt(None));
494 }
495 for hint in hints.iter().skip(6).take(4) {
496 assert_eq!(hint.data_type, DataType::BigIntUnsigned(None));
497 }
498 for hint in hints.iter().skip(10) {
499 assert_eq!(hint.data_type, DataType::Double(ExactNumberInfo::None));
500 }
501 }
502
503 #[test]
504 fn test_parse_json2_type_hint_rejects_default() {
505 for default in ["-5", "abs(-1)"] {
506 let sql = format!(
507 "CREATE TABLE traces (log_json_data JSON2 (status_code INT64 DEFAULT {default}), ts TIMESTAMP TIME INDEX)"
508 );
509 let err = ParserContext::create_with_dialect(
510 &sql,
511 &GreptimeDbDialect {},
512 ParseOptions::default(),
513 )
514 .unwrap_err();
515 assert!(err.to_string().contains("DEFAULT is not supported"));
516 }
517 }
518
519 #[test]
520 fn test_parse_json2_type_hint_rejects_duplicate_path() {
521 let result = ParserContext::create_with_dialect(
522 r#"
523CREATE TABLE traces (
524 log_json_data JSON2 (a.b STRING, a.b INT64),
525 ts TIMESTAMP TIME INDEX,
526)"#,
527 &GreptimeDbDialect {},
528 ParseOptions::default(),
529 );
530
531 assert!(result.is_err());
532 assert!(result.unwrap_err().to_string().contains("duplicated"));
533 }
534
535 #[test]
536 fn test_parse_json2_type_hint_rejects_parent_child_path() {
537 let result = ParserContext::create_with_dialect(
538 r#"
539CREATE TABLE traces (
540 log_json_data JSON2 (a STRING, a.b INT64),
541 ts TIMESTAMP TIME INDEX,
542)"#,
543 &GreptimeDbDialect {},
544 ParseOptions::default(),
545 );
546
547 assert!(result.is_err());
548 assert!(result.unwrap_err().to_string().contains("conflicts"));
549 }
550
551 #[test]
552 fn test_parse_json2_type_hint_rejects_nullability() {
553 for sql in [
554 r#"
555CREATE TABLE traces (
556 log_json_data JSON2 (a STRING NULL NULL),
557 ts TIMESTAMP TIME INDEX,
558)"#,
559 r#"
560CREATE TABLE traces (
561 log_json_data JSON2 (a STRING NOT NULL NOT NULL),
562 ts TIMESTAMP TIME INDEX,
563)"#,
564 r#"
565CREATE TABLE traces (
566 log_json_data JSON2 (a STRING NOT NULL NULL),
567 ts TIMESTAMP TIME INDEX,
568)"#,
569 r#"
570CREATE TABLE traces (
571 log_json_data JSON2 (a STRING NULL NOT NULL),
572 ts TIMESTAMP TIME INDEX,
573)"#,
574 ] {
575 let result = ParserContext::create_with_dialect(
576 sql,
577 &GreptimeDbDialect {},
578 ParseOptions::default(),
579 );
580
581 assert!(result.is_err());
582 assert!(
583 result
584 .unwrap_err()
585 .to_string()
586 .contains("NULL/NOT NULL is not supported")
587 );
588 }
589 }
590}