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