diff --git a/pom.xml b/pom.xml index 68972ad69..4b458bd78 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ nutz jar Nutz - 1.r.73-SNAPSHOT + 1.r.74-SNAPSHOT UTF-8 9.4.34.v20201102 @@ -103,10 +103,16 @@ 3.4.1 test + + com.yashandb + yashandb-jdbc + 1.7.10 + test + com.alibaba druid - 1.1.22 + 1.2.25 test diff --git a/src/org/nutz/dao/DB.java b/src/org/nutz/dao/DB.java index 397ec456a..bff726988 100644 --- a/src/org/nutz/dao/DB.java +++ b/src/org/nutz/dao/DB.java @@ -59,6 +59,10 @@ public enum DB { * DM_MYSQL */ DM_MYSQL, + /** + * YashanDB + */ + YASHAN, /** * TDengine */ diff --git a/src/org/nutz/dao/DatabaseMeta.java b/src/org/nutz/dao/DatabaseMeta.java index b02aa4662..1dabfe68a 100644 --- a/src/org/nutz/dao/DatabaseMeta.java +++ b/src/org/nutz/dao/DatabaseMeta.java @@ -65,6 +65,8 @@ public void setProductName(String productName) { type = DB.DM; } else if (proName.contains("dm mysql")) { type = DB.DM_MYSQL; + } else if (proName.contains("yashandb")) { + type = DB.YASHAN; } else if (proName.contains("tdengine")) { type = DB.TDENGINE; } else { @@ -165,8 +167,12 @@ public boolean isHsql() { public boolean isDerby() { return DB.DERBY == type; } - + public boolean isDm() { return DB.DM == type; } + + public boolean isYashan() { + return DB.YASHAN == type; + } } diff --git a/src/org/nutz/dao/impl/jdbc/yashan/YashanBooleanAdaptor.java b/src/org/nutz/dao/impl/jdbc/yashan/YashanBooleanAdaptor.java new file mode 100644 index 000000000..74746d9ff --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/yashan/YashanBooleanAdaptor.java @@ -0,0 +1,38 @@ +package org.nutz.dao.impl.jdbc.yashan; + +import org.nutz.dao.jdbc.ValueAdaptor; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +/** + * 对 Oracle,Types.BOOLEAN 对于 setNull 是不工作的 其他的数据库都没有这个问题,
+ * 所以,只好把类型设成 INTEGER了 + */ +public class YashanBooleanAdaptor implements ValueAdaptor { + + public Object get(ResultSet rs, String colName) throws SQLException { + boolean re = rs.getBoolean(colName); + return rs.wasNull() ? null : re; + } + + public void set(PreparedStatement stat, Object obj, int i) throws SQLException { + if (null == obj) { + stat.setNull(i, Types.BOOLEAN); + } else { + boolean v; + if (obj instanceof Boolean) + v = (Boolean) obj; + else if (obj instanceof Number) + v = ((Number) obj).intValue() > 0; + else if (obj instanceof Character) + v = Character.toUpperCase((Character) obj) == 'T'; + else + v = Boolean.valueOf(obj.toString()); + stat.setBoolean(i, v); + } + } + +} diff --git a/src/org/nutz/dao/impl/jdbc/yashan/YashanJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/yashan/YashanJdbcExpert.java new file mode 100644 index 000000000..32918cc43 --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/yashan/YashanJdbcExpert.java @@ -0,0 +1,319 @@ +package org.nutz.dao.impl.jdbc.yashan; + +import org.nutz.dao.DB; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.LinkField; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.entity.PkType; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.impl.jdbc.AbstractJdbcExpert; +import org.nutz.dao.impl.jdbc.BlobValueAdaptor2; +import org.nutz.dao.impl.jdbc.ClobValueAdapter2; +import org.nutz.dao.jdbc.JdbcExpertConfigFile; +import org.nutz.dao.jdbc.Jdbcs; +import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.pager.Pager; +import org.nutz.dao.sql.PItem; +import org.nutz.dao.sql.Pojo; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.Pojos; +import org.nutz.lang.Mirror; +import org.nutz.lang.util.NutMap; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class YashanJdbcExpert extends AbstractJdbcExpert { + + //指定崖山表空间的TableMeta' key + private static final String META_TABLESPACE = "yashan-tablespace"; + + //崖山创建表时指定表空间的默认sql + private static String CTS = "tablespace %s\n" + + " pctfree 10\n" + + " initrans 1\n" + + " maxtrans 255\n" + + " storage\n" + + " (\n" + + " initial 64K\n" + + " minextents 1\n" + + " maxextents unlimited\n" + + " )"; + + private static String CSEQ = "CREATE SEQUENCE ${T}_${F}_SEQ MINVALUE 1" + + " MAXVALUE 999999999999 INCREMENT BY 1 START" + + " WITH 1 CACHE 20 NOORDER NOCYCLE"; + private static String DSEQ = "DROP SEQUENCE ${T}_${F}_SEQ"; + + private static String CTRI = "create or replace trigger ${T}_${F}_ST" + + " BEFORE INSERT ON ${T}" + + " FOR EACH ROW" + + " BEGIN " + + " IF :new.${F} IS NULL THEN" + + " SELECT ${T}_${F}_seq.nextval into :new.${F} FROM dual;" + + " END IF;" + + " END ${T}_${F}_ST;"; + + protected boolean ignoreOneRowPager; + + public YashanJdbcExpert(JdbcExpertConfigFile conf) { + super(conf); + } + + public ValueAdaptor getAdaptor(MappingField ef) { + Mirror mirror = ef.getMirror(); + if (mirror.isBoolean()) + return new YashanBooleanAdaptor(); + if (mirror.isOf(Clob.class)) + return new ClobValueAdapter2(Jdbcs.getFilePool()); + if (mirror.isOf(Blob.class)) + return new BlobValueAdaptor2(Jdbcs.getFilePool()); + return super.getAdaptor(ef); + } + + public boolean createEntity(Dao dao, Entity en) { + StringBuilder sb = new StringBuilder("CREATE TABLE " + en.getTableName() + "("); + // 创建字段 + for (MappingField mf : en.getMappingFields()) { + if (mf.isReadonly()) + continue; + sb.append('\n').append(mf.getColumnNameInSql()); + sb.append(' ').append(evalFieldType(mf)); + // 非主键的 @Name,应该加入唯一性约束 + if (mf.isName() && en.getPkType() != PkType.NAME) { + sb.append(" NOT NULL UNIQUE"); + } + // 普通字段 + else { + if (mf.isPk() && en.getPks().size() == 1) + sb.append(" primary key "); + if (mf.isNotNull()) + sb.append(" NOT NULL"); + if (mf.hasDefaultValue() && mf.getColumnType() != ColType.BOOLEAN) + addDefaultValue(sb, mf); + if (mf.isUnsigned() && mf.getColumnType() != ColType.BOOLEAN) // 有点暴力 + sb.append(" Check ( ").append(mf.getColumnNameInSql()).append(" >= 0)"); + } + sb.append(','); + } + + // 结束表字段设置 + sb.setCharAt(sb.length() - 1, ')'); + + //指定表空间 + if (en.hasMeta(META_TABLESPACE)) { + sb.append(String.format(CTS, en.getMeta(META_TABLESPACE))); + } + + List sqls = new ArrayList(); + sqls.add(Sqls.create(sb.toString())); + + // 创建复合主键 + List pks = en.getPks(); + if (pks.size() > 1) { + StringBuilder pkNames = new StringBuilder(); + for (MappingField pk : pks) { + pkNames.append(pk.getColumnName()).append(','); + } + pkNames.setLength(pkNames.length() - 1); + + String pkNames2 = makePksName(en); + + String sql = String.format("alter table %s add constraint primary_key_%s primary key (%s)", + en.getTableName(), + pkNames2, + pkNames); + sqls.add(Sqls.create(sql)); + } + // // 处理非主键unique + // for (MappingField mf : en.getMappingFields()) { + // if(!mf.isPk()) + // continue; + // String sql = + // gSQL("alter table ${T} add constraint unique_key_${F} unique (${F});", + // en.getTableName(),mf.getColumnName()); + // sqls.add(Sqls.create(sql)); + // } + // 处理AutoIncreasement + for (MappingField mf : en.getMappingFields()) { + if (!mf.isAutoIncreasement()) + continue; + // 序列 + sqls.add(Sqls.create(gSQL(CSEQ, en.getTableName(), mf.getColumnName()))); + // 触发器 + sqls.add(Sqls.create(gSQL(CTRI, en.getTableName(), mf.getColumnName()))); + } + + // 创建索引 + sqls.addAll(createIndexs(en)); + + // TODO 详细处理Clob + // TODO 详细处理Blob + + // 执行创建语句 + dao.execute(sqls.toArray(new Sql[sqls.size()])); + // 创建关联表 + createRelation(dao, en); + // 添加注释(表注释与字段注释) + addComment(dao, en); + + return true; + } + + public void formatQuery(Pojo pojo) { + Pager pager = pojo.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) { + if (ignoreOneRowPager && pager.getPageNumber() == 1 && pager.getPageSize() == 1) + return; + pojo.insertFirst(Pojos.Items.wrap("SELECT * FROM (SELECT T.*, ROWNUM RN FROM (")); + pojo.append(Pojos.Items.wrapf(") T WHERE ROWNUM <= %d) WHERE RN > %d", + pager.getOffset() + pager.getPageSize(), + pager.getOffset())); + } + } + + @Override + public void formatQuery(Sql sql) { + Pager pager = sql.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) { + if (ignoreOneRowPager && pager.getPageNumber() == 1 && pager.getPageSize() == 1) + return; + String pre = "SELECT * FROM (SELECT T.*, ROWNUM RN FROM ("; + String last = String.format(") T WHERE ROWNUM <= %d) WHERE RN > %d", + pager.getOffset() + pager.getPageSize(), + pager.getOffset()); + sql.setSourceSql(pre + sql.getSourceSql() + last); + } + } + + public String getDatabaseType() { + return DB.YASHAN.name(); + } + + public String evalFieldType(MappingField mf) { + if (mf.getCustomDbType() != null) + return mf.getCustomDbType(); + int intLen = 4; + switch (mf.getColumnType()) { + case BOOLEAN: + if (mf.hasDefaultValue()) + return "boolean DEFAULT " + getDefaultValue(mf) + " check (" + mf.getColumnNameInSql() + " in(true,false))"; + return "boolean check (" + mf.getColumnNameInSql() + " in(true,false))"; + case TEXT: + return "CLOB"; + case VARCHAR: + // 崖山中 VARCHAR2的宽度单位是字节,加上char才是字符 + return "VARCHAR2(" + mf.getWidth() + " char)"; + + case INT: + int width = mf.getWidth(); + if (width <= 0) { + return "INT"; + } else if (width <= 2) { + return "TINYINT"; + } else if (width <= 4) { + return "SMALLINT"; + } else if (width <= 8) { + return "INT"; + } + return "BIGINT"; + + case FLOAT: + // 用户自定义了精度 + if (mf.getWidth() > 0 && mf.getPrecision() > 0) { + return "NUMBER(" + mf.getWidth() + "," + mf.getPrecision() + ")"; + } + // 用默认精度 + if (mf.getMirror().isDouble()) + return "NUMBER(15,10)"; + return "NUMBER"; + case TIME: + case DATETIME: + case DATE: + return "DATE"; + default: + return super.evalFieldType(mf); + } + } + + @Override + protected String createResultSetMetaSql(Entity en) { + return "select * from " + en.getViewName() + " where rownum <= 1"; + } + + @Override + public boolean dropEntity(Dao dao, Entity en) { + if (super.dropEntity(dao, en)) { + if (en.getPks().isEmpty()) + return true; + List sqls = new ArrayList(); + for (MappingField pk : en.getPks()) { + if (pk.isAutoIncreasement()) { + String sql = gSQL(DSEQ, en.getTableName(), pk.getColumnName()); + sqls.add(Sqls.create(sql)); + } + } + try { + dao.execute(sqls.toArray(new Sql[sqls.size()])); + return true; + } catch (Exception e) { + } + } + return false; + } + + public boolean isSupportAutoIncrement() { + return false; + } + + public boolean addColumnNeedColumn() { + return false; + } + + public boolean supportTimestampDefault() { + return false; + } + + public String wrapKeyword(String columnName, boolean force) { + if (force || keywords.contains(columnName.toUpperCase())) + return "\"" + columnName + "\""; + return null; + } + + // https://docs.oracle.com/cd/B12037_01/server.101/b10755/statviews_1061.htm + public List getIndexNames(Entity en, Connection conn) throws SQLException { + List names = new ArrayList(); + String showIndexs = "SELECT * FROM user_indexes WHERE table_name='" + en.getTableName() + "'"; + PreparedStatement ppstat = conn.prepareStatement(showIndexs); + ResultSet rest = ppstat.executeQuery(); + while (rest.next()) { + String index = rest.getString(2); + names.add(index); + } + return names; + } + + public void setupProperties(NutMap conf) { + super.setupProperties(conf); + this.ignoreOneRowPager = conf.getBoolean("nutz.dao.jdbc.yashan.ignoreOneRowPager", false); + } + + @Override + public PItem formatLeftJoinLink(Object obj, LinkField lnk, Entity en) { + Entity lnkEntity = lnk.getLinkedEntity(); + String linkName = safeTableName(lnk.getName()); + String LJ = String.format("LEFT JOIN %s %s ON %s.%s = %s.%s", + lnkEntity.getTableName(), + linkName, + en.getTableName(), + lnk.getHostField().getColumnNameInSql(), + linkName, + lnk.getLinkedField().getColumnNameInSql()); + return Pojos.Items.wrap(LJ); + } +} diff --git a/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js b/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js index 796af167c..dcc80901d 100644 --- a/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js +++ b/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js @@ -14,7 +14,8 @@ var ioc = { "mariadb.*" : "org.nutz.dao.impl.jdbc.mysql.MysqlJdbcExpert", "postgresql.*" : "org.nutz.dao.impl.jdbc.psql.PsqlJdbcExpert", "db2.*" : "org.nutz.dao.impl.jdbc.db2.Db2JdbcExpert", - "oracle.*" : "org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert", + "oracle.*" : "org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert", + "yashandb.*" : "org.nutz.dao.impl.jdbc.yashan.YashanJdbcExpert", "kingbasees.*" : "org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert", "kingbasepsql.*" : "org.nutz.dao.impl.jdbc.psql.PsqlJdbcExpert", // SqlServer2005 --> 9.0 , SqlServer2008 --> 10.0 diff --git a/test/tmpl.nutz-test.properties b/test/tmpl.nutz-test.properties index b27596684..0dcf12291 100644 --- a/test/tmpl.nutz-test.properties +++ b/test/tmpl.nutz-test.properties @@ -4,6 +4,11 @@ url=jdbc:mysql://127.0.0.1:3306/nutztest?zeroDateTimeBehavior=convertToNull&serv username=root password=root +#driver=com.yashandb.jdbc.Driver +#url=jdbc:yasdb://192.168.1.2:1688/yashan?connectTimeout=60&socketTimeout=120&loginTimeout=60 +#username=root +#password=root + #driver=oracle.jdbc.OracleDriver #url=jdbc:oracle:thin:@//127.0.0.1:1521/XE #username=system