diff --git a/paimon-web-server/pom.xml b/paimon-web-server/pom.xml
index 3c4fa57d1..02ec9b71e 100644
--- a/paimon-web-server/pom.xml
+++ b/paimon-web-server/pom.xml
@@ -76,6 +76,10 @@ under the License.
org.junit.vintage
junit-vintage-engine
+
+ junit
+ junit
+
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java
index cdc0bf18b..64e166f31 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java
@@ -18,13 +18,16 @@
package org.apache.paimon.web.server.controller;
-import org.apache.paimon.web.api.catalog.CatalogCreator;
+import org.apache.paimon.web.api.catalog.PaimonServiceFactory;
+import org.apache.paimon.web.server.data.enums.CatalogMode;
import org.apache.paimon.web.server.data.model.CatalogInfo;
import org.apache.paimon.web.server.data.result.R;
import org.apache.paimon.web.server.data.result.enums.Status;
import org.apache.paimon.web.server.service.CatalogService;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -45,46 +48,39 @@ public class CatalogController {
@Autowired private CatalogService catalogService;
/**
- * Create a filesystem catalog.
+ * Create a catalog.
*
- * @param catalogInfo The catalogInfo for the filesystem catalog.
+ * @param catalogInfo The catalogInfo for the catalog.
* @return The created catalog.
*/
- @PostMapping("/createFilesystemCatalog")
- public R createFilesystemCatalog(@RequestBody CatalogInfo catalogInfo) {
+ @PostMapping("/create")
+ public R createCatalog(@RequestBody CatalogInfo catalogInfo) {
if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
return R.failed(Status.CATALOG_NAME_IS_EXIST, catalogInfo.getCatalogName());
}
try {
- CatalogCreator.createFilesystemCatalog(catalogInfo.getWarehouse());
+ if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.FILESYSTEM.getMode())) {
+ PaimonServiceFactory.createFileSystemCatalogService(
+ catalogInfo.getCatalogName(), catalogInfo.getWarehouse());
+ } else if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.HIVE.getMode())) {
+ if (StringUtils.isNotBlank(catalogInfo.getHiveConfDir())) {
+ PaimonServiceFactory.createHiveCatalogService(
+ catalogInfo.getCatalogName(),
+ catalogInfo.getWarehouse(),
+ catalogInfo.getHiveUri(),
+ catalogInfo.getHiveConfDir());
+ } else {
+ PaimonServiceFactory.createHiveCatalogService(
+ catalogInfo.getCatalogName(),
+ catalogInfo.getWarehouse(),
+ catalogInfo.getHiveUri(),
+ null);
+ }
+ }
return catalogService.save(catalogInfo) ? R.succeed() : R.failed();
} catch (Exception e) {
- e.printStackTrace();
- return R.failed(Status.CATALOG_CREATE_ERROR);
- }
- }
-
- /**
- * Create a hive catalog.
- *
- * @param catalogInfo The information for the hive catalog.
- * @return The created catalog.
- */
- @PostMapping("/createHiveCatalog")
- public R createHiveCatalog(@RequestBody CatalogInfo catalogInfo) {
- if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
- return R.failed(Status.CATALOG_NAME_IS_EXIST, catalogInfo.getCatalogName());
- }
-
- try {
- CatalogCreator.createHiveCatalog(
- catalogInfo.getWarehouse(),
- catalogInfo.getHiveUri(),
- catalogInfo.getHiveConfDir());
- return catalogService.save(catalogInfo) ? R.succeed() : R.failed();
- } catch (Exception e) {
- e.printStackTrace();
+ log.error("Exception with creating catalog.", e);
return R.failed(Status.CATALOG_CREATE_ERROR);
}
}
@@ -101,13 +97,17 @@ public R> getCatalog() {
}
/**
- * Removes a catalog by its ID.
+ * Removes a catalog with given catalog name.
*
- * @param catalogId The ID of the catalog to be removed.
- * @return A response indicating the success or failure of the removal operation.
+ * @param catalogName The catalog name.
+ * @return A response indicating the success or failure of the operation.
*/
- @DeleteMapping("/{catalogId}")
- public R remove(@PathVariable Integer catalogId) {
- return catalogService.removeById(catalogId) ? R.succeed() : R.failed();
+ @DeleteMapping("/remove/{catalogName}")
+ public R removeCatalog(@PathVariable String catalogName) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("catalog_name", catalogName);
+ return catalogService.remove(queryWrapper)
+ ? R.succeed()
+ : R.failed(Status.CATALOG_REMOVE_ERROR);
}
}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java
index 83d0584b1..9ab10db75 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java
@@ -18,20 +18,21 @@
package org.apache.paimon.web.server.controller;
-import org.apache.paimon.catalog.Catalog;
-import org.apache.paimon.web.api.database.DatabaseManager;
+import org.apache.paimon.web.api.catalog.PaimonService;
import org.apache.paimon.web.server.data.model.CatalogInfo;
import org.apache.paimon.web.server.data.model.DatabaseInfo;
import org.apache.paimon.web.server.data.result.R;
import org.apache.paimon.web.server.data.result.enums.Status;
import org.apache.paimon.web.server.service.CatalogService;
-import org.apache.paimon.web.server.util.CatalogUtils;
+import org.apache.paimon.web.server.util.PaimonServiceUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -54,18 +55,18 @@ public class DatabaseController {
* @param databaseInfo The DatabaseInfo object containing the details of the new database.
* @return R indicating the result of the operation.
*/
- @PostMapping("/createDatabase")
+ @PostMapping("/create")
public R createDatabase(@RequestBody DatabaseInfo databaseInfo) {
try {
- CatalogInfo catalogInfo = getCatalogInfo(databaseInfo);
- Catalog catalog = CatalogUtils.getCatalog(catalogInfo);
- if (DatabaseManager.databaseExists(catalog, databaseInfo.getDatabaseName())) {
+ CatalogInfo catalogInfo = getCatalogInfo(databaseInfo.getCatalogName());
+ PaimonService service = PaimonServiceUtils.getPaimonService(catalogInfo);
+ if (service.databaseExists(databaseInfo.getDatabaseName())) {
return R.failed(Status.DATABASE_NAME_IS_EXIST, databaseInfo.getDatabaseName());
}
- DatabaseManager.createDatabase(catalog, databaseInfo.getDatabaseName());
+ service.createDatabase(databaseInfo.getDatabaseName());
return R.succeed();
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Exception with creating database.", e);
return R.failed(Status.DATABASE_CREATE_ERROR);
}
}
@@ -79,17 +80,18 @@ public R createDatabase(@RequestBody DatabaseInfo databaseInfo) {
public R> getAllDatabases() {
List databaseInfoList = new ArrayList<>();
List catalogInfoList = catalogService.list();
- if (catalogInfoList.size() > 0) {
+ if (!CollectionUtils.isEmpty(catalogInfoList)) {
catalogInfoList.forEach(
item -> {
- Catalog catalog = CatalogUtils.getCatalog(item);
- List list = DatabaseManager.listDatabase(catalog);
+ PaimonService service = PaimonServiceUtils.getPaimonService(item);
+ List list = service.listDatabases();
list.forEach(
databaseName -> {
DatabaseInfo info =
DatabaseInfo.builder()
.databaseName(databaseName)
.catalogId(item.getId())
+ .catalogName(item.getCatalogName())
.description("")
.build();
databaseInfoList.add(info);
@@ -99,34 +101,37 @@ public R> getAllDatabases() {
return R.succeed(databaseInfoList);
}
- /**
- * Retrieves the associated CatalogInfo object based on the given DatabaseInfo object.
- *
- * @param databaseInfo The DatabaseInfo object for which to retrieve the associated CatalogInfo.
- * @return The associated CatalogInfo object, or null if it doesn't exist.
- */
- private CatalogInfo getCatalogInfo(DatabaseInfo databaseInfo) {
- LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
- queryWrapper.eq(CatalogInfo::getId, databaseInfo.getCatalogId());
- return catalogService.getOne(queryWrapper);
- }
-
/**
* Removes a database by its name.
*
- * @param databaseInfo The information of the database to be removed.
+ * @param databaseName The database to be removed.
+ * @param catalogName The catalog to which the database to be removed belongs.
* @return A response indicating the success or failure of the removal operation.
* @throws RuntimeException if the database is not found or it is not empty.
*/
- @DeleteMapping("/delete")
- public R remove(@RequestBody DatabaseInfo databaseInfo) {
+ @DeleteMapping("/drop/{databaseName}/{catalogName}")
+ public R dropDatabase(
+ @PathVariable String databaseName, @PathVariable String catalogName) {
try {
- CatalogInfo catalogInfo = getCatalogInfo(databaseInfo);
- Catalog catalog = CatalogUtils.getCatalog(catalogInfo);
- DatabaseManager.dropDatabase(catalog, databaseInfo.getDatabaseName());
+ CatalogInfo catalogInfo = getCatalogInfo(catalogName);
+ PaimonService service = PaimonServiceUtils.getPaimonService(catalogInfo);
+ service.dropDatabase(databaseName);
return R.succeed();
- } catch (Catalog.DatabaseNotEmptyException | Catalog.DatabaseNotExistException e) {
- throw new RuntimeException(e);
+ } catch (Exception e) {
+ log.error("Exception with dropping database.", e);
+ return R.failed(Status.DATABASE_DROP_ERROR);
}
}
+
+ /**
+ * Retrieves the associated CatalogInfo object based on the given catalog name.
+ *
+ * @param catalogName The catalog name.
+ * @return The associated CatalogInfo object, or null if it doesn't exist.
+ */
+ private CatalogInfo getCatalogInfo(String catalogName) {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(CatalogInfo::getCatalogName, catalogName);
+ return catalogService.getOne(queryWrapper);
+ }
}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java
index 33eaa1c1e..07cc45f6a 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java
@@ -18,34 +18,41 @@
package org.apache.paimon.web.server.controller;
-import org.apache.paimon.catalog.Catalog;
import org.apache.paimon.table.Table;
import org.apache.paimon.types.DataField;
-import org.apache.paimon.web.api.database.DatabaseManager;
+import org.apache.paimon.web.api.catalog.PaimonService;
import org.apache.paimon.web.api.table.ColumnMetadata;
-import org.apache.paimon.web.api.table.TableManager;
+import org.apache.paimon.web.api.table.TableChange;
import org.apache.paimon.web.api.table.TableMetadata;
+import org.apache.paimon.web.server.data.model.AlterTableRequest;
import org.apache.paimon.web.server.data.model.CatalogInfo;
import org.apache.paimon.web.server.data.model.TableColumn;
import org.apache.paimon.web.server.data.model.TableInfo;
import org.apache.paimon.web.server.data.result.R;
import org.apache.paimon.web.server.data.result.enums.Status;
import org.apache.paimon.web.server.service.CatalogService;
-import org.apache.paimon.web.server.util.CatalogUtils;
import org.apache.paimon.web.server.util.DataTypeConvertUtils;
+import org.apache.paimon.web.server.util.PaimonDataType;
+import org.apache.paimon.web.server.util.PaimonServiceUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/** Table api controller. */
@Slf4j
@@ -53,6 +60,9 @@
@RequestMapping("/api/table")
public class TableController {
+ private static final String FIELDS_PREFIX = "FIELDS";
+ private static final String DEFAULT_VALUE_SUFFIX = "default-value";
+
@Autowired private CatalogService catalogService;
/**
@@ -61,12 +71,30 @@ public class TableController {
* @param tableInfo The TableInfo object containing information about the table.
* @return R indicating the success or failure of the operation.
*/
- @PostMapping("/createTable")
+ @PostMapping("/create")
public R createTable(@RequestBody TableInfo tableInfo) {
try {
- Catalog catalog = CatalogUtils.getCatalog(getCatalogInfo(tableInfo.getCatalogName()));
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
List partitionKeys = tableInfo.getPartitionKey();
+
Map tableOptions = tableInfo.getTableOptions();
+ List tableColumns = tableInfo.getTableColumns();
+ if (!CollectionUtils.isEmpty(tableColumns)) {
+ for (TableColumn tableColumn : tableColumns) {
+ if (tableColumn.getDefaultValue() != null
+ && !tableColumn.getDefaultValue().equals("")) {
+ tableOptions.put(
+ FIELDS_PREFIX
+ + "."
+ + tableColumn.getField()
+ + "."
+ + DEFAULT_VALUE_SUFFIX,
+ tableColumn.getDefaultValue());
+ }
+ }
+ }
+
TableMetadata tableMetadata =
TableMetadata.builder()
.columns(buildColumns(tableInfo))
@@ -75,19 +103,285 @@ public R createTable(@RequestBody TableInfo tableInfo) {
.options(tableOptions)
.comment(tableInfo.getDescription())
.build();
- if (TableManager.tableExists(
- catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+ if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
}
- TableManager.createTable(
- catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+ service.createTable(
+ tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
return R.succeed();
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Exception with creating table.", e);
return R.failed(Status.TABLE_CREATE_ERROR);
}
}
+ /**
+ * Adds a column to the table.
+ *
+ * @param tableInfo The information of the table, including the catalog name, database name,
+ * table name, and table columns.
+ * @return A response indicating the success or failure of the operation.
+ */
+ @PostMapping("/column/add")
+ public R addColumn(@RequestBody TableInfo tableInfo) {
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+ List tableColumns = tableInfo.getTableColumns();
+ List tableChanges = new ArrayList<>();
+ Map options = new HashMap<>();
+ for (TableColumn tableColumn : tableColumns) {
+ if (tableColumn.getDefaultValue() != null
+ && !tableColumn.getDefaultValue().equals("")) {
+ options.put(
+ FIELDS_PREFIX
+ + "."
+ + tableColumn.getField()
+ + "."
+ + DEFAULT_VALUE_SUFFIX,
+ tableColumn.getDefaultValue());
+ }
+ ColumnMetadata columnMetadata =
+ new ColumnMetadata(
+ tableColumn.getField(),
+ DataTypeConvertUtils.convert(
+ new PaimonDataType(
+ tableColumn.getDataType().getType(),
+ true,
+ tableColumn.getDataType().getPrecision(),
+ tableColumn.getDataType().getScale())),
+ tableColumn.getComment());
+ TableChange.AddColumn add = TableChange.add(columnMetadata);
+ tableChanges.add(add);
+ }
+
+ if (options.size() > 0) {
+ for (Map.Entry entry : options.entrySet()) {
+ TableChange.SetOption setOption =
+ TableChange.set(entry.getKey(), entry.getValue());
+ tableChanges.add(setOption);
+ }
+ }
+ service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with adding column.", e);
+ return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+ }
+ }
+
+ /**
+ * Drops a column from a table.
+ *
+ * @param catalogName The name of the catalog.
+ * @param databaseName The name of the database.
+ * @param tableName The name of the table.
+ * @param columnName The name of the column to be dropped.
+ * @return The result indicating the success or failure of the operation.
+ */
+ @DeleteMapping("/column/drop/{catalogName}/{databaseName}/{tableName}/{columnName}")
+ public R dropColumn(
+ @PathVariable String catalogName,
+ @PathVariable String databaseName,
+ @PathVariable String tableName,
+ @PathVariable String columnName) {
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(catalogName));
+ List tableChanges = new ArrayList<>();
+ TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+ tableChanges.add(dropColumn);
+ service.alterTable(databaseName, tableName, tableChanges);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with dropping column.", e);
+ return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+ }
+ }
+
+ /**
+ * Modify a column in a table.
+ *
+ * @param catalogName The name of the catalog.
+ * @param databaseName The name of the database.
+ * @param tableName The name of the table.
+ * @param alterTableRequest The param of the alter table request.
+ * @return A response indicating the success or failure of the operation.
+ */
+ @PostMapping("/alter")
+ public R alterTable(
+ @RequestParam String catalogName,
+ @RequestParam String databaseName,
+ @RequestParam String tableName,
+ @RequestBody AlterTableRequest alterTableRequest) {
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(catalogName));
+
+ TableColumn oldColumn = alterTableRequest.getOldColumn();
+ TableColumn newColumn = alterTableRequest.getNewColumn();
+
+ List tableChanges = createTableChanges(oldColumn, newColumn);
+
+ if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+ ColumnMetadata columnMetadata =
+ new ColumnMetadata(
+ oldColumn.getField(),
+ DataTypeConvertUtils.convert(oldColumn.getDataType()),
+ oldColumn.getComment());
+
+ TableChange.ModifyColumnName modifyColumnName =
+ TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+ List modifyNameTableChanges = new ArrayList<>();
+ modifyNameTableChanges.add(modifyColumnName);
+ service.alterTable(databaseName, tableName, modifyNameTableChanges);
+ }
+
+ service.alterTable(databaseName, tableName, tableChanges);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with altering table.", e);
+ return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+ }
+ }
+
+ private List createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+ ColumnMetadata columnMetadata =
+ new ColumnMetadata(
+ newColumn.getField(),
+ DataTypeConvertUtils.convert(oldColumn.getDataType()),
+ oldColumn.getComment());
+
+ TableChange.ModifyColumnType modifyColumnType =
+ TableChange.modifyColumnType(
+ columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+ TableChange.ModifyColumnComment modifyColumnComment =
+ TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+ List tableChanges = new ArrayList<>();
+ tableChanges.add(modifyColumnType);
+ tableChanges.add(modifyColumnComment);
+
+ return tableChanges;
+ }
+
+ /**
+ * Adds options to a table.
+ *
+ * @param tableInfo An object containing table information.
+ * @return If the options are successfully added, returns a successful result object. If an
+ * exception occurs, returns a result object with an error status.
+ */
+ @PostMapping("/option/add")
+ public R addOption(@RequestBody TableInfo tableInfo) {
+ List tableChanges = new ArrayList<>();
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+ Map tableOptions = tableInfo.getTableOptions();
+ for (Map.Entry entry : tableOptions.entrySet()) {
+ TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+ tableChanges.add(setOption);
+ }
+ service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with adding option.", e);
+ return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+ }
+ }
+
+ /**
+ * Removes an option from a table.
+ *
+ * @param catalogName The name of the catalog.
+ * @param databaseName The name of the database.
+ * @param tableName The name of the table.
+ * @param key The key of the option to be removed.
+ * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+ * option is successfully removed, the result will be a successful response with no data. If
+ * an error occurs during the operation, the result will be a failed response with an error
+ * code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+ */
+ @PostMapping("/option/remove")
+ public R removeOption(
+ @RequestParam String catalogName,
+ @RequestParam String databaseName,
+ @RequestParam String tableName,
+ @RequestParam String key) {
+ List tableChanges = new ArrayList<>();
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(catalogName));
+ TableChange.RemoveOption removeOption = TableChange.remove(key);
+ tableChanges.add(removeOption);
+ service.alterTable(databaseName, tableName, tableChanges);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with removing option.", e);
+ return R.failed(Status.TABLE_REMOVE_OPTION_ERROR);
+ }
+ }
+
+ /**
+ * Drops a table from the specified database in the given catalog.
+ *
+ * @param catalogName The name of the catalog from which the table will be dropped.
+ * @param databaseName The name of the database from which the table will be dropped.
+ * @param tableName The name of the table to be dropped.
+ * @return A Response object indicating the success or failure of the operation. If the
+ * operation is successful, the response will be R.succeed(). If the operation fails, the
+ * response will be R.failed() with Status.TABLE_DROP_ERROR.
+ * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+ * thrown with the error message.
+ */
+ @DeleteMapping("/drop/{catalogName}/{databaseName}/{tableName}")
+ public R dropTable(
+ @PathVariable String catalogName,
+ @PathVariable String databaseName,
+ @PathVariable String tableName) {
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(catalogName));
+ service.dropTable(databaseName, tableName);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with dropping table.", e);
+ return R.failed(Status.TABLE_DROP_ERROR);
+ }
+ }
+
+ /**
+ * Renames a table in the specified database of the given catalog.
+ *
+ * @param catalogName The name of the catalog where the table resides.
+ * @param databaseName The name of the database where the table resides.
+ * @param fromTableName The current name of the table to be renamed.
+ * @param toTableName The new name for the table.
+ * @return A Response object indicating the success or failure of the operation. If the
+ * operation is successful, the response will be R.succeed(). If the operation fails, the
+ * response will be R.failed() with Status.TABLE_RENAME_ERROR.
+ * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+ * thrown with the error message.
+ */
+ @PostMapping("/rename")
+ public R renameTable(
+ @RequestParam String catalogName,
+ @RequestParam String databaseName,
+ @RequestParam String fromTableName,
+ @RequestParam String toTableName) {
+ try {
+ PaimonService service =
+ PaimonServiceUtils.getPaimonService(getCatalogInfo(catalogName));
+ service.renameTable(databaseName, fromTableName, toTableName);
+ return R.succeed();
+ } catch (Exception e) {
+ log.error("Exception with renaming table.", e);
+ return R.failed(Status.TABLE_RENAME_ERROR);
+ }
+ }
+
/**
* Handler method for the "/getAllTables" endpoint. Retrieves information about all tables and
* returns a response containing the table details.
@@ -98,102 +392,71 @@ public R createTable(@RequestBody TableInfo tableInfo) {
public R> getAllTables() {
List tableInfoList = new ArrayList<>();
List catalogInfoList = catalogService.list();
- if (catalogInfoList.size() > 0) {
- catalogInfoList.forEach(
- item -> {
- Catalog catalog = CatalogUtils.getCatalog(item);
- List databaseList = DatabaseManager.listDatabase(catalog);
- if (databaseList.size() > 0) {
- databaseList.forEach(
- db -> {
- try {
- List tables =
- TableManager.listTables(catalog, db);
- if (tables.size() > 0) {
- tables.forEach(
- t -> {
- try {
- Table table =
- TableManager.getTable(
- catalog, db, t);
- if (table != null) {
- List primaryKeys =
- table.primaryKeys();
- List fields =
- table.rowType()
- .getFields();
- List tableColumns =
- new ArrayList<>();
- if (fields.size() > 0) {
- fields.forEach(
- field -> {
- TableColumn
- .TableColumnBuilder
- builder =
- TableColumn
- .builder()
- .field(
- field
- .name())
- .dataType(
- DataTypeConvertUtils
- .fromPaimonType(
- field
- .type()))
- .comment(
- field
- .description());
- if (primaryKeys
- .size()
- > 0
- && primaryKeys
- .contains(
- field
- .name())) {
- builder
- .isPK(
- true);
- }
- tableColumns
- .add(
- builder
- .build());
- });
- }
- TableInfo tableInfo =
- TableInfo.builder()
- .catalogName(
- item
- .getCatalogName())
- .databaseName(
- db)
- .tableName(
- table
- .name())
- .partitionKey(
- table
- .partitionKeys())
- .tableOptions(
- table
- .options())
- .tableColumns(
- tableColumns)
- .build();
- tableInfoList.add(tableInfo);
- }
- } catch (
- Catalog.TableNotExistException
- e) {
- throw new RuntimeException(e);
- }
- });
+ if (!CollectionUtils.isEmpty(catalogInfoList)) {
+ for (CatalogInfo item : catalogInfoList) {
+ PaimonService service = PaimonServiceUtils.getPaimonService(item);
+ List databaseList = service.listDatabases();
+ if (!CollectionUtils.isEmpty(databaseList)) {
+ for (String db : databaseList) {
+ try {
+ List tables = service.listTables(db);
+ if (!CollectionUtils.isEmpty(tables)) {
+ for (String t : tables) {
+ try {
+ Table table = service.getTable(db, t);
+ if (table != null) {
+ List primaryKeys = table.primaryKeys();
+ List fields = table.rowType().getFields();
+ List tableColumns = new ArrayList<>();
+ Map options = table.options();
+ if (!CollectionUtils.isEmpty(fields)) {
+ for (DataField field : fields) {
+ String key =
+ FIELDS_PREFIX
+ + "."
+ + field.name()
+ + "."
+ + DEFAULT_VALUE_SUFFIX;
+ PaimonDataType dataType =
+ DataTypeConvertUtils.fromPaimonType(
+ field.type());
+ TableColumn.TableColumnBuilder builder =
+ TableColumn.builder()
+ .field(field.name())
+ .dataType(dataType)
+ .comment(field.description());
+ if (primaryKeys.size() > 0
+ && primaryKeys.contains(field.name())) {
+ builder.isPk(true);
+ }
+ if (options.get(key) != null) {
+ builder.defaultValue(options.get(key));
+ }
+ tableColumns.add(builder.build());
+ }
}
- } catch (Catalog.DatabaseNotExistException e) {
- throw new RuntimeException(e);
+ TableInfo tableInfo =
+ TableInfo.builder()
+ .catalogName(item.getCatalogName())
+ .databaseName(db)
+ .tableName(table.name())
+ .partitionKey(table.partitionKeys())
+ .tableOptions(table.options())
+ .tableColumns(tableColumns)
+ .build();
+ tableInfoList.add(tableInfo);
}
- });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
- });
+ }
+ }
+ }
}
return R.succeed(tableInfoList);
}
@@ -207,10 +470,10 @@ public R> getAllTables() {
private List buildPrimaryKeys(TableInfo tableInfo) {
List primaryKeys = new ArrayList<>();
List tableColumns = tableInfo.getTableColumns();
- if (tableColumns != null && tableColumns.size() > 0) {
+ if (!CollectionUtils.isEmpty(tableColumns)) {
tableColumns.forEach(
item -> {
- if (item.isPK()) {
+ if (item.isPk()) {
primaryKeys.add(item.getField());
}
});
@@ -227,13 +490,18 @@ private List buildPrimaryKeys(TableInfo tableInfo) {
private List buildColumns(TableInfo tableInfo) {
List columns = new ArrayList<>();
List tableColumns = tableInfo.getTableColumns();
- if (tableColumns != null && tableColumns.size() > 0) {
+ if (!CollectionUtils.isEmpty(tableColumns)) {
tableColumns.forEach(
item -> {
ColumnMetadata columnMetadata =
new ColumnMetadata(
item.getField(),
- DataTypeConvertUtils.convert(item.getDataType()),
+ DataTypeConvertUtils.convert(
+ new PaimonDataType(
+ item.getDataType().getType(),
+ item.getDataType().isNullable(),
+ item.getDataType().getPrecision(),
+ item.getDataType().getScale())),
item.getComment() != null ? item.getComment() : null);
columns.add(columnMetadata);
});
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/enums/CatalogMode.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/enums/CatalogMode.java
new file mode 100644
index 000000000..2dc029ad1
--- /dev/null
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/enums/CatalogMode.java
@@ -0,0 +1,35 @@
+/*
+ * 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.paimon.web.server.data.enums;
+
+/** Enum representing different catalog modes. */
+public enum CatalogMode {
+ FILESYSTEM("filesystem"),
+ HIVE("hive");
+
+ private final String mode;
+
+ CatalogMode(String mode) {
+ this.mode = mode;
+ }
+
+ public String getMode() {
+ return mode;
+ }
+}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/AlterTableRequest.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/AlterTableRequest.java
new file mode 100644
index 000000000..73f1873f9
--- /dev/null
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/AlterTableRequest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.paimon.web.server.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/** Alter table request. */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AlterTableRequest {
+
+ private TableColumn oldColumn;
+
+ private TableColumn newColumn;
+}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/DatabaseInfo.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/DatabaseInfo.java
index 0f93b7805..5789ec01a 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/DatabaseInfo.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/DatabaseInfo.java
@@ -34,5 +34,7 @@ public class DatabaseInfo {
private Integer catalogId;
+ private String catalogName;
+
private String description;
}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/TableColumn.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/TableColumn.java
index 0593dea0d..62ea3d28a 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/TableColumn.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/TableColumn.java
@@ -18,11 +18,15 @@
package org.apache.paimon.web.server.data.model;
+import org.apache.paimon.web.server.util.PaimonDataType;
+
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import javax.annotation.Nullable;
+
/** TableColumn model. */
@Data
@Builder
@@ -32,11 +36,11 @@ public class TableColumn {
private String field;
- private String dataType;
+ private PaimonDataType dataType;
- private String comment;
+ @Nullable private String comment;
- private boolean isPK;
+ @Nullable private boolean isPk;
- private String defaultValue;
+ @Nullable private String defaultValue;
}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
index c6e5788cc..5239e85e2 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
@@ -56,15 +56,23 @@ public enum Status {
/** ------------catalog-----------------. */
CATALOG_NAME_IS_EXIST(10301, "catalog.name.exist"),
CATALOG_CREATE_ERROR(10302, "catalog.create.error"),
+ CATALOG_REMOVE_ERROR(10303, "catalog.remove.error"),
/** ------------database-----------------. */
DATABASE_NAME_IS_EXIST(10401, "database.name.exist"),
DATABASE_CREATE_ERROR(10402, "database.create.error"),
+ DATABASE_DROP_ERROR(10403, "database.drop.error"),
/** ------------table-----------------. */
TABLE_NAME_IS_EXIST(10501, "table.name.exist"),
TABLE_CREATE_ERROR(10502, "table.create.error"),
- ;
+ TABLE_ADD_COLUMN_ERROR(10503, "table.add.column.error"),
+ TABLE_ADD_OPTION_ERROR(10504, "table.add.option.error"),
+ TABLE_REMOVE_OPTION_ERROR(10505, "table.remove.option.error"),
+ TABLE_DROP_COLUMN_ERROR(10506, "table.drop.column.error"),
+ TABLE_AlTER_COLUMN_ERROR(10507, "table.alter.column.error"),
+ TABLE_DROP_ERROR(10510, "table.drop.error"),
+ TABLE_RENAME_ERROR(10510, "table.rename.error");
private final int code;
private final String msg;
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/CatalogUtils.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/CatalogUtils.java
deleted file mode 100644
index c19668ed3..000000000
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/CatalogUtils.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.paimon.web.server.util;
-
-import org.apache.paimon.catalog.Catalog;
-import org.apache.paimon.web.api.catalog.CatalogCreator;
-import org.apache.paimon.web.server.data.model.CatalogInfo;
-
-/** catalog util. */
-public class CatalogUtils {
-
- /**
- * Get a Catalog based on the provided CatalogInfo.
- *
- * @param catalogInfo The CatalogInfo object containing the catalog details.
- * @return The created Catalog object.
- */
- public static Catalog getCatalog(CatalogInfo catalogInfo) {
- if ("filesystem".equals(catalogInfo.getCatalogType())) {
- return CatalogCreator.createFilesystemCatalog(catalogInfo.getWarehouse());
- } else {
- return CatalogCreator.createHiveCatalog(
- catalogInfo.getWarehouse(),
- catalogInfo.getHiveUri(),
- catalogInfo.getHiveConfDir());
- }
- }
-}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/DataTypeConvertUtils.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/DataTypeConvertUtils.java
index aa4f9f99e..298cc83d7 100644
--- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/DataTypeConvertUtils.java
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/DataTypeConvertUtils.java
@@ -18,74 +18,167 @@
package org.apache.paimon.web.server.util;
+import org.apache.paimon.types.BigIntType;
+import org.apache.paimon.types.BinaryType;
+import org.apache.paimon.types.BooleanType;
+import org.apache.paimon.types.CharType;
import org.apache.paimon.types.DataType;
-import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.DateType;
+import org.apache.paimon.types.DecimalType;
+import org.apache.paimon.types.DoubleType;
+import org.apache.paimon.types.FloatType;
+import org.apache.paimon.types.IntType;
+import org.apache.paimon.types.LocalZonedTimestampType;
+import org.apache.paimon.types.SmallIntType;
+import org.apache.paimon.types.TimeType;
+import org.apache.paimon.types.TimestampType;
+import org.apache.paimon.types.TinyIntType;
+import org.apache.paimon.types.VarBinaryType;
+import org.apache.paimon.types.VarCharType;
/** data type convert util. */
public class DataTypeConvertUtils {
- public static DataType convert(String type) {
- switch (type) {
+ public static DataType convert(PaimonDataType type) {
+ switch (type.getType()) {
case "INT":
- return DataTypes.INT();
+ return new IntType(type.isNullable());
case "TINYINT":
- return DataTypes.TINYINT();
+ return new TinyIntType(type.isNullable());
case "SMALLINT":
- return DataTypes.SMALLINT();
+ return new SmallIntType(type.isNullable());
case "BIGINT":
- return DataTypes.BIGINT();
+ return new BigIntType(type.isNullable());
+ case "CHAR":
+ return new CharType(
+ type.isNullable(), type.getPrecision() > 0 ? type.getPrecision() : 1);
+ case "VARCHAR":
+ return new VarCharType(
+ type.isNullable(), type.getPrecision() > 0 ? type.getPrecision() : 1);
case "STRING":
- return DataTypes.STRING();
+ return new VarCharType(type.isNullable(), Integer.MAX_VALUE);
+ case "BINARY":
+ return new BinaryType(
+ type.isNullable(), type.getPrecision() > 0 ? type.getPrecision() : 1);
+ case "VARBINARY":
+ return new VarBinaryType(
+ type.isNullable(), type.getPrecision() > 0 ? type.getPrecision() : 1);
case "DOUBLE":
- return DataTypes.DOUBLE();
+ return new DoubleType(type.isNullable());
case "BOOLEAN":
- return DataTypes.BOOLEAN();
+ return new BooleanType(type.isNullable());
case "DATE":
- return DataTypes.DATE();
+ return new DateType(type.isNullable());
case "TIME":
- return DataTypes.TIME();
+ return new TimeType(type.isNullable(), 0);
+ case "TIME(precision)":
+ return new TimeType(type.isNullable(), type.getPrecision());
case "TIMESTAMP":
- return DataTypes.TIMESTAMP();
+ return new TimestampType(type.isNullable(), 0);
+ case "TIMESTAMP(precision)":
+ return new TimestampType(type.isNullable(), type.getPrecision());
+ case "TIMESTAMP_MILLIS":
+ return new TimestampType(type.isNullable(), 3);
case "BYTES":
- return DataTypes.BYTES();
+ return new VarBinaryType(type.isNullable(), 0);
case "FLOAT":
- return DataTypes.FLOAT();
+ return new FloatType(type.isNullable());
case "DECIMAL":
- return DataTypes.DECIMAL(38, 0);
+ return new DecimalType(type.isNullable(), type.getPrecision(), type.getScale());
+ case "TIMESTAMP_WITH_LOCAL_TIME_ZONE":
+ return new LocalZonedTimestampType(type.isNullable(), 0);
+ case "TIMESTAMP_WITH_LOCAL_TIME_ZONE(precision)":
+ return new LocalZonedTimestampType(type.isNullable(), type.getPrecision());
default:
throw new RuntimeException("Invalid type: " + type);
}
}
- public static String fromPaimonType(DataType dataType) {
- if (dataType.equals(DataTypes.INT())) {
- return "INT";
- } else if (dataType.equals(DataTypes.TINYINT())) {
- return "TINYINT";
- } else if (dataType.equals(DataTypes.SMALLINT())) {
- return "SMALLINT";
- } else if (dataType.equals(DataTypes.BIGINT())) {
- return "BIGINT";
- } else if (dataType.equals(DataTypes.STRING())) {
- return "STRING";
- } else if (dataType.equals(DataTypes.DOUBLE())) {
- return "DOUBLE";
- } else if (dataType.equals(DataTypes.BOOLEAN())) {
- return "BOOLEAN";
- } else if (dataType.equals(DataTypes.DATE())) {
- return "DATE";
- } else if (dataType.equals(DataTypes.TIME())) {
- return "TIME";
- } else if (dataType.equals(DataTypes.TIMESTAMP())) {
- return "TIMESTAMP";
- } else if (dataType.equals(DataTypes.BYTES())) {
- return "BYTES";
- } else if (dataType.equals(DataTypes.FLOAT())) {
- return "FLOAT";
- } else if (dataType.equals(DataTypes.DECIMAL(38, 0))) {
- return "DECIMAL";
+ public static PaimonDataType fromPaimonType(DataType dataType) {
+ if (dataType instanceof IntType) {
+ return new PaimonDataType("INT", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof TinyIntType) {
+ return new PaimonDataType("TINYINT", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof SmallIntType) {
+ return new PaimonDataType("SMALLINT", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof BigIntType) {
+ return new PaimonDataType("BIGINT", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof VarCharType) {
+ VarCharType varCharType = (VarCharType) dataType;
+ if (varCharType.getLength() == Integer.MAX_VALUE) {
+ return new PaimonDataType("STRING", varCharType.isNullable(), 0, 0);
+ } else {
+ return new PaimonDataType(
+ "VARCHAR", varCharType.isNullable(), varCharType.getLength(), 0);
+ }
+ } else if (dataType instanceof CharType) {
+ CharType charType = (CharType) dataType;
+ return new PaimonDataType("CHAR", charType.isNullable(), charType.getLength(), 0);
+ } else if (dataType instanceof BinaryType) {
+ BinaryType binaryType = (BinaryType) dataType;
+ return new PaimonDataType("BINARY", binaryType.isNullable(), binaryType.getLength(), 0);
+ } else if (dataType instanceof VarBinaryType) {
+ VarBinaryType varBinaryType = (VarBinaryType) dataType;
+ if (varBinaryType.getLength() == Integer.MAX_VALUE) {
+ return new PaimonDataType(
+ "BYTES", varBinaryType.isNullable(), varBinaryType.getLength(), 0);
+ } else {
+ return new PaimonDataType(
+ "VARBINARY", varBinaryType.isNullable(), varBinaryType.getLength(), 0);
+ }
+ } else if (dataType instanceof DoubleType) {
+ return new PaimonDataType("DOUBLE", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof BooleanType) {
+ return new PaimonDataType("BOOLEAN", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof DateType) {
+ return new PaimonDataType("DATE", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof TimeType) {
+ TimeType timeType = (TimeType) dataType;
+ if (timeType.getPrecision() == 0) {
+ return new PaimonDataType("TIME", timeType.isNullable(), 0, 0);
+ } else {
+ return new PaimonDataType(
+ "TIME(precision)", timeType.isNullable(), timeType.getPrecision(), 0);
+ }
+ } else if (dataType instanceof TimestampType) {
+ TimestampType timestampType = (TimestampType) dataType;
+ if (timestampType.getPrecision() == 0) {
+ return new PaimonDataType("TIMESTAMP", timestampType.isNullable(), 0, 0);
+ } else if (timestampType.getPrecision() == 3) {
+ return new PaimonDataType("TIMESTAMP_MILLIS", timestampType.isNullable(), 3, 0);
+ } else {
+ return new PaimonDataType(
+ "TIMESTAMP(precision)",
+ timestampType.isNullable(),
+ timestampType.getPrecision(),
+ 0);
+ }
+ } else if (dataType instanceof FloatType) {
+ return new PaimonDataType("FLOAT", dataType.isNullable(), 0, 0);
+ } else if (dataType instanceof DecimalType) {
+ DecimalType decimalType = (DecimalType) dataType;
+ return new PaimonDataType(
+ "DECIMAL",
+ decimalType.isNullable(),
+ decimalType.getPrecision(),
+ decimalType.getScale());
+ } else if (dataType instanceof LocalZonedTimestampType) {
+ LocalZonedTimestampType localZonedTimestampType = (LocalZonedTimestampType) dataType;
+ if (localZonedTimestampType.getPrecision() == 0) {
+ return new PaimonDataType(
+ "TIMESTAMP_WITH_LOCAL_TIME_ZONE",
+ localZonedTimestampType.isNullable(),
+ 0,
+ 0);
+ } else {
+ return new PaimonDataType(
+ "TIMESTAMP_WITH_LOCAL_TIME_ZONE(precision)",
+ localZonedTimestampType.isNullable(),
+ localZonedTimestampType.getPrecision(),
+ 0);
+ }
} else {
- return "UNKNOWN";
+ return new PaimonDataType("UNKNOWN", dataType.isNullable(), 0, 0);
}
}
}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/PaimonDataType.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/PaimonDataType.java
new file mode 100644
index 000000000..76567ee66
--- /dev/null
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/PaimonDataType.java
@@ -0,0 +1,40 @@
+/*
+ * 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.paimon.web.server.util;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/** Paimon data type. */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PaimonDataType {
+
+ private String type;
+
+ private boolean isNullable;
+
+ private Integer precision;
+
+ private Integer scale;
+}
diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/PaimonServiceUtils.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/PaimonServiceUtils.java
new file mode 100644
index 000000000..f4e3410d3
--- /dev/null
+++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/util/PaimonServiceUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.paimon.web.server.util;
+
+import org.apache.paimon.web.api.catalog.PaimonService;
+import org.apache.paimon.web.api.catalog.PaimonServiceFactory;
+import org.apache.paimon.web.server.data.enums.CatalogMode;
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+
+import org.apache.commons.lang3.StringUtils;
+
+/** Paimon Service util. */
+public class PaimonServiceUtils {
+
+ /**
+ * Get a Paimon Service based on the provided CatalogInfo.
+ *
+ * @param catalogInfo The CatalogInfo object containing the catalog details.
+ * @return The created PaimonService object.
+ */
+ public static PaimonService getPaimonService(CatalogInfo catalogInfo) {
+ PaimonService service;
+ if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.FILESYSTEM.getMode())) {
+ service =
+ PaimonServiceFactory.createFileSystemCatalogService(
+ catalogInfo.getCatalogName(), catalogInfo.getWarehouse());
+ } else if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.HIVE.getMode())) {
+ if (StringUtils.isNotBlank(catalogInfo.getHiveConfDir())) {
+ service =
+ PaimonServiceFactory.createHiveCatalogService(
+ catalogInfo.getCatalogName(),
+ catalogInfo.getWarehouse(),
+ catalogInfo.getHiveUri(),
+ catalogInfo.getHiveConfDir());
+ } else {
+ service =
+ PaimonServiceFactory.createHiveCatalogService(
+ catalogInfo.getCatalogName(),
+ catalogInfo.getWarehouse(),
+ catalogInfo.getHiveUri(),
+ null);
+ }
+ } else {
+ service =
+ PaimonServiceFactory.createFileSystemCatalogService(
+ catalogInfo.getCatalogName(), catalogInfo.getWarehouse());
+ }
+ return service;
+ }
+}
diff --git a/paimon-web-server/src/main/resources/application-dev.yml b/paimon-web-server/src/main/resources/application-dev-h2.yml
similarity index 100%
rename from paimon-web-server/src/main/resources/application-dev.yml
rename to paimon-web-server/src/main/resources/application-dev-h2.yml
diff --git a/paimon-web-server/src/main/resources/application-dev-mysql.yml b/paimon-web-server/src/main/resources/application-dev-mysql.yml
new file mode 100644
index 000000000..67b2fb6d4
--- /dev/null
+++ b/paimon-web-server/src/main/resources/application-dev-mysql.yml
@@ -0,0 +1,21 @@
+# 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.
+
+spring:
+ datasource:
+ url: jdbc:mysql://${MYSQL_ADDR:127.0.0.1:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ username: ${MYSQL_USERNAME:username}
+ password: ${MYSQL_PASSWORD:password}
+ driver-class-name: com.mysql.cj.jdbc.Driver
\ No newline at end of file
diff --git a/paimon-web-server/src/main/resources/application-prod.yml b/paimon-web-server/src/main/resources/application-prod.yml
index 133797283..67b2fb6d4 100644
--- a/paimon-web-server/src/main/resources/application-prod.yml
+++ b/paimon-web-server/src/main/resources/application-prod.yml
@@ -15,7 +15,7 @@
spring:
datasource:
- url: jdbc:mysql://${MYSQL_ADDR:124.221.249.188:3887}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
- username: ${MYSQL_USERNAME:root}
- password: ${MYSQL_PASSWORD:Zhumingye520!@#.}
+ url: jdbc:mysql://${MYSQL_ADDR:127.0.0.1:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ username: ${MYSQL_USERNAME:username}
+ password: ${MYSQL_PASSWORD:password}
driver-class-name: com.mysql.cj.jdbc.Driver
\ No newline at end of file
diff --git a/paimon-web-server/src/main/resources/application.yml b/paimon-web-server/src/main/resources/application.yml
index c978065aa..db7c962e9 100644
--- a/paimon-web-server/src/main/resources/application.yml
+++ b/paimon-web-server/src/main/resources/application.yml
@@ -22,7 +22,7 @@ spring:
application:
name: Paimon-Web-UI
profiles:
- active: dev
+ active: dev-mysql
messages:
basename: i18n/messages
encoding: UTF-8
diff --git a/paimon-web-server/src/main/resources/i18n/messages.properties b/paimon-web-server/src/main/resources/i18n/messages.properties
index 86a4bc8b7..37610ec3c 100644
--- a/paimon-web-server/src/main/resources/i18n/messages.properties
+++ b/paimon-web-server/src/main/resources/i18n/messages.properties
@@ -34,8 +34,17 @@ menu.in.used=This menu is in used
menu.name.exist=This menu name is exist:{0}
menu.path.invalid=This menu path is invalid:{0}
catalog.name.exist=This catalog name {0} is exist
-catalog.create.error=An exception is returned when calling the Paimon API to create a Catalog.
+catalog.create.error=Exception calling Paimon Catalog API to create a Catalog.
+catalog.remove.error=Exception calling Paimon Catalog API to remove a Catalog.
database.name.exist=This database name {0} is exist.
-database.create.error=An exception is returned when calling the Paimon API to create a Database.
+database.create.error=Exception calling Paimon Catalog API to create a Database.
+database.drop.error=Exception calling Paimon Catalog API to drop a Database.
table.name.exist=This table name {0} is exist.
-table.create.error=An exception is returned when calling the Paimon API to create a Table.
+table.create.error=Exception calling Paimon Catalog API to create a Table.
+table.add.column.error=Exception calling Paimon Catalog API to add a Column.
+table.drop.column.error=Exception calling Paimon Catalog API to drop a Column.
+table.add.option.error=Exception calling Paimon Catalog API to add an option.
+table.remove.option.error=Exception calling Paimon Catalog API to remove an option.
+table.alter.column.error=Exception calling Paimon Catalog API to alter a column.
+table.drop.error=Exception calling Paimon Catalog API to drop a table.
+table.rename.error=Exception calling Paimon Catalog API to rename a table.
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java
new file mode 100644
index 000000000..e96ee1389
--- /dev/null
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for CatalogController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class CatalogControllerTest extends ControllerTestBase {
+
+ private static final String catalogPath = "/api/catalog";
+
+ @TempDir java.nio.file.Path tempFile;
+
+ private static final String catalogName = "testCatalog";
+
+ @Test
+ public void testCreateCatalog() throws Exception {
+ CatalogInfo catalogInfo = new CatalogInfo();
+ catalogInfo.setCatalogType("filesystem");
+ catalogInfo.setCatalogName(catalogName);
+ catalogInfo.setWarehouse(tempFile.toUri().toString());
+ catalogInfo.setDelete(false);
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(catalogPath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(catalogInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(catalogPath + "/remove/" + catalogName)
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+ }
+
+ @Test
+ public void testGetAllCatalogs() throws Exception {
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.get(catalogPath + "/getAllCatalogs")
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+}
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/ControllerTestBase.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/ControllerTestBase.java
new file mode 100644
index 000000000..d0bb0a570
--- /dev/null
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/ControllerTestBase.java
@@ -0,0 +1,199 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.dto.LoginDto;
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.model.DatabaseInfo;
+import org.apache.paimon.web.server.data.model.TableColumn;
+import org.apache.paimon.web.server.data.model.TableInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+import org.apache.paimon.web.server.util.PaimonDataType;
+import org.apache.paimon.web.server.util.StringUtils;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockCookie;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** ControllerTestBase. */
+@SpringBootTest
+@AutoConfigureMockMvc
+public class ControllerTestBase {
+
+ private static final String loginPath = "/api/login";
+ private static final String logoutPath = "/api/logout";
+ private static final String catalogPath = "/api/catalog";
+ private static final String databasePath = "/api/database";
+ private static final String tablePath = "/api/table";
+
+ @Value("${spring.application.name}")
+ private String tokenName;
+
+ @Autowired public MockMvc mockMvc;
+
+ public static MockCookie cookie;
+
+ @TempDir java.nio.file.Path tempFile;
+
+ private static final String catalogName = "paimon_catalog";
+
+ private static final String databaseName = "paimon_database";
+
+ private static final String tableName = "paimon_table";
+
+ @BeforeEach
+ public void before() throws Exception {
+ LoginDto login = new LoginDto();
+ login.setUsername("admin");
+ login.setPassword("admin");
+
+ MockHttpServletResponse response =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(loginPath)
+ .content(ObjectMapperUtils.toJSON(login))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse();
+ String result = response.getContentAsString();
+ R> r = ObjectMapperUtils.fromJSON(result, R.class);
+ assertEquals(200, r.getCode());
+
+ assertTrue(StringUtils.isNotBlank(r.getData().toString()));
+
+ cookie = (MockCookie) response.getCookie(tokenName);
+
+ // create default catalog
+ CatalogInfo catalogInfo = new CatalogInfo();
+ catalogInfo.setCatalogType("filesystem");
+ catalogInfo.setCatalogName(catalogName);
+ catalogInfo.setWarehouse(tempFile.toUri().toString());
+ catalogInfo.setDelete(false);
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(catalogPath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(catalogInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+
+ // create default database
+ DatabaseInfo databaseInfo = new DatabaseInfo();
+ databaseInfo.setDatabaseName(databaseName);
+ databaseInfo.setCatalogName(catalogName);
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(databasePath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(databaseInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+
+ // create default table
+ List tableColumns = new ArrayList<>();
+ TableColumn id =
+ new TableColumn("id", PaimonDataType.builder().type("INT").build(), "", false, "0");
+ TableColumn name =
+ new TableColumn(
+ "name", PaimonDataType.builder().type("STRING").build(), "", false, "0");
+ tableColumns.add(id);
+ tableColumns.add(name);
+ TableInfo tableInfo =
+ TableInfo.builder()
+ .catalogName(catalogName)
+ .databaseName(databaseName)
+ .tableName(tableName)
+ .tableColumns(tableColumns)
+ .partitionKey(Lists.newArrayList())
+ .tableOptions(Maps.newHashMap())
+ .build();
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(tableInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+ }
+
+ @AfterEach
+ public void after() throws Exception {
+ String result =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(logoutPath)
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ R> r = ObjectMapperUtils.fromJSON(result, R.class);
+ assertEquals(200, r.getCode());
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(
+ tablePath
+ + "/drop/"
+ + catalogName
+ + "/"
+ + databaseName
+ + "/"
+ + "test_table")
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(
+ databasePath + "/drop/" + databaseName + "/" + catalogName)
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(catalogPath + "/remove/" + catalogName)
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+ }
+}
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/DatabaseControllerTest.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/DatabaseControllerTest.java
new file mode 100644
index 000000000..d23e8fd1e
--- /dev/null
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/DatabaseControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.DatabaseInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for DatabaseController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+public class DatabaseControllerTest extends ControllerTestBase {
+
+ private static final String databasePath = "/api/database";
+
+ private static final String catalogName = "paimon_catalog";
+
+ @Test
+ public void testCreateDatabase() throws Exception {
+ DatabaseInfo databaseInfo = new DatabaseInfo();
+ databaseInfo.setDatabaseName("test_db");
+ databaseInfo.setCatalogName(catalogName);
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(databasePath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(databaseInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(databasePath + "/drop/" + "test_db/" + catalogName)
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+ }
+
+ @Test
+ public void testGetDatabases() throws Exception {
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.get(databasePath + "/getAllDatabases")
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+}
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/PermissionTest.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/PermissionTest.java
index 21d22dd85..1e59b35fc 100644
--- a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/PermissionTest.java
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/PermissionTest.java
@@ -18,103 +18,43 @@
package org.apache.paimon.web.server.controller;
-import org.apache.paimon.web.server.data.dto.LoginDto;
import org.apache.paimon.web.server.data.model.User;
import org.apache.paimon.web.server.data.result.R;
import org.apache.paimon.web.server.data.result.enums.Status;
import org.apache.paimon.web.server.util.ObjectMapperUtils;
-import org.apache.paimon.web.server.util.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
-import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
/** Test of permission service. */
@SpringBootTest
@AutoConfigureMockMvc
-public class PermissionTest {
- private static final String loginPath = "/api/login";
- private static final String logoutPath = "/api/logout";
-
+public class PermissionTest extends ControllerTestBase {
private static final String getUserPath = "/api/user";
- @Value("${spring.application.name}")
- private String tokenName;
-
- @Autowired private MockMvc mockMvc;
-
- private String token;
-
- @BeforeEach
- public void before() throws Exception {
- LoginDto login = new LoginDto();
- login.setUsername("common");
- login.setPassword("21232f297a57a5a743894a0e4a801fc3");
-
- String result =
- mockMvc.perform(
- MockMvcRequestBuilders.post(loginPath)
- .content(ObjectMapperUtils.toJSON(login))
- .contentType(MediaType.APPLICATION_JSON_VALUE)
- .accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().isOk())
- .andDo(MockMvcResultHandlers.print())
- .andReturn()
- .getResponse()
- .getContentAsString();
- R> r = ObjectMapperUtils.fromJSON(result, R.class);
- assertEquals(200, r.getCode());
-
- assertTrue(StringUtils.isNotBlank(r.getData().toString()));
-
- this.token = r.getData().toString();
- }
-
- @AfterEach
- public void after() throws Exception {
- String result =
- mockMvc.perform(
- MockMvcRequestBuilders.post(logoutPath)
- .header(tokenName, token)
- .contentType(MediaType.APPLICATION_JSON_VALUE)
- .accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().isOk())
- .andDo(MockMvcResultHandlers.print())
- .andReturn()
- .getResponse()
- .getContentAsString();
- R> r = ObjectMapperUtils.fromJSON(result, R.class);
- assertEquals(200, r.getCode());
- }
-
@Test
public void testNoPermission() throws Exception {
String responseString =
mockMvc.perform(
MockMvcRequestBuilders.get(getUserPath + "/" + 1)
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().is4xxClientError())
+ .andExpect(MockMvcResultMatchers.status().is2xxSuccessful())
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse()
.getContentAsString();
R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
- assertEquals(Status.FORBIDDEN.getCode(), r.getCode());
+ assertEquals(Status.SUCCESS.getCode(), r.getCode());
}
}
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysMenuControllerTest.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysMenuControllerTest.java
index 6d9f09e23..16cef6222 100644
--- a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysMenuControllerTest.java
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysMenuControllerTest.java
@@ -18,24 +18,17 @@
package org.apache.paimon.web.server.controller;
-import org.apache.paimon.web.server.data.dto.LoginDto;
import org.apache.paimon.web.server.data.model.SysMenu;
import org.apache.paimon.web.server.data.result.R;
import org.apache.paimon.web.server.data.tree.TreeSelect;
import org.apache.paimon.web.server.data.vo.RoleMenuTreeselectVo;
import org.apache.paimon.web.server.util.ObjectMapperUtils;
-import org.apache.paimon.web.server.util.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
-import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@@ -49,67 +42,16 @@
/** Test for SysMenuController. */
@SpringBootTest
@AutoConfigureMockMvc
-public class SysMenuControllerTest {
+public class SysMenuControllerTest extends ControllerTestBase {
private static final String menuPath = "/api/menu";
- private static final String loginPath = "/api/login";
- private static final String logoutPath = "/api/logout";
-
- @Value("${spring.application.name}")
- private String tokenName;
-
- @Autowired private MockMvc mockMvc;
-
- private String token;
-
- @BeforeEach
- public void before() throws Exception {
- LoginDto login = new LoginDto();
- login.setUsername("admin");
- login.setPassword("21232f297a57a5a743894a0e4a801fc3");
-
- String result =
- mockMvc.perform(
- MockMvcRequestBuilders.post(loginPath)
- .content(ObjectMapperUtils.toJSON(login))
- .contentType(MediaType.APPLICATION_JSON_VALUE)
- .accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().isOk())
- .andDo(MockMvcResultHandlers.print())
- .andReturn()
- .getResponse()
- .getContentAsString();
- R> r = ObjectMapperUtils.fromJSON(result, R.class);
- assertEquals(200, r.getCode());
-
- assertTrue(StringUtils.isNotBlank(r.getData().toString()));
-
- this.token = r.getData().toString();
- }
-
- @AfterEach
- public void after() throws Exception {
- String result =
- mockMvc.perform(
- MockMvcRequestBuilders.post(logoutPath)
- .header(tokenName, token)
- .contentType(MediaType.APPLICATION_JSON_VALUE)
- .accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().isOk())
- .andDo(MockMvcResultHandlers.print())
- .andReturn()
- .getResponse()
- .getContentAsString();
- R> r = ObjectMapperUtils.fromJSON(result, R.class);
- assertEquals(200, r.getCode());
- }
@Test
public void testList() throws Exception {
String result =
mockMvc.perform(
MockMvcRequestBuilders.get(menuPath + "/list")
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
@@ -129,7 +71,7 @@ public void testGetInfo() throws Exception {
String result =
mockMvc.perform(
MockMvcRequestBuilders.get(menuPath + "/1")
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
@@ -148,7 +90,7 @@ public void testGetTreeselect() throws Exception {
String result =
mockMvc.perform(
MockMvcRequestBuilders.get(menuPath + "/treeselect")
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
@@ -168,7 +110,7 @@ public void testGetRoleMenuTreeselect() throws Exception {
String result =
mockMvc.perform(
MockMvcRequestBuilders.get(menuPath + "/roleMenuTreeselect/1")
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysRoleControllerTest.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysRoleControllerTest.java
index 216e2699f..4f3276bdc 100644
--- a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysRoleControllerTest.java
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/SysRoleControllerTest.java
@@ -18,26 +18,19 @@
package org.apache.paimon.web.server.controller;
-import org.apache.paimon.web.server.data.dto.LoginDto;
import org.apache.paimon.web.server.data.model.SysRole;
import org.apache.paimon.web.server.data.result.PageR;
import org.apache.paimon.web.server.data.result.R;
import org.apache.paimon.web.server.util.ObjectMapperUtils;
-import org.apache.paimon.web.server.util.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
-import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@@ -50,64 +43,13 @@
@SpringBootTest
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-public class SysRoleControllerTest {
+public class SysRoleControllerTest extends ControllerTestBase {
private static final String rolePath = "/api/role";
- private static final String loginPath = "/api/login";
- private static final String logoutPath = "/api/logout";
private static final int roleId = 3;
private static final String roleName = "test";
- @Value("${spring.application.name}")
- private String tokenName;
-
- @Autowired private MockMvc mockMvc;
-
- private String token;
-
- @BeforeEach
- public void before() throws Exception {
- LoginDto login = new LoginDto();
- login.setUsername("admin");
- login.setPassword("21232f297a57a5a743894a0e4a801fc3");
-
- String result =
- mockMvc.perform(
- MockMvcRequestBuilders.post(loginPath)
- .content(ObjectMapperUtils.toJSON(login))
- .contentType(MediaType.APPLICATION_JSON_VALUE)
- .accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().isOk())
- .andDo(MockMvcResultHandlers.print())
- .andReturn()
- .getResponse()
- .getContentAsString();
- R> r = ObjectMapperUtils.fromJSON(result, R.class);
- assertEquals(200, r.getCode());
-
- assertTrue(StringUtils.isNotBlank(r.getData().toString()));
-
- this.token = r.getData().toString();
- }
-
- @AfterEach
- public void after() throws Exception {
- String result =
- mockMvc.perform(
- MockMvcRequestBuilders.post(logoutPath)
- .header(tokenName, token)
- .contentType(MediaType.APPLICATION_JSON_VALUE)
- .accept(MediaType.APPLICATION_JSON_VALUE))
- .andExpect(MockMvcResultMatchers.status().isOk())
- .andDo(MockMvcResultHandlers.print())
- .andReturn()
- .getResponse()
- .getContentAsString();
- R> r = ObjectMapperUtils.fromJSON(result, R.class);
- assertEquals(200, r.getCode());
- }
-
@Test
@Order(1)
public void testAddRole() throws Exception {
@@ -122,7 +64,7 @@ public void testAddRole() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post(rolePath)
- .header(tokenName, token)
+ .cookie(cookie)
.content(ObjectMapperUtils.toJSON(sysRole))
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
@@ -136,7 +78,7 @@ public void testQueryRole() throws Exception {
String responseString =
mockMvc.perform(
MockMvcRequestBuilders.get(rolePath + "/" + roleId)
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
@@ -167,7 +109,7 @@ public void testEditRole() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.put(rolePath)
- .header(tokenName, token)
+ .cookie(cookie)
.content(ObjectMapperUtils.toJSON(sysRole))
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
@@ -176,7 +118,7 @@ public void testEditRole() throws Exception {
String responseString =
mockMvc.perform(
MockMvcRequestBuilders.get(rolePath + "/" + roleId)
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
@@ -198,7 +140,7 @@ public void testDeleteRole() throws Exception {
String delResponseString =
mockMvc.perform(
MockMvcRequestBuilders.delete(rolePath + "/" + roleId)
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
@@ -217,7 +159,7 @@ public void testGetRoleList() throws Exception {
String responseString =
mockMvc.perform(
MockMvcRequestBuilders.get(rolePath + "/list")
- .header(tokenName, token)
+ .cookie(cookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk())
diff --git a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/TableControllerTest.java b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/TableControllerTest.java
new file mode 100644
index 000000000..f31d1d5ee
--- /dev/null
+++ b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/TableControllerTest.java
@@ -0,0 +1,322 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.AlterTableRequest;
+import org.apache.paimon.web.server.data.model.TableColumn;
+import org.apache.paimon.web.server.data.model.TableInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+import org.apache.paimon.web.server.util.PaimonDataType;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for TableController. */
+public class TableControllerTest extends ControllerTestBase {
+
+ private static final String tablePath = "/api/table";
+
+ private static final String catalogName = "paimon_catalog";
+
+ private static final String databaseName = "paimon_database";
+
+ private static final String tableName = "paimon_table";
+
+ @Test
+ public void testCreateTable() throws Exception {
+ List tableColumns = new ArrayList<>();
+ TableColumn id =
+ new TableColumn("id", PaimonDataType.builder().type("INT").build(), "", false, "0");
+ TableColumn name =
+ new TableColumn(
+ "name", PaimonDataType.builder().type("STRING").build(), "", false, "0");
+ tableColumns.add(id);
+ tableColumns.add(name);
+ TableInfo tableInfo =
+ TableInfo.builder()
+ .catalogName(catalogName)
+ .databaseName(databaseName)
+ .tableName("test_table")
+ .tableColumns(tableColumns)
+ .partitionKey(Lists.newArrayList())
+ .tableOptions(Maps.newHashMap())
+ .build();
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(tableInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(
+ tablePath
+ + "/drop/"
+ + catalogName
+ + "/"
+ + databaseName
+ + "/"
+ + "test_table")
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE));
+ }
+
+ @Test
+ public void testAddColumn() throws Exception {
+ List tableColumns = new ArrayList<>();
+ TableColumn age =
+ new TableColumn(
+ "age",
+ PaimonDataType.builder().type("INT").isNullable(true).build(),
+ "",
+ false,
+ "0");
+ tableColumns.add(age);
+ TableInfo tableInfo =
+ TableInfo.builder()
+ .catalogName(catalogName)
+ .databaseName(databaseName)
+ .tableName(tableName)
+ .tableColumns(tableColumns)
+ .partitionKey(Lists.newArrayList())
+ .tableOptions(Maps.newHashMap())
+ .build();
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/column/add")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(tableInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+
+ @Test
+ public void testDropColumn() throws Exception {
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.delete(
+ tablePath
+ + "/drop/"
+ + catalogName
+ + "/"
+ + databaseName
+ + "/"
+ + tableName
+ + "/"
+ + "name")
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+
+ @Test
+ public void testAlterTable() throws Exception {
+ TableColumn oldColumn =
+ new TableColumn("id", PaimonDataType.builder().type("INT").build(), "", false, "0");
+
+ TableColumn newColumn =
+ new TableColumn(
+ "age", PaimonDataType.builder().type("BIGINT").build(), "", false, "0");
+
+ AlterTableRequest alterTableRequest = new AlterTableRequest();
+ alterTableRequest.setOldColumn(oldColumn);
+ alterTableRequest.setNewColumn(newColumn);
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/alter")
+ .cookie(cookie)
+ .param("catalogName", catalogName)
+ .param("databaseName", databaseName)
+ .param("tableName", tableName)
+ .content(ObjectMapperUtils.toJSON(alterTableRequest))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+
+ @Test
+ public void testAddOption() throws Exception {
+ Map option = new HashMap<>();
+ option.put("bucket", "2");
+
+ TableInfo tableInfo =
+ TableInfo.builder()
+ .catalogName(catalogName)
+ .databaseName(databaseName)
+ .tableName(tableName)
+ .tableColumns(Lists.newArrayList())
+ .partitionKey(Lists.newArrayList())
+ .tableOptions(option)
+ .build();
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/option/add")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(tableInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+
+ @Test
+ public void testRemoveOption() throws Exception {
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/option/remove")
+ .cookie(cookie)
+ .param("catalogName", catalogName)
+ .param("databaseName", databaseName)
+ .param("tableName", tableName)
+ .param("key", "bucket")
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+
+ @Test
+ public void testRenameTable() throws Exception {
+ List tableColumns = new ArrayList<>();
+ TableColumn id =
+ new TableColumn("id", PaimonDataType.builder().type("INT").build(), "", false, "0");
+ TableColumn name =
+ new TableColumn(
+ "name", PaimonDataType.builder().type("STRING").build(), "", false, "0");
+ tableColumns.add(id);
+ tableColumns.add(name);
+ TableInfo tableInfo =
+ TableInfo.builder()
+ .catalogName(catalogName)
+ .databaseName(databaseName)
+ .tableName("test_table_01")
+ .tableColumns(tableColumns)
+ .partitionKey(Lists.newArrayList())
+ .tableOptions(Maps.newHashMap())
+ .build();
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/create")
+ .cookie(cookie)
+ .content(ObjectMapperUtils.toJSON(tableInfo))
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk());
+
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.post(tablePath + "/rename")
+ .cookie(cookie)
+ .param("catalogName", catalogName)
+ .param("databaseName", databaseName)
+ .param("fromTableName", "test_table_01")
+ .param("toTableName", "test_table_02")
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+
+ @Test
+ public void testGetTables() throws Exception {
+ String responseString =
+ mockMvc.perform(
+ MockMvcRequestBuilders.get(tablePath + "/getAllTables")
+ .cookie(cookie)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON_VALUE))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ R r = ObjectMapperUtils.fromJSON(responseString, new TypeReference>() {});
+ assertEquals(200, r.getCode());
+ }
+}
diff --git a/pom.xml b/pom.xml
index 151ffe238..9ff1baa75 100644
--- a/pom.xml
+++ b/pom.xml
@@ -358,6 +358,7 @@ under the License.
release/**
paimon-web-ui/node_modules/**
+ paimon-web-ui/dist/**
paimon-web-ui-new/node_modules/**
diff --git a/scripts/sql/paimon-mysql.sql b/scripts/sql/paimon-mysql.sql
new file mode 100644
index 000000000..d3b004453
--- /dev/null
+++ b/scripts/sql/paimon-mysql.sql
@@ -0,0 +1,174 @@
+-- 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.
+
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE if not exists `user`
+(
+ `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
+ `username` varchar(50) NOT NULL COMMENT 'username',
+ `password` varchar(50) NULL DEFAULT NULL COMMENT 'password',
+ `nickname` varchar(50) NULL DEFAULT NULL COMMENT 'nickname',
+ `user_type` int NOT NULL DEFAULT 0 COMMENT 'login type (0:LOCAL,1:LDAP)',
+ `url` varchar(100) NULL DEFAULT NULL COMMENT 'avatar url',
+ `mobile` varchar(20) NULL DEFAULT NULL COMMENT 'mobile phone',
+ `email` varchar(100) NULL DEFAULT NULL COMMENT 'email',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'is enable',
+ `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'is delete',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `tenant`;
+CREATE TABLE if not exists `tenant`
+(
+ `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
+ `name` varchar(64) NULL DEFAULT NULL COMMENT 'tenant name',
+ `description` varchar(255) NULL DEFAULT NULL COMMENT 'tenant description',
+ `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'is delete',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `user_tenant`;
+CREATE TABLE if not exists `user_tenant`
+(
+ `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
+ `user_id` int(11) NOT NULL COMMENT 'user id',
+ `tenant_id` int(11) NOT NULL COMMENT 'tenant id',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `sys_role`;
+CREATE TABLE if not exists `sys_role`
+(
+ `id` int(11) not null auto_increment primary key comment 'id',
+ `role_name` varchar(30) not null comment 'role name',
+ `role_key` varchar(100) not null comment 'role key',
+ `sort` int(4) not null comment 'sort',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'is enable',
+ `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'is delete',
+ `remark` varchar(500) default null comment 'remark',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `sys_menu`;
+CREATE TABLE if not exists `sys_menu`
+(
+ `id` int(11) not null auto_increment primary key comment 'id',
+ `menu_name` varchar(50) not null comment 'menu name',
+ `parent_id` int(11) default 0 comment 'parent id',
+ `sort` int(4) default 0 comment 'sort',
+ `path` varchar(200) default '' comment 'route path',
+ `query` varchar(255) default null comment 'route params',
+ `is_cache` int(1) default 0 comment 'is cache(0:cache 1:no_cache)',
+ `type` char(1) default '' comment 'menu type(M:directory C:menu F:button)',
+ `visible` char(1) default 0 comment 'is visible(0:display 1:hide)',
+ `component` varchar(255) default null comment 'component path',
+ `is_frame` int(1) default 0 comment 'is frame',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'is enable',
+ `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'is delete',
+ `perms` varchar(100) default null comment 'menu perms',
+ `icon` varchar(100) default '#' comment 'menu icon',
+ `remark` varchar(500) default '' comment 'remark',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `user_role`;
+CREATE TABLE if not exists `user_role`
+(
+ `id` int(11) not null auto_increment primary key comment 'id',
+ `user_id` int(11) not null comment 'user id',
+ `role_id` int(11) not null comment 'role id',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time',
+ unique key `idx_user_role` (`user_id`, `role_id`)
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `role_menu`;
+CREATE TABLE if not exists `role_menu`
+(
+ `id` int(11) not null auto_increment primary key comment 'id',
+ `role_id` int(11) not null comment 'role id',
+ `menu_id` int(11) not null comment 'menu id',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time',
+ unique key `idx_role_menu` (`role_id`, `menu_id`)
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `catalog`;
+CREATE TABLE if not exists `catalog`
+(
+ `id` int(11) not null auto_increment primary key comment 'id',
+ `catalog_type` varchar(50) not null comment 'catalog type',
+ `catalog_name` varchar(100) not null comment 'catalog name',
+ `warehouse` varchar(200) not null comment 'warehouse',
+ `hive_uri` varchar(200) comment 'hive uri',
+ `hive_conf_dir` varchar(100) comment 'catalog name',
+ `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'is delete',
+ `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+
+INSERT INTO `user` ( id, username, password, nickname, mobile
+ , email, enabled, is_delete)
+VALUES ( 1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 'Admin', 0
+ , 'admin@paimon.com', 1, 0);
+INSERT INTO `user` (id, username, password, nickname, mobile, email, enabled, is_delete)
+VALUES (2, 'common', '21232f297a57a5a743894a0e4a801fc3', 'common', 0, 'common@paimon.com', 1, 0);
+
+INSERT INTO `tenant` (id, name, description)
+VALUES (1, 'DefaultTenant', 'DefaultTenant');
+
+INSERT INTO `user_tenant` (`id`, `user_id`, `tenant_id`)
+VALUES (1, 1, 1);
+
+insert into sys_role (id, role_name, role_key, sort)
+values (1, 'admin', 'admin', 1),
+ (2, 'common', 'common', 2);
+
+insert into sys_menu (id, menu_name, parent_id, sort, path, component, is_frame, type, perms, icon, remark)
+values (1, 'all', 0, 1, 'system', null, 1, 'M', 'system', 'admin', 'system root path'),
+ (100, 'user manager', 1, 1, 'user', 'user/index', 1, 'C', 'system:user:list', 'user', 'user manager'),
+ (1000, 'user query', 100, 1, '', '', 1, 'F', 'system:user:query', '#', ''),
+ (1001, 'user add', 100, 2, '', '', 1, 'F', 'system:user:add', '#', ''),
+ (1002, 'user edit', 100, 3, '', '', 1, 'F', 'system:user:edit', '#', ''),
+ (1003, 'user del', 100, 4, '', '', 1, 'F', 'system:user:remove', '#', ''),
+ (1004, 'user reset', 100, 5, '', '', 1, 'F', 'system:user:resetPwd', '#', ''),
+ (200, 'role manager', 1, 1, 'role', 'role/index', 1, 'C', 'system:role:list', 'role', 'role manager'),
+ (2000, 'role query', 200, 1, '', '', 1, 'F', 'system:role:query', '#', ''),
+ (2001, 'role add', 200, 2, '', '', 1, 'F', 'system:role:add', '#', ''),
+ (2002, 'role edit', 200, 3, '', '', 1, 'F', 'system:role:edit', '#', ''),
+ (2003, 'role del', 200, 4, '', '', 1, 'F', 'system:role:remove', '#', ''),
+ (300, 'menu manager', 1, 1, 'menu', 'menu/index', 1, 'C', 'system:menu:list', 'menu', 'menu manager'),
+ (3000, 'menu query', 300, 1, '', '', 1, 'F', 'system:menu:query', '#', ''),
+ (3001, 'menu add', 300, 2, '', '', 1, 'F', 'system:menu:add', '#', ''),
+ (3002, 'menu edit', 300, 3, '', '', 1, 'F', 'system:menu:edit', '#', ''),
+ (3003, 'menu del', 300, 4, '', '', 1, 'F', 'system:menu:remove', '#', '');
+
+insert into user_role (id, user_id, role_id)
+values (1, 1, 1), (2, 2, 2);
+
+insert into role_menu (role_id, menu_id)
+values (1, 1),
+ (1, 100),
+ (1, 1000),
+ (1, 1001),
+ (1, 1002),
+ (1, 1003),
+ (1, 1004);