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