diff --git a/clients/cli/build.gradle.kts b/clients/cli/build.gradle.kts index 71608ee911d..9358808f9f5 100644 --- a/clients/cli/build.gradle.kts +++ b/clients/cli/build.gradle.kts @@ -24,6 +24,7 @@ plugins { dependencies { implementation(libs.commons.cli.new) + implementation(libs.commons.csv) implementation(libs.guava) implementation(libs.slf4j.api) implementation(libs.slf4j.simple) diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java b/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java index 29b0b347523..c13b77265a9 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java @@ -35,6 +35,11 @@ public class DefaultConverter { * @return An instance of the appropriate default value. */ public static Expression convert(String defaultValue, String dataType) { + + if (dataType == null || dataType.isEmpty()) { + return Column.DEFAULT_VALUE_NOT_SET; + } + Type convertedDatatype = ParseType.toType(dataType); if (defaultValue == null || defaultValue.isEmpty()) { diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java index 351a40fc46d..774f4d790cf 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java @@ -50,7 +50,7 @@ public class ErrorMessages { public static final String TAG_EMPTY = "Error: Must configure --tag option."; public static final String UNKNOWN_ROLE = "Unknown role."; public static final String ROLE_EXISTS = "Role already exists."; - + public static final String TABLE_EXISTS = "Table already exists."; public static final String INVALID_SET_COMMAND = "Unsupported combination of options either use --name, --user, --group or --property and --value."; public static final String INVALID_REMOVE_COMMAND = diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java index ade9947de76..683efe9f37c 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java @@ -315,7 +315,9 @@ private void handleTableCommand() { newTableDetails(url, ignore, metalake, catalog, schema, table).handle(); } } else if (CommandActions.CREATE.equals(command)) { - // TODO + String columnFile = line.getOptionValue(GravitinoOptions.COLUMNFILE); + String comment = line.getOptionValue(GravitinoOptions.COMMENT); + newCreateTable(url, ignore, metalake, catalog, schema, table, columnFile, comment).handle(); } else if (CommandActions.DELETE.equals(command)) { boolean force = line.hasOption(GravitinoOptions.FORCE); newDeleteTable(url, ignore, force, metalake, catalog, schema, table).handle(); diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java index 4663650ea32..fe09380d40e 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java @@ -50,6 +50,7 @@ public class GravitinoOptions { public static final String ROLE = "role"; public static final String AUDIT = "audit"; public static final String FORCE = "force"; + public static final String COLUMNFILE = "columnfile"; public static final String INDEX = "index"; public static final String DISTRIBUTION = "distribution"; public static final String PARTITION = "partition"; @@ -96,6 +97,7 @@ public Options options() { options.addOption(createArgOption(DEFAULT, "default column value")); options.addOption(createSimpleOption("o", OWNER, "display entity owner")); options.addOption(createArgOption("r", ROLE, "role name")); + options.addOption(createArgOption(COLUMNFILE, "CSV file describing columns")); // Properties and tags can have multiple values options.addOption(createArgsOption("p", PROPERTIES, "property name/value pairs")); @@ -104,7 +106,7 @@ public Options options() { // Force delete entities and rename metalake operations options.addOption(createSimpleOption("f", FORCE, "force operation")); - options.addOption(createArgOption(null, OUTPUT, "output format (plain/table)")); + options.addOption(createArgOption(OUTPUT, "output format (plain/table)")); return options; } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ReadTableCSV.java b/clients/cli/src/main/java/org/apache/gravitino/cli/ReadTableCSV.java new file mode 100644 index 00000000000..3a9ca3a6dea --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ReadTableCSV.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.gravitino.cli; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.gravitino.rel.Column; + +public class ReadTableCSV { + + private enum ExpectedColumns { + NAME("Name"), + DATATYPE("Datatype"), + COMMENT("Comment"), + NULLABLE("Nullable"), + AUTOINCREMENT("AutoIncrement"), + DEFAULTVALUE("DefaultValue"), + DEFAULTTYPE("DefaultType"); + + private final String name; + + ExpectedColumns(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + public Column[] columns(Map> tableData) { + List names = tableData.get(ExpectedColumns.NAME.getName()); + List datatypes = tableData.get(ExpectedColumns.DATATYPE.getName()); + List comments = tableData.get(ExpectedColumns.COMMENT.getName()); + List nullables = tableData.get(ExpectedColumns.NULLABLE.getName()); + List autoIncs = tableData.get(ExpectedColumns.AUTOINCREMENT.getName()); + List defaultTypes = tableData.get(ExpectedColumns.DEFAULTTYPE.getName()); + List defaulValues = tableData.get(ExpectedColumns.DEFAULTVALUE.getName()); + int size = names.size(); + Column[] columns = new Column[size]; + + for (int i = 0; i < size; i++) { + String columnName = names.get(i); + String datatype = datatypes.get(i); + String comment = comments.get(i); + boolean nullable = nullables.get(i).equals("true"); + boolean auto = autoIncs.get(i).equals("true"); + String defaultValue = defaulValues.get(i); + String defaultType = defaultTypes.get(i); + + if (defaultType == null || defaultType.isEmpty()) { + defaultType = datatype; + } + + Column column = + Column.of( + columnName, + ParseType.toType(datatype), + comment, + nullable, + auto, + DefaultConverter.convert(defaultValue, defaultType)); + columns[i] = column; + } + + return columns; + } + + public Map> parse(String csvFile) { + + // Initialize a Map to store each column's values in a list + HashMap> tableData = new HashMap<>(); + for (ExpectedColumns column : ExpectedColumns.values()) { + tableData.put(column.getName(), new ArrayList<>()); + } + + try (BufferedReader reader = + Files.newBufferedReader(Paths.get(csvFile), StandardCharsets.UTF_8)) { + CSVParser csvParser = + new CSVParser( + reader, + CSVFormat.Builder.create() + .setHeader( + Arrays.stream(ExpectedColumns.values()) + .map(ExpectedColumns::getName) + .toArray(String[]::new)) + .setIgnoreHeaderCase(true) + .setSkipHeaderRecord(true) + .setTrim(true) + .setIgnoreEmptyLines(true) + .build()); + for (CSVRecord cvsRecord : csvParser) { + String defaultValue = null; + String value = null; + + for (ExpectedColumns column : ExpectedColumns.values()) { + switch (column) { + case NULLABLE: + defaultValue = "true"; + break; + case AUTOINCREMENT: + defaultValue = "false"; + break; + default: + defaultValue = null; + break; + } + + try { + value = cvsRecord.get(column.getName()); + } catch (IllegalArgumentException exp) { + value = defaultValue; // missing value + } + + tableData.get(column.getName()).add(value); + } + } + } catch (IOException exp) { + System.err.println(exp.getMessage()); + } + + return tableData; + } +} diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java index 7c5d62ccf83..ac285804e73 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java @@ -33,6 +33,7 @@ import org.apache.gravitino.cli.commands.CreateMetalake; import org.apache.gravitino.cli.commands.CreateRole; import org.apache.gravitino.cli.commands.CreateSchema; +import org.apache.gravitino.cli.commands.CreateTable; import org.apache.gravitino.cli.commands.CreateTag; import org.apache.gravitino.cli.commands.CreateTopic; import org.apache.gravitino.cli.commands.CreateUser; @@ -815,4 +816,16 @@ protected UpdateColumnDefault newUpdateColumnDefault( return new UpdateColumnDefault( url, ignore, metalake, catalog, schema, table, column, defaultValue, dataType); } + + protected CreateTable newCreateTable( + String url, + boolean ignore, + String metalake, + String catalog, + String schema, + String table, + String columnFile, + String comment) { + return new CreateTable(url, ignore, metalake, catalog, schema, table, columnFile, comment); + } } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTable.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTable.java new file mode 100644 index 00000000000..91e7b6e3046 --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTable.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.gravitino.cli.commands; + +import java.util.List; +import java.util.Map; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.cli.ErrorMessages; +import org.apache.gravitino.cli.ReadTableCSV; +import org.apache.gravitino.client.GravitinoClient; +import org.apache.gravitino.exceptions.NoSuchCatalogException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchSchemaException; +import org.apache.gravitino.exceptions.TableAlreadyExistsException; +import org.apache.gravitino.rel.Column; + +public class CreateTable extends Command { + protected final String metalake; + protected final String catalog; + protected final String schema; + protected final String table; + protected final String columnFile; + protected final String comment; + + /** + * Create a new table. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + * @param metalake The name of the metalake. + * @param catalog The name of the catalog. + * @param schema The name of the schema. + * @param table The name of the table. + * @param columnFile The file name containing the CSV column info. + * @param comment The table's comment. + */ + public CreateTable( + String url, + boolean ignoreVersions, + String metalake, + String catalog, + String schema, + String table, + String columnFile, + String comment) { + super(url, ignoreVersions); + this.metalake = metalake; + this.catalog = catalog; + this.schema = schema; + this.table = table; + this.columnFile = columnFile; + this.comment = comment; + } + + /** Create a new table. */ + @Override + public void handle() { + NameIdentifier tableName; + GravitinoClient client; + ReadTableCSV readTableCSV = new ReadTableCSV(); + Map> tableData; + Column[] columns; + + try { + tableName = NameIdentifier.of(schema, table); + client = buildClient(metalake); + } catch (NoSuchMetalakeException err) { + System.err.println(ErrorMessages.UNKNOWN_METALAKE); + return; + } catch (Exception exp) { + System.err.println("Error initializing client or table name: " + exp.getMessage()); + return; + } + + try { + tableData = readTableCSV.parse(columnFile); + columns = readTableCSV.columns(tableData); + } catch (Exception exp) { + System.err.println("Error reading or parsing column file: " + exp.getMessage()); + return; + } + + try { + client.loadCatalog(catalog).asTableCatalog().createTable(tableName, columns, comment, null); + } catch (NoSuchCatalogException err) { + System.err.println(ErrorMessages.UNKNOWN_CATALOG); + return; + } catch (NoSuchSchemaException err) { + System.err.println(ErrorMessages.UNKNOWN_SCHEMA); + return; + } catch (TableAlreadyExistsException err) { + System.err.println(ErrorMessages.TABLE_EXISTS); + return; + } catch (Exception exp) { + System.err.println(exp.getMessage()); + return; + } + + System.out.println(table + " created"); + } +} diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.java index 7aa20bd3791..8dc892b6fa0 100644 --- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.java +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.java @@ -28,6 +28,30 @@ class TestDefaultConverter { + @Test + void testConvertNulls() { + String defaultValue = null; + String dataType = null; + Expression result = DefaultConverter.convert(defaultValue, dataType); + + assertEquals( + Column.DEFAULT_VALUE_NOT_SET, + result, + "Expected DEFAULT_VALUE_NOT_SET for null defaultValue."); + } + + @Test + void testConvertEmpty() { + String defaultValue = ""; + String dataType = ""; + Expression result = DefaultConverter.convert(defaultValue, dataType); + + assertEquals( + Column.DEFAULT_VALUE_NOT_SET, + result, + "Expected DEFAULT_VALUE_NOT_SET for null defaultValue."); + } + @Test void testConvertNullDefaultValue() { String defaultValue = null; diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestReadTableCSV.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestReadTableCSV.java new file mode 100644 index 00000000000..d0e8001aa2e --- /dev/null +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestReadTableCSV.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.gravitino.cli.DefaultConverter; +import org.apache.gravitino.cli.ParseType; +import org.apache.gravitino.cli.ReadTableCSV; +import org.apache.gravitino.rel.Column; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TestReadTableCSV { + + private ReadTableCSV readTableCSV; + private Path tempFile; + + @BeforeEach + public void setUp() throws IOException { + readTableCSV = new ReadTableCSV(); + tempFile = Files.createTempFile("test-data", ".csv"); + + // Write the header the CSV file + try (BufferedWriter writer = Files.newBufferedWriter(tempFile, StandardCharsets.UTF_8)) { + CSVPrinter csvPrinter = + new CSVPrinter( + writer, + CSVFormat.Builder.create() + .setHeader( + "Name", + "Datatype", + "Comment", + "Nullable", + "AutoIncrement", + "DefaultValue", + "DefaultType") + .build()); + + // Print records to the CSV file + csvPrinter.printRecord("name", "String", "Sample comment"); + csvPrinter.printRecord("ID", "Integer", "Another comment", "false", "true", "0"); + csvPrinter.printRecord( + "location", "String", "More comments", "false", "false", "Sydney", "String"); + } + } + + @Test + public void testParse() { + Map> tableData = readTableCSV.parse(tempFile.toString()); + + // Validate the data in the tableData map + assertEquals( + Arrays.asList("name", "ID", "location"), tableData.get("Name"), "Name column should match"); + assertEquals( + Arrays.asList("String", "Integer", "String"), + tableData.get("Datatype"), + "Datatype column should match"); + assertEquals( + Arrays.asList("Sample comment", "Another comment", "More comments"), + tableData.get("Comment"), + "Comment column should match"); + assertEquals( + Arrays.asList("true", "false", "false"), + tableData.get("Nullable"), + "Nullable column should match"); + assertEquals( + Arrays.asList("false", "true", "false"), + tableData.get("AutoIncrement"), + "AutoIncrement column should match"); + assertEquals( + Arrays.asList(null, "0", "Sydney"), + tableData.get("DefaultValue"), + "DefaultValue column should match"); + assertEquals( + Arrays.asList(null, null, "String"), + tableData.get("DefaultType"), + "DefaultType column should match"); + } + + @Test + void testColumns() { + ReadTableCSV readTableCSV = new ReadTableCSV(); + Map> tableData = readTableCSV.parse(tempFile.toString()); + Column[] columns = readTableCSV.columns(tableData); + Column expectedColumn1 = + Column.of("name", ParseType.toType("STRING"), "Sample comment", true, false, null); + Column expectedColumn2 = + Column.of( + "ID", + ParseType.toType("INTEGER"), + "Another comment", + false, + true, + DefaultConverter.convert("0", "INTEGER")); + Column expectedColumn3 = + Column.of( + "location", + ParseType.toType("STRING"), + "More comments", + false, + false, + DefaultConverter.convert("Sydney", "STRING")); + + assertEquals(3, columns.length, "Number of columns should match"); + + assertEquals(expectedColumn1.name(), columns[0].name()); + assertEquals(expectedColumn1.dataType(), columns[0].dataType()); + assertEquals(expectedColumn1.comment(), columns[0].comment()); + assertEquals(expectedColumn1.nullable(), columns[0].nullable()); + assertEquals(expectedColumn1.autoIncrement(), columns[0].autoIncrement()); + assertEquals(expectedColumn1.defaultValue(), columns[0].defaultValue()); + + assertEquals(expectedColumn2.name(), columns[1].name()); + assertEquals(expectedColumn2.dataType(), columns[1].dataType()); + assertEquals(expectedColumn2.comment(), columns[1].comment()); + assertEquals(expectedColumn2.nullable(), columns[1].nullable()); + assertEquals(expectedColumn2.autoIncrement(), columns[1].autoIncrement()); + assertEquals(expectedColumn2.defaultValue(), columns[1].defaultValue()); + + assertEquals(expectedColumn3.name(), columns[2].name()); + assertEquals(expectedColumn3.dataType(), columns[2].dataType()); + assertEquals(expectedColumn3.comment(), columns[2].comment()); + assertEquals(expectedColumn3.nullable(), columns[2].nullable()); + assertEquals(expectedColumn3.autoIncrement(), columns[2].autoIncrement()); + assertEquals(expectedColumn3.defaultValue(), columns[2].defaultValue()); + } +} diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java index e800bc60eb5..07cbdbdcc6c 100644 --- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java @@ -27,6 +27,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; +import org.apache.gravitino.cli.commands.CreateTable; import org.apache.gravitino.cli.commands.DeleteTable; import org.apache.gravitino.cli.commands.ListIndexes; import org.apache.gravitino.cli.commands.ListTableProperties; @@ -379,4 +380,34 @@ void testupdateTableNmeCommand() { commandLine.handleCommandLine(); verify(mockUpdate).handle(); } + + @Test + void testCreateTable() { + CreateTable mockCreate = mock(CreateTable.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users"); + when(mockCommandLine.hasOption(GravitinoOptions.COLUMNFILE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.COLUMNFILE)).thenReturn("users.csv"); + when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.TABLE, CommandActions.CREATE)); + doReturn(mockCreate) + .when(commandLine) + .newCreateTable( + GravitinoCommandLine.DEFAULT_URL, + false, + "metalake_demo", + "catalog", + "schema", + "users", + "users.csv", + "comment"); + commandLine.handleCommandLine(); + verify(mockCreate).handle(); + } } diff --git a/docs/cli.md b/docs/cli.md index 75eb219cee7..c0e86886960 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -33,6 +33,7 @@ The general structure for running commands with the Gravitino CLI is `gcli entit -a,--audit display audit information --auto column value auto-increments (true/false) -c,--comment entity comment + --columnfile CSV file describing columns -d,--distribution display distribution information --datatype column data type --default default column value @@ -367,6 +368,16 @@ Setting and removing schema properties is not currently supported by the Java AP ### Table commands +When creating a table the columns are specified in CSV file specifying the name of the column, the datatype, a comment, true or false if the column is nullable, true or false if the column is auto incremented, a default value and a default type. Not all of the columns need to be specifed just the name and datatype columns. If not specified comment default to null, nullability to true and auto increment to false. If only the default value is specified it defaults to the same data type as the column. + +Example CSV file +```text +Name,Datatype,Comment,Nullable,AutoIncrement,DefaultValue,DefaultType +name,String,person's name +ID,Integer,unique id,false,true +location,String,city they work in,false,false,Sydney,String +``` + #### Show all tables ```bash @@ -431,6 +442,11 @@ gcli table set --name catalog_postgres.hr.salaries --property test --value value gcli table remove --name catalog_postgres.hr.salaries --property test ``` +#### Create a table + +```bash +gcli table create --name catalog_postgres.hr.salaries --comment "comment" --columnfile ~/table.csv +``` ### User commands diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b7441ea297..f1f29cf186d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ hadoop-minikdc = "3.3.0" htrace-core4 = "4.1.0-incubating" httpclient5 = "5.2.1" mockserver = "5.15.0" +commons-csv = "1.12.0" commons-lang3 = "3.14.0" commons-lang = "2.6" commons-logging = "1.2" @@ -178,6 +179,7 @@ airlift-resolver = { group = "io.airlift.resolver", name = "resolver", version.r httpclient5 = { group = "org.apache.httpcomponents.client5", name = "httpclient5", version.ref = "httpclient5" } mockserver-netty = { group = "org.mock-server", name = "mockserver-netty", version.ref = "mockserver" } mockserver-client-java = { group = "org.mock-server", name = "mockserver-client-java", version.ref = "mockserver" } +commons-csv = { group = "org.apache.commons", name = "commons-csv", version.ref = "commons-csv" } commons-lang = { group = "commons-lang", name = "commons-lang", version.ref = "commons-lang" } commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang3" } commons-logging = { group = "commons-logging", name = "commons-logging", version.ref = "commons-logging" }