chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:52 +08:00
commit 980028369b
6111 changed files with 1026474 additions and 0 deletions
@@ -0,0 +1,53 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.dialect;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.impl.enums.postgresql.TrustedPostgresqlFunctionEnum;
/**
* PostgreSQL database dialect.
*
* @author xiweng.yy
*/
public class PostgresqlDatabaseDialect extends AbstractDatabaseDialect {
@Override
public String getType() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String getFunction(String functionName) {
return TrustedPostgresqlFunctionEnum.getFunctionByName(functionName);
}
@Override
public String getLimitPageSqlWithMark(String sql) {
return sql + " OFFSET ? LIMIT ? ";
}
@Override
public String getLimitPageSql(String sql, int pageNo, int pageSize) {
return sql + " OFFSET " + getPagePrevNum(pageNo, pageSize) + " LIMIT " + pageSize;
}
@Override
public String getLimitPageSqlWithOffset(String sql, int startOffset, int pageSize) {
return sql + " OFFSET " + startOffset + " LIMIT " + pageSize;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.enums.postgresql;
import java.util.HashMap;
import java.util.Map;
/**
* The TrustedSqlFunctionEnum enum class is used to enumerate and manage a list of trusted built-in SQL functions.
* By using this enum, you can verify whether a given SQL function is part of the trusted functions list
* to avoid potential SQL injection risks.
*
* @author caoyanan
*/
public enum TrustedPostgresqlFunctionEnum {
/**
* NOW().
*/
NOW("NOW()", "NOW()");
private static final Map<String, TrustedPostgresqlFunctionEnum> LOOKUP_MAP = new HashMap<>();
static {
for (TrustedPostgresqlFunctionEnum entry : TrustedPostgresqlFunctionEnum.values()) {
LOOKUP_MAP.put(entry.functionName, entry);
}
}
private final String functionName;
private final String function;
TrustedPostgresqlFunctionEnum(String functionName, String function) {
this.functionName = functionName;
this.function = function;
}
/**
* Get the function name.
*
* @param functionName function name
* @return function
*/
public static String getFunctionByName(String functionName) {
TrustedPostgresqlFunctionEnum entry = LOOKUP_MAP.get(functionName);
if (entry != null) {
return entry.function;
}
throw new IllegalArgumentException(
String.format("Invalid function name: %s", functionName));
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 1999-2026 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.impl.enums.postgresql.TrustedPostgresqlFunctionEnum;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.AiResourceMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ext.WhereBuilder;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
/**
* The postgresql implementation of {@link AiResourceMapper}.
*
* @author nacos
*/
public class AiResourceMapperByPostgresql extends AbstractMapper implements AiResourceMapper {
@Override
public MapperResult findAiResourceFetchRows(MapperContext context) {
WhereBuilder where = new WhereBuilder(
"SELECT id,gmt_create,gmt_modified,name,type,c_desc,status,namespace_id,"
+ "biz_tags,ext,c_from,version_info,meta_version,scope,owner,download_count "
+ "FROM ai_resource");
where.eq("namespace_id", context.getWhereParameter(FieldConstant.NAMESPACE_ID));
appendExtraQueryCondition(where, context);
MapperResult built = where.build();
String sql = built.getSql() + resolveOrderByClause(context) + " LIMIT "
+ context.getPageSize() + " OFFSET "
+ context.getStartRow();
return new MapperResult(sql, built.getParamList());
}
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String getFunction(String functionName) {
return TrustedPostgresqlFunctionEnum.getFunctionByName(functionName);
}
}
@@ -0,0 +1,74 @@
/*
* Copyright 1999-2026 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.impl.enums.postgresql.TrustedPostgresqlFunctionEnum;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.AiResourceVersionMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ext.WhereBuilder;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
/**
* The postgresql implementation of {@link AiResourceVersionMapper}.
*
* @author nacos
*/
public class AiResourceVersionMapperByPostgresql extends AbstractMapper
implements AiResourceVersionMapper {
@Override
public MapperResult findAiResourceVersionFetchRows(MapperContext context) {
WhereBuilder where = new WhereBuilder(
"SELECT id,gmt_create,gmt_modified,type,author,name,c_desc,status,version,namespace_id,storage,publish_pipeline_info,download_count "
+ "FROM ai_resource_version");
where.eq("namespace_id", context.getWhereParameter(FieldConstant.NAMESPACE_ID));
where.and().eq("name", context.getWhereParameter(FieldConstant.NAME));
Object type = context.getWhereParameter(FieldConstant.TYPE);
if (type != null && StringUtils.isNotBlank(String.valueOf(type))) {
where.and().eq("type", type);
}
Object status = context.getWhereParameter(FieldConstant.STATUS);
if (status != null && StringUtils.isNotBlank(String.valueOf(status))) {
where.and().eq("status", status);
}
Object version = context.getWhereParameter(FieldConstant.VERSION);
if (version != null && StringUtils.isNotBlank(String.valueOf(version))) {
where.and().eq("version", version);
}
MapperResult built = where.build();
String sql = built.getSql() + " ORDER BY gmt_modified DESC LIMIT " + context.getPageSize()
+ " OFFSET "
+ context.getStartRow();
return new MapperResult(sql, built.getParamList());
}
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String getFunction(String functionName) {
return TrustedPostgresqlFunctionEnum.getFunctionByName(functionName);
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 1999-2024 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.impl.enums.postgresql.TrustedPostgresqlFunctionEnum;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import java.util.Collections;
/**
* The postgresql implementation of ConfigInfoGrayMapper.
*
* @author WangzJi
**/
public class ConfigInfoGrayMapperByPostgresql extends AbstractMapper
implements ConfigInfoGrayMapper {
@Override
public MapperResult findAllConfigInfoGrayForDumpAllFetchRows(MapperContext context) {
String sql =
" SELECT id,data_id,group_id,tenant_id,gray_name,gray_rule,app_name,content,md5,gmt_modified "
+ " FROM config_info_gray ORDER BY id LIMIT " + context.getPageSize() + " OFFSET "
+ context.getStartRow();
return new MapperResult(sql, Collections.emptyList());
}
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String getFunction(String functionName) {
return TrustedPostgresqlFunctionEnum.getFunctionByName(functionName);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.impl.enums.postgresql.TrustedPostgresqlFunctionEnum;
import com.alibaba.nacos.plugin.datasource.impl.base.BaseConfigInfoMapper;
/**
* The postgresql implementation of ConfigInfoMapper.
*
* @author Long Yu
**/
public class ConfigInfoMapperByPostgresql extends BaseConfigInfoMapper {
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String getFunction(String functionName) {
return TrustedPostgresqlFunctionEnum.getFunctionByName(functionName);
}
}
@@ -0,0 +1,96 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.ArrayUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.impl.base.BaseConfigTagsRelationMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ext.WhereBuilder;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
/**
* The postgresql implementation of ConfigTagsRelationMapper.
*
* @author Long Yu
**/
public class ConfigTagsRelationMapperByPostgresql extends BaseConfigTagsRelationMapper {
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public MapperResult findConfigInfoLike4PageFetchRows(MapperContext context) {
final String tenant = (String) context.getWhereParameter(FieldConstant.TENANT_ID);
final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID);
final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID);
final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME);
final String content = (String) context.getWhereParameter(FieldConstant.CONTENT);
final String[] tagArr = (String[]) context.getWhereParameter(FieldConstant.TAG_ARR);
final String[] types = (String[]) context.getWhereParameter(FieldConstant.TYPE);
// 构建内层查询:根据标签条件筛选配置
WhereBuilder innerWhere = new WhereBuilder(
"SELECT a.id,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content,a.md5,a.encrypted_data_key,a.type,a.c_desc "
+ "FROM config_info a ");
innerWhere.like("a.tenant_id", tenant);
if (StringUtils.isNotBlank(dataId)) {
innerWhere.and().like("a.data_id", dataId);
}
if (StringUtils.isNotBlank(group)) {
innerWhere.and().like("a.group_id", group);
}
if (StringUtils.isNotBlank(appName)) {
innerWhere.and().eq("a.app_name", appName);
}
if (StringUtils.isNotBlank(content)) {
innerWhere.and().like("a.content", content);
}
if (!ArrayUtils.isEmpty(tagArr)) {
innerWhere.and().exists("SELECT 1 FROM config_tags_relation b WHERE ", sub -> {
sub.eqColumn("b.id", "a.id").and().startParentheses();
for (int i = 0; i < tagArr.length; i++) {
if (i != 0) {
sub.or();
}
sub.like("b.tag_name", tagArr[i]);
}
sub.endParentheses();
});
}
if (!ArrayUtils.isEmpty(types)) {
innerWhere.and().in("a.type", types);
}
innerWhere.offset(context.getStartRow(), context.getPageSize());
MapperResult innerResult = innerWhere.build();
// 构建外层查询:获取筛选出的配置的完整标签信息
// 使用exists和标量子查询规避group by ...content..带来的大字段分组开销
final String sql =
"SELECT c.id,c.data_id,c.group_id,c.tenant_id,c.app_name,c.content,c.md5,c.encrypted_data_key,c.type,c.c_desc,"
+ "(SELECT STRING_AGG(tag_name, ',') FROM config_tags_relation d WHERE d.id = c.id) as config_tags "
+ "FROM (" + innerResult.getSql() + ") c ";
return new MapperResult(sql, innerResult.getParamList());
}
}
@@ -0,0 +1,143 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.NamespaceUtil;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.impl.base.BaseGroupCapacityMapper;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The PostgreSQL implementation of GroupCapacityMapper.
* Override methods to remove MySQL backticks around 'usage' field.
*
* @author Long Yu
**/
public class GroupCapacityMapperByPostgresql extends BaseGroupCapacityMapper {
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public MapperResult select(MapperContext context) {
String sql =
"SELECT id, quota, usage, max_size, max_aggr_count, max_aggr_size, group_id FROM group_capacity "
+ "WHERE group_id = ?";
return new MapperResult(sql,
Collections.singletonList(context.getWhereParameter(FieldConstant.GROUP_ID)));
}
@Override
public MapperResult insertIntoSelect(MapperContext context) {
List<Object> paramList = new ArrayList<>();
paramList.add(context.getUpdateParameter(FieldConstant.GROUP_ID));
paramList.add(context.getUpdateParameter(FieldConstant.QUOTA));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_SIZE));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_AGGR_COUNT));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_AGGR_SIZE));
paramList.add(context.getUpdateParameter(FieldConstant.GMT_CREATE));
paramList.add(context.getUpdateParameter(FieldConstant.GMT_MODIFIED));
String sql =
"INSERT INTO group_capacity (group_id, quota, usage, max_size, max_aggr_count, max_aggr_size,"
+ " max_history_count, gmt_create, gmt_modified)"
+ " SELECT ?, ?, count(*), ?, ?, ?, 0, ?, ? FROM config_info";
return new MapperResult(sql, paramList);
}
@Override
public MapperResult insertIntoSelectByWhere(MapperContext context) {
final String sql =
"INSERT INTO group_capacity (group_id, quota, usage, max_size, max_aggr_count, max_aggr_size,"
+ " max_history_count, gmt_create, gmt_modified)"
+ " SELECT ?, ?, count(*), ?, ?, ?, 0, ?, ? FROM config_info WHERE group_id=? AND tenant_id = '"
+ NamespaceUtil.getNamespaceDefaultId() + "'";
List<Object> paramList = new ArrayList<>();
paramList.add(context.getUpdateParameter(FieldConstant.GROUP_ID));
paramList.add(context.getUpdateParameter(FieldConstant.QUOTA));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_SIZE));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_AGGR_COUNT));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_AGGR_SIZE));
paramList.add(context.getUpdateParameter(FieldConstant.GMT_CREATE));
paramList.add(context.getUpdateParameter(FieldConstant.GMT_MODIFIED));
paramList.add(context.getWhereParameter(FieldConstant.GROUP_ID));
return new MapperResult(sql, paramList);
}
@Override
public MapperResult incrementUsageByWhereQuotaEqualZero(MapperContext context) {
return new MapperResult(
"UPDATE group_capacity SET usage = usage + 1, gmt_modified = ? WHERE group_id = ? AND usage < ? AND quota = 0",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.GROUP_ID),
context.getWhereParameter(FieldConstant.USAGE)));
}
@Override
public MapperResult incrementUsageByWhereQuotaNotEqualZero(MapperContext context) {
return new MapperResult(
"UPDATE group_capacity SET usage = usage + 1, gmt_modified = ? WHERE group_id = ? AND usage < quota AND quota != 0",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.GROUP_ID)));
}
@Override
public MapperResult incrementUsageByWhere(MapperContext context) {
return new MapperResult(
"UPDATE group_capacity SET usage = usage + 1, gmt_modified = ? WHERE group_id = ?",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.GROUP_ID)));
}
@Override
public MapperResult decrementUsageByWhere(MapperContext context) {
return new MapperResult(
"UPDATE group_capacity SET usage = usage - 1, gmt_modified = ? WHERE group_id = ? AND usage > 0",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.GROUP_ID)));
}
@Override
public MapperResult updateUsage(MapperContext context) {
return new MapperResult(
"UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info), gmt_modified = ? WHERE group_id = ?",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.GROUP_ID)));
}
@Override
public MapperResult updateUsageByWhere(MapperContext context) {
return new MapperResult(
"UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = '"
+ NamespaceUtil.getNamespaceDefaultId() + "'),"
+ " gmt_modified = ? WHERE group_id= ?",
CollectionUtils.list(context.getWhereParameter(FieldConstant.GROUP_ID),
context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.GROUP_ID)));
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.impl.enums.postgresql.TrustedPostgresqlFunctionEnum;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.HistoryConfigInfoMapper;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
/**
* The postgresql implementation of HistoryConfigInfoMapper.
*
* @author Long Yu
**/
public class HistoryConfigInfoMapperByPostgresql extends AbstractMapper
implements HistoryConfigInfoMapper {
@Override
public MapperResult removeConfigHistory(MapperContext context) {
String sql =
"WITH temp_table as (SELECT id FROM his_config_info WHERE gmt_modified < ? LIMIT ? ) "
+ "DELETE FROM his_config_info WHERE id in (SELECT id FROM temp_table) ";
return new MapperResult(sql,
CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME),
context.getWhereParameter(FieldConstant.LIMIT_SIZE)));
}
@Override
public MapperResult pageFindConfigHistoryFetchRows(MapperContext context) {
String sql =
"SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,ext_info,publish_type,gray_name,gmt_create,gmt_modified "
+ "FROM his_config_info WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC LIMIT "
+ context.getPageSize() + " OFFSET " + context.getStartRow();
return new MapperResult(sql,
CollectionUtils.list(context.getWhereParameter(FieldConstant.DATA_ID),
context.getWhereParameter(FieldConstant.GROUP_ID),
context.getWhereParameter(FieldConstant.TENANT_ID)));
}
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String getFunction(String functionName) {
return TrustedPostgresqlFunctionEnum.getFunctionByName(functionName);
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.impl.base.BaseTenantCapacityMapper;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The PostgreSQL implementation of TenantCapacityMapper.
* Override methods to remove MySQL backticks around 'usage' field.
*
* @author Long Yu
**/
public class TenantCapacityMapperByPostgresql extends BaseTenantCapacityMapper {
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public MapperResult select(MapperContext context) {
String sql =
"SELECT id, quota, usage, max_size, max_aggr_count, max_aggr_size, tenant_id FROM tenant_capacity "
+ "WHERE tenant_id = ?";
return new MapperResult(sql,
Collections.singletonList(context.getWhereParameter(FieldConstant.TENANT_ID)));
}
@Override
public MapperResult incrementUsageWithDefaultQuotaLimit(MapperContext context) {
return new MapperResult(
"UPDATE tenant_capacity SET usage = usage + 1, gmt_modified = ? WHERE tenant_id = ? AND usage <"
+ " ? AND quota = 0",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.TENANT_ID),
context.getWhereParameter(FieldConstant.USAGE)));
}
@Override
public MapperResult incrementUsageWithQuotaLimit(MapperContext context) {
return new MapperResult(
"UPDATE tenant_capacity SET usage = usage + 1, gmt_modified = ? WHERE tenant_id = ? AND usage < "
+ "quota AND quota != 0",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.TENANT_ID)));
}
@Override
public MapperResult incrementUsage(MapperContext context) {
return new MapperResult(
"UPDATE tenant_capacity SET usage = usage + 1, gmt_modified = ? WHERE tenant_id = ?",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.TENANT_ID)));
}
@Override
public MapperResult decrementUsage(MapperContext context) {
return new MapperResult(
"UPDATE tenant_capacity SET usage = usage - 1, gmt_modified = ? WHERE tenant_id = ? AND usage > 0",
CollectionUtils.list(context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.TENANT_ID)));
}
@Override
public MapperResult correctUsage(MapperContext context) {
return new MapperResult(
"UPDATE tenant_capacity SET usage = (SELECT count(*) FROM config_info WHERE tenant_id = ?), "
+ "gmt_modified = ? WHERE tenant_id = ?",
CollectionUtils.list(context.getWhereParameter(FieldConstant.TENANT_ID),
context.getUpdateParameter(FieldConstant.GMT_MODIFIED),
context.getWhereParameter(FieldConstant.TENANT_ID)));
}
@Override
public MapperResult insertTenantCapacity(MapperContext context) {
List<Object> paramList = new ArrayList<>();
paramList.add(context.getUpdateParameter(FieldConstant.TENANT_ID));
paramList.add(context.getUpdateParameter(FieldConstant.QUOTA));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_SIZE));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_AGGR_COUNT));
paramList.add(context.getUpdateParameter(FieldConstant.MAX_AGGR_SIZE));
paramList.add(context.getUpdateParameter(FieldConstant.GMT_CREATE));
paramList.add(context.getUpdateParameter(FieldConstant.GMT_MODIFIED));
paramList.add(context.getWhereParameter(FieldConstant.TENANT_ID));
return new MapperResult(
"INSERT INTO tenant_capacity (tenant_id, quota, usage, max_size, max_aggr_count, max_aggr_size, "
+ "max_history_count, gmt_create, gmt_modified)"
+ " SELECT ?, ?, count(*), ?, ?, ?, 0, ?, ? FROM config_info WHERE tenant_id=?",
paramList);
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.impl.base.BaseTenantInfoMapper;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
/**
* The postgresql implementation of TenantInfoMapper.
* Override insert/update to convert epoch-millis (bigint) to PostgreSQL timestamp
* via {@code TO_TIMESTAMP(? / 1000.0)}.
*
* @author Long Yu
**/
public class TenantInfoMapperByPostgresql extends BaseTenantInfoMapper {
private static final String COLUMN_SEPARATOR = "@";
@Override
public String getDataSource() {
return DatabaseTypeConstant.POSTGRESQL;
}
@Override
public String insert(List<String> columns) {
StringJoiner columnJoiner = new StringJoiner(", ", "(", ")");
StringJoiner valueJoiner = new StringJoiner(",", "(", ")");
for (String col : columns) {
String[] parts = col.split(COLUMN_SEPARATOR, 2);
String colName = parts[0];
columnJoiner.add(colName);
if (parts.length > 1) {
valueJoiner.add(getFunction(parts[1]));
} else if (isTimestampColumn(colName)) {
valueJoiner.add("TO_TIMESTAMP(? / 1000.0)");
} else {
valueJoiner.add("?");
}
}
return "INSERT INTO " + getTableName() + columnJoiner + " VALUES" + valueJoiner;
}
@Override
public String update(List<String> columns, List<String> where) {
StringJoiner setJoiner = new StringJoiner(",");
for (String col : columns) {
String[] parts = col.split(COLUMN_SEPARATOR, 2);
String colName = parts[0];
String value;
if (parts.length > 1) {
value = getFunction(parts[1]);
} else if (isTimestampColumn(colName)) {
value = "TO_TIMESTAMP(? / 1000.0)";
} else {
value = "?";
}
setJoiner.add(colName + " = " + value);
}
StringBuilder sql =
new StringBuilder("UPDATE ").append(getTableName()).append(" SET ").append(setJoiner);
if (CollectionUtils.isNotEmpty(where)) {
sql.append(" WHERE ");
sql.append(
where.stream().map(str -> (str + " = ?")).collect(Collectors.joining(" AND ")));
}
return sql.toString();
}
private boolean isTimestampColumn(String columnName) {
return "gmt_create".equals(columnName) || "gmt_modified".equals(columnName);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Optional: grant read/write on existing Nacos tables to application role `nacos` only.
*
* When to use:
* Load pg-schema.sql as a DBA/superuser (tables then belong to that role). Create the app user:
* CREATE USER nacos WITH PASSWORD 'your-password';
* Then run this script while connected to the Nacos database as the same role that owns the tables
* (often superuser), so privileges apply to all current objects.
*
* What you get:
* USAGE on public schema, SELECT/INSERT/UPDATE/DELETE on all tables, USAGE+SELECT on sequences
* (covers bigserial / identity). No CREATE/DROP on schema, no superuser.
*
* CONNECT: use `psql -d your_nacos_db` so the session can open the DB, or from another database run:
* GRANT CONNECT ON DATABASE your_nacos_db TO nacos;
*/
GRANT USAGE ON SCHEMA public TO nacos;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO nacos;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nacos;
-- Objects created later by the same owner (e.g. future migrations run as superuser):
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO nacos;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO nacos;
@@ -0,0 +1,556 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-- ----------------------------
-- Table structure for config_info
-- ----------------------------
DROP TABLE IF EXISTS "config_info";
CREATE TABLE "config_info" (
"id" bigserial NOT NULL,
"data_id" varchar(255) NOT NULL,
"group_id" varchar(255) ,
"content" text NOT NULL,
"md5" varchar(32) ,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"src_user" text ,
"src_ip" varchar(20) ,
"app_name" varchar(128) ,
"tenant_id" varchar(128) NOT NULL DEFAULT '',
"c_desc" varchar(256) ,
"c_use" varchar(64) ,
"effect" varchar(64) ,
"type" varchar(64) ,
"c_schema" text ,
"encrypted_data_key" text NOT NULL
)
;
COMMENT ON COLUMN "config_info"."id" IS 'id';
COMMENT ON COLUMN "config_info"."data_id" IS 'data_id';
COMMENT ON COLUMN "config_info"."content" IS 'content';
COMMENT ON COLUMN "config_info"."md5" IS 'md5';
COMMENT ON COLUMN "config_info"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "config_info"."gmt_modified" IS '修改时间';
COMMENT ON COLUMN "config_info"."src_user" IS 'source user';
COMMENT ON COLUMN "config_info"."src_ip" IS 'source ip';
COMMENT ON COLUMN "config_info"."tenant_id" IS '租户字段';
COMMENT ON COLUMN "config_info"."encrypted_data_key" IS '秘钥';
COMMENT ON TABLE "config_info" IS 'config_info';
-- ----------------------------
-- Table structure for config_info_gray
-- ----------------------------
DROP TABLE IF EXISTS "config_info_gray";
CREATE TABLE "config_info_gray" (
"id" bigserial NOT NULL,
"data_id" varchar(255) NOT NULL,
"group_id" varchar(128) NOT NULL,
"content" text NOT NULL,
"md5" varchar(32),
"src_user" text,
"src_ip" varchar(100),
"gmt_create" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"app_name" varchar(128),
"tenant_id" varchar(128) NOT NULL DEFAULT '',
"gray_name" varchar(128) NOT NULL,
"gray_rule" text NOT NULL,
"encrypted_data_key" varchar(256) NOT NULL DEFAULT ''
);
COMMENT ON COLUMN "config_info_gray"."id" IS 'id';
COMMENT ON COLUMN "config_info_gray"."data_id" IS 'data_id';
COMMENT ON COLUMN "config_info_gray"."group_id" IS 'group_id';
COMMENT ON COLUMN "config_info_gray"."content" IS 'content';
COMMENT ON COLUMN "config_info_gray"."md5" IS 'md5';
COMMENT ON COLUMN "config_info_gray"."src_user" IS 'source user';
COMMENT ON COLUMN "config_info_gray"."src_ip" IS 'source ip';
COMMENT ON COLUMN "config_info_gray"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "config_info_gray"."gmt_modified" IS '修改时间';
COMMENT ON COLUMN "config_info_gray"."app_name" IS 'app_name';
COMMENT ON COLUMN "config_info_gray"."tenant_id" IS '租户字段';
COMMENT ON COLUMN "config_info_gray"."gray_name" IS '灰度名称';
COMMENT ON COLUMN "config_info_gray"."gray_rule" IS '灰度规则';
COMMENT ON COLUMN "config_info_gray"."encrypted_data_key" IS '秘钥';
COMMENT ON TABLE "config_info_gray" IS 'config_info_gray';
-- 创建索引
CREATE UNIQUE INDEX "uk_configinfogray_datagrouptenantgray" ON "config_info_gray" USING btree ("data_id", "group_id", "tenant_id", "gray_name");
CREATE INDEX "idx_dataid_gmt_modified_gray" ON "config_info_gray" USING btree ("data_id", "gmt_modified");
CREATE INDEX "idx_gmt_modified_gray" ON "config_info_gray" USING btree ("gmt_modified");
ALTER TABLE "config_info_gray" ADD CONSTRAINT "config_info_gray_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Records of config_info_gray
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for config_tags_relation
-- ----------------------------
DROP TABLE IF EXISTS "config_tags_relation";
CREATE TABLE "config_tags_relation" (
"id" bigint NOT NULL,
"tag_name" varchar(128) NOT NULL,
"tag_type" varchar(64) ,
"data_id" varchar(255) NOT NULL,
"group_id" varchar(128) NOT NULL,
"tenant_id" varchar(128) NOT NULL DEFAULT '',
"nid" bigserial NOT NULL
)
;
COMMENT ON COLUMN "config_tags_relation"."id" IS 'id';
COMMENT ON COLUMN "config_tags_relation"."tag_name" IS 'tag_name';
COMMENT ON COLUMN "config_tags_relation"."tag_type" IS 'tag_type';
COMMENT ON COLUMN "config_tags_relation"."data_id" IS 'data_id';
COMMENT ON COLUMN "config_tags_relation"."group_id" IS 'group_id';
COMMENT ON COLUMN "config_tags_relation"."tenant_id" IS 'tenant_id';
COMMENT ON TABLE "config_tags_relation" IS 'config_tag_relation';
-- ----------------------------
-- Records of config_tags_relation
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for group_capacity
-- ----------------------------
DROP TABLE IF EXISTS "group_capacity";
CREATE TABLE "group_capacity" (
"id" bigserial NOT NULL,
"group_id" varchar(128) NOT NULL,
"quota" int4 NOT NULL,
"usage" int4 NOT NULL,
"max_size" int4 NOT NULL,
"max_aggr_count" int4 NOT NULL,
"max_aggr_size" int4 NOT NULL,
"max_history_count" int4 NOT NULL DEFAULT 0,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP
)
;
COMMENT ON COLUMN "group_capacity"."id" IS '主键ID';
COMMENT ON COLUMN "group_capacity"."group_id" IS 'Group ID,空字符表示整个集群';
COMMENT ON COLUMN "group_capacity"."quota" IS '配额,0表示使用默认值';
COMMENT ON COLUMN "group_capacity"."usage" IS '使用量';
COMMENT ON COLUMN "group_capacity"."max_size" IS '单个配置大小上限,单位为字节,0表示使用默认值';
COMMENT ON COLUMN "group_capacity"."max_aggr_count" IS '聚合子配置最大个数,,0表示使用默认值';
COMMENT ON COLUMN "group_capacity"."max_aggr_size" IS '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值';
COMMENT ON COLUMN "group_capacity"."max_history_count" IS '最大变更历史数量';
COMMENT ON COLUMN "group_capacity"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "group_capacity"."gmt_modified" IS '修改时间';
COMMENT ON TABLE "group_capacity" IS '集群、各Group容量信息表';
-- ----------------------------
-- Records of group_capacity
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for his_config_info
-- ----------------------------
DROP TABLE IF EXISTS "his_config_info";
CREATE TABLE "his_config_info" (
"id" int8 NOT NULL,
"nid" bigserial NOT NULL,
"data_id" varchar(255) NOT NULL,
"group_id" varchar(128) NOT NULL,
"app_name" varchar(128) ,
"content" text NOT NULL,
"md5" varchar(32) ,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"src_user" text ,
"src_ip" varchar(20) ,
"op_type" char(10) ,
"tenant_id" varchar(128) NOT NULL DEFAULT '',
"encrypted_data_key" text NOT NULL,
"publish_type" varchar(50) DEFAULT 'formal',
"gray_name" varchar(50),
"ext_info" text
)
;
COMMENT ON COLUMN "his_config_info"."app_name" IS 'app_name';
COMMENT ON COLUMN "his_config_info"."tenant_id" IS '租户字段';
COMMENT ON COLUMN "his_config_info"."encrypted_data_key" IS '秘钥';
COMMENT ON COLUMN "his_config_info"."publish_type" IS 'publish type gray or formal';
COMMENT ON COLUMN "his_config_info"."gray_name" IS 'gray name';
COMMENT ON COLUMN "his_config_info"."ext_info" IS 'ext info';
COMMENT ON TABLE "his_config_info" IS '多租户改造';
-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS "permissions";
CREATE TABLE "permissions" (
"role" varchar(50) NOT NULL,
"resource" varchar(512) NOT NULL,
"action" varchar(8) NOT NULL
)
;
-- ----------------------------
-- Records of permissions
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for roles
-- ----------------------------
DROP TABLE IF EXISTS "roles";
CREATE TABLE "roles" (
"username" varchar(50) NOT NULL,
"role" varchar(50) NOT NULL
)
;
-- ----------------------------
-- Records of roles
-- ----------------------------
-- No seed rows: MySQL schema also leaves `roles` empty so the console can run first-time admin setup.
-- A row like ('nacos', 'ROLE_ADMIN') blocks that flow when the DB account name is `nacos` (common JDBC user).
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for tenant_capacity
-- ----------------------------
DROP TABLE IF EXISTS "tenant_capacity";
CREATE TABLE "tenant_capacity" (
"id" bigserial NOT NULL,
"tenant_id" varchar(128) NOT NULL,
"quota" int4 NOT NULL,
"usage" int4 NOT NULL,
"max_size" int4 NOT NULL,
"max_aggr_count" int4 NOT NULL,
"max_aggr_size" int4 NOT NULL,
"max_history_count" int4 NOT NULL DEFAULT 0,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP
)
;
COMMENT ON COLUMN "tenant_capacity"."id" IS '主键ID';
COMMENT ON COLUMN "tenant_capacity"."tenant_id" IS 'Tenant ID';
COMMENT ON COLUMN "tenant_capacity"."quota" IS '配额,0表示使用默认值';
COMMENT ON COLUMN "tenant_capacity"."usage" IS '使用量';
COMMENT ON COLUMN "tenant_capacity"."max_size" IS '单个配置大小上限,单位为字节,0表示使用默认值';
COMMENT ON COLUMN "tenant_capacity"."max_aggr_count" IS '聚合子配置最大个数';
COMMENT ON COLUMN "tenant_capacity"."max_aggr_size" IS '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值';
COMMENT ON COLUMN "tenant_capacity"."max_history_count" IS '最大变更历史数量';
COMMENT ON COLUMN "tenant_capacity"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "tenant_capacity"."gmt_modified" IS '修改时间';
COMMENT ON TABLE "tenant_capacity" IS '租户容量信息表';
-- ----------------------------
-- Records of tenant_capacity
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for tenant_info
-- ----------------------------
DROP TABLE IF EXISTS "tenant_info";
CREATE TABLE "tenant_info" (
"id" bigserial NOT NULL,
"kp" varchar(128) NOT NULL,
"tenant_id" varchar(128) ,
"tenant_name" varchar(128) ,
"tenant_desc" varchar(256) ,
"create_source" varchar(32) ,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP
)
;
COMMENT ON COLUMN "tenant_info"."id" IS 'id';
COMMENT ON COLUMN "tenant_info"."kp" IS 'kp';
COMMENT ON COLUMN "tenant_info"."tenant_id" IS 'tenant_id';
COMMENT ON COLUMN "tenant_info"."tenant_name" IS 'tenant_name';
COMMENT ON COLUMN "tenant_info"."tenant_desc" IS 'tenant_desc';
COMMENT ON COLUMN "tenant_info"."create_source" IS 'create_source';
COMMENT ON COLUMN "tenant_info"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "tenant_info"."gmt_modified" IS '修改时间';
COMMENT ON TABLE "tenant_info" IS 'tenant_info';
-- ----------------------------
-- Records of tenant_info
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS "users";
CREATE TABLE "users" (
"username" varchar(50) NOT NULL,
"password" varchar(500) NOT NULL,
"enabled" boolean NOT NULL
)
;
-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Indexes structure for table config_info
-- ----------------------------
CREATE UNIQUE INDEX "uk_configinfo_datagrouptenant" ON "config_info" ("data_id","group_id","tenant_id");
-- ----------------------------
-- Primary Key structure for table config_info
-- ----------------------------
ALTER TABLE "config_info" ADD CONSTRAINT "config_info_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Indexes structure for table config_tags_relation
-- ----------------------------
CREATE INDEX "idx_tenant_id" ON "config_tags_relation" USING btree (
"tenant_id"
);
CREATE UNIQUE INDEX "uk_configtagrelation_configidtag" ON "config_tags_relation" USING btree (
"id",
"tag_name",
"tag_type"
);
-- ----------------------------
-- Primary Key structure for table config_tags_relation
-- ----------------------------
ALTER TABLE "config_tags_relation" ADD CONSTRAINT "config_tags_relation_pkey" PRIMARY KEY ("nid");
-- ----------------------------
-- Indexes structure for table group_capacity
-- ----------------------------
CREATE UNIQUE INDEX "uk_group_id" ON "group_capacity" USING btree (
"group_id"
);
-- ----------------------------
-- Primary Key structure for table group_capacity
-- ----------------------------
ALTER TABLE "group_capacity" ADD CONSTRAINT "group_capacity_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Indexes structure for table his_config_info
-- ----------------------------
CREATE INDEX "idx_did" ON "his_config_info" USING btree (
"data_id"
);
CREATE INDEX "idx_gmt_create" ON "his_config_info" USING btree (
"gmt_create"
);
CREATE INDEX "idx_gmt_modified" ON "his_config_info" USING btree (
"gmt_modified"
);
-- ----------------------------
-- Primary Key structure for table his_config_info
-- ----------------------------
ALTER TABLE "his_config_info" ADD CONSTRAINT "his_config_info_pkey" PRIMARY KEY ("nid");
-- ----------------------------
-- Indexes structure for table permissions
-- ----------------------------
CREATE UNIQUE INDEX "uk_role_permission" ON "permissions" USING btree (
"role",
"resource",
"action"
);
-- ----------------------------
-- Indexes structure for table roles
-- ----------------------------
CREATE UNIQUE INDEX "uk_username_role" ON "roles" USING btree (
"username",
"role"
);
-- ----------------------------
-- Indexes structure for table tenant_capacity
-- ----------------------------
CREATE UNIQUE INDEX "uk_tenant_id" ON "tenant_capacity" USING btree (
"tenant_id"
);
-- ----------------------------
-- Primary Key structure for table tenant_capacity
-- ----------------------------
ALTER TABLE "tenant_capacity" ADD CONSTRAINT "tenant_capacity_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Indexes structure for table tenant_info
-- ----------------------------
CREATE UNIQUE INDEX "uk_tenant_info_kptenantid" ON "tenant_info" USING btree (
"kp",
"tenant_id"
);
-- ----------------------------
-- Table structure for pipeline_execution
-- ----------------------------
DROP TABLE IF EXISTS "pipeline_execution";
CREATE TABLE "pipeline_execution" (
"execution_id" varchar(64) NOT NULL,
"resource_type" varchar(32) NOT NULL,
"resource_name" varchar(256) NOT NULL,
"namespace_id" varchar(128) DEFAULT NULL,
"version" varchar(64) DEFAULT NULL,
"status" varchar(32) NOT NULL,
"pipeline" text NOT NULL,
"create_time" bigint NOT NULL,
"update_time" bigint NOT NULL
);
COMMENT ON COLUMN "pipeline_execution"."execution_id" IS '执行ID';
COMMENT ON COLUMN "pipeline_execution"."resource_type" IS '资源类型';
COMMENT ON COLUMN "pipeline_execution"."resource_name" IS '资源名称';
COMMENT ON COLUMN "pipeline_execution"."namespace_id" IS '命名空间ID';
COMMENT ON COLUMN "pipeline_execution"."version" IS '版本';
COMMENT ON COLUMN "pipeline_execution"."status" IS '执行状态';
COMMENT ON COLUMN "pipeline_execution"."pipeline" IS 'pipeline节点结果JSON';
COMMENT ON COLUMN "pipeline_execution"."create_time" IS '创建时间';
COMMENT ON COLUMN "pipeline_execution"."update_time" IS '修改时间';
COMMENT ON TABLE "pipeline_execution" IS 'AI资源发布审核Pipeline执行记录';
-- ----------------------------
-- Primary Key structure for table pipeline_execution
-- ----------------------------
ALTER TABLE "pipeline_execution" ADD CONSTRAINT "pipeline_execution_pkey" PRIMARY KEY ("execution_id");
-- ----------------------------
-- Table structure for ai_resource
-- ----------------------------
DROP TABLE IF EXISTS "ai_resource";
CREATE TABLE "ai_resource" (
"id" bigserial NOT NULL,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" varchar(256) NOT NULL,
"type" varchar(32) NOT NULL,
"c_desc" varchar(1024),
"status" varchar(32),
"namespace_id" varchar(128) NOT NULL DEFAULT '',
"biz_tags" varchar(1024),
"ext" text,
"c_from" varchar(256) NOT NULL DEFAULT 'local',
"version_info" text,
"meta_version" bigint NOT NULL DEFAULT 1,
"scope" varchar(16) NOT NULL DEFAULT 'PRIVATE',
"owner" varchar(128) NOT NULL DEFAULT '',
"download_count" bigint NOT NULL DEFAULT 0
)
;
COMMENT ON COLUMN "ai_resource"."id" IS 'id';
COMMENT ON COLUMN "ai_resource"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "ai_resource"."gmt_modified" IS '修改时间';
COMMENT ON COLUMN "ai_resource"."name" IS '资源名称';
COMMENT ON COLUMN "ai_resource"."type" IS '资源类型';
COMMENT ON COLUMN "ai_resource"."c_desc" IS '资源描述';
COMMENT ON COLUMN "ai_resource"."status" IS '资源状态';
COMMENT ON COLUMN "ai_resource"."namespace_id" IS '命名空间ID';
COMMENT ON COLUMN "ai_resource"."biz_tags" IS '业务标签';
COMMENT ON COLUMN "ai_resource"."ext" IS '扩展信息(JSON)';
COMMENT ON COLUMN "ai_resource"."c_from" IS '来源标识(导入/同步来源)';
COMMENT ON COLUMN "ai_resource"."version_info" IS '版本信息(JSON)';
COMMENT ON COLUMN "ai_resource"."meta_version" IS '元数据版本(乐观锁)';
COMMENT ON COLUMN "ai_resource"."scope" IS '可见性: PUBLIC/PRIVATE';
COMMENT ON COLUMN "ai_resource"."owner" IS '创建者用户名';
COMMENT ON COLUMN "ai_resource"."download_count" IS '下载次数';
COMMENT ON TABLE "ai_resource" IS 'AI资源元数据表';
-- ----------------------------
-- Primary Key structure for table ai_resource
-- ----------------------------
ALTER TABLE "ai_resource" ADD CONSTRAINT "ai_resource_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Indexes structure for table ai_resource
-- ----------------------------
CREATE UNIQUE INDEX "uk_ai_resource_ns_name_type" ON "ai_resource" USING btree (
"namespace_id",
"name",
"type",
"c_from"
);
CREATE INDEX "idx_ai_resource_name" ON "ai_resource" USING btree ("name");
CREATE INDEX "idx_ai_resource_type" ON "ai_resource" USING btree ("type");
CREATE INDEX "idx_ai_resource_gmt_modified" ON "ai_resource" USING btree ("gmt_modified");
-- ----------------------------
-- Table structure for ai_resource_version
-- ----------------------------
DROP TABLE IF EXISTS "ai_resource_version";
CREATE TABLE "ai_resource_version" (
"id" bigserial NOT NULL,
"gmt_create" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"gmt_modified" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"type" varchar(32) NOT NULL,
"author" varchar(128),
"name" varchar(256) NOT NULL,
"c_desc" varchar(1024),
"status" varchar(32) NOT NULL,
"version" varchar(64) NOT NULL,
"namespace_id" varchar(128) NOT NULL DEFAULT '',
"storage" text,
"publish_pipeline_info" text,
"download_count" bigint NOT NULL DEFAULT 0
)
;
COMMENT ON COLUMN "ai_resource_version"."id" IS 'id';
COMMENT ON COLUMN "ai_resource_version"."gmt_create" IS '创建时间';
COMMENT ON COLUMN "ai_resource_version"."gmt_modified" IS '修改时间';
COMMENT ON COLUMN "ai_resource_version"."type" IS '资源类型';
COMMENT ON COLUMN "ai_resource_version"."author" IS '作者';
COMMENT ON COLUMN "ai_resource_version"."name" IS '资源名称';
COMMENT ON COLUMN "ai_resource_version"."c_desc" IS '版本描述';
COMMENT ON COLUMN "ai_resource_version"."status" IS '版本状态';
COMMENT ON COLUMN "ai_resource_version"."version" IS '版本号';
COMMENT ON COLUMN "ai_resource_version"."namespace_id" IS '命名空间ID';
COMMENT ON COLUMN "ai_resource_version"."storage" IS '存储信息(JSON)';
COMMENT ON COLUMN "ai_resource_version"."publish_pipeline_info" IS '发布流水线信息(JSON)';
COMMENT ON COLUMN "ai_resource_version"."download_count" IS '下载次数';
COMMENT ON TABLE "ai_resource_version" IS 'AI资源版本表';
-- ----------------------------
-- Primary Key structure for table ai_resource_version
-- ----------------------------
ALTER TABLE "ai_resource_version" ADD CONSTRAINT "ai_resource_version_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Indexes structure for table ai_resource_version
-- ----------------------------
CREATE UNIQUE INDEX "uk_ai_resource_ver_ns_name_type_ver" ON "ai_resource_version" USING btree (
"namespace_id",
"name",
"type",
"version"
);
CREATE INDEX "idx_ai_resource_ver_name" ON "ai_resource_version" USING btree ("name");
CREATE INDEX "idx_ai_resource_ver_status" ON "ai_resource_version" USING btree ("status");
CREATE INDEX "idx_ai_resource_ver_gmt_modified" ON "ai_resource_version" USING btree ("gmt_modified");
@@ -0,0 +1,48 @@
/*
* Copyright 1999-2026 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-- PostgreSQL upgrade script for deployments that still contain nullable tenant_id
-- values in config-related tables.
--
-- IMPORTANT:
-- 1. Review and clean duplicate logical rows before replacing NULL tenant_id
-- values with '' on config_info, otherwise the UPDATE may conflict with the
-- existing unique index on (data_id, group_id, tenant_id).
-- 2. Apply this script before upgrading to a Nacos build that validates the
-- PostgreSQL tenant schema on startup.
--
-- Suggested pre-check for duplicate logical config rows:
-- SELECT data_id, group_id, COUNT(*)
-- FROM config_info
-- WHERE tenant_id IS NULL
-- GROUP BY data_id, group_id
-- HAVING COUNT(*) > 1;
ALTER TABLE config_info ALTER COLUMN tenant_id SET DEFAULT '';
UPDATE config_info SET tenant_id = '' WHERE tenant_id IS NULL;
ALTER TABLE config_info ALTER COLUMN tenant_id SET NOT NULL;
ALTER TABLE config_info_gray ALTER COLUMN tenant_id SET DEFAULT '';
UPDATE config_info_gray SET tenant_id = '' WHERE tenant_id IS NULL;
ALTER TABLE config_info_gray ALTER COLUMN tenant_id SET NOT NULL;
ALTER TABLE config_tags_relation ALTER COLUMN tenant_id SET DEFAULT '';
UPDATE config_tags_relation SET tenant_id = '' WHERE tenant_id IS NULL;
ALTER TABLE config_tags_relation ALTER COLUMN tenant_id SET NOT NULL;
ALTER TABLE his_config_info ALTER COLUMN tenant_id SET DEFAULT '';
UPDATE his_config_info SET tenant_id = '' WHERE tenant_id IS NULL;
ALTER TABLE his_config_info ALTER COLUMN tenant_id SET NOT NULL;
@@ -0,0 +1,17 @@
#
# Copyright 1999-2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
com.alibaba.nacos.plugin.datasource.impl.dialect.PostgresqlDatabaseDialect
@@ -0,0 +1,25 @@
#
# Copyright 1999-2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigInfoMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigInfoGrayMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigTagsRelationMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.GroupCapacityMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.HistoryConfigInfoMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.TenantCapacityMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.TenantInfoMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.AiResourceMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.AiResourceVersionMapperByPostgresql
@@ -0,0 +1,39 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.enums.postgresql;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* TrustedPostgresqlFunctionEnumTest
*
* @author Ken
*/
public class TrustedPostgresqlFunctionEnumTest {
@Test
void testGetFunctionByName() {
Assertions.assertEquals("NOW()", TrustedPostgresqlFunctionEnum.getFunctionByName("NOW()"));
}
@Test()
void testGetFunctionByErrorName() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> TrustedPostgresqlFunctionEnum.getFunctionByName("UNKNOWN"));
}
}
@@ -0,0 +1,144 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.plugin.datasource.constants.ContextConstant;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* ConfigInfoMapperByPostgresqlTest
*
* @author Ken
*/
class ConfigInfoMapperByPostgresqlTest {
int startRow = 0;
int pageSize = 5;
String appName = "appName";
String groupId = "groupId";
String tenantId = "tenantId";
String dataId = "dataId";
String id = "id";
MapperContext context;
private ConfigInfoMapperByPostgresql configInfoMapperByPostgresql;
@BeforeEach
void setUp() throws Exception {
configInfoMapperByPostgresql = new ConfigInfoMapperByPostgresql();
context = new MapperContext(startRow, pageSize);
context.putWhereParameter(FieldConstant.ID, id);
context.putWhereParameter(FieldConstant.APP_NAME, appName);
context.putWhereParameter(FieldConstant.TENANT_ID, tenantId);
context.putWhereParameter(FieldConstant.GROUP_ID, groupId);
context.putWhereParameter(FieldConstant.DATA_ID, dataId);
}
@Test
void testFindAllConfigInfoFragment() {
//with content
context.putContextParameter(ContextConstant.NEED_CONTENT, "true");
MapperResult needContentMapperResult =
configInfoMapperByPostgresql.findAllConfigInfoFragment(context);
assertEquals(
"SELECT id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,type,encrypted_data_key "
+ "FROM config_info WHERE id > ? ORDER BY id ASC OFFSET " + startRow + " LIMIT "
+ pageSize,
needContentMapperResult.getSql());
assertArrayEquals(new Object[] {id}, needContentMapperResult.getParamList().toArray());
//without content
context.putContextParameter(ContextConstant.NEED_CONTENT, "false");
MapperResult withoutContentMapperResult =
configInfoMapperByPostgresql.findAllConfigInfoFragment(context);
assertEquals(
"SELECT id,data_id,group_id,tenant_id,app_name,md5,gmt_modified,type,encrypted_data_key "
+ "FROM config_info WHERE id > ? ORDER BY id ASC OFFSET " + startRow + " LIMIT "
+ pageSize,
withoutContentMapperResult.getSql());
assertArrayEquals(new Object[] {id}, withoutContentMapperResult.getParamList().toArray());
}
@Test
void testFindConfigInfo4PageFetchRows() {
MapperResult mapperResult =
configInfoMapperByPostgresql.findConfigInfo4PageFetchRows(context);
assertEquals(
"SELECT id,data_id,group_id,tenant_id,app_name,content,md5,type,encrypted_data_key,c_desc "
+ "FROM config_info WHERE tenant_id=? AND data_id=? AND group_id=? AND app_name=? "
+ "OFFSET " + startRow + " LIMIT " + pageSize,
mapperResult.getSql());
assertArrayEquals(new Object[] {tenantId, dataId, groupId, appName},
mapperResult.getParamList().toArray());
}
@Test
void testFindConfigInfoLike4PageFetchRows() {
MapperResult mapperResult =
configInfoMapperByPostgresql.findConfigInfoLike4PageFetchRows(context);
assertEquals(
"SELECT id,data_id,group_id,tenant_id,app_name,content,md5,encrypted_data_key,type,c_desc "
+ "FROM config_info WHERE tenant_id LIKE ? AND data_id LIKE ? AND group_id LIKE ? AND app_name = ? "
+ "OFFSET " + startRow + " LIMIT " + pageSize,
mapperResult.getSql());
assertArrayEquals(new Object[] {tenantId, dataId, groupId, appName},
mapperResult.getParamList().toArray());
}
@Test
void testFindConfigInfoBaseLikeFetchRows() {
MapperResult mapperResult =
configInfoMapperByPostgresql.findConfigInfoBaseLikeFetchRows(context);
assertEquals("SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE "
+ " 1=1 AND tenant_id='' AND data_id LIKE ? AND group_id LIKE ? "
+ "OFFSET " + startRow + " LIMIT " + pageSize, mapperResult.getSql());
assertArrayEquals(new Object[] {dataId, groupId}, mapperResult.getParamList().toArray());
}
@Test
void testGetTableName() {
String tableName = configInfoMapperByPostgresql.getTableName();
assertEquals(TableConstant.CONFIG_INFO, tableName);
}
@Test
void testGetDataSource() {
String dataSource = configInfoMapperByPostgresql.getDataSource();
assertEquals(DatabaseTypeConstant.POSTGRESQL, dataSource);
}
}
@@ -0,0 +1,110 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* ConfigTagsRelationMapperByPostgresqlTest
*
* @author Ken
*/
public class ConfigTagsRelationMapperByPostgresqlTest {
int startRow = 0;
int pageSize = 5;
String tenantId = "tenantId";
String dataId = "dataId";
String groupId = "groupId";
String appName = "appName";
String content = "content";
String[] tagArr = new String[] {"tagA", "tagB", "tagC"};
String[] typeArr = new String[] {"text", "json", "xml", "yaml", "properties"};
MapperContext context;
private ConfigTagsRelationMapperByPostgresql configTagsRelationMapperByPostgresql;
@BeforeEach
void setUp() throws Exception {
configTagsRelationMapperByPostgresql = new ConfigTagsRelationMapperByPostgresql();
context = new MapperContext(startRow, pageSize);
context.putWhereParameter(FieldConstant.TENANT_ID, tenantId);
context.putWhereParameter(FieldConstant.DATA_ID, dataId);
context.putWhereParameter(FieldConstant.GROUP_ID, groupId);
context.putWhereParameter(FieldConstant.APP_NAME, appName);
context.putWhereParameter(FieldConstant.CONTENT, content);
context.putWhereParameter(FieldConstant.TAG_ARR, tagArr);
context.putWhereParameter(FieldConstant.TYPE, typeArr);
}
@Test
void testFindConfigInfoLike4PageFetchRows() {
MapperResult mapperResult =
configTagsRelationMapperByPostgresql.findConfigInfoLike4PageFetchRows(context);
String sql = mapperResult.getSql();
// 验证是否存在标量子查询
assertTrue(sql.contains(
"(SELECT STRING_AGG(tag_name, ',') FROM config_tags_relation d WHERE d.id = c.id)"));
// 验证是否存在EXISTS
assertTrue(sql.contains(" EXISTS "));
// 验证是否存在标签的子查询
assertTrue(sql.contains("SELECT 1 FROM config_tags_relation"));
assertEquals(
"SELECT c.id,c.data_id,c.group_id,c.tenant_id,c.app_name,c.content,c.md5,c.encrypted_data_key,"
+ "c.type,c.c_desc,(SELECT STRING_AGG(tag_name, ',') FROM config_tags_relation d WHERE d.id = c.id) as config_tags "
+ "FROM (SELECT a.id,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content,a.md5,a.encrypted_data_key,a.type,a.c_desc "
+ "FROM config_info a WHERE a.tenant_id LIKE ? AND a.data_id LIKE ? AND a.group_id LIKE ? AND a.app_name = ? "
+ "AND a.content LIKE ? AND EXISTS ( SELECT 1 FROM config_tags_relation b WHERE b.id = a.id "
+ "AND ( b.tag_name LIKE ? OR b.tag_name LIKE ? OR b.tag_name LIKE ? ) ) AND a.type IN (?, ?, ?, ?, ?) "
+ "OFFSET " + startRow + " ROWS FETCH NEXT " + pageSize + " ROWS ONLY) c ",
mapperResult.getSql());
List<Object> expectedParams = new ArrayList<>();
expectedParams.add(tenantId);
expectedParams.add(dataId);
expectedParams.add(groupId);
expectedParams.add(appName);
expectedParams.add(content);
expectedParams.addAll(Arrays.asList(tagArr));
expectedParams.addAll(Arrays.asList(typeArr));
assertArrayEquals(expectedParams.toArray(), mapperResult.getParamList().toArray());
}
}
@@ -0,0 +1,303 @@
/*
* Copyright 1999-2026 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import com.alibaba.nacos.common.utils.NamespaceUtil;
import com.alibaba.nacos.plugin.datasource.constants.DatabaseTypeConstant;
import com.alibaba.nacos.plugin.datasource.constants.FieldConstant;
import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.impl.dialect.PostgresqlDatabaseDialect;
import com.alibaba.nacos.plugin.datasource.mapper.AiResourceMapper;
import com.alibaba.nacos.plugin.datasource.model.MapperContext;
import com.alibaba.nacos.plugin.datasource.model.MapperResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
class PostgresqlMapperCoverageTest {
private final int startRow = 3;
private final int pageSize = 7;
private final String tenantId = "tenantId";
private final String groupId = "groupId";
private final String namespaceId = "namespaceId";
private final long modifiedTime = 1000L;
private MapperContext context;
@BeforeEach
void setUp() {
context = new MapperContext(startRow, pageSize);
context.putWhereParameter(FieldConstant.TENANT_ID, tenantId);
context.putWhereParameter(FieldConstant.GROUP_ID, groupId);
context.putWhereParameter(FieldConstant.NAMESPACE_ID, namespaceId);
context.putWhereParameter(FieldConstant.NAME, "nacos");
context.putWhereParameter(FieldConstant.TYPE, "mcp");
context.putWhereParameter(FieldConstant.STATUS, "stable");
context.putWhereParameter(FieldConstant.VERSION, "1.0.0");
context.putWhereParameter(FieldConstant.ID, 12L);
context.putWhereParameter(FieldConstant.LIMIT_SIZE, 20);
context.putWhereParameter(FieldConstant.START_TIME, modifiedTime);
context.putWhereParameter(FieldConstant.DATA_ID, "dataId");
context.putUpdateParameter(FieldConstant.GROUP_ID, groupId);
context.putUpdateParameter(FieldConstant.TENANT_ID, tenantId);
context.putUpdateParameter(FieldConstant.QUOTA, 10);
context.putUpdateParameter(FieldConstant.MAX_SIZE, 100);
context.putUpdateParameter(FieldConstant.MAX_AGGR_COUNT, 5);
context.putUpdateParameter(FieldConstant.MAX_AGGR_SIZE, 200);
context.putUpdateParameter(FieldConstant.GMT_CREATE, 100L);
context.putUpdateParameter(FieldConstant.GMT_MODIFIED, modifiedTime);
context.putWhereParameter(FieldConstant.USAGE, 99);
}
@Test
void testPostgresqlDatabaseDialect() {
PostgresqlDatabaseDialect dialect = new PostgresqlDatabaseDialect();
assertEquals(DatabaseTypeConstant.POSTGRESQL, dialect.getType());
assertEquals("NOW()", dialect.getFunction("NOW()"));
assertEquals("SELECT OFFSET ? LIMIT ? ", dialect.getLimitPageSqlWithMark("SELECT"));
assertEquals("SELECT OFFSET 14 LIMIT 7", dialect.getLimitPageSql("SELECT", 3, 7));
assertEquals("SELECT OFFSET 3 LIMIT 7",
dialect.getLimitPageSqlWithOffset("SELECT", 3, 7));
}
@Test
void testTenantInfoMapperInsertAndUpdate() {
TenantInfoMapperByPostgresql mapper = new TenantInfoMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, mapper.getDataSource());
assertEquals("NOW()", mapper.getFunction("NOW()"));
assertEquals(TableConstant.TENANT_INFO, mapper.getTableName());
assertEquals(
"INSERT INTO tenant_info(gmt_create, tenant_id, gmt_modified) "
+ "VALUES(TO_TIMESTAMP(? / 1000.0),?,NOW())",
mapper.insert(Arrays.asList("gmt_create", "tenant_id", "gmt_modified@NOW()")));
assertEquals(
"UPDATE tenant_info SET gmt_modified = TO_TIMESTAMP(? / 1000.0),"
+ "tenant_id = ?,gmt_create = NOW() WHERE tenant_id = ?",
mapper.update(Arrays.asList("gmt_modified", "tenant_id", "gmt_create@NOW()"),
Collections.singletonList("tenant_id")));
assertEquals("UPDATE tenant_info SET tenant_id = ?",
mapper.update(Collections.singletonList("tenant_id"), Collections.emptyList()));
}
@Test
void testGroupCapacityMapper() {
GroupCapacityMapperByPostgresql mapper = new GroupCapacityMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, mapper.getDataSource());
assertEquals("NOW()", mapper.getFunction("NOW()"));
assertResult(mapper.select(context),
"SELECT id, quota, usage, max_size, max_aggr_count, max_aggr_size, group_id "
+ "FROM group_capacity WHERE group_id = ?",
groupId);
assertResult(mapper.insertIntoSelect(context),
"INSERT INTO group_capacity (group_id, quota, usage, max_size, max_aggr_count,"
+ " max_aggr_size, max_history_count, gmt_create, gmt_modified)"
+ " SELECT ?, ?, count(*), ?, ?, ?, 0, ?, ? FROM config_info",
groupId, 10, 100, 5, 200, 100L, modifiedTime);
assertResult(mapper.insertIntoSelectByWhere(context),
"INSERT INTO group_capacity (group_id, quota, usage, max_size, max_aggr_count,"
+ " max_aggr_size, max_history_count, gmt_create, gmt_modified)"
+ " SELECT ?, ?, count(*), ?, ?, ?, 0, ?, ? FROM config_info "
+ "WHERE group_id=? AND tenant_id = '" + NamespaceUtil
.getNamespaceDefaultId()
+ "'",
groupId, 10, 100, 5, 200, 100L, modifiedTime, groupId);
assertResult(mapper.incrementUsageByWhereQuotaEqualZero(context),
"UPDATE group_capacity SET usage = usage + 1, gmt_modified = ? "
+ "WHERE group_id = ? AND usage < ? AND quota = 0",
modifiedTime, groupId, 99);
assertResult(mapper.incrementUsageByWhereQuotaNotEqualZero(context),
"UPDATE group_capacity SET usage = usage + 1, gmt_modified = ? "
+ "WHERE group_id = ? AND usage < quota AND quota != 0",
modifiedTime, groupId);
assertResult(mapper.incrementUsageByWhere(context),
"UPDATE group_capacity SET usage = usage + 1, gmt_modified = ? WHERE group_id = ?",
modifiedTime, groupId);
assertResult(mapper.decrementUsageByWhere(context),
"UPDATE group_capacity SET usage = usage - 1, gmt_modified = ? "
+ "WHERE group_id = ? AND usage > 0",
modifiedTime, groupId);
assertResult(mapper.updateUsage(context),
"UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info), "
+ "gmt_modified = ? WHERE group_id = ?",
modifiedTime, groupId);
assertResult(mapper.updateUsageByWhere(context),
"UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info "
+ "WHERE group_id=? AND tenant_id = '" + NamespaceUtil
.getNamespaceDefaultId()
+ "'), gmt_modified = ? WHERE group_id= ?",
groupId, modifiedTime, groupId);
}
@Test
void testTenantCapacityMapper() {
TenantCapacityMapperByPostgresql mapper = new TenantCapacityMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, mapper.getDataSource());
assertEquals("NOW()", mapper.getFunction("NOW()"));
assertResult(mapper.select(context),
"SELECT id, quota, usage, max_size, max_aggr_count, max_aggr_size, tenant_id "
+ "FROM tenant_capacity WHERE tenant_id = ?",
tenantId);
assertResult(mapper.incrementUsageWithDefaultQuotaLimit(context),
"UPDATE tenant_capacity SET usage = usage + 1, gmt_modified = ? "
+ "WHERE tenant_id = ? AND usage < ? AND quota = 0",
modifiedTime, tenantId, 99);
assertResult(mapper.incrementUsageWithQuotaLimit(context),
"UPDATE tenant_capacity SET usage = usage + 1, gmt_modified = ? "
+ "WHERE tenant_id = ? AND usage < quota AND quota != 0",
modifiedTime, tenantId);
assertResult(mapper.incrementUsage(context),
"UPDATE tenant_capacity SET usage = usage + 1, gmt_modified = ? "
+ "WHERE tenant_id = ?",
modifiedTime, tenantId);
assertResult(mapper.decrementUsage(context),
"UPDATE tenant_capacity SET usage = usage - 1, gmt_modified = ? "
+ "WHERE tenant_id = ? AND usage > 0",
modifiedTime, tenantId);
assertResult(mapper.correctUsage(context),
"UPDATE tenant_capacity SET usage = (SELECT count(*) FROM config_info "
+ "WHERE tenant_id = ?), gmt_modified = ? WHERE tenant_id = ?",
tenantId, modifiedTime, tenantId);
assertResult(mapper.insertTenantCapacity(context),
"INSERT INTO tenant_capacity (tenant_id, quota, usage, max_size, max_aggr_count, "
+ "max_aggr_size, max_history_count, gmt_create, gmt_modified)"
+ " SELECT ?, ?, count(*), ?, ?, ?, 0, ?, ? FROM config_info WHERE tenant_id=?",
tenantId, 10, 100, 5, 200, 100L, modifiedTime, tenantId);
}
@Test
void testHistoryConfigInfoMapper() {
HistoryConfigInfoMapperByPostgresql mapper = new HistoryConfigInfoMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, mapper.getDataSource());
assertEquals("NOW()", mapper.getFunction("NOW()"));
assertEquals(TableConstant.HIS_CONFIG_INFO, mapper.getTableName());
assertResult(mapper.removeConfigHistory(context),
"WITH temp_table as (SELECT id FROM his_config_info WHERE gmt_modified < ? LIMIT ? ) "
+ "DELETE FROM his_config_info WHERE id in (SELECT id FROM temp_table) ",
modifiedTime, 20);
assertResult(mapper.pageFindConfigHistoryFetchRows(context),
"SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,ext_info,"
+ "publish_type,gray_name,gmt_create,gmt_modified FROM his_config_info "
+ "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC "
+ "LIMIT 7 OFFSET 3",
"dataId", groupId, tenantId);
}
@Test
void testSimplePostgresqlMappers() {
ConfigInfoMapperByPostgresql configInfoMapper = new ConfigInfoMapperByPostgresql();
assertEquals("NOW()", configInfoMapper.getFunction("NOW()"));
ConfigInfoGrayMapperByPostgresql grayMapper = new ConfigInfoGrayMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, grayMapper.getDataSource());
assertEquals("NOW()", grayMapper.getFunction("NOW()"));
assertEquals(TableConstant.CONFIG_INFO_GRAY, grayMapper.getTableName());
assertResult(grayMapper.findAllConfigInfoGrayForDumpAllFetchRows(context),
" SELECT id,data_id,group_id,tenant_id,gray_name,gray_rule,app_name,content,"
+ "md5,gmt_modified FROM config_info_gray ORDER BY id LIMIT 7 OFFSET 3");
}
@Test
void testAiResourceMapper() {
AiResourceMapperByPostgresql mapper = new AiResourceMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, mapper.getDataSource());
assertEquals("NOW()", mapper.getFunction("NOW()"));
assertEquals(TableConstant.AI_RESOURCE, mapper.getTableName());
context.putWhereParameter(FieldConstant.BIZ_TAGS, "tag");
context.putWhereParameter(FieldConstant.SCOPE, "public");
context.putWhereParameter(FieldConstant.OWNER, "owner");
context.putWhereParameter(FieldConstant.ORDER_BY, FieldConstant.ORDER_BY_DOWNLOAD_COUNT);
assertResult(mapper.findAiResourceFetchRows(context),
"SELECT id,gmt_create,gmt_modified,name,type,c_desc,status,namespace_id,biz_tags,"
+ "ext,c_from,version_info,meta_version,scope,owner,download_count "
+ "FROM ai_resource WHERE namespace_id = ? AND name LIKE ? "
+ "AND biz_tags LIKE ? AND type = ? AND scope = ? AND owner = ? "
+ "ORDER BY download_count DESC LIMIT 7 OFFSET 3",
namespaceId, "nacos", "tag", "mcp", "public", "owner");
MapperContext alwaysEmptyContext = new MapperContext(startRow, pageSize);
alwaysEmptyContext.putWhereParameter(FieldConstant.NAMESPACE_ID, namespaceId);
alwaysEmptyContext.putWhereParameter(AiResourceMapper.QUERY_CONDITION_ALWAYS_EMPTY, true);
assertResult(mapper.findAiResourceFetchRows(alwaysEmptyContext),
"SELECT id,gmt_create,gmt_modified,name,type,c_desc,status,namespace_id,biz_tags,"
+ "ext,c_from,version_info,meta_version,scope,owner,download_count "
+ "FROM ai_resource WHERE namespace_id = ? AND 1 = ? "
+ "ORDER BY gmt_modified DESC LIMIT 7 OFFSET 3",
namespaceId, 0);
MapperContext orContext = new MapperContext(startRow, pageSize);
orContext.putWhereParameter(FieldConstant.NAMESPACE_ID, namespaceId);
Map<Object, Object> orGroup = new LinkedHashMap<>();
orGroup.put(FieldConstant.STATUS, "stable");
orGroup.put(FieldConstant.TYPE, Arrays.asList("mcp", "a2a"));
orContext.putWhereParameter(AiResourceMapper.QUERY_CONDITION_OR_GROUP, orGroup);
assertResult(mapper.findAiResourceFetchRows(orContext),
"SELECT id,gmt_create,gmt_modified,name,type,c_desc,status,namespace_id,biz_tags,"
+ "ext,c_from,version_info,meta_version,scope,owner,download_count "
+ "FROM ai_resource WHERE namespace_id = ? AND ( status = ? OR type IN (?, ?) ) "
+ "ORDER BY gmt_modified DESC LIMIT 7 OFFSET 3",
namespaceId, "stable", "mcp", "a2a");
}
@Test
void testAiResourceVersionMapper() {
AiResourceVersionMapperByPostgresql mapper = new AiResourceVersionMapperByPostgresql();
assertEquals(DatabaseTypeConstant.POSTGRESQL, mapper.getDataSource());
assertEquals("NOW()", mapper.getFunction("NOW()"));
assertEquals(TableConstant.AI_RESOURCE_VERSION, mapper.getTableName());
assertResult(mapper.findAiResourceVersionFetchRows(context),
"SELECT id,gmt_create,gmt_modified,type,author,name,c_desc,status,version,namespace_id,"
+ "storage,publish_pipeline_info,download_count FROM ai_resource_version "
+ "WHERE namespace_id = ? AND name = ? AND type = ? AND status = ? "
+ "AND version = ? ORDER BY gmt_modified DESC LIMIT 7 OFFSET 3",
namespaceId, "nacos", "mcp", "stable", "1.0.0");
MapperContext minimalContext = new MapperContext(startRow, pageSize);
minimalContext.putWhereParameter(FieldConstant.NAMESPACE_ID, namespaceId);
minimalContext.putWhereParameter(FieldConstant.NAME, "nacos");
minimalContext.putWhereParameter(FieldConstant.TYPE, "");
minimalContext.putWhereParameter(FieldConstant.STATUS, "");
minimalContext.putWhereParameter(FieldConstant.VERSION, "");
assertResult(mapper.findAiResourceVersionFetchRows(minimalContext),
"SELECT id,gmt_create,gmt_modified,type,author,name,c_desc,status,version,namespace_id,"
+ "storage,publish_pipeline_info,download_count FROM ai_resource_version "
+ "WHERE namespace_id = ? AND name = ? ORDER BY gmt_modified DESC LIMIT 7 OFFSET 3",
namespaceId, "nacos");
}
private static void assertResult(MapperResult result, String sql, Object... parameters) {
assertEquals(normalizeSql(sql), normalizeSql(result.getSql()));
assertArrayEquals(parameters, result.getParamList().toArray());
}
private static String normalizeSql(String sql) {
return sql.replaceAll("\\s+", " ").trim();
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 1999-2026 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PostgresqlTenantMigrationScriptTest {
@Test
void testMigrationScriptExistsAndCoversAllTenantTables() throws IOException {
try (InputStream inputStream =
getClass().getClassLoader()
.getResourceAsStream("META-INF/pg-upgrade-null-tenant-id.sql")) {
assertNotNull(inputStream);
String sql = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
assertTrue(
sql.contains("ALTER TABLE config_info ALTER COLUMN tenant_id SET DEFAULT ''"));
assertTrue(
sql.contains("ALTER TABLE config_info_gray ALTER COLUMN tenant_id SET DEFAULT ''"));
assertTrue(sql.contains(
"ALTER TABLE config_tags_relation ALTER COLUMN tenant_id SET DEFAULT ''"));
assertTrue(
sql.contains("ALTER TABLE his_config_info ALTER COLUMN tenant_id SET DEFAULT ''"));
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 1999-2026 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.datasource.impl.postgresql;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class PostgresqlTenantSchemaResourceTest {
@Test
void testTenantColumnsAreHardenedInSchemaResource() throws IOException {
try (InputStream inputStream =
getClass().getClassLoader().getResourceAsStream("META-INF/pg-schema.sql")) {
assertNotNull(inputStream);
String schema = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
Matcher matcher = Pattern.compile("\"tenant_id\" varchar\\(128\\) NOT NULL DEFAULT ''")
.matcher(schema);
int count = 0;
while (matcher.find()) {
count++;
}
assertEquals(4, count);
}
}
}