Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持json事物, delete、put 支持子查询, mysql8 with-as表达式等 #481

Merged
merged 6 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions APIJSONORM/src/main/java/apijson/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public JSONObject setUserIdIn(List<Object> list) {
public static final String KEY_ORDER = "@order"; //排序方式
public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段
public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出
public static final String KEY_METHOD = "@method"; //json对象配置操作方法

public static final List<String> TABLE_KEY_LIST;
static {
Expand Down
2 changes: 1 addition & 1 deletion APIJSONORM/src/main/java/apijson/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ public static boolean isNotEmpty(String s, boolean trim) {
PATTERN_ALPHA = Pattern.compile("^[a-zA-Z]+$");
PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$");
PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$");
PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过
PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_:]+$");//已用55个中英字符测试通过
//newest phone regex expression reference https://github.com/VincentSit/ChinaMobilePhoneNumberRegex
PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$");
PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$");
Expand Down
191 changes: 168 additions & 23 deletions APIJSONORM/src/main/java/apijson/orm/AbstractParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
*/
public abstract class AbstractParser<T extends Object> implements Parser<T>, ParserCreator<T>, VerifierCreator<T>, SQLCreator {
protected static final String TAG = "AbstractParser";

protected Map<Object, RequestMethod> key_method_Map = new HashMap<>();
/**
* 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。
* 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。
Expand Down Expand Up @@ -572,28 +572,11 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers
return request;//需要指定JSON结构的get请求可以改为post请求。一般只有对安全性要求高的才会指定,而这种情况用明文的GET方式几乎肯定不安全
}

if (StringUtil.isEmpty(tag, true)) {
throw new IllegalArgumentException("请在最外层传 tag !一般是 Table 名,例如 \"tag\": \"User\" ");
}

//获取指定的JSON结构 <<<<<<<<<<<<
JSONObject object = null;
String error = "";
try {
object = getStructure("Request", method.name(), tag, version);
} catch (Exception e) {
error = e.getMessage();
}
if (object == null) { //empty表示随意操作 || object.isEmpty()) {
throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.name() + ", tag: " + tag + " 对应的 structure !"
+ "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!");
}

//获取指定的JSON结构 >>>>>>>>>>>>>>
JSONObject target = wrapRequest(method, tag, object, true);
// if (StringUtil.isEmpty(tag, true)) {
// throw new IllegalArgumentException("请在最外层传 tag !一般是 Table 名,例如 \"tag\": \"User\" ");
// }

//JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {}
return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator);
return batchVerify(method, tag, version, name, request, maxUpdateCount, creator);
}


Expand Down Expand Up @@ -1047,6 +1030,8 @@ public JSONObject onObjectParse(final JSONObject request
if (op == null) {
op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable);
}
// 对象 - 设置 method
setOpMethod(request, op, name);
op = op.parse(name, isReuse);

JSONObject response = null;
Expand Down Expand Up @@ -2022,7 +2007,8 @@ protected void onBegin() {
*/
protected void onCommit() {
// Log.d(TAG, "onCommit >>");
if (RequestMethod.isQueryMethod(requestMethod)) {
// this.sqlExecutor.getTransactionIsolation() 只有json第一次执行才会设置, get请求=0
if (RequestMethod.isQueryMethod(requestMethod) && this.sqlExecutor.getTransactionIsolation() == Connection.TRANSACTION_NONE ) {
return;
}

Expand Down Expand Up @@ -2068,4 +2054,163 @@ protected void onClose() {
queryResultMap = null;
}

private void setOpMethod(JSONObject request,ObjectParser op, String key) {
if(key != null && request.getString(apijson.JSONObject.KEY_METHOD) != null) {
String _method = request.getString(apijson.JSONObject.KEY_METHOD);
if( _method != null) {
RequestMethod method = RequestMethod.valueOf(_method.toUpperCase());
this.setMethod(method);
op.setMethod(method);
}
}
}

protected JSONObject getRequestStructure(RequestMethod method, String tag, int version) throws Exception {
// 获取指定的JSON结构 <<<<<<<<<<<<
JSONObject object = null;
String error = "";
try {
object = getStructure("Request", method.name(), tag, version);
} catch (Exception e) {
error = e.getMessage();
}
if (object == null) { // empty表示随意操作 || object.isEmpty()) {
throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.name() + ", tag: " + tag + " 对应的 structure !" + "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!");
}
return object;
}

private JSONObject batchVerify(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request, int maxUpdateCount, SQLCreator creator) throws Exception {
JSONObject jsonObject = new JSONObject(true);
if (request.keySet() == null || request.keySet().size() == 0) {
throw new IllegalArgumentException("json对象格式不正确 !,例如 \"User\": {}");
}

for (String key : request.keySet()) {
// key重复直接抛错(xxx:alias, xxx:alias[])
if (jsonObject.containsKey(key) || jsonObject.containsKey(key + apijson.JSONObject.KEY_ARRAY)) {
throw new IllegalArgumentException("对象名重复,请添加别名区分 ! ,重复对象名为: " + key);
}

// @post、@get等RequestMethod
try {
if (key.startsWith("@")) {
try {
// 如果不匹配,不处理即可
RequestMethod l_method = RequestMethod.valueOf(key.substring(1).toUpperCase());
if (l_method != null) {
if (request.get(key) instanceof JSONArray) {
for (Object objKey : request.getJSONArray(key)) {
key_method_Map.put(objKey, l_method);
}
continue;
} else {
throw new IllegalArgumentException("参数 " + key + " 必须是数组格式 ! ,例如: [\"Moment\", \"Comment[]\"]");
}
}
} catch (Exception e) {
}
}

// 如果对象设置了@method, 优先使用 对象内部的@method
// 对于没有显式声明操作方法的,直接用 URL(/get, /post 等) 对应的默认操作方法
// 将method 设置到每个object, op执行会解析
if (request.get(key) instanceof JSONObject) {
if(request.getJSONObject(key).getString(apijson.JSONObject.KEY_METHOD) == null) {
if (key_method_Map.get(key) == null) {
// 数组会解析为对象进行校验,做一下兼容
if(key_method_Map.get(key + apijson.JSONObject.KEY_ARRAY) == null) {
request.getJSONObject(key).put(apijson.JSONObject.KEY_METHOD, method);
}else {
request.getJSONObject(key).put(apijson.JSONObject.KEY_METHOD, key_method_Map.get(key + apijson.JSONObject.KEY_ARRAY));
}
} else {
request.getJSONObject(key).put(apijson.JSONObject.KEY_METHOD, key_method_Map.get(key));
}
}

// get请求不校验
RequestMethod _method = RequestMethod.valueOf(request.getJSONObject(key).getString(apijson.JSONObject.KEY_METHOD).toUpperCase());
if (RequestMethod.isPublicMethod(_method)) {
jsonObject.put(key, request.getJSONObject(key));
continue;
}
}

if (key.startsWith("@") || key.endsWith("@")) {
jsonObject.put(key, request.get(key));
continue;
}


if (request.get(key) instanceof JSONObject || request.get(key) instanceof JSONArray) {
RequestMethod _method = null;
if (request.get(key) instanceof JSONObject) {
_method = RequestMethod.valueOf(request.getJSONObject(key).getString(apijson.JSONObject.KEY_METHOD).toUpperCase());
} else {
if (key_method_Map.get(key) == null) {
_method = method;
} else {
_method = key_method_Map.get(key);
}
}

String _tag = buildTag(request, key);
JSONObject requestItem = new JSONObject();
requestItem.put(_tag, request.get(key));
JSONObject object = getRequestStructure(_method, _tag, version);
JSONObject ret = objectVerify(_method, _tag, version, name, requestItem, maxUpdateCount, creator, object);
jsonObject.put(key, ret.get(_tag));
} else {
jsonObject.put(key, request.get(key));
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e);
}
}

return jsonObject;
}

/**
* { "xxx:aa":{ "@tag": "" }}
* 生成规则:
* 1、@tag存在,tag=@tag
* 2、@tag不存在
* 1)、存在别名
* key=对象: tag=key去除别名
* key=数组: tag=key去除别名 + []
* 2)、不存在别名
* tag=key
* tag=key + []
* @param request
* @param key
* @return
*/
private String buildTag(JSONObject request, String key) {
String _tag = null;
if (request.get(key) instanceof JSONObject && request.getJSONObject(key).getString("@tag") != null) {
_tag = request.getJSONObject(key).getString("@tag");
} else {
int keyIndex = key.indexOf(":");
if (keyIndex != -1) {
_tag = key.substring(0, keyIndex);
if (apijson.JSONObject.isTableArray(key)) {
_tag += apijson.JSONObject.KEY_ARRAY;
}
} else {
// 不存在别名
_tag = key;
}
}
return _tag;
}

protected JSONObject objectVerify(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request, int maxUpdateCount, SQLCreator creator, JSONObject object) throws Exception {
// 获取指定的JSON结构 >>>>>>>>>>>>>>
JSONObject target = wrapRequest(method, tag, object, true);
// JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {}
return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator);
}
}
90 changes: 86 additions & 4 deletions APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,9 @@ public abstract class AbstractSQLConfig implements SQLConfig {

}

// mysql8版本以上,子查询支持with as表达式
private List<String> withAsExpreSqlList = null;
protected List<Object> withAsExprePreparedValueList = new ArrayList<>();
private int[] dbVersionNums = null;
@Override
public int[] getDBVersionNums() {
Expand Down Expand Up @@ -3909,6 +3912,39 @@ else if (isPresto() || isTrino()) {

//key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/**
* 只要 method != RequestMethod.POST 就都支持 with-as表达式
* @param cfg
* @param subquery
* @return
* @throws Exception
*/
private String withAsExpreSubqueryString(SQLConfig cfg, Subquery subquery) throws Exception {
if(cfg.getMethod() != RequestMethod.POST && this.withAsExpreSqlList == null) {
this.setWithAsExpreList();
}
String withAsExpreSql;
if(this.withAsExpreSqlList != null) {
String withQuoteName = getQuote() + subquery.getKey() + getQuote();
this.withAsExpreSqlList.add(" " + withQuoteName + " AS " + "(" + cfg.getSQL(isPrepared()) + ") ");
withAsExpreSql = " SELECT * FROM " + withQuoteName;

// 预编译参数
List<Object> subPvl = cfg.getPreparedValueList();
if (subPvl != null && subPvl.isEmpty() == false) {
this.withAsExprePreparedValueList.addAll(subPvl);
cfg.setPreparedValueList(new ArrayList<>());
}
}else {
withAsExpreSql = cfg.getSQL(isPrepared());
// mysql 才存在这个问题, 主表和子表是一张表
if(this.isMySQL() && StringUtil.equals(this.getTable(), subquery.getFrom())) {
withAsExpreSql = " SELECT * FROM (" + withAsExpreSql+") AS " + getQuote() + subquery.getKey() + getQuote();
}
}
return withAsExpreSql;
}

@Override
public String getSubqueryString(Subquery subquery) throws Exception {
if (subquery == null) {
Expand All @@ -3919,7 +3955,8 @@ public String getSubqueryString(Subquery subquery) throws Exception {
SQLConfig cfg = subquery.getConfig();

cfg.setPreparedValueList(new ArrayList<>());
String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") ";
String withAsExpreSql = withAsExpreSubqueryString(cfg, subquery);
String sql = (range == null || range.isEmpty() ? "" : range) + "(" + withAsExpreSql + ") ";

//// SELECT .. FROM(SELECT ..) .. WHERE .. 格式需要把子查询中的预编译值提前
//// 如果外查询 SELECT concat(`name`,?) 这种 SELECT 里也有预编译值,那就不能这样简单反向
Expand Down Expand Up @@ -4123,19 +4160,24 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
return null;
}

String cSql = null;
switch (config.getMethod()) {
case POST:
return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString();
case PUT:
if(config.isClickHouse()){
return "ALTER TABLE " + tablePath + " UPDATE" + config.getSetString() + config.getWhereString(true);
}
return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : "");
cSql = "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : "");
cSql = buildWithAsExpreSql(config, cSql);
return cSql;
case DELETE:
if(config.isClickHouse()){
return "ALTER TABLE " + tablePath + " DELETE" + config.getWhereString(true);
}
return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT
cSql = "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT
cSql = buildWithAsExpreSql(config, cSql);
return cSql;
default:
String explain = config.isExplain() ? (config.isSQLServer() ? "SET STATISTICS PROFILE ON " : (config.isOracle() || config.isDameng() || config.isKingBase() ? "EXPLAIN PLAN FOR " : "EXPLAIN ")) : "";
if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { // FIXME 为啥是 code 而不是 count ?
Expand All @@ -4156,8 +4198,32 @@ public static String getSQL(AbstractSQLConfig config) throws Exception {
return explain + config.getOraclePageSql(sql);
}

return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(tablePath, config) + config.getLimitString();
cSql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(tablePath, config) + config.getLimitString();
cSql = buildWithAsExpreSql(config, cSql);
return explain + cSql;
}
}

private static String buildWithAsExpreSql(@NotNull AbstractSQLConfig config, String cSql) throws Exception {
if(config.withAsExpreSqlList != null && config.withAsExpreSqlList.size() > 0) {
String withAsExpreSql = "WITH ";
// 只有一条
if(config.withAsExpreSqlList.size() == 1) {
withAsExpreSql += config.withAsExpreSqlList.get(0) + "\n" + cSql;
}else {
int lastIndex = config.withAsExpreSqlList.size() - 1;
for (int i = 0; i < config.withAsExpreSqlList.size(); i++) {
if(i == lastIndex) {
withAsExpreSql += config.withAsExpreSqlList.get(i) + "\n" + cSql;
}else {
withAsExpreSql += config.withAsExpreSqlList.get(i) + ",\n";
}
}
}
cSql = withAsExpreSql;
config.setWithAsExpreList();
}
return cSql;
}

/**Oracle的分页获取
Expand Down Expand Up @@ -5508,4 +5574,20 @@ public void onMissingKey4Combine(String name, JSONObject request, String combine

}

private void setWithAsExpreList() {
// mysql8版本以上,子查询支持with as表达式
if(this.isMySQL() && this.getDBVersionNums()[0] >= 8) {
this.withAsExpreSqlList = new ArrayList<>();
}
}

@Override
public List<Object> getWithAsExprePreparedValueList() {
return this.withAsExprePreparedValueList;
}

@Override
public void setWithAsExprePreparedValueList(List<Object> list) {
this.withAsExprePreparedValueList = list;
}
}
9 changes: 9 additions & 0 deletions APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,15 @@ else if (RequestMethod.isGetMethod(config.getMethod(), true)) {
}

List<Object> valueList = config.isPrepared() ? config.getPreparedValueList() : null;
List<Object> withAsExprePreparedValueList = config.isPrepared() ? config.getWithAsExprePreparedValueList() : null;

// 不同数据库, 预编译mysql使用with-as
if (valueList != null && withAsExprePreparedValueList != null && withAsExprePreparedValueList.size() > 0) {
withAsExprePreparedValueList.addAll(valueList);
valueList = withAsExprePreparedValueList;
// 多条POST/PUT/DELETE语句的情况,需要重新初始化
config.setWithAsExprePreparedValueList(new ArrayList<>());
}

if (valueList != null && valueList.isEmpty() == false) {
for (int i = 0; i < valueList.size(); i++) {
Expand Down
Loading