clickhouse_sql_parser/
create.rs

1// vim: set expandtab ts=4 sw=4:
2use std::str;
3use std::str::FromStr;
4use std::fmt; 
5
6use nom::{
7    IResult,
8    error::{ ErrorKind, ParseError},
9    branch::alt,
10    sequence::{delimited, preceded, tuple},
11    combinator::{map, opt, recognize},
12    character::complete::{digit1, multispace0, multispace1, one_of, },
13    bytes::complete::{tag, tag_no_case, take_until, },
14    multi::{many0, separated_list,},
15};
16
17use crate::{
18    sql_identifier,
19    ws_sep_comma,
20    column_identifier_no_alias,
21    SqlTypeOpts,
22    type_identifier,
23    ttl_expression,
24    statement_terminator,
25    schema_table_reference,
26    sql_expression,
27};
28use crate::column::{
29    ColumnSpecification,
30    ColumnOption,
31    Column,
32};
33use crate::table::Table;
34
35#[derive(Clone, Debug, Eq, PartialEq, Hash)]
36pub struct CreateTableStatement {
37    pub table: Table,
38    pub fields: Vec<ColumnSpecification>,
39    //pub indexes: Vec<..>,
40    pub engine: Engine,
41}
42
43impl fmt::Display for CreateTableStatement {
44    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45        write!(
46            f,
47            "CREATE TABLE {} (\n",
48            self.table
49        )?;
50        write!(f, "  {}",
51            self.fields
52                .iter()
53                .map(|c| format!("{}", c)) 
54                .collect::<Vec<String>>()
55                .join(",\n  ")
56        )?;
57        write!(f,"\n) {};", self.engine)
58    }
59}
60
61
62#[derive(Debug, PartialEq)]
63pub enum CodecError<I> {
64  Nom(I, ErrorKind),
65}
66
67impl<I> ParseError<I> for CodecError<I> {
68  fn from_error_kind(input: I, kind: ErrorKind) -> Self {
69    CodecError::Nom(input, kind)
70  }
71
72  fn append(_: I, _: ErrorKind, other: Self) -> Self {
73    other
74  }
75}
76
77#[derive(Clone, Debug, Eq, PartialEq, Hash)]
78pub enum CodecDeltaLevel {
79    L1,
80    L2,
81    L4,
82    L8,
83}
84impl From<char> for CodecDeltaLevel {
85    fn from(t: char) -> CodecDeltaLevel {
86        match t {
87            '1' => CodecDeltaLevel::L1,
88            '2' => CodecDeltaLevel::L2,
89            '4' => CodecDeltaLevel::L4,
90            '8' => CodecDeltaLevel::L8,
91            l => panic!("Unsupported level '{}' for codec delta", l),
92        }
93    }
94}
95impl fmt::Display for CodecDeltaLevel {
96    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97        match *self {
98            CodecDeltaLevel::L1 => write!(f, "1"),
99            CodecDeltaLevel::L2 => write!(f, "2"),
100            CodecDeltaLevel::L4 => write!(f, "4"),
101            CodecDeltaLevel::L8 => write!(f, "8"),
102        }
103    }
104}
105
106
107#[derive(Clone, Debug, Eq, PartialEq, Hash)]
108pub enum Codec {
109    None,
110    ZSTD(Option<u8>), // from 1 to 22
111    LZ4,
112    LZ4HC(Option<u8>), // from 1 to 12
113    Delta(Option<CodecDeltaLevel>), // 1, 2, 4, 8
114    DoubleDelta,
115    Gorilla,
116    T64,
117}
118impl fmt::Display for Codec {
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        match self {
121            Codec::None => write!(f, "NONE"),
122            Codec::LZ4 => write!(f, "LZ4"),
123            Codec::DoubleDelta => write!(f, "DoubleDelta"),
124            Codec::Gorilla => write!(f, "Gorilla"),
125            Codec::T64 => write!(f, "T64"),
126            Codec::ZSTD(None) => write!(f, "ZSTD"),
127            Codec::ZSTD(Some(l)) => write!(f, "ZSTD({})", l),
128            Codec::LZ4HC(None) => write!(f, "LZ4HC"),
129            Codec::LZ4HC(Some(l)) => write!(f, "LZ4HC({})", l),
130            Codec::Delta(None) => write!(f, "Delta"),
131            Codec::Delta(Some(l)) => write!(f, "Delta({})", l),
132        }
133    }
134}
135#[derive(Clone, Debug, Eq, PartialEq, Hash)]
136pub struct CodecList(pub Vec<Codec>);
137
138impl fmt::Display for CodecList {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        write!(f, "{}",
141            self.0.iter().map(|c| format!("{}", c)).collect::<Vec<String>>().join(", ")
142        )
143    }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq, Hash)]
147pub struct ColumnTTL {
148    column: String,
149    interval: Option<String>,
150}
151impl fmt::Display for ColumnTTL {
152    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153        write!(f,"TTL {}", self.column)?;
154
155        match self.interval {
156            Some(ref interval) => write!(f, " + {}", interval),
157            None => Ok(()),
158        }
159    }
160}
161
162impl<'a> From<&'a str> for ColumnTTL {
163    fn from(t: &str) -> ColumnTTL {
164        ColumnTTL {
165            column: String::from(t),
166            interval: None,
167        }
168    }
169}
170impl<'a> From<(&'a str, &'a str)> for ColumnTTL {
171    fn from(t: (&str, &str)) -> ColumnTTL {
172        ColumnTTL {
173            column: String::from(t.0),
174            interval: Some(String::from(t.1)),
175        }
176    }
177}
178
179
180#[derive(Clone, Debug, Eq, Hash, PartialEq)]
181pub enum Engine {
182    Distributed(EngineDistributed),
183    Memory,
184    MergeTree(EngineMergeTree),
185    ReplicatedMergeTree(EngineReplicatedMergeTree),
186}
187
188impl fmt::Display for Engine {
189    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190        match self {
191            Engine::Distributed(e) => write!(f, "ENGINE = {}", e),
192            Engine::Memory => write!(f, "ENGINE = Memory"),
193            Engine::MergeTree(e) => write!(f, "ENGINE = {}", e),
194            Engine::ReplicatedMergeTree(e) => write!(f, "ENGINE = {}", e),
195        }
196    }
197}
198
199#[derive(Clone, Debug, Eq, Hash, PartialEq)]
200pub struct EngineDistributed {
201    cluster_name: String,
202    schema: String,
203    table: String,
204    // The sharding expression can be any expression from constants and table
205    // columns that returns an integer.
206    sharding_key: Option<String>,
207    policy_name: Option<String>,
208}
209
210impl fmt::Display for EngineDistributed {
211    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212        write!(f,"DISTRIBUTED( {}, {}, {}",
213            self.cluster_name,
214            match self.schema.as_str() {
215                "" => "''",
216                s => s,
217            },
218            self.table
219        )?;
220        if let Some(ref expr) = self.sharding_key {
221            write!(f,", {}", expr)?;
222            if let Some(ref name) = self.policy_name {
223                write!(f,", {}", name)?;
224            }
225        }
226        write!(f,")")
227    }
228}
229
230#[derive(Clone, Debug, Eq, Hash, PartialEq)]
231pub struct EngineMergeTree(String);
232
233impl fmt::Display for EngineMergeTree {
234    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
235        // write `self` cause `stack overflow` - why?
236        write!(f,"{}", self.0)
237    }
238}
239
240#[derive(Clone, Debug, Eq, Hash, PartialEq)]
241pub struct EngineReplicatedMergeTree(String);
242
243impl fmt::Display for EngineReplicatedMergeTree {
244    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
245        // write `self` cause `stack overflow` - why?
246        write!(f,"{}", self.0)
247    }
248}
249
250pub fn creation(i: &[u8]) -> IResult<&[u8], CreateTableStatement>
251{
252    let (remaining_input, (_, _, _, _, table, _, _, _, fields_list, _, _, _, engine, _)) =
253        tuple((
254            tag_no_case("create"),
255            multispace1,
256            tag_no_case("table"),
257            multispace1,
258            schema_table_reference,
259            multispace0,
260            tag("("),
261            multispace0,
262            field_specification_list,
263            multispace0,
264            tag(")"),
265            multispace0,
266            engine_spec,
267            opt(statement_terminator),
268        ))(i)?;
269
270    // "table AS alias" isn't legal in CREATE statements
271    assert!(table.alias.is_none());
272    // attach table names to columns:
273    let fields = fields_list
274        .into_iter()
275        .map(|field| {
276            let column = Column {
277                table: Some(table.name.clone()),
278                ..field.column
279            };
280
281            ColumnSpecification { column, ..field }
282        })
283        .collect();
284
285    Ok((
286        remaining_input,
287        CreateTableStatement {
288            table,
289            fields,
290            //indexes,
291            engine,
292        },
293    ))
294}
295
296fn engine_distributed(i: &[u8]) -> IResult<&[u8], Engine> {
297    // Distributed(logs, default, hits[, sharding_key[, policy_name]])
298    map(
299        tuple((
300            tag_no_case("Distributed"),
301            multispace0,
302            tag("("),
303            multispace0,
304            sql_expression, // cluster
305            ws_sep_comma,
306            alt((
307                sql_expression, // schema
308                map(tag("''"), |_| "".as_bytes()),
309            )),
310            ws_sep_comma,
311            sql_expression, // table
312            opt(tuple((
313                ws_sep_comma,
314                sql_expression, // sharding_key
315                opt(tuple((
316                    ws_sep_comma,
317                    sql_identifier, // policy_name
318                ))),
319            ))),
320            multispace0,
321            tag(")")
322        )),
323        |(_,_,_,_,cluster,_,schema,_,table,sharding_opts,_,_)| {
324            let (sharding_key, policy_name) = match sharding_opts {
325                Some((_, key, None)) => (
326                    Some(str::from_utf8(key).unwrap().into()),
327                    None
328                ),
329                Some((_, key, Some((_, policy)))) => (
330                    Some(str::from_utf8(key).unwrap().into()),
331                    Some(str::from_utf8(policy).unwrap().into())
332                ),
333                _ => (None, None),
334            };
335
336            Engine::Distributed(EngineDistributed {
337                cluster_name: str::from_utf8(cluster).unwrap().into(),
338                schema: str::from_utf8(schema).unwrap().into(),
339                table: str::from_utf8(table).unwrap().into(),
340                sharding_key,
341                policy_name,
342            })
343        }
344    )(i)
345}
346
347fn engine_merge_tree(i: &[u8]) -> IResult<&[u8], Engine> {
348    // MergeTree PARTITION BY toYYYYMMDD(eventDate) PRIMARY KEY metric ORDER BY metric SETTINGS index_granularity = 8192
349    // ENGINE = MergeTree()
350    // [PARTITION BY expr]
351    // [ORDER BY expr]
352    // [PRIMARY KEY expr]
353    // [SAMPLE BY expr]
354    map(
355        recognize(tuple((
356            tag_no_case("MergeTree"),
357            many0(alt((
358                engine_merge_tree_partition,
359                engine_merge_tree_orderby,
360                engine_merge_tree_primary,
361                engine_merge_tree_sample,
362                engine_merge_tree_ttl,
363                engine_merge_tree_settings,
364            ))),
365        ))),
366        |s| {
367            Engine::MergeTree(EngineMergeTree(str::from_utf8(s).unwrap().to_string()))
368        }
369    )(i)
370}
371fn engine_merge_tree_partition(i: &[u8]) -> IResult<&[u8], &[u8]> {
372    recognize(tuple((
373        multispace1,
374        tag_no_case("PARTITION"),
375        multispace1,
376        tag_no_case("BY"),
377        multispace1,
378        sql_expression,
379    )))(i)
380}
381fn engine_merge_tree_orderby(i: &[u8]) -> IResult<&[u8], &[u8]> {
382    recognize(tuple((
383        multispace1,
384        tag_no_case("ORDER"),
385        multispace1,
386        tag_no_case("BY"),
387        multispace1,
388        sql_expression,
389    )))(i)
390}
391fn engine_merge_tree_primary(i: &[u8]) -> IResult<&[u8], &[u8]> {
392    recognize(tuple((
393        multispace1,
394        tag_no_case("PRIMARY"),
395        multispace1,
396        tag_no_case("BY"),
397        multispace1,
398        sql_expression,
399    )))(i)
400}
401fn engine_merge_tree_sample(i: &[u8]) -> IResult<&[u8], &[u8]> {
402    recognize(tuple((
403        multispace1,
404        tag_no_case("SAMPLE"),
405        multispace1,
406        tag_no_case("BY"),
407        multispace1,
408        sql_expression,
409    )))(i)
410}
411fn engine_merge_tree_ttl(i: &[u8]) -> IResult<&[u8], &[u8]> {
412    // TTL [expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx']], ...
413    recognize(tuple((
414        multispace0,
415        tag_no_case("TTL"),
416        multispace0,
417        separated_list(ws_sep_comma, tuple((
418            ttl_expression,
419            multispace0,
420            opt(tuple((
421                alt((
422                    tag_no_case("delete"),
423                    tag_no_case("delete"),
424                    recognize(tuple((
425                        tag_no_case("to"),
426                        multispace1,
427                        tag_no_case("disk"),
428                        multispace1,
429                        delimited(tag("'"), take_until("'"), tag("'")),
430                    ))),
431                    recognize(tuple((
432                        tag_no_case("to"),
433                        multispace1,
434                        tag_no_case("volume"),
435                        multispace1,
436                        delimited(tag("'"), take_until("'"), tag("'")),
437                    ))),
438                )),
439                multispace0,
440            ))),
441        ))),
442    )))(i)
443}
444fn engine_merge_tree_settings(i: &[u8]) -> IResult<&[u8], &[u8]> {
445    // [SETTINGS name=value, ...]
446    recognize(tuple((
447        multispace0,
448        tag_no_case("SETTINGS"),
449        multispace0,
450        separated_list(ws_sep_comma, engine_merge_tree_settings_kv)
451    )))(i)
452}
453fn engine_merge_tree_settings_kv(i: &[u8]) -> IResult<&[u8], &[u8]> {
454    let numeric = recognize(tuple((
455        alt((
456            tag_no_case("index_granularity"),
457            tag_no_case("index_granularity_bytes"),
458            tag_no_case("enable_mixed_granularity_parts"),
459            tag_no_case("use_minimalistic_part_header_in_zookeeper"),
460            tag_no_case("min_merge_bytes_to_use_direct_io"),
461            tag_no_case("merge_with_ttl_timeout"),
462            tag_no_case("write_final_mark"),
463            tag_no_case("merge_max_block_size"),
464            tag_no_case("min_bytes_for_wide_part"),
465            tag_no_case("min_rows_for_wide_part"),
466        )),
467        multispace0,
468        tag("="),
469        multispace0,
470        digit1,
471    )));
472
473    let string = recognize(tuple((
474        //alt((
475            tag_no_case("storage_policy"),
476        //)),
477        multispace0,
478        tag("="),
479        multispace0,
480        delimited(tag("'"), take_until("'"), tag("'")),
481    )));
482
483    alt((
484        numeric,
485        string,
486    ))(i)
487}
488
489fn engine_replicated_merge_tree(i: &[u8]) -> IResult<&[u8], Engine> {
490    //  ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}', ver)
491    //  PARTITION BY toYYYYMM(EventDate)
492    //  ORDER BY (CounterID, EventDate, intHash32(UserID))
493    //  SAMPLE BY intHash32(UserID)
494    map(
495        recognize(tuple((
496            tag_no_case("ReplicatedMergeTree"),
497            multispace0,
498            tag("("),
499            multispace0,
500            sql_expression, // zookeeper path
501            multispace0,
502            tag(","),
503            multispace0,
504            sql_expression, // replica name
505            opt(tuple((
506                multispace0,
507                tag(","),
508                multispace0,
509                sql_expression, // engine params (FIXME: not for all Replicated*MergeTree engines?
510            ))),
511            multispace0,
512            tag(")"),
513            multispace0,
514            many0(alt((
515                engine_merge_tree_partition,
516                engine_merge_tree_orderby,
517                engine_merge_tree_primary,
518                engine_merge_tree_sample,
519                engine_merge_tree_ttl,
520                engine_merge_tree_settings,
521            ))),
522        ))),
523        |s| {
524            Engine::ReplicatedMergeTree(EngineReplicatedMergeTree(str::from_utf8(s).unwrap().to_string()))
525        }
526    )(i)
527}
528
529fn engine_memory(i: &[u8]) -> IResult<&[u8], Engine> {
530    map(tag_no_case("memory"), |_| Engine::Memory)(i)
531}
532
533fn engine(i: &[u8]) -> IResult<&[u8], Engine> {
534    alt((
535       engine_distributed, 
536       engine_memory,
537       engine_merge_tree,
538       engine_replicated_merge_tree,
539    ))(i)
540}
541
542fn engine_spec(i: &[u8]) -> IResult<&[u8], Engine> {
543    map(
544        tuple((
545            tag_no_case("engine"),
546            multispace0,
547            tag("="),
548            multispace0,
549            engine,
550        )),
551        |(_, _, _, _, engine)| engine
552    )(i)
553}
554
555pub fn field_specification_opts(i: &[u8]) -> IResult<&[u8], SqlTypeOpts> {
556    alt((
557        map(delimited(
558            tag_no_case("Nullable("),
559            type_identifier,
560            tag(")"),
561        ), |ftype| SqlTypeOpts{ftype, nullable: true, lowcardinality:false}),
562        map(delimited(
563            tag_no_case("LowCardinality("),
564            type_identifier,
565            tag(")"),
566        ), |ftype| SqlTypeOpts{ftype, nullable: false, lowcardinality:true}),
567        map(delimited(
568            tag_no_case("LowCardinality(Nullable("),
569            type_identifier,
570            tag("))"),
571        ), |ftype| SqlTypeOpts{ftype, nullable: true, lowcardinality:true}),
572        map(type_identifier,
573            |ftype| SqlTypeOpts{ftype, nullable: false, lowcardinality: false}),
574    ))(i)
575}
576
577pub fn field_specification(i: &[u8]) -> IResult<&[u8], ColumnSpecification> {
578    let (remaining_input, (column, field_type, option, comment, codec, ttl)) = tuple((
579        column_identifier_no_alias,
580        delimited(
581            multispace1,
582            field_specification_opts,
583            multispace0),
584        alt((
585            opt(column_default),
586            opt(column_materialized),
587            //column_alias,
588        )),
589        opt(preceded(multispace0, column_comment)),
590        opt(preceded(multispace0, column_codec_list)),
591        opt(preceded(multispace0, column_ttl)),
592    ))(i)?;
593
594    Ok((
595        remaining_input,
596        ColumnSpecification {
597            column,
598            sql_type: field_type.ftype,
599            codec,
600            ttl,
601            nullable: field_type.nullable,
602            option,
603            comment,
604            lowcardinality: field_type.lowcardinality,
605        },
606    ))
607}
608pub fn column_comment(i: &[u8]) -> IResult<&[u8], String> {
609    let (remaining_input, (_, _, comment)) = tuple((
610        tag_no_case("COMMENT"),
611        multispace0,
612        delimited(tag("'"), take_until("'"), tag("'")),
613    ))(i)?;
614
615    Ok((remaining_input, str::from_utf8(comment).unwrap().to_string() ))
616}
617
618// Parse rule for a comma-separated list.
619pub fn field_specification_list(i: &[u8]) -> IResult<&[u8], Vec<ColumnSpecification>> {
620    separated_list(ws_sep_comma, field_specification)(i)
621}
622
623pub fn column_codec_list(i: &[u8]) -> IResult<&[u8], CodecList> {
624
625    let (remaining_input, (_, _, list)) = tuple((
626        tag_no_case("codec"),
627        multispace0,
628        delimited(
629            delimited(multispace0, tag("("), multispace0),
630            separated_list(ws_sep_comma, column_codec),
631            delimited(multispace0, tag(")"), multispace0),
632        ),
633    ))(i)?;
634
635    Ok((remaining_input, CodecList(list)))
636}
637
638pub fn column_codec(i: &[u8]) -> IResult<&[u8], Codec> {
639    let none = map( tag_no_case("none"), |_| Codec::None);
640    let lz4  = map( tag_no_case("lz4"), |_| Codec::LZ4);
641    let doubledelta = map( tag_no_case("DoubleDelta"), |_| Codec::DoubleDelta);
642    let gorilla = map( tag_no_case("gorilla"), |_| Codec::Gorilla);
643    let t64 = map( tag_no_case("t64"), |_| Codec::T64);
644    let delta = map(
645        preceded(
646            tag_no_case("delta"), opt( delimited(
647                    delimited(multispace0, tag("("), multispace0),
648                    one_of("1248"),
649                    delimited(multispace0, tag(")"), multispace0),
650            )),
651        ),
652        |l| match l {
653            Some(l) => Codec::Delta(Some(l.into())), // FIXME use try_from and process error
654            None => Codec::Delta(None),
655        },
656    );
657    let zstd = map(
658        preceded(
659            tag_no_case("zstd"), opt( delimited(
660                    delimited(multispace0, tag("("), multispace0),
661                    digit1,
662                    delimited(multispace0, tag(")"), multispace0),
663            )),
664        ),
665        |s: Option<&[u8]>| match s {
666            Some(l) => {
667                let l = u8::from_str(str::from_utf8(l).unwrap()).unwrap();
668                if l >= 1 && l <= 22 {
669                    Codec::ZSTD(Some(l)) // FIXME process error
670                }else{
671                    panic!("Unsupported level '{}' for codec ZSTD", l)
672                }
673            },
674            None => Codec::ZSTD(None),
675        },
676    );
677    let lz4hc = map(
678        preceded(
679            tag_no_case("LZ4HC"), opt( delimited(
680                    delimited(multispace0, tag("("), multispace0),
681                    digit1,
682                    delimited(multispace0, tag(")"), multispace0),
683            )),
684        ),
685        |s: Option<&[u8]>| match s {
686            Some(l) => {
687                let l = u8::from_str(str::from_utf8(l).unwrap()).unwrap();
688                if l <= 12 {
689                    Codec::LZ4HC(Some(l)) // FIXME process error
690                }else{
691                    panic!("Unsupported level '{}' for codec LZ4HC", l)
692                }
693            },
694            None => Codec::LZ4HC(None),
695        },
696    );
697    alt((
698        none,
699        zstd,
700        lz4hc,
701        lz4,
702        delta,
703        doubledelta,
704        gorilla,
705        t64,
706    ))(i)
707}
708
709pub fn column_ttl(i: &[u8]) -> IResult<&[u8], ColumnTTL> {
710    let ttl = map(
711        tuple((multispace0, tag_no_case("TTL"), multispace0, sql_identifier, multispace0)),
712        |(_, _, _, name, _)| ColumnTTL::from(str::from_utf8(name).unwrap()),
713    );
714    let ttl_interval = map(
715        tuple((multispace0, tag_no_case("TTL-FIXME"), multispace0, sql_identifier, multispace0)),
716        |(_, _, _, name, _)| ColumnTTL::from(str::from_utf8(name).unwrap()),
717    );
718
719    alt((
720            ttl,
721            ttl_interval,
722    ))(i)
723}
724
725fn column_default(i: &[u8]) -> IResult<&[u8], ColumnOption> {
726    let (remaining_input, (_, _, _, def, _)) = tuple((
727        multispace0,
728        tag_no_case("default"),
729        multispace1,
730        sql_expression,
731        multispace0,
732    ))(i)?;
733
734    Ok((remaining_input, ColumnOption::DefaultValue(
735        str::from_utf8(def).unwrap().to_string()
736    )))
737}
738
739fn column_materialized(i: &[u8]) -> IResult<&[u8], ColumnOption> {
740    let (remaining_input, (_, _, _, def, _)) = tuple((
741        multispace0,
742        tag_no_case("default"),
743        multispace1,
744        sql_expression,
745        multispace0,
746    ))(i)?;
747
748    Ok((remaining_input, ColumnOption::Materialized(
749        str::from_utf8(def).unwrap().to_string()
750    )))
751}
752
753#[cfg(test)]
754mod test {
755    use super::*;
756    use crate::*;
757
758    #[test]
759    fn t_column_ttl() {
760        let string = "TTL time_column";
761        let res = column_ttl(string.as_bytes());
762        assert_eq!(
763            res.unwrap().1,
764            ColumnTTL::from("time_column"),
765        );
766    }
767
768    #[test]
769    fn t_column_codec() {
770        let patterns = vec![
771            ( "none", Codec::None ),
772            ( "lz4", Codec::LZ4 ),
773
774            ( "delta",    Codec::Delta(None) ),
775            ( "delta(4)", Codec::Delta(Some(CodecDeltaLevel::L4)) ),
776
777            ( "zstd",    Codec::ZSTD(None) ),
778            ( "zstd(3)", Codec::ZSTD(Some(3)) ),
779
780            ( "lz4hc",     Codec::LZ4HC(None) ),
781            ( "lz4hc(11)", Codec::LZ4HC(Some(11)) ),
782            ( "lz4hc(0)",  Codec::LZ4HC(Some(0)) ),
783        ];
784        parse_set_for_test(column_codec, patterns);
785    }
786
787    #[test]
788    fn t_column_codec_list() {
789        let patterns = vec![
790            (
791                "codec(delta(4),lz4)",
792                CodecList(vec![ Codec::Delta(Some(CodecDeltaLevel::L4)), Codec::LZ4 ])
793            ),
794            (
795                "codec( delta ( 4 ) , lz4)",
796                CodecList(vec![ Codec::Delta(Some(CodecDeltaLevel::L4)), Codec::LZ4 ])
797            ),
798            (
799                "codec(delta(4))",
800                CodecList(vec![ Codec::Delta(Some(CodecDeltaLevel::L4))])
801            ),
802        ];
803
804        parse_set_for_test(column_codec_list, patterns);
805    }
806
807    #[test]
808    fn t_field_spec() {
809        let patterns = vec![
810            ( "LowCardinality(Nullable(String))", SqlTypeOpts{ftype: SqlType::String, nullable: true, lowcardinality: true,} ),
811            ( "Nullable(String)", SqlTypeOpts{ftype: SqlType::String, nullable: true, lowcardinality: false,} ),
812            ( "Int8", SqlTypeOpts{ftype: SqlType::Int(TypeSize::B8), nullable: false, lowcardinality: false,} ),
813        ];
814        parse_set_for_test(field_specification_opts, patterns);
815    }
816    
817    #[test]
818    fn t_field_opts_display() {
819        let patterns = vec![
820            ( "String", "String".to_string()),
821            ( "Nullable(String)", "Nullable(String)".to_string()),
822            ( "LowCardinality(String)", "LowCardinality(String)".to_string()),
823            ( "LowCardinality(Nullable(String))", "LowCardinality(Nullable(String))".to_string()),
824        ];
825        parse_set_for_test(|i| field_specification_opts(i)
826                .map(|(_, o)| ("".as_bytes(), format!("{}", o))),
827            patterns);
828    }
829
830    #[test]
831    fn t_engine() {
832        let patterns = vec![
833            ( "Memory", Engine::Memory ),
834            (
835                "Distributed('cluster1', 'schema1', 'table1', rand() )",
836                Engine::Distributed(EngineDistributed {
837                    cluster_name: "'cluster1'".into(),
838                    schema: "'schema1'".into(),
839                    table: "'table1'".into(),
840                    sharding_key: Some("rand()".into()),
841                    policy_name: None,
842                })
843            ),
844            (
845                "Distributed('cluster1', '', 'table1', rand() )",
846                Engine::Distributed(EngineDistributed {
847                    cluster_name: "'cluster1'".into(),
848                    schema: "''".into(),
849                    table: "'table1'".into(),
850                    sharding_key: Some("rand()".into()),
851                    policy_name: None,
852                })
853            ),
854        ];
855        parse_set_for_test(engine, patterns);
856    }
857
858    #[test]
859    fn t_column_display_codec_ttl_nullable() {
860        let cs = ColumnSpecification {
861            column: "time_local".into(),
862            sql_type: SqlType::DateTime(None),
863            codec: Some(CodecList(vec![ Codec::Delta(Some(CodecDeltaLevel::L1)), Codec::LZ4, Codec::ZSTD(None) ])),
864            ttl: Some(ColumnTTL::from(("1", "2"))),
865            nullable: true,
866            option: None,
867            comment: None,
868            lowcardinality: false,
869        };
870
871        let exp = "`time_local` Nullable(DateTime) CODEC(Delta(1), LZ4, ZSTD) TTL 1 + 2";
872        assert_eq!(exp, format!("{}", cs).as_str());
873    }
874
875
876    #[test]
877    fn t_column_display_codec_ttl() {
878        let cs = ColumnSpecification {
879            column: "time_local".into(),
880            sql_type: SqlType::DateTime(None),
881            codec: Some(CodecList(vec![ Codec::Delta(Some(CodecDeltaLevel::L1)), Codec::LZ4, Codec::ZSTD(None) ])),
882            ttl: Some(ColumnTTL::from(("1", "2"))),
883            nullable: false,
884            option: None,
885            comment: None,
886            lowcardinality: false,
887        };
888
889        let exp = "`time_local` DateTime CODEC(Delta(1), LZ4, ZSTD) TTL 1 + 2";
890        assert_eq!(exp, format!("{}", cs).as_str());
891    }
892
893    #[test]
894    fn t_column_display() {
895        let patterns = vec![
896            (
897                "`reg` UInt32 DEFAULT CAST(0, 'UInt32') COMMENT 'комментарий' CODEC(Delta(4))",
898                "`reg` UInt32 DEFAULT CAST(0, 'UInt32') COMMENT 'комментарий' CODEC(Delta(4))".to_string()
899            ),
900            (
901                "`reg` UInt32 CODEC(Delta(4))",
902                "`reg` UInt32 CODEC(Delta(4))".to_string()
903            ),
904            (
905                "`reg` UInt32 CODEC(Delta(4))",
906                "`reg` UInt32 CODEC(Delta(4))".to_string()
907            ),
908        ];
909        parse_set_for_test(|i| field_specification(i)
910                .map(|(_, o)| ("".as_bytes(), format!("{}", o))),
911            patterns);
912    }
913
914
915
916}