support pgsql create/update

This commit is contained in:
heshujun
2023-10-09 23:02:34 +08:00
parent 04462d7811
commit b0e98c902a
8 changed files with 3135 additions and 84 deletions

View File

@ -5,6 +5,7 @@ import java.sql.Connection;
import ai.chat2db.spi.DBManage;
import ai.chat2db.spi.jdbc.DefaultDBManage;
import ai.chat2db.spi.sql.ConnectInfo;
import ai.chat2db.spi.sql.SQLExecutor;
public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
@Override
@ -49,4 +50,11 @@ public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
return newUrl;
}
@Override
public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
String sql = "DROP TABLE "+ tableName;
SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null);
}
}

View File

@ -1,41 +1,34 @@
package ai.chat2db.plugin.postgresql;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import ai.chat2db.plugin.postgresql.builder.PostgreSQLSqlBuilder;
import ai.chat2db.plugin.postgresql.type.PostgreSQLCharsetEnum;
import ai.chat2db.plugin.postgresql.type.PostgreSQLCollationEnum;
import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum;
import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum;
import ai.chat2db.server.tools.common.util.EasyCollectionUtils;
import ai.chat2db.spi.MetaData;
import ai.chat2db.spi.SqlBuilder;
import ai.chat2db.spi.jdbc.DefaultMetaService;
import ai.chat2db.spi.model.Database;
import ai.chat2db.spi.model.Function;
import ai.chat2db.spi.model.Procedure;
import ai.chat2db.spi.model.Schema;
import ai.chat2db.spi.model.Table;
import ai.chat2db.spi.model.Trigger;
import ai.chat2db.spi.model.*;
import ai.chat2db.spi.sql.SQLExecutor;
import com.alibaba.druid.sql.visitor.functions.If;
import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import static ai.chat2db.plugin.postgresql.consts.SQLConst.FUNCTION_SQL;
public class PostgreSQLMetaData extends DefaultMetaService implements MetaData {
@Override
public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) {
SQLExecutor.getInstance().executeSql(connection, FUNCTION_SQL.replaceFirst("tableSchema", schemaName),
resultSet -> null);
String ddlSql = "select showcreatetable('" + schemaName + "','" + tableName + "') as sql";
return SQLExecutor.getInstance().executeSql(connection, ddlSql, resultSet -> {
try {
if (resultSet.next()) {
return resultSet.getString("sql");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return null;
});
}
private static final String SELECT_KEY_INDEX = "SELECT ccu.table_schema AS Foreign_schema_name, ccu.table_name AS Foreign_table_name, ccu.column_name AS Foreign_column_name, constraint_type AS Constraint_type, tc.CONSTRAINT_NAME AS Key_name, tc.TABLE_NAME, kcu.Column_name, tc.is_deferrable, tc.initially_deferred FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.TABLE_SCHEMA = '%s' AND tc.TABLE_NAME = '%s';";
@Override
public List<Database> databases(Connection connection) {
@ -58,55 +51,20 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData {
});
}
@Override
public List<Schema> schemas(Connection connection, String databaseName) {
return SQLExecutor.getInstance().execute(connection,
"SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> {
List<Schema> databases = new ArrayList<>();
while (resultSet.next()) {
Schema schema = new Schema();
String name = resultSet.getString("schema_name");
String catalogName = resultSet.getString("catalog_name");
schema.setName(name);
schema.setDatabaseName(catalogName);
databases.add(schema);
}
return databases;
});
}
private static final String SELECT_TABLE_INDEX = "SELECT tmp.INDISPRIMARY AS Index_primary, tmp.TABLE_SCHEM, tmp.TABLE_NAME, tmp.NON_UNIQUE, tmp.INDEX_QUALIFIER, tmp.INDEX_NAME AS Key_name, tmp.indisclustered, tmp.ORDINAL_POSITION AS Seq_in_index, TRIM ( BOTH '\"' FROM pg_get_indexdef ( tmp.CI_OID, tmp.ORDINAL_POSITION, FALSE ) ) AS Column_name,CASE tmp.AM_NAME WHEN 'btree' THEN CASE tmp.I_INDOPTION [ tmp.ORDINAL_POSITION - 1 ] & 1 :: SMALLINT WHEN 1 THEN 'D' ELSE'A' END ELSE NULL END AS Collation, tmp.CARDINALITY, tmp.PAGES, tmp.FILTER_CONDITION , tmp.AM_NAME AS Index_method, tmp.DESCRIPTION AS Index_comment FROM ( SELECT n.nspname AS TABLE_SCHEM, ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME,i.INDISPRIMARY , i.indisclustered , ( information_schema._pg_expandarray ( i.indkey ) ).n AS ORDINAL_POSITION, ci.reltuples AS CARDINALITY, ci.relpages AS PAGES, pg_get_expr ( i.indpred, i.indrelid ) AS FILTER_CONDITION, ci.OID AS CI_OID, i.indoption AS I_INDOPTION, am.amname AS AM_NAME , d.description FROM pg_class ct JOIN pg_namespace n ON ( ct.relnamespace = n.OID ) JOIN pg_index i ON ( ct.OID = i.indrelid ) JOIN pg_class ci ON ( ci.OID = i.indexrelid ) JOIN pg_am am ON ( ci.relam = am.OID ) left outer join pg_description d on i.indexrelid = d.objoid WHERE n.nspname = '%s' AND ct.relname = '%s' ) AS tmp ;";
private static String ROUTINES_SQL
= " SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p "
+ "where p.prokind = '%s' and p.proname='%s';";
@Override
public Function function(Connection connection, @NotEmpty String databaseName, String schemaName,
String functionName) {
String sql = String.format(ROUTINES_SQL, "f", functionName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
Function function = new Function();
function.setDatabaseName(databaseName);
function.setSchemaName(schemaName);
function.setFunctionName(functionName);
if (resultSet.next()) {
function.setFunctionBody(resultSet.getString("code"));
}
return function;
});
}
= " SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p "
+ "where p.prokind = '%s' and p.proname='%s';";
private static String TRIGGER_SQL
= "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS "
+ "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t"
+ ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s' AND t.tgname ='%s';";
= "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS "
+ "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t"
+ ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s' AND t.tgname ='%s';";
private static String TRIGGER_SQL_LIST
= "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS "
+ "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t"
+ ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s';";
= "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS "
+ "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t"
+ ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s';";
private static String VIEW_SQL
= "SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname = '%s' AND viewname = '%s';";
@Override
public List<Trigger> triggers(Connection connection, String databaseName, String schemaName) {
@ -124,9 +82,76 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData {
});
}
@Override
public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) {
SQLExecutor.getInstance().executeSql(connection, FUNCTION_SQL.replaceFirst("tableSchema", schemaName),
resultSet -> null);
String ddlSql = "select showcreatetable('" + schemaName + "','" + tableName + "') as sql";
return SQLExecutor.getInstance().executeSql(connection, ddlSql, resultSet -> {
try {
if (resultSet.next()) {
return resultSet.getString("sql");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return null;
});
}
@Override
public List<Schema> schemas(Connection connection, String databaseName) {
return SQLExecutor.getInstance().execute(connection,
"SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> {
List<Schema> databases = new ArrayList<>();
while (resultSet.next()) {
Schema schema = new Schema();
String name = resultSet.getString("schema_name");
String catalogName = resultSet.getString("catalog_name");
schema.setName(name);
schema.setDatabaseName(catalogName);
databases.add(schema);
}
return databases;
});
}
@Override
public Function function(Connection connection, @NotEmpty String databaseName, String schemaName,
String functionName) {
String sql = String.format(ROUTINES_SQL, "f", functionName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
Function function = new Function();
function.setDatabaseName(databaseName);
function.setSchemaName(schemaName);
function.setFunctionName(functionName);
if (resultSet.next()) {
function.setFunctionBody(resultSet.getString("code"));
}
return function;
});
}
@Override
public Table view(Connection connection, String databaseName, String schemaName, String viewName) {
String sql = String.format(VIEW_SQL, schemaName, viewName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
Table table = new Table();
table.setDatabaseName(databaseName);
table.setSchemaName(schemaName);
table.setName(viewName);
if (resultSet.next()) {
table.setDdl(resultSet.getString("definition"));
}
return table;
});
}
@Override
public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName,
String triggerName) {
String triggerName) {
String sql = String.format(TRIGGER_SQL, schemaName, triggerName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
@ -144,7 +169,7 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData {
@Override
public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName,
String procedureName) {
String procedureName) {
String sql = String.format(ROUTINES_SQL, "p", procedureName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
Procedure procedure = new Procedure();
@ -158,21 +183,114 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData {
});
}
private static String VIEW_SQL
= "SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname = '%s' AND viewname = '%s';";
@Override
public List<TableIndex> indexes(Connection connection, String databaseName, String schemaName, String tableName) {
String constraintSql = String.format(SELECT_KEY_INDEX, schemaName, tableName);
Map<String, String> constraintMap = new HashMap();
LinkedHashMap<String, TableIndex> foreignMap = new LinkedHashMap();
SQLExecutor.getInstance().execute(connection, constraintSql, resultSet -> {
while (resultSet.next()) {
String keyName = resultSet.getString("Key_name");
String constraintType = resultSet.getString("Constraint_type");
constraintMap.put(keyName, constraintType);
if (StringUtils.equalsIgnoreCase(constraintType, PostgreSQLIndexTypeEnum.FOREIGN.getKeyword())) {
TableIndex tableIndex = foreignMap.get(keyName);
String columnName = resultSet.getString("Column_name");
if (tableIndex == null) {
tableIndex = new TableIndex();
tableIndex.setDatabaseName(databaseName);
tableIndex.setSchemaName(schemaName);
tableIndex.setTableName(tableName);
tableIndex.setName(keyName);
tableIndex.setForeignSchemaName(resultSet.getString("Foreign_schema_name"));
tableIndex.setForeignTableName(resultSet.getString("Foreign_table_name"));
tableIndex.setForeignColumnNamelist(Lists.newArrayList(columnName));
tableIndex.setType(PostgreSQLIndexTypeEnum.FOREIGN.getName());
foreignMap.put(keyName, tableIndex);
} else {
tableIndex.getForeignColumnNamelist().add(columnName);
}
}
}
return null;
});
String sql = String.format(SELECT_TABLE_INDEX, schemaName, tableName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
LinkedHashMap<String, TableIndex> map = new LinkedHashMap(foreignMap);
while (resultSet.next()) {
String keyName = resultSet.getString("Key_name");
TableIndex tableIndex = map.get(keyName);
if (tableIndex != null) {
List<TableIndexColumn> columnList = tableIndex.getColumnList();
columnList.add(getTableIndexColumn(resultSet));
columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition))
.collect(Collectors.toList());
tableIndex.setColumnList(columnList);
} else {
TableIndex index = new TableIndex();
index.setDatabaseName(databaseName);
index.setSchemaName(schemaName);
index.setTableName(tableName);
index.setName(keyName);
index.setUnique(!StringUtils.equals("t", resultSet.getString("NON_UNIQUE")));
index.setMethod(resultSet.getString("Index_method"));
index.setComment(resultSet.getString("Index_comment"));
List<TableIndexColumn> tableIndexColumns = new ArrayList<>();
tableIndexColumns.add(getTableIndexColumn(resultSet));
index.setColumnList(tableIndexColumns);
String constraintType = constraintMap.get(keyName);
if (StringUtils.equals("t", resultSet.getString("Index_primary"))) {
index.setType(PostgreSQLIndexTypeEnum.PRIMARY.getName());
} else if (StringUtils.equalsIgnoreCase(constraintType, PostgreSQLIndexTypeEnum.UNIQUE.getName())) {
index.setType(PostgreSQLIndexTypeEnum.UNIQUE.getName());
} else {
index.setType(PostgreSQLIndexTypeEnum.NORMAL.getName());
}
map.put(keyName, index);
}
}
return map.values().stream().collect(Collectors.toList());
});
}
@Override
public Table view(Connection connection, String databaseName, String schemaName, String viewName) {
String sql = String.format(VIEW_SQL, schemaName, viewName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
Table table = new Table();
table.setDatabaseName(databaseName);
table.setSchemaName(schemaName);
table.setName(viewName);
if (resultSet.next()) {
table.setDdl(resultSet.getString("definition"));
public List<TableColumn> columns(Connection connection, String databaseName, String schemaName, String tableName) {
List<TableColumn> columnList = super.columns(connection, databaseName, schemaName, tableName);
EasyCollectionUtils.stream(columnList).forEach(v -> {
if (StringUtils.equalsIgnoreCase(v.getColumnType(), "bpchar")) {
v.setColumnType(PostgreSQLColumnTypeEnum.CHAR.getColumnType().getTypeName().toUpperCase());
} else {
v.setColumnType(v.getColumnType().toUpperCase());
}
return table;
});
return columnList;
}
private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException {
TableIndexColumn tableIndexColumn = new TableIndexColumn();
tableIndexColumn.setColumnName(resultSet.getString("Column_name"));
tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index"));
tableIndexColumn.setCollation(resultSet.getString("Collation"));
tableIndexColumn.setAscOrDesc(resultSet.getString("Collation"));
return tableIndexColumn;
}
@Override
public SqlBuilder getSqlBuilder() {
return new PostgreSQLSqlBuilder();
}
@Override
public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) {
return TableMeta.builder()
.columnTypes(PostgreSQLColumnTypeEnum.getTypes())
.charsets(PostgreSQLCharsetEnum.getCharsets())
.collations(PostgreSQLCollationEnum.getCollations())
.build();
}
}

View File

@ -0,0 +1,160 @@
package ai.chat2db.plugin.postgresql.builder;
import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum;
import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum;
import ai.chat2db.spi.SqlBuilder;
import ai.chat2db.spi.model.Table;
import ai.chat2db.spi.model.TableColumn;
import ai.chat2db.spi.model.TableIndex;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PostgreSQLSqlBuilder implements SqlBuilder {
@Override
public String buildCreateTableSql(Table table) {
StringBuilder script = new StringBuilder();
script.append("CREATE TABLE ");
script.append("\"").append(table.getName()).append("\"").append(" (").append(" ").append("\n");
// append column
for (TableColumn column : table.getColumnList()) {
if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) {
continue;
}
PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(column.getColumnType());
script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n");
}
Map<Boolean, List<TableIndex>> tableIndexMap = table.getIndexList().stream()
.collect(Collectors.partitioningBy(v -> PostgreSQLIndexTypeEnum.NORMAL.getName().equals(v.getType())));
// append constraint key
List<TableIndex> constraintList = tableIndexMap.get(Boolean.FALSE);
if (CollectionUtils.isNotEmpty(constraintList)) {
for (TableIndex index : constraintList) {
if (StringUtils.isBlank(index.getName()) || StringUtils.isBlank(index.getType())) {
continue;
}
PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(index.getType());
script.append("\t").append("").append(indexTypeEnum.buildIndexScript(index));
script.append(",\n");
}
}
script = new StringBuilder(script.substring(0, script.length() - 2));
script.append("\n)").append(";");
// append index
List<TableIndex> tableIndexList = tableIndexMap.get(Boolean.TRUE);
for (TableIndex tableIndex : tableIndexList) {
if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) {
continue;
}
script.append("\n");
PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType());
script.append("").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";");
}
// append comment
if (StringUtils.isNotBlank(table.getComment())) {
script.append("\n");
script.append("COMMENT ON TABLE").append(" ").append("\"").append(table.getName()).append("\" IS '")
.append(table.getComment()).append("';\n");
}
List<TableColumn> tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList();
for (TableColumn tableColumn : tableColumnList) {
PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType());
script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n");
;
}
List<TableIndex> indexList = table.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList();
for (TableIndex index : indexList) {
PostgreSQLIndexTypeEnum indexEnum = PostgreSQLIndexTypeEnum.getByType(index.getType());
script.append(indexEnum.buildIndexComment(index)).append("\n");
;
}
return script.toString();
}
@Override
public String buildModifyTaleSql(Table oldTable, Table newTable) {
StringBuilder script = new StringBuilder();
if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) {
script.append("ALTER TABLE ").append("\"").append(oldTable.getName()).append("\"");
script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n");
}
newTable.setColumnList(newTable.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList());
newTable.setIndexList(newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList());
//update name
List<TableColumn> columnNameList = newTable.getColumnList().stream().filter(v ->
v.getOldName() != null && !StringUtils.equals(v.getOldName(), v.getName())).toList();
for (TableColumn tableColumn : columnNameList) {
script.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" ").append("RENAME COLUMN \"")
.append(tableColumn.getOldName()).append("\" TO \"").append(tableColumn.getName()).append("\";\n");
}
Map<Boolean, List<TableIndex>> tableIndexMap = newTable.getIndexList().stream()
.collect(Collectors.partitioningBy(v -> PostgreSQLIndexTypeEnum.NORMAL.getName().equals(v.getType())));
StringBuilder scriptModify = new StringBuilder();
Boolean modify = false;
scriptModify.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" \n");
// append modify column
for (TableColumn tableColumn : newTable.getColumnList()) {
PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType());
scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n");
modify = true;
}
// append modify constraint
for (TableIndex tableIndex : tableIndexMap.get(Boolean.FALSE)) {
if (StringUtils.isNotBlank(tableIndex.getType())) {
PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType());
scriptModify.append("\t").append(indexTypeEnum.buildModifyIndex(tableIndex)).append(",\n");
modify = true;
}
}
if (BooleanUtils.isTrue(modify)) {
script.append(scriptModify);
script = new StringBuilder(script.substring(0, script.length() - 2));
script.append(";\n");
}
// append modify index
for (TableIndex tableIndex : tableIndexMap.get(Boolean.TRUE)) {
if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) {
PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType());
script.append(indexTypeEnum.buildModifyIndex(tableIndex)).append(";\n");
}
}
// append comment
if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) {
script.append("\n");
script.append("COMMENT ON TABLE").append(" ").append("\"").append(newTable.getName()).append("\" IS '")
.append(newTable.getComment()).append("';\n");
}
for (TableColumn tableColumn : newTable.getColumnList()) {
PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType());
script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n");
;
}
List<TableIndex> indexList = newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList();
for (TableIndex index : indexList) {
PostgreSQLIndexTypeEnum indexEnum = PostgreSQLIndexTypeEnum.getByType(index.getType());
script.append(indexEnum.buildIndexComment(index)).append("\n");
}
return script.toString();
}
}

View File

@ -0,0 +1,68 @@
package ai.chat2db.plugin.postgresql.type;
import ai.chat2db.spi.model.Charset;
import java.util.Arrays;
import java.util.List;
public enum PostgreSQLCharsetEnum {
BIG5("BIG5",null),
EUC_CN("EUC_CN",null),
EUC_JP("EUC_JP",null),
EUC_JIS_2004("EUC_JIS_2004",null),
EUC_KR("EUC_KR",null),
EUC_TW("EUC_TW",null),
GB18030("GB18030",null),
GBK("GBK",null),
ISO_8859_5("ISO_8859_5",null),
ISO_8859_6("ISO_8859_6",null),
ISO_8859_7("ISO_8859_7",null),
ISO_8859_8("ISO_8859_8",null),
JOHAB("JOHAB",null),
KOI8R("KOI8R",null),
KOI8U("KOI8U",null),
LATIN1("LATIN1",null),
LATIN2("LATIN2",null),
LATIN3("LATIN3",null),
LATIN4("LATIN4",null),
LATIN5("LATIN5",null),
LATIN6("LATIN6",null),
LATIN7("LATIN7",null),
LATIN8("LATIN8",null),
LATIN9("LATIN9",null),
LATIN10("LATIN10",null),
MULE_INTERNAL("MULE_INTERNAL",null),
SJIS("SJIS",null),
SHIFT_JIS_2004("SHIFT_JIS_2004",null),
SQL_ASCII("SQL_ASCII",null),
UHC("UHC",null),
UTF8("UTF8",null),
WIN866("WIN866",null),
WIN874("WIN874",null),
WIN1250("WIN1250",null),
WIN1251("WIN1251",null),
WIN1252("WIN1252",null),
WIN1253("WIN1253",null),
WIN1254("WIN1254",null),
WIN1255("WIN1255",null),
WIN1256("WIN1256",null),
WIN1257("WIN1257",null),
WIN1258("WIN1258",null),
;
private Charset charset;
PostgreSQLCharsetEnum(String charsetName, String defaultCollationName) {
this.charset = new Charset(charsetName, defaultCollationName);
}
public static List<Charset> getCharsets() {
return Arrays.stream(PostgreSQLCharsetEnum.values()).map(PostgreSQLCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList());
}
public Charset getCharset() {
return charset;
}
}

View File

@ -0,0 +1,233 @@
package ai.chat2db.plugin.postgresql.type;
import ai.chat2db.spi.ColumnBuilder;
import ai.chat2db.spi.enums.EditStatus;
import ai.chat2db.spi.model.ColumnType;
import ai.chat2db.spi.model.TableColumn;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public enum PostgreSQLColumnTypeEnum implements ColumnBuilder {
BIGSERIAL("BIGSERIAL", false, false, true, false, false, false, true, true, false, false),
BIT("BIT", true, false, true, false, false, false, true, true, false, false),
BOOL("BOOL", false, false, true, false, false, false, true, true, false, false),
BOX("BOX", false, false, true, false, false, false, true, true, false, false),
BYTEA("BYTEA", false, false, true, false, false, false, true, true, false, false),
CHAR("CHAR", true, false, true, false, false, true, true, true, false, false),
CIDR("CIDR", false, false, true, false, false, false, true, true, false, false),
CIRCLE("CIRCLE", false, false, true, false, false, false, true, true, false, false),
DATE("DATE", false, false, true, false, false, false, true, true, false, false),
DECIMAL("DECIMAL", true, false, true, false, false, false, true, true, false, false),
FLOAT4("FLOAT4", false, false, true, false, false, false, true, true, false, false),
FLOAT8("FLOAT8", false, false, true, false, false, false, true, true, false, false),
INET("INET", false, false, true, false, false, false, true, true, false, false),
INT2("INT2", false, false, true, false, false, false, true, true, false, false),
INT4("INT4", false, false, true, false, false, false, true, true, false, false),
INT8("INT8", false, false, true, false, false, false, true, true, false, false),
INTERVAL("INTERVAL", false, false, true, false, false, false, true, true, false, false),
JSON("JSON", false, false, true, false, false, false, true, true, false, false),
JSONB("JSONB", false, false, true, false, false, false, true, true, false, false),
LINE("LINE", false, false, true, false, false, false, true, true, false, false),
LSEG("LSEG", false, false, true, false, false, false, true, true, false, false),
MACADDR("MACADDR", false, false, true, false, false, false, true, true, false, false),
MONEY("MONEY", false, false, true, false, false, false, true, true, false, false),
NUMERIC("NUMERIC", true, false, true, false, false, false, true, true, false, false),
PATH("PATH", false, false, true, false, false, false, true, true, false, false),
POINT("POINT", false, false, true, false, false, false, true, true, false, false),
POLYGON("POLYGON", false, false, true, false, false, false, true, true, false, false),
SERIAL("SERIAL", false, false, true, false, false, false, true, true, false, false),
SERIAL2("SERIAL2", false, false, true, false, false, false, true, true, false, false),
SERIAL4("SERIAL4", false, false, true, false, false, false, true, true, false, false),
SERIAL8("SERIAL8", false, false, true, false, false, false, true, true, false, false),
SMALLSERIAL("SMALLSERIAL", false, false, true, false, false, false, true, true, false, false),
TEXT("TEXT", false, false, true, false, false, true, true, true, false, false),
TIME("TIME", true, false, true, false, false, false, true, true, false, false),
TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, false, false),
TIMESTAMPTZ("TIMESTAMPTZ", true, false, true, false, false, false, true, true, false, false),
TIMETZ("TIMETZ", true, false, true, false, false, false, true, true, false, false),
TSQUERY("TSQUERY", false, false, true, false, false, false, true, true, false, false),
TSVECTOR("TSVECTOR", false, false, true, false, false, false, true, true, false, false),
TXID_SNAPSHOT("TXID_SNAPSHOT", false, false, true, false, false, false, true, true, false, false),
UUID("UUID", false, false, true, false, false, false, true, true, false, false),
VARBIT("VARBIT", true, false, true, false, false, false, true, true, false, false),
VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false, false),
XML("XML", false, false, true, false, false, false, true, true, false, false),
;
private static Map<String, PostgreSQLColumnTypeEnum> COLUMN_TYPE_MAP = Maps.newHashMap();
static {
for (PostgreSQLColumnTypeEnum value : PostgreSQLColumnTypeEnum.values()) {
COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value);
}
}
private ColumnType columnType;
PostgreSQLColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) {
this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false);
}
public static PostgreSQLColumnTypeEnum getByType(String dataType) {
return COLUMN_TYPE_MAP.get(dataType.toUpperCase());
}
public static List<ColumnType> getTypes() {
return Arrays.stream(PostgreSQLColumnTypeEnum.values()).map(columnTypeEnum ->
columnTypeEnum.getColumnType()
).toList();
}
public ColumnType getColumnType() {
return columnType;
}
@Override
public String buildCreateColumnSql(TableColumn column) {
PostgreSQLColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase());
if (type == null) {
return "";
}
StringBuilder script = new StringBuilder();
script.append("\"").append(column.getName()).append("\"").append(" ");
script.append(buildDataType(column, type)).append(" ");
script.append(buildCollation(column, type)).append(" ");
script.append(buildNullable(column, type)).append(" ");
script.append(buildDefaultValue(column, type)).append(" ");
return script.toString();
}
private String buildCollation(TableColumn column, PostgreSQLColumnTypeEnum type) {
if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) {
return "";
}
return StringUtils.join("\"", column.getCollationName(), "\"");
}
@Override
public String buildModifyColumn(TableColumn column) {
if (EditStatus.DELETE.name().equals(column.getEditStatus())) {
return StringUtils.join("DROP COLUMN `", column.getName() + "`");
}
if (EditStatus.ADD.name().equals(column.getEditStatus())) {
return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(column));
}
if (EditStatus.MODIFY.name().equals(column.getEditStatus())) {
StringBuilder script = new StringBuilder();
script.append("ALTER COLUMN \"").append(column.getName()).append("\" TYPE ").append(buildDataType(column, this)).append(",\n");
if (column.getNullable() != null && 1 == column.getNullable()) {
script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" DROP NOT NULL ,\n");
} else {
script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" SET NOT NULL ,\n");
}
String defaultValue = buildDefaultValue(column, this);
if (StringUtils.isNotBlank(defaultValue)) {
script.append("ALTER COLUMN \"").append(column.getName()).append("\" SET ").append(defaultValue).append(",\n");
}
script = new StringBuilder(script.substring(0, script.length() - 2));
return script.toString();
}
return "";
}
public String buildComment(TableColumn column, PostgreSQLColumnTypeEnum type) {
if (!this.columnType.isSupportComments() || column.getComment() == null
|| EditStatus.DELETE.name().equals(column.getEditStatus())) {
return "";
}
return StringUtils.join("COMMENT ON COLUMN", " \"", column.getTableName(),
"\".\"", column.getName(), "\" IS '", column.getComment(), "';");
}
private String buildDefaultValue(TableColumn column, PostgreSQLColumnTypeEnum type) {
if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) {
return "";
}
if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){
return StringUtils.join("DEFAULT ''");
}
if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){
return StringUtils.join("DEFAULT NULL");
}
if (Arrays.asList(CHAR, VARCHAR).contains(type)) {
return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'");
}
if (Arrays.asList(TIMESTAMP, TIME, TIMETZ, TIMESTAMPTZ, DATE).contains(type)) {
if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) {
return StringUtils.join("DEFAULT ", column.getDefaultValue());
}
return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'");
}
return StringUtils.join("DEFAULT ", column.getDefaultValue());
}
private String buildNullable(TableColumn column, PostgreSQLColumnTypeEnum type) {
if (!type.getColumnType().isSupportNullable()) {
return "";
}
if (column.getNullable() != null && 1 == column.getNullable()) {
return "NULL";
} else {
return "NOT NULL";
}
}
private String buildDataType(TableColumn column, PostgreSQLColumnTypeEnum type) {
String columnType = type.columnType.getTypeName();
if (Arrays.asList(VARCHAR, CHAR).contains(type)) {
if (column.getColumnSize() == null ) {
return columnType;
}
return StringUtils.join(columnType, "(", column.getColumnSize(), ")");
}
if (Arrays.asList(VARBIT, BIT).contains(type)) {
if (column.getColumnSize() == null ) {
return columnType;
}
return StringUtils.join(columnType, "(", column.getColumnSize(), ")");
}
if (Arrays.asList(TIME, TIMETZ, TIMESTAMPTZ, TIMESTAMP).contains(type)) {
if (column.getColumnSize() == null || column.getColumnSize() == 0) {
return columnType;
} else {
return StringUtils.join(columnType, "(", column.getColumnSize(), ")");
}
}
if (Arrays.asList(DECIMAL, NUMERIC).contains(type)) {
if (column.getColumnSize() == null && column.getDecimalDigits() == null) {
return columnType;
}
if (column.getColumnSize() != null && column.getDecimalDigits() == null) {
return StringUtils.join(columnType, "(", column.getColumnSize() + ")");
} else {
return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")");
}
}
return columnType;
}
}

View File

@ -0,0 +1,165 @@
package ai.chat2db.plugin.postgresql.type;
import ai.chat2db.spi.enums.EditStatus;
import ai.chat2db.spi.model.TableIndex;
import ai.chat2db.spi.model.TableIndexColumn;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
public enum PostgreSQLIndexTypeEnum {
PRIMARY("Primary", "PRIMARY KEY"),
FOREIGN("Foreign", "FOREIGN KEY"),
NORMAL("Normal", "INDEX"),
UNIQUE("Unique", "UNIQUE"),
;
private String name;
private String keyword;
PostgreSQLIndexTypeEnum(String name, String keyword) {
this.name = name;
this.keyword = keyword;
}
public static PostgreSQLIndexTypeEnum getByType(String type) {
for (PostgreSQLIndexTypeEnum value : PostgreSQLIndexTypeEnum.values()) {
if (value.name.equalsIgnoreCase(type)) {
return value;
}
}
return null;
}
public String getName() {
return name;
}
public String getKeyword() {
return keyword;
}
public String buildIndexScript(TableIndex tableIndex) {
StringBuilder script = new StringBuilder();
if (NORMAL.equals(this)) {
script.append("CREATE").append(" ");
script.append(buildIndexUnique(tableIndex)).append(" ");
script.append(buildIndexConcurrently(tableIndex)).append(" ");
script.append(buildIndexName(tableIndex)).append(" ");
script.append("ON ").append("\"").append(tableIndex.getTableName()).append("\"").append(" ");
script.append(buildIndexMethod(tableIndex)).append(" ");
script.append(buildIndexColumn(tableIndex));
} else {
script.append("CONSTRAINT").append(" ");
script.append(buildIndexName(tableIndex)).append(" ");
script.append(keyword).append(" ");
script.append(buildIndexColumn(tableIndex));
script.append(buildForeignColum(tableIndex));
}
return script.toString();
}
private String buildForeignColum(TableIndex tableIndex) {
if (FOREIGN.equals(this)) {
StringBuilder script = new StringBuilder();
script.append(" REFERENCES ");
if (StringUtils.isNotBlank(tableIndex.getForeignSchemaName())) {
script.append(tableIndex.getForeignSchemaName()).append(".");
}
if (StringUtils.isNotBlank(tableIndex.getForeignTableName())) {
script.append(tableIndex.getForeignTableName()).append(" ");
}
if (CollectionUtils.isNotEmpty(tableIndex.getForeignColumnNamelist())) {
script.append("(");
for (String column : tableIndex.getForeignColumnNamelist()) {
if (StringUtils.isNotBlank(column)) {
script.append("\"").append(column).append("\"").append(",");
}
}
script.deleteCharAt(script.length() - 1);
script.append(")");
}
return script.toString();
}
return "";
}
private String buildIndexMethod(TableIndex tableIndex) {
if (StringUtils.isNotBlank(tableIndex.getMethod())) {
return "USING " + tableIndex.getMethod();
} else {
return "";
}
}
private String buildIndexConcurrently(TableIndex tableIndex) {
if (BooleanUtils.isTrue(tableIndex.getConcurrently())) {
return "CONCURRENTLY";
} else {
return "";
}
}
private String buildIndexUnique(TableIndex tableIndex) {
if (BooleanUtils.isTrue(tableIndex.getUnique())) {
return "UNIQUE " + keyword;
} else {
return keyword;
}
}
public String buildIndexComment(TableIndex tableIndex) {
if (StringUtils.isBlank(tableIndex.getComment()) || EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) {
return "";
} else if (NORMAL.equals(this)) {
return StringUtils.join("COMMENT ON INDEX", " ",
"\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';");
} else {
return StringUtils.join("COMMENT ON CONSTRAINT", " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(),
"\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';");
}
}
private String buildIndexColumn(TableIndex tableIndex) {
StringBuilder script = new StringBuilder();
script.append("(");
for (TableIndexColumn column : tableIndex.getColumnList()) {
if (StringUtils.isNotBlank(column.getColumnName())) {
script.append("\"").append(column.getColumnName()).append("\"").append(",");
}
}
script.deleteCharAt(script.length() - 1);
script.append(")");
return script.toString();
}
private String buildIndexName(TableIndex tableIndex) {
return "\"" + tableIndex.getName() + "\"";
}
public String buildModifyIndex(TableIndex tableIndex) {
boolean isNormal = NORMAL.equals(this);
if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) {
return buildDropIndex(tableIndex);
}
if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) {
return StringUtils.join(buildDropIndex(tableIndex), isNormal ? ";\n" : ",\n\tADD ", buildIndexScript(tableIndex));
}
if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) {
return StringUtils.join(isNormal ? "" : "ADD ", buildIndexScript(tableIndex));
}
return "";
}
private String buildDropIndex(TableIndex tableIndex) {
if (NORMAL.equals(this)) {
return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(), "\"");
}
return StringUtils.join("DROP CONSTRAINT \"", tableIndex.getOldName(), "\"");
}
}