1use 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 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>), LZ4,
112 LZ4HC(Option<u8>), Delta(Option<CodecDeltaLevel>), 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 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!(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!(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 assert!(table.alias.is_none());
272 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 engine,
292 },
293 ))
294}
295
296fn engine_distributed(i: &[u8]) -> IResult<&[u8], Engine> {
297 map(
299 tuple((
300 tag_no_case("Distributed"),
301 multispace0,
302 tag("("),
303 multispace0,
304 sql_expression, ws_sep_comma,
306 alt((
307 sql_expression, map(tag("''"), |_| "".as_bytes()),
309 )),
310 ws_sep_comma,
311 sql_expression, opt(tuple((
313 ws_sep_comma,
314 sql_expression, opt(tuple((
316 ws_sep_comma,
317 sql_identifier, ))),
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 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 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 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 tag_no_case("storage_policy"),
476 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 map(
495 recognize(tuple((
496 tag_no_case("ReplicatedMergeTree"),
497 multispace0,
498 tag("("),
499 multispace0,
500 sql_expression, multispace0,
502 tag(","),
503 multispace0,
504 sql_expression, opt(tuple((
506 multispace0,
507 tag(","),
508 multispace0,
509 sql_expression, ))),
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 )),
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
618pub 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())), 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)) }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)) }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}