From de1ce5d4c2c72827ba8d873301a5ee743bab6a44 Mon Sep 17 00:00:00 2001 From: zgq <203083679@qq.com> Date: Fri, 24 May 2024 21:35:41 +0800 Subject: [PATCH] SQLValueProcessor --- .../mysql/type/MysqlValueProcessorEnum.java | 71 ++++++++++++++++++ .../mysql/value/MysqlValueProcessor.java | 44 +++++++++++ .../main/java/ai/chat2db/spi/MetaData.java | 2 + .../ai/chat2db/spi/SQLValueProcessor.java | 10 +++ .../chat2db/spi/jdbc/DefaultMetaService.java | 13 +++- .../spi/jdbc/DefaultSQLValueProcessor.java | 74 +++++++++++++++++++ .../chat2db/spi/jdbc/DefaultSqlBuilder.java | 33 ++++++++- 7 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlValueProcessorEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/MysqlValueProcessor.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SQLValueProcessor.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSQLValueProcessor.java diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlValueProcessorEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlValueProcessorEnum.java new file mode 100644 index 00000000..a9a69e6b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlValueProcessorEnum.java @@ -0,0 +1,71 @@ +package ai.chat2db.plugin.mysql.type; + +import ai.chat2db.spi.SQLValueProcessor; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.WKBReader; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.sql.ResultSet; +import java.sql.SQLException; + +public enum MysqlValueProcessorEnum implements SQLValueProcessor { + GEOMETRY{ + @Override + public String getSqlValueString(ResultSet rs, int index) throws SQLException { + try { + InputStream inputStream = rs.getBinaryStream(index); + Geometry dbGeometry = null; + if (inputStream != null) { + + //convert the stream to a byte[] array + //so it can be passed to the WKBReader + byte[] buffer = new byte[255]; + + int bytesRead = 0; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((bytesRead = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + + byte[] geometryAsBytes = baos.toByteArray(); + + if (geometryAsBytes.length < 5) { + throw new Exception("Invalid geometry inputStream - less than five bytes"); + } + + //first four bytes of the geometry are the SRID, + //followed by the actual WKB. Determine the SRID + //here + byte[] sridBytes = new byte[4]; + System.arraycopy(geometryAsBytes, 0, sridBytes, 0, 4); + boolean bigEndian = (geometryAsBytes[4] == 0x00); + + int srid = 0; + if (bigEndian) { + for (int i = 0; i < sridBytes.length; i++) { + srid = (srid << 8) + (sridBytes[i] & 0xff); + } + } else { + for (int i = 0; i < sridBytes.length; i++) { + srid += (sridBytes[i] & 0xff) << (8 * i); + } + } + + //use the JTS WKBReader for WKB parsing + WKBReader wkbReader = new WKBReader(); + + //copy the byte array, removing the first four + //SRID bytes + byte[] wkb = new byte[geometryAsBytes.length - 4]; + System.arraycopy(geometryAsBytes, 4, wkb, 0, wkb.length); + dbGeometry = wkbReader.read(wkb); + dbGeometry.setSRID(srid); + } + return dbGeometry.toString(); + } catch (Exception e) { + return rs.getString(index); + } + } + }; +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/MysqlValueProcessor.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/MysqlValueProcessor.java new file mode 100644 index 00000000..5aadec81 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/MysqlValueProcessor.java @@ -0,0 +1,44 @@ +package ai.chat2db.plugin.mysql.value; + +import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; +import ai.chat2db.plugin.mysql.type.MysqlValueProcessorEnum; +import ai.chat2db.spi.jdbc.DefaultSQLValueProcessor; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author: zgq + * @date: 2024年05月24日 21:02 + */ +public class MysqlValueProcessor extends DefaultSQLValueProcessor { + /** + * @param rs + * @param index + * @return + * @throws SQLException + */ + @Override + public String getSqlValueString(ResultSet rs, int index) throws SQLException { + Object obj = rs.getObject(index); + if (obj == null) { + return null; + } + String columnTypeName = rs.getMetaData().getColumnTypeName(index); + if (MysqlColumnTypeEnum.GEOMETRY.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.POINT.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.LINESTRING.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.POLYGON.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.MULTIPOINT.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.MULTILINESTRING.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.MULTIPOLYGON.name().equalsIgnoreCase(columnTypeName) + || MysqlColumnTypeEnum.GEOMETRYCOLLECTION.name().equalsIgnoreCase(columnTypeName) + ) { + return MysqlValueProcessorEnum.GEOMETRY.getSqlValueString(rs, index); + } else { + super.getSqlValueString(rs, index); + } + return null; + } + +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index e6dc2bde..79c34772 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -244,6 +244,8 @@ public interface MetaData { */ ValueHandler getValueHandler(); + SQLValueProcessor getSQLValueProcessor(); + /** * Get command executor. diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SQLValueProcessor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SQLValueProcessor.java new file mode 100644 index 00000000..a485836e --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SQLValueProcessor.java @@ -0,0 +1,10 @@ +package ai.chat2db.spi; + + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface SQLValueProcessor { + + String getSqlValueString(ResultSet rs, int index) throws SQLException; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index a84d43df..752d04b5 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -1,10 +1,7 @@ package ai.chat2db.spi.jdbc; import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.spi.CommandExecutor; -import ai.chat2db.spi.MetaData; -import ai.chat2db.spi.SqlBuilder; -import ai.chat2db.spi.ValueHandler; +import ai.chat2db.spi.*; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import com.google.common.collect.Lists; @@ -167,6 +164,14 @@ public class DefaultMetaService implements MetaData { return new DefaultValueHandler(); } + /** + * @return + */ + @Override + public SQLValueProcessor getSQLValueProcessor() { + return new DefaultSQLValueProcessor(); + } + @Override public CommandExecutor getCommandExecutor() { return SQLExecutor.getInstance(); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSQLValueProcessor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSQLValueProcessor.java new file mode 100644 index 00000000..d9f694d1 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSQLValueProcessor.java @@ -0,0 +1,74 @@ +package ai.chat2db.spi.jdbc; + +import ai.chat2db.spi.SQLValueProcessor; +import com.google.common.io.BaseEncoding; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; + +/** + * @author: zgq + * @date: 2024年05月24日 14:30 + */ +public class DefaultSQLValueProcessor implements SQLValueProcessor { + /** + * @param rs + * @param index + * @return + */ + @Override + public String getSqlValueString(ResultSet rs, int index) throws SQLException { + Object object = rs.getObject(index); + if (Objects.isNull(object)) { + return "NULL"; + } + if (object instanceof BigDecimal bigDecimal) { + return bigDecimal.toPlainString(); + } else if (object instanceof Float f) { + return BigDecimal.valueOf(f).toPlainString(); + } else if (object instanceof Double d) { + return BigDecimal.valueOf(d).toPlainString(); + } else if (object instanceof Number n) { + return n.toString(); + } else if (object instanceof Boolean) { + return (Boolean) object ? "1" : "0"; + } else if (object instanceof byte[]) { + return converterByteArray2Str((byte[]) object); + } else if (object instanceof Blob B) { + return converterByteArray2Str(B.getBytes(1, Math.toIntExact(B.length()))); + } else if (object instanceof Clob c) { + return converterClob2Str(c); + } + return "'" + escapeString(object) + "'"; + } + + private String escapeString(Object object) { + String s = (String) object; + return s.replace("\\", "\\\\").replace("'", "''"); + } + + private String converterClob2Str(Clob c) { + StringBuilder stringBuilder = new StringBuilder(); + try (Reader reader = c.getCharacterStream()) { + BufferedReader bufferedReader = new BufferedReader(reader); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + return escapeString(stringBuilder.toString()); + } catch (SQLException | IOException e) { + throw new RuntimeException(e); + } + } + + private String converterByteArray2Str(byte[] bytes) { + return "0x" + BaseEncoding.base16().encode(bytes); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java index d63d1603..0273682e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -17,7 +17,9 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class DefaultSqlBuilder implements SqlBuilder { @@ -122,13 +124,13 @@ public class DefaultSqlBuilder implements SqlBuilder
{ if (table == null || CollectionUtils.isEmpty(table.getColumnList()) || StringUtils.isBlank(type)) { return ""; } - if(DmlType.INSERT.name().equalsIgnoreCase(type)) { + if (DmlType.INSERT.name().equalsIgnoreCase(type)) { return getInsertSql(table.getName(), table.getColumnList()); - } else if(DmlType.UPDATE.name().equalsIgnoreCase(type)) { + } else if (DmlType.UPDATE.name().equalsIgnoreCase(type)) { return getUpdateSql(table.getName(), table.getColumnList()); - } else if(DmlType.DELETE.name().equalsIgnoreCase(type)) { + } else if (DmlType.DELETE.name().equalsIgnoreCase(type)) { return getDeleteSql(table.getName(), table.getColumnList()); - }else if(DmlType.SELECT.name().equalsIgnoreCase(type)) { + } else if (DmlType.SELECT.name().equalsIgnoreCase(type)) { return getSelectSql(table.getName(), table.getColumnList()); } return ""; @@ -187,6 +189,29 @@ public class DefaultSqlBuilder implements SqlBuilder
{ return script.toString(); } + private String getInsertSql(String schemaName, String tableName, List columnList, List valueList) { + StringBuilder script = new StringBuilder(); + script.append("INSERT INTO ").append(schemaName).append(".").append(tableName) + .append(" (") + .append(String.join(",", columnList)) + .append(") VALUES (") + .append(String.join(",", valueList)) + .append(");"); + return script.toString(); + } + + private String getMultiInsertSql(String schemaName, String tableName, List columnList, List> valueList) { + StringBuilder script = new StringBuilder(); + script.append("INSERT INTO ").append(schemaName).append(".").append(tableName) + .append(" (") + .append(String.join(",", columnList)) + .append(") VALUES ") + .append(valueList.stream() + .map(values -> "(" + String.join(",", values) + ")") + .collect(Collectors.joining(",\n"))) + .append(");"); + return script.toString(); + } private List getPrimaryColumns(List
headerList) { if (CollectionUtils.isEmpty(headerList)) { return Lists.newArrayList();