diff --git a/src/main/java/org/redkale/source/AbstractDataSqlSource.java b/src/main/java/org/redkale/source/AbstractDataSqlSource.java
new file mode 100644
index 000000000..13ba55839
--- /dev/null
+++ b/src/main/java/org/redkale/source/AbstractDataSqlSource.java
@@ -0,0 +1,3283 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.redkale.source;
+
+import java.io.Serializable;
+import java.math.*;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+import java.util.function.*;
+import java.util.logging.*;
+import java.util.stream.Stream;
+import org.redkale.annotation.AutoLoad;
+import org.redkale.annotation.*;
+import org.redkale.annotation.ResourceListener;
+import org.redkale.annotation.ResourceType;
+import static org.redkale.boot.Application.*;
+import org.redkale.net.AsyncGroup;
+import org.redkale.persistence.Table;
+import org.redkale.service.Local;
+import org.redkale.source.EntityInfo.EntityColumn;
+import org.redkale.util.*;
+
+/**
+ * DataSource的SQL抽象实现类
+ * 注意: 所有的操作只能作用在一张表上,不能同时变更多张表
+ *
+ *
+ * 详情见: https://redkale.org
+ *
+ * @author zhangjx
+ */
+@Local
+@AutoLoad(false)
+@SuppressWarnings("unchecked")
+@ResourceType(DataSource.class)
+public abstract class AbstractDataSqlSource extends AbstractDataSource implements DataSqlSource, Function {
+
+ //不存在分表时最大重试次数
+ protected static final int MAX_RETRYS = 3;
+
+ protected static final Flipper FLIPPER_ONE = new Flipper(1);
+
+ protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
+
+ protected String name;
+
+ protected boolean cacheForbidden;
+
+ protected String dbtype;
+
+ private boolean autoDDL;
+
+ protected Properties readConfProps;
+
+ protected Properties writeConfProps;
+
+ @Resource(name = RESNAME_APP_CLIENT_ASYNCGROUP, required = false)
+ protected AsyncGroup clientAsyncGroup;
+
+ //配置 APP_EXECUTOR资源为null
+ @Resource(name = RESNAME_APP_EXECUTOR, required = false)
+ protected ExecutorService workExecutor;
+
+ protected BiFunction sqlFormatter;
+
+ protected BiConsumer errorCompleteConsumer = (r, t) -> {
+ //if (t != null) logger.log(Level.INFO, "CompletableFuture complete error", (Throwable) t);
+ };
+
+ protected final BiFunction> fullloader = (s, i)
+ -> ((CompletableFuture) querySheetDBAsync(i, false, false, false, null, null, (FilterNode) null)).thenApply(e -> e == null ? new ArrayList() : e.list(true));
+
+ //超过多少毫秒视为较慢, 会打印警告级别的日志, 默认值: 2000
+ protected long slowmsWarn;
+
+ //超过多少毫秒视为很慢, 会打印错误级别的日志, 默认值: 3000
+ protected long slowmsError;
+
+ //用于反向LIKE使用
+ protected String containSQL;
+
+ //用于反向LIKE使用
+ protected String notContainSQL;
+
+ //用于判断表不存在的使用, 多个SQLState用;隔开
+ protected String tableNotExistSqlstates;
+
+ //用于复制表结构使用, sql语句必须包含IF NOT EXISTS判断,确保重复执行不会报错
+ protected String tablecopySQL;
+
+ protected AnyValue config;
+
+ private EntityInfo currEntityInfo;
+
+ public AbstractDataSqlSource() {
+ }
+
+ @Override
+ public void init(AnyValue conf) {
+ super.init(conf);
+ this.config = conf;
+ if (conf.getAnyValue("read") == null) { //没有读写分离
+ Properties rwConf = new Properties();
+ conf.forEach((k, v) -> rwConf.put(k, decryptProperty(k, v)));
+ this.dbtype = parseDbtype(rwConf.getProperty(DATA_SOURCE_URL));
+ initProperties(rwConf);
+ this.readConfProps = rwConf;
+ this.writeConfProps = rwConf;
+ } else { //读写分离
+ Properties readConf = new Properties();
+ Properties writeConf = new Properties();
+ conf.getAnyValue("read").forEach((k, v) -> readConf.put(k, decryptProperty(k, v)));
+ conf.getAnyValue("write").forEach((k, v) -> writeConf.put(k, decryptProperty(k, v)));
+ this.dbtype = parseDbtype(readConf.getProperty(DATA_SOURCE_URL));
+ initProperties(readConf);
+ initProperties(writeConf);
+ this.readConfProps = readConf;
+ this.writeConfProps = writeConf;
+ }
+ this.name = conf.getValue("name", "");
+ this.sqlFormatter = (info, val) -> formatValueToString(info, val);
+ afterResourceChange();
+ }
+
+ protected void afterResourceChange() {
+ this.autoDDL = "true".equals(readConfProps.getProperty(DATA_SOURCE_TABLE_AUTODDL, "false").trim());
+
+ this.containSQL = readConfProps.getProperty(DATA_SOURCE_CONTAIN_SQLTEMPLATE, "LOCATE(${keystr}, ${column}) > 0");
+ this.notContainSQL = readConfProps.getProperty(DATA_SOURCE_NOTCONTAIN_SQLTEMPLATE, "LOCATE(${keystr}, ${column}) = 0");
+
+ this.tableNotExistSqlstates = ";" + readConfProps.getProperty(DATA_SOURCE_TABLENOTEXIST_SQLSTATES, "42000;42S02") + ";";
+ this.tablecopySQL = readConfProps.getProperty(DATA_SOURCE_TABLECOPY_SQLTEMPLATE, "CREATE TABLE IF NOT EXISTS ${newtable} LIKE ${oldtable}");
+
+ this.cacheForbidden = "NONE".equalsIgnoreCase(readConfProps.getProperty(DATA_SOURCE_CACHEMODE));
+ this.slowmsWarn = Integer.parseInt(readConfProps.getProperty(DATA_SOURCE_SLOWMS_WARN, "2000").trim());
+ this.slowmsError = Integer.parseInt(readConfProps.getProperty(DATA_SOURCE_SLOWMS_ERROR, "3000").trim());
+ }
+
+ @Override
+ @ResourceListener
+ public void onResourceChange(ResourceEvent[] events) {
+ if (events == null || events.length < 1) {
+ return;
+ }
+ //不支持读写分离模式的动态切换
+ if (readConfProps == writeConfProps && (events[0].name().startsWith("read.") || events[0].name().startsWith("write."))) {
+ throw new SourceException("DataSource(name=" + resourceName() + ") not support to change to read/write separation mode");
+ }
+ if (readConfProps != writeConfProps && (!events[0].name().startsWith("read.") && !events[0].name().startsWith("write."))) {
+ throw new SourceException("DataSource(name=" + resourceName() + ") not support to change to non read/write separation mode");
+ }
+
+ StringBuilder sb = new StringBuilder();
+ if (readConfProps == writeConfProps) {
+ List allEvents = new ArrayList<>();
+ Properties newProps = new Properties();
+ newProps.putAll(this.readConfProps);
+ for (ResourceEvent event : events) { //可能需要解密
+ String newValue = decryptProperty(event.name(), event.newValue().toString());
+ allEvents.add(ResourceEvent.create(event.name(), newValue, event.oldValue()));
+ newProps.put(event.name(), newValue);
+ sb.append("DataSource(name=").append(resourceName()).append(") change '")
+ .append(event.name()).append("' to '").append(event.coverNewValue()).append("'\r\n");
+ }
+ updateOneResourceChange(newProps, allEvents.toArray(new ResourceEvent[allEvents.size()]));
+ for (ResourceEvent event : allEvents) {
+ this.readConfProps.put(event.name(), event.newValue());
+ }
+ } else {
+ List readEvents = new ArrayList<>();
+ List writeEvents = new ArrayList<>();
+ Properties newReadProps = new Properties();
+ newReadProps.putAll(this.readConfProps);
+ Properties newWriteProps = new Properties();
+ newWriteProps.putAll(this.writeConfProps);
+ for (ResourceEvent event : events) {
+ if (event.name().startsWith("read.")) {
+ String newName = event.name().substring("read.".length());
+ String newValue = decryptProperty(event.name(), event.newValue().toString());
+ readEvents.add(ResourceEvent.create(newName, newValue, event.oldValue()));
+ newReadProps.put(event.name(), newValue);
+ } else {
+ String newName = event.name().substring("write.".length());
+ String newValue = decryptProperty(event.name(), event.newValue().toString());
+ writeEvents.add(ResourceEvent.create(newName, newValue, event.oldValue()));
+ newWriteProps.put(event.name(), newValue);
+ }
+ sb.append("DataSource(name=").append(resourceName()).append(") change '")
+ .append(event.name()).append("' to '").append(event.coverNewValue()).append("'\r\n");
+ }
+ if (!readEvents.isEmpty()) {
+ updateReadResourceChange(newReadProps, readEvents.toArray(new ResourceEvent[readEvents.size()]));
+ }
+ if (!writeEvents.isEmpty()) {
+ updateWriteResourceChange(newWriteProps, writeEvents.toArray(new ResourceEvent[writeEvents.size()]));
+ }
+ //更新Properties
+ if (!readEvents.isEmpty()) {
+ for (ResourceEvent event : readEvents) {
+ this.readConfProps.put(event.name(), event.newValue());
+ }
+ }
+ if (!writeEvents.isEmpty()) {
+ for (ResourceEvent event : writeEvents) {
+ this.writeConfProps.put(event.name(), event.newValue());
+ }
+ }
+ }
+ afterResourceChange();
+ if (sb.length() > 0) {
+ logger.log(Level.INFO, sb.toString());
+ }
+ }
+
+ protected void updateOneResourceChange(Properties newProps, ResourceEvent[] events) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected void updateReadResourceChange(Properties newReadProps, ResourceEvent[] events) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected void updateWriteResourceChange(Properties newWriteProps, ResourceEvent[] events) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected void slowLog(long startTime, String... sqls) {
+ long cost = System.currentTimeMillis() - startTime;
+ if (slowmsError > 0 && cost > slowmsError) {
+ logger.log(Level.SEVERE, DataSource.class.getSimpleName() + "(name='" + resourceName() + "') slow sql cost " + cost + " ms, content: " + Arrays.toString(sqls));
+ } else if (slowmsWarn > 0 && cost > slowmsWarn) {
+ logger.log(Level.WARNING, DataSource.class.getSimpleName() + "(name='" + resourceName() + "') very slow sql cost " + cost + " ms, content: " + Arrays.toString(sqls));
+ }
+ }
+
+ protected String parseNotExistTableName(SQLException e) {
+ String errmsg = e.getMessage();
+ char quote = '"';
+ String tableName = null;
+ int pos = errmsg.indexOf(quote);
+ if (pos < 0) {
+ quote = '\'';
+ pos = errmsg.indexOf(quote);
+ }
+ if (pos >= 0) {
+ int pos2 = errmsg.indexOf(quote, pos + 1);
+ if (pos2 > pos) {
+ tableName = errmsg.substring(pos + 1, pos2);
+ }
+ }
+ return tableName;
+ }
+
+ //解密可能存在的加密字段, 可重载
+ protected String decryptProperty(String key, String value) {
+ return value;
+ }
+
+ protected void initProperties(Properties props) {
+ if ("oracle".equals(this.dbtype)) {
+ props.setProperty(DATA_SOURCE_CONTAIN_SQLTEMPLATE, "INSTR(${keystr}, ${column}) > 0");
+ props.setProperty(DATA_SOURCE_NOTCONTAIN_SQLTEMPLATE, "INSTR(${keystr}, ${column}) = 0");
+ if (!props.containsKey(DATA_SOURCE_TABLENOTEXIST_SQLSTATES)) {
+ props.setProperty(DATA_SOURCE_TABLENOTEXIST_SQLSTATES, "42000;42S02");
+ }
+ if (!props.containsKey(DATA_SOURCE_TABLECOPY_SQLTEMPLATE)) {
+ //注意:此语句复制表结构会导致默认值和主键信息的丢失
+ props.setProperty(DATA_SOURCE_TABLECOPY_SQLTEMPLATE, "CREATE TABLE IF NOT EXISTS ${newtable} AS SELECT * FROM ${oldtable} WHERE 1=2");
+ }
+ } else if ("sqlserver".equals(this.dbtype)) {
+ props.setProperty(DATA_SOURCE_CONTAIN_SQLTEMPLATE, "CHARINDEX(${column}, ${keystr}) > 0");
+ props.setProperty(DATA_SOURCE_NOTCONTAIN_SQLTEMPLATE, "CHARINDEX(${column}, ${keystr}) = 0");
+ } else if ("postgresql".equals(this.dbtype)) {
+ if (!props.containsKey(DATA_SOURCE_TABLECOPY_SQLTEMPLATE)) { //注意:此语句复制表结构会导致默认值和主键信息的丢失
+ //注意:postgresql不支持跨库复制表结构
+ //props.setProperty(DATA_SOURCE_TABLECOPY_SQLTEMPLATE, "CREATE TABLE ${newtable} AS (SELECT * FROM ${oldtable} LIMIT 0)");
+ props.setProperty(DATA_SOURCE_TABLECOPY_SQLTEMPLATE, "CREATE TABLE IF NOT EXISTS ${newtable} (LIKE ${oldtable} INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING INDEXES)");
+ }
+ if (!props.containsKey(DATA_SOURCE_TABLENOTEXIST_SQLSTATES)) {
+ props.setProperty(DATA_SOURCE_TABLENOTEXIST_SQLSTATES, "42P01;3F000");
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (readConfProps == null) { //compileMode模式下会为null
+ return getClass().getSimpleName() + "{}";
+ }
+ if (readConfProps == writeConfProps) {
+ String url = readConfProps.getProperty(DATA_SOURCE_URL);
+ int pos = url.indexOf('?');
+ if (pos > 0) {
+ url = url.substring(0, pos) + "...";
+ }
+ return getClass().getSimpleName() + "{url=" + url + "}";
+ } else {
+ String readUrl = readConfProps.getProperty(DATA_SOURCE_URL);
+ int pos = readUrl.indexOf('?');
+ if (pos > 0) {
+ readUrl = readUrl.substring(0, pos) + "...";
+ }
+ String writeUrl = writeConfProps.getProperty(DATA_SOURCE_URL);
+ pos = writeUrl.indexOf('?');
+ if (pos > 0) {
+ writeUrl = writeUrl.substring(0, pos) + "...";
+ }
+ return getClass().getSimpleName() + "{readurl=" + readUrl + ",writeurl=" + writeUrl + "}";
+ }
+ }
+
+ //生成创建表的SQL
+ protected String[] createTableSqls(EntityInfo info) {
+ if (info == null || !autoDDL) {
+ return null;
+ }
+ Table table = info.getType().getAnnotation(Table.class);
+ if ("mysql".equals(dbtype())) { //mysql
+ StringBuilder sb = new StringBuilder();
+ sb.append("CREATE TABLE IF NOT EXISTS `").append(info.getOriginTable()).append("`(\n");
+ EntityColumn primary = null;
+ T one = info.constructorAttributes == null ? info.getCreator().create() : null;
+ for (EntityColumn column : info.getDDLColumns()) {
+ if (column.primary) {
+ primary = column;
+ }
+ String sqltype = "VARCHAR(" + column.length + ")";
+ String sqlnull = column.primary ? "NOT NULL" : "NULL";
+ if (column.type == boolean.class || column.type == Boolean.class) {
+ sqltype = "TINYINT(1)";
+ Boolean val = one == null ? null : (Boolean) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val != null && val ? 1 : 0);
+ } else if (column.type == byte.class || column.type == Byte.class) {
+ sqltype = "TINYINT";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val.byteValue());
+ } else if (column.type == short.class || column.type == Short.class) {
+ sqltype = "SMALLINT";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == char.class || column.type == Character.class) {
+ sqltype = "SMALLINT UNSIGNED";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val.intValue());
+ } else if (column.type == int.class || column.type == Integer.class || column.type == AtomicInteger.class) {
+ sqltype = "INT";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == long.class || column.type == Long.class || column.type == AtomicLong.class || column.type == LongAdder.class) {
+ sqltype = "BIGINT";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == float.class || column.type == Float.class) {
+ sqltype = "FLOAT";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == double.class || column.type == Double.class) {
+ sqltype = "DOUBLE";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == BigInteger.class) {
+ sqltype = "DECIMAL";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ } else {
+ sqltype += "(19,2)";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == BigDecimal.class) {
+ sqltype = "DECIMAL";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == String.class) {
+ if (column.length < 65535) {
+ String val = one == null ? null : (String) info.getAttribute(column.field).get(one);
+ if (val != null) {
+ sqlnull = "NOT NULL DEFAULT '" + val.replace('\'', '"') + "'";
+ } else if (column.primary) {
+ sqlnull = "NOT NULL DEFAULT ''";
+ }
+ } else if (column.length == 65535) {
+ sqltype = "TEXT";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ } else if (column.length <= 16777215) {
+ sqltype = "MEDIUMTEXT";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ } else {
+ sqltype = "LONGTEXT";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ }
+ } else if (column.type == byte[].class) {
+ if (column.length <= 65535) {
+ sqltype = "BLOB";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ } else if (column.length <= 16777215) {
+ sqltype = "MEDIUMBLOB";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ } else {
+ sqltype = "LONGBLOB";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ }
+ } else if (column.type == java.time.LocalDate.class || column.type == java.util.Date.class || "java.sql.Date".equals(column.type.getName())) {
+ sqltype = "DATE";
+ } else if (column.type == java.time.LocalTime.class || "java.sql.Time".equals(column.type.getName())) {
+ sqltype = "TIME";
+ } else if (column.type == java.time.LocalDateTime.class || "java.sql.Timestamp".equals(column.type.getName())) {
+ sqltype = "DATETIME";
+ } else { //JavaBean
+ sqltype = column.length >= 65535 ? "TEXT" : ("VARCHAR(" + column.length + ")");
+ sqlnull = !column.nullable ? "NOT NULL DEFAULT ''" : "NULL";
+ }
+ sb.append(" `").append(column.column).append("` ").append(sqltype).append(" ").append(sqlnull);
+ if (column.comment != null && !column.comment.isEmpty()) {
+ sb.append(" COMMENT '").append(column.comment.replace('\'', '"')).append("'");
+ }
+ sb.append(",\n");
+ }
+ sb.append(" PRIMARY KEY (`").append(primary.column).append("`)\n");
+ sb.append(")ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4");
+ if (table != null && !table.comment().isEmpty()) {
+ sb.append(" COMMENT '").append(table.comment().replace('\'', '"')).append("'");
+ }
+ return Utility.ofArray(sb.toString());
+ } else if ("postgresql".equals(dbtype())) { //postgresql
+ StringBuilder sb = new StringBuilder();
+ sb.append("CREATE TABLE IF NOT EXISTS ").append(info.getOriginTable()).append("(\n");
+ EntityColumn primary = null;
+ T one = info.constructorAttributes == null ? info.getCreator().create() : null;
+ List comments = new ArrayList<>();
+ if (table != null && !table.comment().isEmpty()) {
+ comments.add("COMMENT ON TABLE " + info.getOriginTable() + " IS '" + table.comment().replace('\'', '"') + "'");
+ }
+ for (EntityColumn column : info.getDDLColumns()) {
+ if (column.primary) {
+ primary = column;
+ }
+ String sqltype = "VARCHAR(" + column.length + ")";
+ String sqlnull = column.primary ? "NOT NULL" : "NULL";
+ if (column.type == boolean.class || column.type == Boolean.class) {
+ sqltype = "BOOL";
+ Boolean val = one == null ? null : (Boolean) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val != null && val ? 1 : 0);
+ } else if (column.type == byte.class || column.type == Byte.class) {
+ sqltype = "INT2";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val.byteValue());
+ } else if (column.type == short.class || column.type == Short.class) {
+ sqltype = "INT2";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == char.class || column.type == Character.class) {
+ sqltype = "INT2 UNSIGNED";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val.intValue());
+ } else if (column.type == int.class || column.type == Integer.class || column.type == AtomicInteger.class) {
+ sqltype = "INT4";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == long.class || column.type == Long.class || column.type == AtomicLong.class || column.type == LongAdder.class) {
+ sqltype = "INT8";
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == float.class || column.type == Float.class) {
+ sqltype = "FLOAT4";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == double.class || column.type == Double.class) {
+ sqltype = "FLOAT8";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == BigInteger.class) {
+ sqltype = "NUMERIC";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ } else {
+ sqltype += "(19,2)";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == BigDecimal.class) {
+ sqltype = "NUMERIC";
+ if (column.precision > 0) {
+ sqltype += "(" + column.precision + "," + column.scale + ")";
+ }
+ Number val = one == null ? null : (Number) info.getAttribute(column.field).get(one);
+ sqlnull = "NOT NULL DEFAULT " + (val == null ? 0 : val);
+ } else if (column.type == String.class) {
+ if (column.length < 65535) {
+ String val = one == null ? null : (String) info.getAttribute(column.field).get(one);
+ if (val != null) {
+ sqlnull = "NOT NULL DEFAULT '" + val.replace('\'', '"') + "'";
+ } else if (column.primary) {
+ sqlnull = "NOT NULL DEFAULT ''";
+ }
+ } else {
+ sqltype = "TEXT";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ }
+ } else if (column.type == byte[].class) {
+ sqltype = "BYTEA";
+ if (!column.nullable) {
+ sqlnull = "NOT NULL";
+ }
+ } else if (column.type == java.time.LocalDate.class || column.type == java.util.Date.class || "java.sql.Date".equals(column.type.getName())) {
+ sqltype = "DATE";
+ } else if (column.type == java.time.LocalTime.class || "java.sql.Time".equals(column.type.getName())) {
+ sqltype = "TIME";
+ } else if (column.type == java.time.LocalDateTime.class || "java.sql.Timestamp".equals(column.type.getName())) {
+ sqltype = "TIMESTAMP";
+ } else { //JavaBean
+ sqltype = column.length >= 65535 ? "TEXT" : ("VARCHAR(" + column.length + ")");
+ sqlnull = !column.nullable ? "NOT NULL DEFAULT ''" : "NULL";
+ }
+ sb.append(" ").append(column.column).append(" ").append(sqltype).append(" ").append(sqlnull);
+ if (column.comment != null && !column.comment.isEmpty()) {
+ //postgresql不支持DDL中直接带comment
+ comments.add("COMMENT ON COLUMN " + info.getOriginTable() + "." + column.column + " IS '" + column.comment.replace('\'', '"') + "'");
+ }
+ sb.append(",\n");
+ }
+ sb.append(" PRIMARY KEY (").append(primary.column).append(")\n");
+ sb.append(")");
+ return Utility.append(Utility.ofArray(sb.toString()), comments);
+ }
+ return null;
+ }
+
+ @Local
+ protected boolean isTableNotExist(EntityInfo info, String sqlCode) {
+ return sqlCode != null && !sqlCode.isEmpty() && tableNotExistSqlstates.contains(';' + sqlCode + ';');
+ }
+
+ @Local
+ protected String getTableCopySQL(EntityInfo info, String newTable) {
+ return tablecopySQL.replace("${newtable}", newTable).replace("${oldtable}", info.table);
+ }
+
+ @Local
+ protected Serializable getSQLAttrValue(EntityInfo info, Attribute attr, Serializable val) {
+ if (val != null && !(val instanceof Number) && !(val instanceof CharSequence) && !(val instanceof java.util.Date)
+ && !val.getClass().getName().startsWith("java.sql.") && !val.getClass().getName().startsWith("java.time.")) {
+ val = info.jsonConvert.convertTo(attr.genericType(), val);
+ } else if (val == null && info.isNotNullJson(attr)) {
+ val = "";
+ }
+ return val;
+ }
+
+ @Local
+ protected Map> getInsertQuestionPrepareInfo(EntityInfo info, T... entitys) {
+ Map> map = new LinkedHashMap<>();//一定要是LinkedHashMap
+ for (T entity : entitys) {
+ String table = info.getTable(entity);
+ map.computeIfAbsent(table, t -> new PrepareInfo(info.getInsertQuestionPrepareSQL(entity))).addEntity(entity);
+ }
+ return map;
+ }
+
+ @Local
+ protected Map> getInsertDollarPrepareInfo(EntityInfo info, T... entitys) {
+ Map> map = new LinkedHashMap<>();//一定要是LinkedHashMap
+ for (T entity : entitys) {
+ String table = info.getTable(entity);
+ map.computeIfAbsent(table, t -> new PrepareInfo(info.getInsertDollarPrepareSQL(entity))).addEntity(entity);
+ }
+ return map;
+ }
+
+ @Local
+ protected Map> getUpdateQuestionPrepareInfo(EntityInfo info, T... entitys) {
+ Map> map = new LinkedHashMap<>(); //一定要是LinkedHashMap
+ for (T entity : entitys) {
+ String table = info.getTable(entity);
+ map.computeIfAbsent(table, t -> new PrepareInfo(info.getUpdateQuestionPrepareSQL(entity))).addEntity(entity);
+ }
+ return map;
+ }
+
+ @Local
+ protected Map> getUpdateDollarPrepareInfo(EntityInfo info, T... entitys) {
+ Map> map = new LinkedHashMap<>();//一定要是LinkedHashMap
+ for (T entity : entitys) {
+ String table = info.getTable(entity);
+ map.computeIfAbsent(table, t -> new PrepareInfo(info.getUpdateDollarPrepareSQL(entity))).addEntity(entity);
+ }
+ return map;
+ }
+
+ @Local
+ protected Serializable getEntityAttrValue(EntityInfo info, Attribute attr, T entity) {
+ Serializable val = info.getSQLValue(attr, entity);
+ Class clazz = attr.type();
+ if (clazz == String.class
+ || clazz == int.class || clazz == long.class
+ || clazz == Integer.class || clazz == Long.class
+ || clazz == short.class || clazz == Short.class
+ || clazz == float.class || clazz == Float.class
+ || clazz == double.class || clazz == Double.class) {
+ return val;
+ }
+ return getSQLAttrValue(info, attr, val);
+ }
+
+ @Override
+ public void destroy(AnyValue config) {
+ super.destroy(config);
+ }
+
+ @Override
+ @Local
+ public void compile(Class clazz) {
+ EntityInfo.compile(clazz, this);
+ }
+
+ @Local
+ public final String dbtype() {
+ if (dbtype == null) {
+ throw new NullPointerException("dbtype is null");
+ }
+ return dbtype;
+ }
+
+ @Local
+ public final boolean autoddl() {
+ return autoDDL;
+ }
+
+ @Local
+ public abstract int directExecute(String sql);
+
+ @Local
+ public abstract int[] directExecute(String... sqls);
+
+ @Local
+ public abstract V directQuery(String sql, Function handler);
+
+ //是否异步
+ protected abstract boolean isAsync();
+
+ //index从1开始
+ protected abstract String prepareParamSign(int index);
+
+ //插入纪录
+ protected abstract CompletableFuture insertDBAsync(final EntityInfo info, T... entitys);
+
+ //删除记录
+ protected abstract CompletableFuture deleteDBAsync(final EntityInfo info, String[] tables, Flipper flipper, FilterNode node, Map> pkmap, final String... sqls);
+
+ //清空表
+ protected abstract CompletableFuture clearTableDBAsync(final EntityInfo info, String[] tables, FilterNode node, final String... sqls);
+
+ //建表
+ protected abstract CompletableFuture createTableDBAsync(final EntityInfo info, String copyTableSql, Serializable pk, final String... sqls);
+
+ //删除表
+ protected abstract CompletableFuture dropTableDBAsync(final EntityInfo info, String[] tables, FilterNode node, final String... sqls);
+
+ //更新纪录
+ protected abstract CompletableFuture updateEntityDBAsync(final EntityInfo info, T... entitys);
+
+ //更新纪录
+ protected abstract CompletableFuture updateColumnDBAsync(final EntityInfo info, Flipper flipper, final UpdateSqlInfo sql);
+
+ //查询Number Map数据
+ protected abstract CompletableFuture