1use std::collections::HashMap;
18
19use arrow_schema::extension::ExtensionType;
20use common_meta::SchemaOptions;
21use datatypes::extension::json::{Json2ExtensionType, parse_legacy_json2_settings};
22use datatypes::schema::{
23 COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_BACKEND,
24 COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE,
25 COLUMN_FULLTEXT_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE,
26 COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE, COMMENT_KEY,
27 ColumnDefaultConstraint, ColumnSchema, FulltextBackend, SchemaRef,
28};
29use datatypes::types::JsonFormat;
30use snafu::ResultExt;
31use sql::ast::{ColumnDef, ColumnOption, ColumnOptionDef, DataType, Expr, Ident, ObjectName};
32use sql::dialect::GreptimeDbDialect;
33use sql::parser::ParserContext;
34use sql::statements::create::{Column, ColumnExtensions, CreateTable, TableConstraint};
35use sql::statements::{self, OptionMap, concrete_data_type_to_sql_data_type};
36use store_api::metric_engine_consts::{is_metric_engine, is_metric_engine_internal_column};
37use table::metadata::{TableInfoRef, TableMeta};
38use table::requests::{
39 COMMENT_KEY as TABLE_COMMENT_KEY, FILE_TABLE_META_KEY, SKIP_WAL_KEY, TTL_KEY,
40 WRITE_BUFFER_SIZE_KEY,
41};
42
43use crate::error::{
44 ConvertSqlTypeSnafu, ConvertSqlValueSnafu, GetFulltextOptionsSnafu,
45 GetSkippingIndexOptionsSnafu, Result, SqlSnafu,
46};
47
48fn create_sql_options(table_meta: &TableMeta, schema_options: Option<SchemaOptions>) -> OptionMap {
50 let table_opts = &table_meta.options;
51 let mut options = OptionMap::default();
52 if let Some(write_buffer_size) = table_opts.write_buffer_size {
53 options.insert(
54 WRITE_BUFFER_SIZE_KEY.to_string(),
55 write_buffer_size.to_string(),
56 );
57 }
58 if let Some(ttl) = table_opts.ttl.map(|t| t.to_string()) {
59 options.insert(TTL_KEY.to_string(), ttl);
60 } else if let Some(database_ttl) = schema_options
61 .as_ref()
62 .and_then(|o| o.ttl)
63 .map(|ttl| ttl.to_string())
64 {
65 options.insert(TTL_KEY.to_string(), database_ttl);
66 };
67 for (k, v) in table_opts
68 .extra_options
69 .iter()
70 .filter(|(k, _)| k != &FILE_TABLE_META_KEY)
71 {
72 options.insert(k.clone(), v.clone());
73 }
74 if table_opts.skip_wal {
75 options.insert(SKIP_WAL_KEY.to_string(), true.to_string());
76 }
77 options
78}
79
80#[inline]
81fn column_option_def(option: ColumnOption) -> ColumnOptionDef {
82 ColumnOptionDef { name: None, option }
83}
84
85fn create_column(column_schema: &ColumnSchema, quote_style: char) -> Result<Column> {
86 let name = &column_schema.name;
87 let mut options = Vec::with_capacity(2);
88 let mut extensions = ColumnExtensions::default();
89
90 if column_schema.is_nullable() {
91 options.push(column_option_def(ColumnOption::Null));
92 } else {
93 options.push(column_option_def(ColumnOption::NotNull));
94 }
95
96 if let Some(c) = column_schema.default_constraint() {
97 let expr = match c {
98 ColumnDefaultConstraint::Value(v) => Expr::Value(
99 statements::value_to_sql_value(v)
100 .with_context(|_| ConvertSqlValueSnafu { value: v.clone() })?
101 .into(),
102 ),
103 ColumnDefaultConstraint::Function(expr) => {
104 ParserContext::parse_function(expr, &GreptimeDbDialect {}).context(SqlSnafu)?
105 }
106 };
107
108 options.push(column_option_def(ColumnOption::Default(expr)));
109 }
110
111 if let Some(c) = column_schema.metadata().get(COMMENT_KEY) {
112 options.push(column_option_def(ColumnOption::Comment(c.clone())));
113 }
114
115 if let Some(opt) = column_schema
116 .fulltext_options()
117 .context(GetFulltextOptionsSnafu)?
118 && opt.enable
119 {
120 let mut map = HashMap::from([
121 (
122 COLUMN_FULLTEXT_OPT_KEY_ANALYZER.to_string(),
123 opt.analyzer.to_string(),
124 ),
125 (
126 COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE.to_string(),
127 opt.case_sensitive.to_string(),
128 ),
129 (
130 COLUMN_FULLTEXT_OPT_KEY_BACKEND.to_string(),
131 opt.backend.to_string(),
132 ),
133 ]);
134 if opt.backend == FulltextBackend::Bloom {
135 map.insert(
136 COLUMN_FULLTEXT_OPT_KEY_GRANULARITY.to_string(),
137 opt.granularity.to_string(),
138 );
139 map.insert(
140 COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE.to_string(),
141 opt.false_positive_rate().to_string(),
142 );
143 }
144 extensions.fulltext_index_options = Some(map.into());
145 }
146
147 if let Some(opt) = column_schema
148 .skipping_index_options()
149 .context(GetSkippingIndexOptionsSnafu)?
150 {
151 let map = HashMap::from([
152 (
153 COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY.to_string(),
154 opt.granularity.to_string(),
155 ),
156 (
157 COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE.to_string(),
158 opt.false_positive_rate().to_string(),
159 ),
160 (
161 COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE.to_string(),
162 opt.index_type.to_string(),
163 ),
164 ]);
165 extensions.skipping_index_options = Some(map.into());
166 }
167
168 if column_schema.is_inverted_indexed() {
169 extensions.inverted_index_options = Some(HashMap::new().into());
170 }
171
172 if let Some(opt) = column_schema.compression_options()? {
173 extensions.compression_options = Some(opt.to_options_map().into());
174 }
175
176 let mut data_type = concrete_data_type_to_sql_data_type(&column_schema.data_type)
177 .with_context(|_| ConvertSqlTypeSnafu {
178 datatype: column_schema.data_type.clone(),
179 })?;
180
181 if matches!(
182 &column_schema.data_type,
183 datatypes::data_type::ConcreteDataType::Json(json_type)
184 if matches!(json_type.format, JsonFormat::Json2(_))
185 ) {
186 data_type = DataType::Custom(ObjectName::from(vec![Ident::new("JSON2")]), vec![]);
187 }
188
189 let settings = if let Some(extension) = column_schema.extension_type::<Json2ExtensionType>()? {
190 Some(extension.metadata().json_settings().clone())
191 } else {
192 parse_legacy_json2_settings(column_schema.metadata())?
193 };
194 if let Some(settings) = settings {
195 extensions.set_json_settings(settings).context(SqlSnafu)?;
196 }
197
198 Ok(Column {
199 column_def: ColumnDef {
200 name: Ident::with_quote(quote_style, name),
201 data_type,
202 options,
203 },
204 extensions,
205 })
206}
207
208fn primary_key_columns_for_show_create<'a>(
212 table_meta: &'a TableMeta,
213 engine: &str,
214) -> Vec<&'a String> {
215 let is_metric_engine = is_metric_engine(engine);
216 if is_metric_engine {
217 table_meta
218 .row_key_column_names()
219 .filter(|name| !is_metric_engine_internal_column(name))
220 .collect()
221 } else {
222 table_meta.row_key_column_names().collect()
223 }
224}
225
226fn create_table_constraints(
227 engine: &str,
228 schema: &SchemaRef,
229 table_meta: &TableMeta,
230 quote_style: char,
231) -> Vec<TableConstraint> {
232 let mut constraints = Vec::with_capacity(2);
233 if let Some(timestamp_column) = schema.timestamp_column() {
234 let column_name = ×tamp_column.name;
235 constraints.push(TableConstraint::TimeIndex {
236 column: Ident::with_quote(quote_style, column_name),
237 });
238 }
239 if !table_meta.primary_key_indices.is_empty() {
240 let columns = primary_key_columns_for_show_create(table_meta, engine)
241 .into_iter()
242 .map(|name| Ident::with_quote(quote_style, name))
243 .collect();
244 constraints.push(TableConstraint::PrimaryKey { columns });
245 }
246
247 constraints
248}
249
250pub fn create_table_stmt(
252 table_info: &TableInfoRef,
253 schema_options: Option<SchemaOptions>,
254 quote_style: char,
255) -> Result<CreateTable> {
256 let table_meta = &table_info.meta;
257 let table_name = &table_info.name;
258 let schema = &table_info.meta.schema;
259 let is_metric_engine = is_metric_engine(&table_meta.engine);
260 let columns = schema
261 .column_schemas()
262 .iter()
263 .filter_map(|c| {
264 if is_metric_engine && is_metric_engine_internal_column(&c.name) {
265 None
266 } else {
267 Some(create_column(c, quote_style))
268 }
269 })
270 .collect::<Result<Vec<_>>>()?;
271
272 let constraints = create_table_constraints(&table_meta.engine, schema, table_meta, quote_style);
273
274 let mut options = create_sql_options(table_meta, schema_options);
275 if let Some(comment) = &table_info.desc
276 && options.get(TABLE_COMMENT_KEY).is_none()
277 {
278 options.insert(format!("'{TABLE_COMMENT_KEY}'"), comment.clone());
279 }
280
281 Ok(CreateTable {
282 if_not_exists: true,
283 table_id: table_info.ident.table_id,
284 name: ObjectName::from(vec![Ident::with_quote(quote_style, table_name)]),
285 columns,
286 engine: table_meta.engine.clone(),
287 constraints,
288 options,
289 partitions: None,
290 })
291}
292
293#[cfg(test)]
294mod tests {
295 use std::sync::Arc;
296 use std::time::Duration;
297
298 use common_time::timestamp::TimeUnit;
299 use datatypes::extension::json::JsonExtensionType;
300 use datatypes::prelude::ConcreteDataType;
301 use datatypes::schema::{FulltextOptions, Schema, SchemaRef, SkippingIndexOptions};
302 use table::metadata::*;
303 use table::requests::{
304 FILE_TABLE_FORMAT_KEY, FILE_TABLE_LOCATION_KEY, FILE_TABLE_META_KEY, TableOptions,
305 };
306
307 use super::*;
308
309 #[test]
310 fn test_show_create_table_sql() {
311 let schema = vec![
312 ColumnSchema::new("id", ConcreteDataType::uint32_datatype(), true)
313 .with_skipping_options(SkippingIndexOptions {
314 granularity: 4096,
315 ..Default::default()
316 })
317 .unwrap(),
318 ColumnSchema::new("host", ConcreteDataType::string_datatype(), true)
319 .with_inverted_index(true),
320 ColumnSchema::new("cpu", ConcreteDataType::float64_datatype(), true),
321 ColumnSchema::new("disk", ConcreteDataType::float32_datatype(), true),
322 ColumnSchema::new("msg", ConcreteDataType::string_datatype(), true)
323 .with_fulltext_options(FulltextOptions {
324 enable: true,
325 ..Default::default()
326 })
327 .unwrap(),
328 ColumnSchema::new("embedding", ConcreteDataType::vector_datatype(4), true),
329 ColumnSchema::new(
330 "ts",
331 ConcreteDataType::timestamp_datatype(TimeUnit::Millisecond),
332 false,
333 )
334 .with_default_constraint(Some(ColumnDefaultConstraint::Function(String::from(
335 "current_timestamp()",
336 ))))
337 .unwrap()
338 .with_time_index(true),
339 ];
340
341 let table_schema = SchemaRef::new(Schema::new(schema));
342 let table_name = "system_metrics";
343 let schema_name = "public".to_string();
344 let catalog_name = "greptime".to_string();
345
346 let mut options = table::requests::TableOptions {
347 ttl: Some(Duration::from_secs(30).into()),
348 skip_wal: true,
349 ..Default::default()
350 };
351
352 let _ = options
353 .extra_options
354 .insert("compaction.type".to_string(), "twcs".to_string());
355
356 let meta = TableMetaBuilder::empty()
357 .schema(table_schema)
358 .primary_key_indices(vec![0, 1])
359 .value_indices(vec![2, 3])
360 .engine("mito".to_string())
361 .next_column_id(0)
362 .options(options)
363 .created_on(Default::default())
364 .build()
365 .unwrap();
366
367 let info = Arc::new(
368 TableInfoBuilder::default()
369 .table_id(1024)
370 .table_version(0 as TableVersion)
371 .name(table_name)
372 .schema_name(schema_name)
373 .catalog_name(catalog_name)
374 .desc(None)
375 .table_type(TableType::Base)
376 .meta(meta)
377 .build()
378 .unwrap(),
379 );
380
381 let stmt = create_table_stmt(&info, None, '"').unwrap();
382
383 let sql = format!("\n{}", stmt);
384 assert_eq!(
385 r#"
386CREATE TABLE IF NOT EXISTS "system_metrics" (
387 "id" INT UNSIGNED NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '4096', type = 'BLOOM'),
388 "host" STRING NULL INVERTED INDEX,
389 "cpu" DOUBLE NULL,
390 "disk" FLOAT NULL,
391 "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
392 "embedding" VECTOR(4) NULL,
393 "ts" TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(),
394 TIME INDEX ("ts"),
395 PRIMARY KEY ("id", "host")
396)
397ENGINE=mito
398WITH(
399 'compaction.type' = 'twcs',
400 skip_wal = 'true',
401 ttl = '30s'
402)"#,
403 sql
404 );
405
406 let mut table_meta = info.meta.clone();
407 table_meta.options.skip_wal = false;
408 table_meta
409 .options
410 .extra_options
411 .insert(SKIP_WAL_KEY.to_string(), false.to_string());
412 assert_eq!(
413 Some("false"),
414 create_sql_options(&table_meta, None).get(SKIP_WAL_KEY)
415 );
416
417 let mut schema_options = SchemaOptions::default();
418 schema_options
419 .extra_options
420 .insert(SKIP_WAL_KEY.to_string(), true.to_string());
421 assert_eq!(
422 Some("false"),
423 create_sql_options(&table_meta, Some(schema_options)).get(SKIP_WAL_KEY)
424 );
425 }
426
427 #[test]
428 fn test_show_create_legacy_json_with_json_extension() {
429 let mut json_column = ColumnSchema::new("j", ConcreteDataType::json_datatype(), true);
430 json_column.with_extension_type(&JsonExtensionType);
431
432 let table_schema = SchemaRef::new(Schema::new(vec![
433 json_column,
434 ColumnSchema::new(
435 "ts",
436 ConcreteDataType::timestamp_datatype(TimeUnit::Millisecond),
437 false,
438 )
439 .with_time_index(true),
440 ]));
441 let table_name = "legacy_json";
442 let meta = TableMetaBuilder::empty()
443 .schema(table_schema)
444 .primary_key_indices(vec![])
445 .value_indices(vec![0])
446 .engine("mito".to_string())
447 .next_column_id(0)
448 .options(Default::default())
449 .created_on(Default::default())
450 .build()
451 .unwrap();
452
453 let info = Arc::new(
454 TableInfoBuilder::default()
455 .table_id(1024)
456 .table_version(0 as TableVersion)
457 .name(table_name)
458 .schema_name("public")
459 .catalog_name("greptime")
460 .desc(None)
461 .table_type(TableType::Base)
462 .meta(meta)
463 .build()
464 .unwrap(),
465 );
466
467 let stmt = create_table_stmt(&info, None, '"').unwrap();
468 let sql = format!("\n{}", stmt);
469 assert_eq!(
470 r#"
471CREATE TABLE IF NOT EXISTS "legacy_json" (
472 "j" JSON NULL,
473 "ts" TIMESTAMP(3) NOT NULL,
474 TIME INDEX ("ts")
475)
476ENGINE=mito
477"#,
478 sql
479 );
480 }
481
482 #[test]
483 fn test_show_create_external_table_sql() {
484 let schema = vec![
485 ColumnSchema::new("host", ConcreteDataType::string_datatype(), true),
486 ColumnSchema::new("cpu", ConcreteDataType::float64_datatype(), true),
487 ];
488 let table_schema = SchemaRef::new(Schema::new(schema));
489 let table_name = "system_metrics";
490 let schema_name = "public".to_string();
491 let catalog_name = "greptime".to_string();
492 let mut options: TableOptions = Default::default();
493 let _ = options
494 .extra_options
495 .insert(FILE_TABLE_LOCATION_KEY.to_string(), "foo.csv".to_string());
496 let _ = options.extra_options.insert(
497 FILE_TABLE_META_KEY.to_string(),
498 "{{\"files\":[\"foo.csv\"]}}".to_string(),
499 );
500 let _ = options
501 .extra_options
502 .insert(FILE_TABLE_FORMAT_KEY.to_string(), "csv".to_string());
503 let meta = TableMetaBuilder::empty()
504 .schema(table_schema)
505 .primary_key_indices(vec![])
506 .engine("file".to_string())
507 .next_column_id(0)
508 .options(options)
509 .created_on(Default::default())
510 .build()
511 .unwrap();
512
513 let info = Arc::new(
514 TableInfoBuilder::default()
515 .table_id(1024)
516 .table_version(0 as TableVersion)
517 .name(table_name)
518 .schema_name(schema_name)
519 .catalog_name(catalog_name)
520 .desc(None)
521 .table_type(TableType::Base)
522 .meta(meta)
523 .build()
524 .unwrap(),
525 );
526
527 let stmt = create_table_stmt(&info, None, '"').unwrap();
528
529 let sql = format!("\n{}", stmt);
530 assert_eq!(
531 r#"
532CREATE EXTERNAL TABLE IF NOT EXISTS "system_metrics" (
533 "host" STRING NULL,
534 "cpu" DOUBLE NULL,
535
536)
537ENGINE=file
538WITH(
539 format = 'csv',
540 location = 'foo.csv'
541)"#,
542 sql
543 );
544 }
545}