sql/parsers/create_parser/
json.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use snafu::ResultExt;
16use sqlparser::parser::Parser;
17use sqlparser::tokenizer::Token;
18
19use crate::error::{Result, SyntaxSnafu};
20use crate::statements::OptionMap;
21use crate::util;
22
23pub(super) fn parse_json_datatype_options(parser: &mut Parser<'_>) -> Result<Option<OptionMap>> {
24    if parser.consume_token(&Token::LParen) {
25        let result = parser
26            .parse_comma_separated0(Parser::parse_sql_option, Token::RParen)
27            .context(SyntaxSnafu)
28            .and_then(|options| {
29                options
30                    .into_iter()
31                    .map(util::parse_option_string)
32                    .collect::<Result<Vec<_>>>()
33            })?;
34        parser.expect_token(&Token::RParen).context(SyntaxSnafu)?;
35        Ok(Some(OptionMap::new(result)))
36    } else {
37        Ok(None)
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use sqlparser::ast::{DataType, Expr, Ident, StructField};
44
45    use crate::dialect::GreptimeDbDialect;
46    use crate::parser::{ParseOptions, ParserContext};
47    use crate::statements::OptionMap;
48    use crate::statements::create::{
49        Column, JSON_FORMAT_FULL_STRUCTURED, JSON_FORMAT_PARTIAL, JSON_FORMAT_RAW, JSON_OPT_FIELDS,
50        JSON_OPT_FORMAT, JSON_OPT_UNSTRUCTURED_KEYS,
51    };
52    use crate::statements::statement::Statement;
53    use crate::util::OptionValue;
54
55    #[test]
56    fn test_parse_json_datatype_options() {
57        fn parse(sql: &str) -> Option<OptionMap> {
58            let Statement::CreateTable(mut create_table) = ParserContext::create_with_dialect(
59                sql,
60                &GreptimeDbDialect {},
61                ParseOptions::default(),
62            )
63            .unwrap()
64            .remove(0) else {
65                unreachable!()
66            };
67
68            let Column {
69                column_def,
70                extensions,
71            } = create_table.columns.remove(0);
72            assert_eq!(column_def.name.to_string(), "my_json");
73            assert_eq!(column_def.data_type, DataType::JSON);
74            assert!(column_def.options.is_empty());
75
76            extensions.json_datatype_options
77        }
78
79        let sql = r#"
80CREATE TABLE json_data (
81    my_json JSON(format = "partial", fields = Struct<i Int, "o.a" String, "o.b" String, `x.y.z` Float64>),
82    ts TIMESTAMP TIME INDEX,
83)"#;
84        let options = parse(sql).unwrap();
85        assert_eq!(options.len(), 2);
86        let option = options.value(JSON_OPT_FIELDS);
87        let expected = OptionValue::try_new(Expr::Struct {
88            values: vec![],
89            fields: vec![
90                StructField {
91                    field_name: Some(Ident::new("i")),
92                    field_type: DataType::Int(None),
93                    options: None,
94                },
95                StructField {
96                    field_name: Some(Ident::with_quote('"', "o.a")),
97                    field_type: DataType::String(None),
98                    options: None,
99                },
100                StructField {
101                    field_name: Some(Ident::with_quote('"', "o.b")),
102                    field_type: DataType::String(None),
103                    options: None,
104                },
105                StructField {
106                    field_name: Some(Ident::with_quote('`', "x.y.z")),
107                    field_type: DataType::Float64,
108                    options: None,
109                },
110            ],
111        })
112        .ok();
113        assert_eq!(option, expected.as_ref());
114
115        let sql = r#"
116CREATE TABLE json_data (
117    my_json JSON(format = "partial", unstructured_keys = ["k", "foo.bar", "a.b.c"]),
118    ts TIMESTAMP TIME INDEX,
119)"#;
120        let options = parse(sql).unwrap();
121        assert_eq!(options.len(), 2);
122        assert_eq!(
123            options.value(JSON_OPT_FORMAT).and_then(|x| x.as_string()),
124            Some(JSON_FORMAT_PARTIAL)
125        );
126        let expected = vec!["k", "foo.bar", "a.b.c"];
127        assert_eq!(
128            options
129                .value(JSON_OPT_UNSTRUCTURED_KEYS)
130                .and_then(|x| x.as_list()),
131            Some(expected)
132        );
133
134        let sql = r#"
135CREATE TABLE json_data (
136    my_json JSON(format = "structured"),
137    ts TIMESTAMP TIME INDEX,
138)"#;
139        let options = parse(sql).unwrap();
140        assert_eq!(options.len(), 1);
141        assert_eq!(
142            options.value(JSON_OPT_FORMAT).and_then(|x| x.as_string()),
143            Some(JSON_FORMAT_FULL_STRUCTURED)
144        );
145
146        let sql = r#"
147CREATE TABLE json_data (
148    my_json JSON(format = "raw"),
149    ts TIMESTAMP TIME INDEX,
150)"#;
151        let options = parse(sql).unwrap();
152        assert_eq!(options.len(), 1);
153        assert_eq!(
154            options.value(JSON_OPT_FORMAT).and_then(|x| x.as_string()),
155            Some(JSON_FORMAT_RAW)
156        );
157
158        let sql = r#"
159CREATE TABLE json_data (
160    my_json JSON(),
161    ts TIMESTAMP TIME INDEX,
162)"#;
163        let options = parse(sql).unwrap();
164        assert!(options.is_empty());
165
166        let sql = r#"
167CREATE TABLE json_data (
168    my_json JSON,
169    ts TIMESTAMP TIME INDEX,
170)"#;
171        let options = parse(sql);
172        assert!(options.is_none());
173    }
174}