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