├── .springjavaformatconfig ├── src └── main │ ├── java │ └── com │ │ └── pig4cloud │ │ └── plugin │ │ ├── impl │ │ └── postgresql │ │ │ ├── TenantInfoMapperByPostgresql.java │ │ │ ├── ConfigMigrateMapperByPostgresql.java │ │ │ ├── ConfigInfoGrayMapperByPostgresql.java │ │ │ ├── ConfigInfoTagMapperByPostgresql.java │ │ │ ├── TrustedPgFunctionEnum.java │ │ │ ├── ConfigInfoBetaMapperByPostgresql.java │ │ │ ├── HistoryConfigInfoMapperByPostgresql.java │ │ │ ├── TenantCapacityMapperByPostgresql.java │ │ │ ├── PostgresqlAbstractMapper.java │ │ │ ├── ConfigTagsRelationMapperByPostgresql.java │ │ │ ├── GroupCapacityMapperByPostgresql.java │ │ │ └── ConfigInfoMapperByPostgresql.java │ │ └── constants │ │ └── DataSourceConstant.java │ └── resources │ └── META-INF │ └── services │ └── com.alibaba.nacos.plugin.datasource.mapper.Mapper ├── .gitignore ├── .github └── workflows │ └── maven.yml ├── README.md ├── sql └── nacos-pg.sql └── pom.xml /.springjavaformatconfig: -------------------------------------------------------------------------------- 1 | java-baseline=8 2 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/TenantInfoMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.plugin.datasource.mapper.TenantInfoMapper; 4 | import com.pig4cloud.plugin.constants.DataSourceConstant; 5 | 6 | public class TenantInfoMapperByPostgresql extends com.pig4cloud.plugin.impl.postgresql.PostgresqlAbstractMapper 7 | implements TenantInfoMapper { 8 | 9 | @Override 10 | public String getDataSource() { 11 | return DataSourceConstant.POSTGRESQL; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/ConfigMigrateMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.plugin.datasource.mapper.ConfigMigrateMapper; 4 | import com.pig4cloud.plugin.constants.DataSourceConstant; 5 | 6 | /** 7 | * @author mrdaios 8 | */ 9 | public class ConfigMigrateMapperByPostgresql extends PostgresqlAbstractMapper implements ConfigMigrateMapper { 10 | 11 | @Override 12 | public String getDataSource() { 13 | return DataSourceConstant.POSTGRESQL; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper: -------------------------------------------------------------------------------- 1 | com.pig4cloud.plugin.impl.postgresql.ConfigInfoGrayMapperByPostgresql 2 | com.pig4cloud.plugin.impl.postgresql.ConfigInfoBetaMapperByPostgresql 3 | com.pig4cloud.plugin.impl.postgresql.ConfigInfoMapperByPostgresql 4 | com.pig4cloud.plugin.impl.postgresql.ConfigInfoTagMapperByPostgresql 5 | com.pig4cloud.plugin.impl.postgresql.ConfigMigrateMapperByPostgresql 6 | com.pig4cloud.plugin.impl.postgresql.ConfigTagsRelationMapperByPostgresql 7 | com.pig4cloud.plugin.impl.postgresql.HistoryConfigInfoMapperByPostgresql 8 | com.pig4cloud.plugin.impl.postgresql.TenantInfoMapperByPostgresql 9 | com.pig4cloud.plugin.impl.postgresql.TenantCapacityMapperByPostgresql 10 | com.pig4cloud.plugin.impl.postgresql.GroupCapacityMapperByPostgresql 11 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/constants/DataSourceConstant.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2022 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.pig4cloud.plugin.constants; 18 | 19 | /** 20 | * The data source name. 21 | * 22 | * @author hyx 23 | **/ 24 | 25 | public class DataSourceConstant { 26 | 27 | public static final String MYSQL = "mysql"; 28 | 29 | public static final String DERBY = "derby"; 30 | 31 | public static final String POSTGRESQL = "postgresql"; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/ConfigInfoGrayMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper; 4 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 5 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 6 | import com.pig4cloud.plugin.constants.DataSourceConstant; 7 | 8 | import java.util.Collections; 9 | 10 | public class ConfigInfoGrayMapperByPostgresql extends PostgresqlAbstractMapper implements ConfigInfoGrayMapper { 11 | 12 | @Override 13 | public MapperResult findAllConfigInfoGrayForDumpAllFetchRows(MapperContext context) { 14 | String sql = "SELECT id,data_id,group_id,tenant_id,gray_name,gray_rule,app_name,content,md5,gmt_modified " 15 | + "FROM config_info_gray ORDER BY id LIMIT " + context.getPageSize() + " OFFSET " 16 | + context.getStartRow(); 17 | return new MapperResult(sql, Collections.emptyList()); 18 | } 19 | 20 | @Override 21 | public String getDataSource() { 22 | return DataSourceConstant.POSTGRESQL; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/ConfigInfoTagMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoTagMapper; 4 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 5 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 6 | import com.pig4cloud.plugin.constants.DataSourceConstant; 7 | 8 | import java.util.Collections; 9 | 10 | public class ConfigInfoTagMapperByPostgresql extends PostgresqlAbstractMapper implements ConfigInfoTagMapper { 11 | 12 | @Override 13 | public MapperResult findAllConfigInfoTagForDumpAllFetchRows(MapperContext context) { 14 | String sql = " SELECT t.id,data_id,group_id,tenant_id,tag_id,app_name,content,md5,gmt_modified " 15 | + " FROM ( SELECT id FROM config_info_tag ORDER BY id LIMIT " + context.getPageSize() + " offset " 16 | + context.getStartRow() + " ) " + "g, config_info_tag t WHERE g.id = t.id "; 17 | return new MapperResult(sql, Collections.emptyList()); 18 | } 19 | 20 | @Override 21 | public String getDataSource() { 22 | return DataSourceConstant.POSTGRESQL; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/TrustedPgFunctionEnum.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author lengleng 8 | * @date 2024/8/25 9 | */ 10 | public enum TrustedPgFunctionEnum { 11 | 12 | /** 13 | * NOW(). 14 | */ 15 | NOW("NOW()", "NOW()"); 16 | 17 | private static final Map LOOKUP_MAP = new HashMap<>(); 18 | 19 | static { 20 | for (TrustedPgFunctionEnum entry : TrustedPgFunctionEnum.values()) { 21 | LOOKUP_MAP.put(entry.functionName, entry); 22 | } 23 | } 24 | 25 | private final String functionName; 26 | 27 | private final String function; 28 | 29 | TrustedPgFunctionEnum(String functionName, String function) { 30 | this.functionName = functionName; 31 | this.function = function; 32 | } 33 | 34 | /** 35 | * Get the function name. 36 | * @param functionName function name 37 | * @return function 38 | */ 39 | public static String getFunctionByName(String functionName) { 40 | TrustedPgFunctionEnum entry = LOOKUP_MAP.get(functionName); 41 | if (entry != null) { 42 | return entry.function; 43 | } 44 | throw new IllegalArgumentException(String.format("Invalid function name: %s", functionName)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/ConfigInfoBetaMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoBetaMapper; 4 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 5 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 6 | import com.pig4cloud.plugin.constants.DataSourceConstant; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class ConfigInfoBetaMapperByPostgresql extends PostgresqlAbstractMapper implements ConfigInfoBetaMapper { 12 | 13 | @Override 14 | public MapperResult findAllConfigInfoBetaForDumpAllFetchRows(MapperContext context) { 15 | int startRow = context.getStartRow(); 16 | int pageSize = context.getPageSize(); 17 | String sql = " SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,beta_ips,encrypted_data_key " 18 | + " FROM ( SELECT id FROM config_info_beta ORDER BY id LIMIT " + pageSize + " offset " + startRow 19 | + " )" + " g, config_info_beta t WHERE g.id = t.id "; 20 | List paramList = new ArrayList<>(); 21 | paramList.add(startRow); 22 | paramList.add(pageSize); 23 | 24 | return new MapperResult(sql, paramList); 25 | } 26 | 27 | @Override 28 | public String getDataSource() { 29 | return DataSourceConstant.POSTGRESQL; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: nacos-datasource-plugin-pg 5 | 6 | on: 7 | push: 8 | branches: [ master,dev ] 9 | pull_request: 10 | branches: [ master,dev ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '17' 21 | distribution: 'temurin' 22 | 23 | - name: mvn clean install 24 | run: mvn clean install 25 | 26 | - name: mvn spring-javaformat:validate 27 | id: validate 28 | run: mvn spring-javaformat:validate 29 | continue-on-error: true 30 | 31 | - name: Auto format code if validation fails 32 | if: steps.validate.outcome == 'failure' 33 | run: mvn spring-javaformat:apply 34 | 35 | - name: Create Pull Request for formatting changes 36 | if: steps.validate.outcome == 'failure' 37 | uses: peter-evans/create-pull-request@v5 38 | with: 39 | token: ${{ secrets.GITHUB_TOKEN }} 40 | commit-message: 'Auto-format code with spring-javaformat' 41 | title: 'Auto-format: Fix code formatting issues' 42 | body: | 43 | This PR was automatically created because the spring-javaformat validation failed. 44 | 45 | The following changes have been applied: 46 | - Applied spring-javaformat:apply to fix formatting issues 47 | 48 | Please review and merge if the changes look correct. 49 | branch: auto-format-${{ github.run_number }} 50 | delete-branch: true 51 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/HistoryConfigInfoMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.common.utils.CollectionUtils; 4 | import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; 5 | import com.alibaba.nacos.plugin.datasource.constants.TableConstant; 6 | import com.alibaba.nacos.plugin.datasource.mapper.HistoryConfigInfoMapper; 7 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 8 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 9 | import com.pig4cloud.plugin.constants.DataSourceConstant; 10 | 11 | public class HistoryConfigInfoMapperByPostgresql extends PostgresqlAbstractMapper implements HistoryConfigInfoMapper { 12 | 13 | public MapperResult removeConfigHistory(MapperContext context) { 14 | String sql = "DELETE FROM his_config_info WHERE gmt_modified < ? LIMIT ?"; 15 | return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME), 16 | context.getWhereParameter(FieldConstant.LIMIT_SIZE))); 17 | } 18 | 19 | public MapperResult pageFindConfigHistoryFetchRows(MapperContext context) { 20 | 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 " 21 | + "FROM his_config_info WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC " 22 | + "LIMIT " + context.getPageSize() + " OFFSET " + context.getStartRow(); 23 | return new MapperResult(sql, CollectionUtils.list(new Object[] { context.getWhereParameter("dataId"), 24 | context.getWhereParameter("groupId"), context.getWhereParameter("tenantId") })); 25 | } 26 | 27 | @Override 28 | public String getTableName() { 29 | return TableConstant.HIS_CONFIG_INFO; 30 | } 31 | 32 | @Override 33 | public String getDataSource() { 34 | return DataSourceConstant.POSTGRESQL; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nacos PostgreSQL 数据源插件 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/com.pig4cloud.plugin/nacos-datasource-plugin-postgresql.svg?style=flat-square)](https://maven.badges.herokuapp.com/maven-central/com.pig4cloud.plugin/nacos-datasource-plugin-postgresql) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | 6 | ## 项目介绍 7 | 8 | 本插件为 Nacos(2.2.0 版本及以上)提供 PostgreSQL 数据库的数据源支持。通过 SPI 机制实现,您只需在 `application.properties` 配置文件中修改 `spring.datasource.platform` 属性即可启用 PostgreSQL 数据库。 9 | 10 | > Nacos 官方默认支持 MySQL 和 Derby 数据库,本插件扩展了对 PostgreSQL 数据库的支持。 11 | 12 | ## 版本兼容性 13 | 14 | | Nacos 版本 | 插件版本 | 15 | |-----------------|-------| 16 | | 2.2.0 - 2.3.0 | 0.0.2 | 17 | | 2.3.1 - 2.3.2 | 0.0.3 | 18 | | 2.4.0 - 2.4.3 | 0.0.4 | 19 | | 2.5.0 - | 0.0.5 | 20 | | 3.0.0 - | 0.0.6 | 21 | | 3.0.1.0 - | 0.0.7 | 22 | 23 | ## 快速开始 24 | 25 | ### 1. 添加依赖 26 | 27 | 在项目的 `pom.xml` 中添加以下依赖(注意:依赖已上传至 Maven 中央仓库,请勿使用阿里云代理): 28 | 29 | ```xml 30 | 31 | com.pig4cloud.plugin 32 | nacos-datasource-plugin-postgresql 33 | ${plugin.version} 34 | 35 | 36 | 37 | org.postgresql 38 | postgresql 39 | 40 | ``` 41 | 42 | ### 2. 执行 SQL 脚本初始化 43 | 44 | 执行 SQL 脚本初始化数据库表结构,脚本位置:`./sql/nacos-pg.sql` 45 | 46 | 47 | ### 3. 配置数据源 48 | 49 | 在 Nacos 配置文件中添加以下配置: 50 | 51 | ```properties 52 | spring.sql.init.platform=postgresql 53 | db.num=1 54 | db.url.0=jdbc:postgresql://127.0.0.1:5432/postgres 55 | db.user=postgres 56 | db.password=postgres 57 | db.pool.config.driver-class-name=org.postgresql.Driver 58 | ``` 59 | 60 | ## 参与贡献 61 | 62 | 我们欢迎所有形式的贡献,如果您有任何改进建议或功能扩展,请提交 Pull Request。 63 | 64 | ## 开源协议 65 | 66 | 本项目采用 Apache License 2.0 开源协议 - 详情请参见 [LICENSE](LICENSE) 文件。 67 | 68 | ![Nacos 插件化实现](https://minio.pigx.top/oss/202212/1671179590.jpg) 69 | 70 | ![](https://minio.pigx.top/oss/202212/1671180565.png) 71 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/TenantCapacityMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.common.utils.CollectionUtils; 4 | import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; 5 | import com.alibaba.nacos.plugin.datasource.constants.TableConstant; 6 | import com.alibaba.nacos.plugin.datasource.mapper.TenantCapacityMapper; 7 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 8 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 9 | import com.pig4cloud.plugin.constants.DataSourceConstant; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | public class TenantCapacityMapperByPostgresql extends PostgresqlAbstractMapper implements TenantCapacityMapper { 16 | 17 | @Override 18 | public MapperResult select(MapperContext context) { 19 | String sql = "SELECT id, quota, `usage`, max_size, max_aggr_count, max_aggr_size, tenant_id FROM tenant_capacity WHERE tenant_id = ?"; 20 | return new MapperResult(sql, Collections.singletonList(context.getWhereParameter("tenantId"))); 21 | } 22 | 23 | @Override 24 | public MapperResult getCapacityList4CorrectUsage(MapperContext context) { 25 | String sql = "SELECT id, tenant_id FROM tenant_capacity WHERE id>? LIMIT ?"; 26 | return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.ID), 27 | context.getWhereParameter(FieldConstant.LIMIT_SIZE))); 28 | } 29 | 30 | @Override 31 | public MapperResult incrementUsageWithDefaultQuotaLimit(MapperContext context) { 32 | return new MapperResult( 33 | "UPDATE tenant_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE tenant_id = ? AND `usage` < ? AND quota = 0", 34 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 35 | context.getWhereParameter("tenantId"), context.getWhereParameter("usage") })); 36 | } 37 | 38 | @Override 39 | public MapperResult incrementUsageWithQuotaLimit(MapperContext context) { 40 | return new MapperResult( 41 | "UPDATE tenant_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE tenant_id = ? AND `usage` < quota AND quota != 0", 42 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 43 | context.getWhereParameter("tenantId") })); 44 | } 45 | 46 | @Override 47 | public MapperResult incrementUsage(MapperContext context) { 48 | return new MapperResult( 49 | "UPDATE tenant_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE tenant_id = ?", 50 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 51 | context.getWhereParameter("tenantId") })); 52 | } 53 | 54 | @Override 55 | public MapperResult decrementUsage(MapperContext context) { 56 | return new MapperResult( 57 | "UPDATE tenant_capacity SET `usage` = `usage` - 1, gmt_modified = ? WHERE tenant_id = ? AND `usage` > 0", 58 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 59 | context.getWhereParameter("tenantId") })); 60 | } 61 | 62 | @Override 63 | public MapperResult correctUsage(MapperContext context) { 64 | return new MapperResult( 65 | "UPDATE tenant_capacity SET `usage` = (SELECT count(*) FROM config_info WHERE tenant_id = ?), gmt_modified = ? WHERE tenant_id = ?", 66 | CollectionUtils.list(new Object[] { context.getWhereParameter("tenantId"), 67 | context.getUpdateParameter("gmtModified"), context.getWhereParameter("tenantId") })); 68 | } 69 | 70 | @Override 71 | public MapperResult insertTenantCapacity(MapperContext context) { 72 | List paramList = new ArrayList(); 73 | paramList.add(context.getUpdateParameter("tenantId")); 74 | paramList.add(context.getUpdateParameter("quota")); 75 | paramList.add(context.getUpdateParameter("maxSize")); 76 | paramList.add(context.getUpdateParameter("maxAggrCount")); 77 | paramList.add(context.getUpdateParameter("maxAggrSize")); 78 | paramList.add(context.getUpdateParameter("gmtCreate")); 79 | paramList.add(context.getUpdateParameter("gmtModified")); 80 | paramList.add(context.getWhereParameter("tenantId")); 81 | return new MapperResult( 82 | "INSERT INTO tenant_capacity (tenant_id, quota, `usage`, max_size, max_aggr_count, max_aggr_size, gmt_create, gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE tenant_id=?", 83 | paramList); 84 | } 85 | 86 | @Override 87 | public String getTableName() { 88 | return TableConstant.TENANT_CAPACITY; 89 | } 90 | 91 | @Override 92 | public String getDataSource() { 93 | return DataSourceConstant.POSTGRESQL; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/PostgresqlAbstractMapper.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.common.utils.CollectionUtils; 4 | import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | /** 10 | * @author Fxz 11 | * @version 0.0.1 12 | * @date 2022/12/19 21:01 13 | */ 14 | public abstract class PostgresqlAbstractMapper extends AbstractMapper { 15 | 16 | @Override 17 | public String select(List columns, List where) { 18 | StringBuilder sql = new StringBuilder("SELECT "); 19 | 20 | for (int i = 0; i < columns.size(); i++) { 21 | sql.append(columns.get(i)); 22 | if (i == columns.size() - 1) { 23 | sql.append(" "); 24 | } 25 | else { 26 | sql.append(","); 27 | } 28 | } 29 | sql.append("FROM "); 30 | sql.append(getTableName()); 31 | sql.append(" "); 32 | 33 | if (where.size() == 0) { 34 | return sql.toString(); 35 | } 36 | 37 | sql.append("WHERE "); 38 | for (int i = 0; i < where.size(); i++) { 39 | String column = where.get(i); 40 | 41 | // 租户列特殊处理 避免前端传空字符串是Oracle查询不到数据 42 | if ("tenant_id".equalsIgnoreCase(column)) { 43 | sql.append("("); 44 | sql.append(column).append(" = ").append("?"); 45 | sql.append(" OR "); 46 | sql.append(column).append(" IS NULL "); 47 | sql.append(")"); 48 | } 49 | else { 50 | sql.append(column).append(" = ").append("?"); 51 | } 52 | 53 | if (i != where.size() - 1) { 54 | sql.append(" AND "); 55 | } 56 | } 57 | return sql.toString(); 58 | } 59 | 60 | @Override 61 | public String update(List columns, List where) { 62 | StringBuilder sql = new StringBuilder(); 63 | String method = "UPDATE "; 64 | sql.append(method); 65 | sql.append(this.getTableName()).append(" ").append("SET "); 66 | 67 | for (int i = 0; i < columns.size(); ++i) { 68 | String[] parts = ((String) columns.get(i)).split("@"); 69 | String column = parts[0]; 70 | if (parts.length == 2) { 71 | sql.append(column).append(" = ").append(this.getFunction(parts[1])); 72 | } 73 | else { 74 | sql.append(column).append(" = ").append("?"); 75 | } 76 | 77 | if (i != columns.size() - 1) { 78 | sql.append(","); 79 | } 80 | } 81 | 82 | if (CollectionUtils.isEmpty(where)) { 83 | return sql.toString(); 84 | } 85 | else { 86 | sql.append(" WHERE "); 87 | sql.append(where.stream().map((str) -> str + " = ?").collect(Collectors.joining(" AND "))); 88 | return sql.toString(); 89 | } 90 | } 91 | 92 | @Override 93 | public String delete(List params) { 94 | StringBuilder sql = new StringBuilder(); 95 | String method = "DELETE "; 96 | sql.append(method).append("FROM ").append(getTableName()).append(" ").append("WHERE "); 97 | for (int i = 0; i < params.size(); i++) { 98 | String column = params.get(i); 99 | if ("tenant_id".equalsIgnoreCase(column)) { 100 | sql.append(" ("); 101 | sql.append(column).append(" = ").append("?"); 102 | sql.append(" OR "); 103 | sql.append(column).append(" IS NULL "); 104 | sql.append(")"); 105 | } 106 | else { 107 | sql.append(column).append(" = ").append("?"); 108 | } 109 | if (i != params.size() - 1) { 110 | sql.append(" AND "); 111 | } 112 | } 113 | 114 | return sql.toString(); 115 | } 116 | 117 | @Override 118 | public String count(List where) { 119 | StringBuilder sql = new StringBuilder(); 120 | String method = "SELECT "; 121 | sql.append(method); 122 | sql.append("COUNT(*) FROM "); 123 | sql.append(getTableName()); 124 | sql.append(" "); 125 | 126 | if (null == where || where.size() == 0) { 127 | return sql.toString(); 128 | } 129 | 130 | sql.append("WHERE "); 131 | for (int i = 0; i < where.size(); i++) { 132 | String column = where.get(i); 133 | if ("tenant_id".equalsIgnoreCase(column)) { 134 | sql.append("("); 135 | sql.append(column).append(" = ").append("?"); 136 | sql.append(" OR "); 137 | sql.append(column).append(" IS NULL "); 138 | sql.append(")"); 139 | } 140 | else { 141 | sql.append(column).append(" = ").append("?"); 142 | } 143 | if (i != where.size() - 1) { 144 | sql.append(" AND "); 145 | } 146 | } 147 | return sql.toString(); 148 | } 149 | 150 | /** 151 | * Get function by functionName. 152 | * @param functionName functionName 153 | * @return function 154 | */ 155 | @Override 156 | public String getFunction(String functionName) { 157 | return TrustedPgFunctionEnum.getFunctionByName(functionName); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/ConfigTagsRelationMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.common.utils.ArrayUtils; 4 | import com.alibaba.nacos.common.utils.StringUtils; 5 | import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; 6 | import com.alibaba.nacos.plugin.datasource.mapper.ConfigTagsRelationMapper; 7 | import com.alibaba.nacos.plugin.datasource.mapper.ext.WhereBuilder; 8 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 9 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 10 | import com.pig4cloud.plugin.constants.DataSourceConstant; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class ConfigTagsRelationMapperByPostgresql extends PostgresqlAbstractMapper implements ConfigTagsRelationMapper { 16 | 17 | @Override 18 | public MapperResult findConfigInfo4PageFetchRows(MapperContext context) { 19 | final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME); 20 | final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); 21 | final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); 22 | final String content = (String) context.getWhereParameter(FieldConstant.CONTENT); 23 | final String tenantId = (String) context.getWhereParameter(FieldConstant.TENANT_ID); 24 | final String[] tagArr = (String[]) context.getWhereParameter(FieldConstant.TAG_ARR); 25 | 26 | List paramList = new ArrayList<>(); 27 | StringBuilder where = new StringBuilder(" WHERE "); 28 | final String baseSql = "SELECT a.id,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content FROM config_info a LEFT JOIN " 29 | + "config_tags_relation b ON a.id=b.id"; 30 | 31 | where.append(" a.tenant_id=? "); 32 | paramList.add(tenantId); 33 | 34 | if (StringUtils.isNotBlank(dataId)) { 35 | where.append(" AND a.data_id=? "); 36 | paramList.add(dataId); 37 | } 38 | if (StringUtils.isNotBlank(group)) { 39 | where.append(" AND a.group_id=? "); 40 | paramList.add(group); 41 | } 42 | if (StringUtils.isNotBlank(appName)) { 43 | where.append(" AND a.app_name=? "); 44 | paramList.add(appName); 45 | } 46 | if (!StringUtils.isBlank(content)) { 47 | where.append(" AND a.content LIKE ? "); 48 | paramList.add(content); 49 | } 50 | where.append(" AND b.tag_name IN ("); 51 | for (int i = 0; i < tagArr.length; i++) { 52 | if (i != 0) { 53 | where.append(", "); 54 | } 55 | where.append('?'); 56 | paramList.add(tagArr[i]); 57 | } 58 | where.append(") "); 59 | String sql = baseSql + where + " OFFSET " + context.getStartRow() + " ROWS FETCH NEXT " + context.getPageSize() 60 | + " ROWS ONLY"; 61 | return new MapperResult(sql, paramList); 62 | } 63 | 64 | @Override 65 | public MapperResult findConfigInfoLike4PageFetchRows(MapperContext context) { 66 | final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME); 67 | final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); 68 | final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); 69 | final String content = (String) context.getWhereParameter(FieldConstant.CONTENT); 70 | final String tenantId = (String) context.getWhereParameter(FieldConstant.TENANT_ID); 71 | final String[] tagArr = (String[]) context.getWhereParameter(FieldConstant.TAG_ARR); 72 | final String[] types = (String[]) context.getWhereParameter(FieldConstant.TYPE); 73 | 74 | WhereBuilder where = new WhereBuilder( 75 | "SELECT a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content,a.type FROM config_info a LEFT JOIN " 76 | + "config_tags_relation b ON a.id=b.id"); 77 | 78 | where.like("a.tenant_id", tenantId); 79 | 80 | if (StringUtils.isNotBlank(dataId)) { 81 | where.and().like("a.data_id", dataId); 82 | } 83 | if (StringUtils.isNotBlank(group)) { 84 | where.and().like("a.group_id", group); 85 | } 86 | if (StringUtils.isNotBlank(appName)) { 87 | where.and().eq("a.app_name", appName); 88 | } 89 | if (StringUtils.isNotBlank(content)) { 90 | where.and().like("a.content", content); 91 | } 92 | if (!ArrayUtils.isEmpty(tagArr)) { 93 | where.and().startParentheses(); 94 | for (int i = 0; i < tagArr.length; i++) { 95 | if (i != 0) { 96 | where.or(); 97 | } 98 | where.like("b.tag_name", tagArr[i]); 99 | } 100 | where.endParentheses(); 101 | } 102 | if (!ArrayUtils.isEmpty(types)) { 103 | where.and().in("a.type", types); 104 | } 105 | 106 | where.offset(context.getStartRow(), context.getPageSize()); 107 | return where.build(); 108 | } 109 | 110 | @Override 111 | public String getDataSource() { 112 | return DataSourceConstant.POSTGRESQL; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/GroupCapacityMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.common.utils.CollectionUtils; 4 | import com.alibaba.nacos.common.utils.NamespaceUtil; 5 | import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; 6 | import com.alibaba.nacos.plugin.datasource.mapper.GroupCapacityMapper; 7 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 8 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 9 | import com.pig4cloud.plugin.constants.DataSourceConstant; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | public class GroupCapacityMapperByPostgresql extends PostgresqlAbstractMapper implements GroupCapacityMapper { 16 | 17 | @Override 18 | public MapperResult select(MapperContext context) { 19 | String sql = "SELECT id, quota, `usage`, max_size, max_aggr_count, max_aggr_size, group_id FROM group_capacity WHERE group_id = ?"; 20 | return new MapperResult(sql, Collections.singletonList(context.getWhereParameter("groupId"))); 21 | } 22 | 23 | @Override 24 | public MapperResult insertIntoSelect(MapperContext context) { 25 | List paramList = new ArrayList(); 26 | paramList.add(context.getUpdateParameter("groupId")); 27 | paramList.add(context.getUpdateParameter("quota")); 28 | paramList.add(context.getUpdateParameter("maxSize")); 29 | paramList.add(context.getUpdateParameter("maxAggrCount")); 30 | paramList.add(context.getUpdateParameter("maxAggrSize")); 31 | paramList.add(context.getUpdateParameter("gmtCreate")); 32 | paramList.add(context.getUpdateParameter("gmtModified")); 33 | String sql = "INSERT INTO group_capacity (group_id, quota, `usage`, max_size, max_aggr_count, max_aggr_size,gmt_create, gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info"; 34 | return new MapperResult(sql, paramList); 35 | } 36 | 37 | @Override 38 | public MapperResult insertIntoSelectByWhere(MapperContext context) { 39 | String sql = "INSERT INTO group_capacity (group_id, quota, `usage`, max_size, max_aggr_count, max_aggr_size, gmt_create, gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE group_id=? AND tenant_id = '" 40 | + NamespaceUtil.getNamespaceDefaultId() + "'"; 41 | List paramList = new ArrayList(); 42 | paramList.add(context.getUpdateParameter("groupId")); 43 | paramList.add(context.getUpdateParameter("quota")); 44 | paramList.add(context.getUpdateParameter("maxSize")); 45 | paramList.add(context.getUpdateParameter("maxAggrCount")); 46 | paramList.add(context.getUpdateParameter("maxAggrSize")); 47 | paramList.add(context.getUpdateParameter("gmtCreate")); 48 | paramList.add(context.getUpdateParameter("gmtModified")); 49 | paramList.add(context.getWhereParameter("groupId")); 50 | return new MapperResult(sql, paramList); 51 | } 52 | 53 | @Override 54 | public MapperResult incrementUsageByWhereQuotaEqualZero(MapperContext context) { 55 | return new MapperResult( 56 | "UPDATE group_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE group_id = ? AND `usage` < ? AND quota = 0", 57 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 58 | context.getWhereParameter("groupId"), context.getWhereParameter("usage") })); 59 | } 60 | 61 | @Override 62 | public MapperResult incrementUsageByWhereQuotaNotEqualZero(MapperContext context) { 63 | return new MapperResult( 64 | "UPDATE group_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE group_id = ? AND `usage` < quota AND quota != 0", 65 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 66 | context.getWhereParameter("groupId") })); 67 | } 68 | 69 | @Override 70 | public MapperResult incrementUsageByWhere(MapperContext context) { 71 | return new MapperResult("UPDATE group_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE group_id = ?", 72 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 73 | context.getWhereParameter("groupId") })); 74 | } 75 | 76 | @Override 77 | public MapperResult decrementUsageByWhere(MapperContext context) { 78 | return new MapperResult( 79 | "UPDATE group_capacity SET `usage` = `usage` - 1, gmt_modified = ? WHERE group_id = ? AND `usage` > 0", 80 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 81 | context.getWhereParameter("groupId") })); 82 | } 83 | 84 | @Override 85 | public MapperResult updateUsage(MapperContext context) { 86 | return new MapperResult( 87 | "UPDATE group_capacity SET `usage` = (SELECT count(*) FROM config_info), gmt_modified = ? WHERE group_id = ?", 88 | CollectionUtils.list(new Object[] { context.getUpdateParameter("gmtModified"), 89 | context.getWhereParameter("groupId") })); 90 | } 91 | 92 | @Override 93 | public MapperResult updateUsageByWhere(MapperContext context) { 94 | return new MapperResult( 95 | "UPDATE group_capacity SET `usage` = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = '" 96 | + NamespaceUtil.getNamespaceDefaultId() + "'), gmt_modified = ? WHERE group_id= ?", 97 | CollectionUtils.list(new Object[] { context.getWhereParameter("groupId"), 98 | context.getUpdateParameter("gmtModified"), context.getWhereParameter("groupId") })); 99 | } 100 | 101 | @Override 102 | public MapperResult selectGroupInfoBySize(MapperContext context) { 103 | String sql = "SELECT id, group_id FROM group_capacity WHERE id > ? LIMIT ?"; 104 | return new MapperResult(sql, 105 | CollectionUtils.list(context.getWhereParameter(FieldConstant.ID), context.getPageSize())); 106 | } 107 | 108 | @Override 109 | public String getDataSource() { 110 | return DataSourceConstant.POSTGRESQL; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /sql/nacos-pg.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE config_info ( 2 | id SERIAL PRIMARY KEY, 3 | data_id varchar(255) NOT NULL, 4 | group_id varchar(128) DEFAULT NULL, 5 | content text NOT NULL, 6 | md5 varchar(32) DEFAULT NULL, 7 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | gmt_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | src_user text, 10 | src_ip varchar(50) DEFAULT NULL, 11 | app_name varchar(128) DEFAULT NULL, 12 | tenant_id varchar(128) DEFAULT '', 13 | c_desc varchar(256) DEFAULT NULL, 14 | c_use varchar(64) DEFAULT NULL, 15 | effect varchar(64) DEFAULT NULL, 16 | type varchar(64) DEFAULT NULL, 17 | c_schema text, 18 | encrypted_data_key varchar(1024) DEFAULT '' 19 | ); 20 | 21 | COMMENT ON TABLE config_info IS 'config_info'; 22 | COMMENT ON COLUMN config_info.id IS 'id'; 23 | COMMENT ON COLUMN config_info.data_id IS 'data_id'; 24 | COMMENT ON COLUMN config_info.content IS 'content'; 25 | COMMENT ON COLUMN config_info.md5 IS 'md5'; 26 | COMMENT ON COLUMN config_info.gmt_create IS '创建时间'; 27 | COMMENT ON COLUMN config_info.gmt_modified IS '修改时间'; 28 | COMMENT ON COLUMN config_info.src_user IS 'source user'; 29 | COMMENT ON COLUMN config_info.src_ip IS 'source ip'; 30 | COMMENT ON COLUMN config_info.tenant_id IS '租户字段'; 31 | COMMENT ON COLUMN config_info.encrypted_data_key IS '密钥'; 32 | 33 | CREATE UNIQUE INDEX IF NOT EXISTS uk_configinfo_datagrouptenant ON config_info (data_id, group_id, tenant_id); 34 | 35 | CREATE TABLE config_info_gray ( 36 | id SERIAL PRIMARY KEY, 37 | data_id varchar(255) NOT NULL, 38 | group_id varchar(128) NOT NULL, 39 | content text NOT NULL, 40 | md5 varchar(32) DEFAULT NULL, 41 | src_user text, 42 | src_ip varchar(100) DEFAULT NULL, 43 | gmt_create timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 44 | gmt_modified timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 45 | app_name varchar(128) DEFAULT NULL, 46 | tenant_id varchar(128) DEFAULT '', 47 | gray_name varchar(128) NOT NULL, 48 | gray_rule text NOT NULL, 49 | encrypted_data_key varchar(256) DEFAULT '' 50 | ); 51 | 52 | COMMENT ON TABLE config_info_gray IS 'config_info_gray'; 53 | 54 | CREATE UNIQUE INDEX IF NOT EXISTS uk_configinfogray_datagrouptenantgray ON config_info_gray (data_id, group_id, tenant_id, gray_name); 55 | CREATE INDEX IF NOT EXISTS idx_dataid_gmt_modified ON config_info_gray (data_id, gmt_modified); 56 | CREATE INDEX IF NOT EXISTS idx_gmt_modified ON config_info_gray (gmt_modified); 57 | 58 | CREATE TABLE config_info_beta ( 59 | id SERIAL PRIMARY KEY, 60 | data_id varchar(255) NOT NULL, 61 | group_id varchar(128) NOT NULL, 62 | app_name varchar(128) DEFAULT NULL, 63 | content text NOT NULL, 64 | beta_ips varchar(1024) DEFAULT NULL, 65 | md5 varchar(32) DEFAULT NULL, 66 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 67 | gmt_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 68 | src_user text, 69 | src_ip varchar(50) DEFAULT NULL, 70 | tenant_id varchar(128) DEFAULT '', 71 | encrypted_data_key varchar(1024) DEFAULT '' 72 | ); 73 | 74 | COMMENT ON TABLE config_info_beta IS 'config_info_beta'; 75 | 76 | CREATE UNIQUE INDEX IF NOT EXISTS uk_configinfobeta_datagrouptenant ON config_info_beta (data_id, group_id, tenant_id); 77 | 78 | CREATE TABLE config_info_tag ( 79 | id SERIAL PRIMARY KEY, 80 | data_id varchar(255) NOT NULL, 81 | group_id varchar(128) NOT NULL, 82 | tenant_id varchar(128) DEFAULT '', 83 | tag_id varchar(128) NOT NULL, 84 | app_name varchar(128) DEFAULT NULL, 85 | content text NOT NULL, 86 | md5 varchar(32) DEFAULT NULL, 87 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 88 | gmt_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 89 | src_user text, 90 | src_ip varchar(50) DEFAULT NULL 91 | ); 92 | 93 | COMMENT ON TABLE config_info_tag IS 'config_info_tag'; 94 | 95 | CREATE UNIQUE INDEX IF NOT EXISTS uk_configinfotag_datagrouptenanttag ON config_info_tag (data_id, group_id, tenant_id, tag_id); 96 | 97 | CREATE TABLE config_tags_relation ( 98 | id bigint NOT NULL, 99 | tag_name varchar(128) NOT NULL, 100 | tag_type varchar(64) DEFAULT NULL, 101 | data_id varchar(255) NOT NULL, 102 | group_id varchar(128) NOT NULL, 103 | tenant_id varchar(128) DEFAULT '', 104 | nid SERIAL PRIMARY KEY 105 | ); 106 | 107 | COMMENT ON TABLE config_tags_relation IS 'config_tag_relation'; 108 | 109 | CREATE UNIQUE INDEX IF NOT EXISTS uk_configtagrelation_configidtag ON config_tags_relation (id, tag_name, tag_type); 110 | CREATE INDEX IF NOT EXISTS idx_tenant_id ON config_tags_relation (tenant_id); 111 | 112 | CREATE TABLE group_capacity ( 113 | id SERIAL PRIMARY KEY, 114 | group_id varchar(128) NOT NULL DEFAULT '', 115 | quota int NOT NULL DEFAULT '0', 116 | usage int NOT NULL DEFAULT '0', 117 | max_size int NOT NULL DEFAULT '0', 118 | max_aggr_count int NOT NULL DEFAULT '0', 119 | max_aggr_size int NOT NULL DEFAULT '0', 120 | max_history_count int NOT NULL DEFAULT '0', 121 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 122 | gmt_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 123 | ); 124 | 125 | COMMENT ON TABLE group_capacity IS '集群、各Group容量信息表'; 126 | COMMENT ON COLUMN group_capacity.id IS '主键ID'; 127 | COMMENT ON COLUMN group_capacity.group_id IS 'Group ID,空字符表示整个集群'; 128 | COMMENT ON COLUMN group_capacity.quota IS '配额,0表示使用默认值'; 129 | COMMENT ON COLUMN group_capacity.usage IS '使用量'; 130 | COMMENT ON COLUMN group_capacity.max_size IS '单个配置大小上限,单位为字节,0表示使用默认值'; 131 | COMMENT ON COLUMN group_capacity.max_aggr_count IS '聚合子配置最大个数,,0表示使用默认值'; 132 | COMMENT ON COLUMN group_capacity.max_aggr_size IS '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值'; 133 | COMMENT ON COLUMN group_capacity.max_history_count IS '最大变更历史数量'; 134 | 135 | CREATE UNIQUE INDEX IF NOT EXISTS uk_group_id ON group_capacity (group_id); 136 | 137 | CREATE TABLE his_config_info ( 138 | id bigint NOT NULL, 139 | nid SERIAL PRIMARY KEY, 140 | data_id varchar(255) NOT NULL, 141 | group_id varchar(128) NOT NULL, 142 | app_name varchar(128) DEFAULT NULL, 143 | content text NOT NULL, 144 | md5 varchar(32) DEFAULT NULL, 145 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 146 | gmt_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 147 | src_user text, 148 | src_ip varchar(50) DEFAULT NULL, 149 | op_type char(10) DEFAULT NULL, 150 | tenant_id varchar(128) DEFAULT '', 151 | encrypted_data_key varchar(1024) DEFAULT '', 152 | publish_type varchar(50) DEFAULT 'formal', 153 | gray_name varchar(50) DEFAULT NULL, 154 | ext_info text DEFAULT NULL 155 | ); 156 | 157 | COMMENT ON TABLE his_config_info IS '多租户改造'; 158 | 159 | CREATE INDEX IF NOT EXISTS idx_gmt_create ON his_config_info (gmt_create); 160 | CREATE INDEX IF NOT EXISTS idx_gmt_modified ON his_config_info (gmt_modified); 161 | CREATE INDEX IF NOT EXISTS idx_did ON his_config_info (data_id); 162 | 163 | CREATE TABLE tenant_capacity ( 164 | id SERIAL PRIMARY KEY, 165 | tenant_id varchar(128) NOT NULL DEFAULT '', 166 | quota int NOT NULL DEFAULT '0', 167 | usage int NOT NULL DEFAULT '0', 168 | max_size int NOT NULL DEFAULT '0', 169 | max_aggr_count int NOT NULL DEFAULT '0', 170 | max_aggr_size int NOT NULL DEFAULT '0', 171 | max_history_count int NOT NULL DEFAULT '0', 172 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 173 | gmt_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 174 | ); 175 | 176 | COMMENT ON TABLE tenant_capacity IS '租户容量信息表'; 177 | COMMENT ON COLUMN tenant_capacity.id IS '主键ID'; 178 | COMMENT ON COLUMN tenant_capacity.tenant_id IS 'Tenant ID'; 179 | COMMENT ON COLUMN tenant_capacity.quota IS '配额,0表示使用默认值'; 180 | COMMENT ON COLUMN tenant_capacity.usage IS '使用量'; 181 | COMMENT ON COLUMN tenant_capacity.max_size IS '单个配置大小上限,单位为字节,0表示使用默认值'; 182 | COMMENT ON COLUMN tenant_capacity.max_aggr_count IS '聚合子配置最大个数'; 183 | COMMENT ON COLUMN tenant_capacity.max_aggr_size IS '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值'; 184 | COMMENT ON COLUMN tenant_capacity.max_history_count IS '最大变更历史数量'; 185 | 186 | CREATE UNIQUE INDEX IF NOT EXISTS uk_tenant_id ON tenant_capacity (tenant_id); 187 | 188 | CREATE TABLE tenant_info ( 189 | id SERIAL PRIMARY KEY, 190 | kp varchar(128) NOT NULL, 191 | tenant_id varchar(128) DEFAULT '', 192 | tenant_name varchar(128) DEFAULT '', 193 | tenant_desc varchar(256) DEFAULT NULL, 194 | create_source varchar(32) DEFAULT NULL, 195 | gmt_create bigint NOT NULL, 196 | gmt_modified bigint NOT NULL 197 | ); 198 | 199 | COMMENT ON TABLE tenant_info IS 'tenant_info'; 200 | 201 | CREATE UNIQUE INDEX IF NOT EXISTS uk_tenant_info_kptenantid ON tenant_info (kp, tenant_id); 202 | CREATE INDEX IF NOT EXISTS idx_tenant_id ON tenant_info (tenant_id); 203 | 204 | CREATE TABLE users ( 205 | username varchar(50) PRIMARY KEY, 206 | password varchar(500) NOT NULL, 207 | enabled boolean NOT NULL 208 | ); 209 | 210 | COMMENT ON TABLE users IS 'users'; 211 | COMMENT ON COLUMN users.username IS 'username'; 212 | COMMENT ON COLUMN users.password IS 'password'; 213 | COMMENT ON COLUMN users.enabled IS 'enabled'; 214 | 215 | CREATE TABLE roles ( 216 | username varchar(50) NOT NULL, 217 | role varchar(50) NOT NULL 218 | ); 219 | 220 | COMMENT ON TABLE roles IS 'roles'; 221 | COMMENT ON COLUMN roles.username IS 'username'; 222 | COMMENT ON COLUMN roles.role IS 'role'; 223 | 224 | CREATE UNIQUE INDEX IF NOT EXISTS idx_user_role ON roles (username, role); 225 | 226 | CREATE TABLE permissions ( 227 | role varchar(50) NOT NULL, 228 | resource varchar(128) NOT NULL, 229 | action varchar(8) NOT NULL 230 | ); 231 | 232 | COMMENT ON TABLE permissions IS 'permissions'; 233 | COMMENT ON COLUMN permissions.role IS 'role'; 234 | COMMENT ON COLUMN permissions.resource IS 'resource'; 235 | COMMENT ON COLUMN permissions.action IS 'action'; 236 | 237 | CREATE UNIQUE INDEX IF NOT EXISTS uk_role_permission ON permissions (role, resource, action); 238 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/impl/postgresql/ConfigInfoMapperByPostgresql.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.impl.postgresql; 2 | 3 | import com.alibaba.nacos.common.utils.CollectionUtils; 4 | import com.alibaba.nacos.common.utils.NamespaceUtil; 5 | import com.alibaba.nacos.common.utils.StringUtils; 6 | import com.alibaba.nacos.plugin.datasource.constants.ContextConstant; 7 | import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; 8 | import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoMapper; 9 | import com.alibaba.nacos.plugin.datasource.model.MapperContext; 10 | import com.alibaba.nacos.plugin.datasource.model.MapperResult; 11 | import com.pig4cloud.plugin.constants.DataSourceConstant; 12 | 13 | import java.sql.Timestamp; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | public class ConfigInfoMapperByPostgresql extends PostgresqlAbstractMapper implements ConfigInfoMapper { 19 | 20 | @Override 21 | public MapperResult findConfigInfoByAppFetchRows(MapperContext context) { 22 | final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME); 23 | final String tenantId = (String) context.getWhereParameter(FieldConstant.TENANT_ID); 24 | String sql = "SELECT id,data_id,group_id,tenant_id,app_name,content FROM config_info" 25 | + " WHERE tenant_id LIKE ? AND app_name= ?" + " LIMIT " + context.getPageSize() + " offset " 26 | + context.getStartRow(); 27 | return new MapperResult(sql, CollectionUtils.list(tenantId, appName)); 28 | } 29 | 30 | @Override 31 | public MapperResult getTenantIdList(MapperContext context) { 32 | String sql = "SELECT tenant_id FROM config_info WHERE tenant_id != '" + NamespaceUtil.getNamespaceDefaultId() 33 | + "' GROUP BY tenant_id LIMIT " + context.getPageSize() + " offset " + context.getStartRow(); 34 | return new MapperResult(sql, Collections.emptyList()); 35 | } 36 | 37 | @Override 38 | public MapperResult getGroupIdList(MapperContext context) { 39 | String sql = "SELECT group_id FROM config_info WHERE tenant_id ='" + NamespaceUtil.getNamespaceDefaultId() 40 | + "' GROUP BY group_id LIMIT " + context.getPageSize() + " offset " + context.getStartRow(); 41 | return new MapperResult(sql, Collections.emptyList()); 42 | } 43 | 44 | @Override 45 | public MapperResult findAllConfigKey(MapperContext context) { 46 | String sql = " SELECT data_id,group_id,app_name FROM ( " 47 | + " SELECT id FROM config_info WHERE tenant_id LIKE ? ORDER BY id LIMIT " + context.getPageSize() 48 | + " offset " + context.getStartRow() + " )" + " g, config_info t WHERE g.id = t.id "; 49 | return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.TENANT_ID))); 50 | } 51 | 52 | @Override 53 | public MapperResult findAllConfigInfoBaseFetchRows(MapperContext context) { 54 | String sql = "SELECT t.id,data_id,group_id,content,md5" 55 | + " FROM ( SELECT id FROM config_info ORDER BY id LIMIT " + context.getPageSize() + " offset " 56 | + context.getStartRow() + " )" + " g, config_info t WHERE g.id = t.id "; 57 | return new MapperResult(sql, Collections.emptyList()); 58 | } 59 | 60 | @Override 61 | public MapperResult findAllConfigInfoFragment(MapperContext context) { 62 | String contextParameter = context.getContextParameter(ContextConstant.NEED_CONTENT); 63 | boolean needContent = contextParameter != null && Boolean.parseBoolean(contextParameter); 64 | String sql = "SELECT id,data_id,group_id,tenant_id,app_name," + (needContent ? "content," : "") 65 | + "md5,gmt_modified,type,encrypted_data_key FROM config_info WHERE id > ? ORDER BY id ASC LIMIT " 66 | + context.getPageSize() + " offset " + context.getStartRow(); 67 | return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.ID))); 68 | } 69 | 70 | @Override 71 | public MapperResult findChangeConfigFetchRows(MapperContext context) { 72 | final String tenant = (String) context.getWhereParameter(FieldConstant.TENANT_ID); 73 | final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); 74 | final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); 75 | final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME); 76 | final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; 77 | final Timestamp startTime = (Timestamp) context.getWhereParameter(FieldConstant.START_TIME); 78 | final Timestamp endTime = (Timestamp) context.getWhereParameter(FieldConstant.END_TIME); 79 | 80 | List paramList = new ArrayList<>(); 81 | 82 | final String sqlFetchRows = "SELECT id,data_id,group_id,tenant_id,app_name,type,md5,gmt_modified FROM config_info WHERE "; 83 | String where = " 1=1 "; 84 | if (!StringUtils.isBlank(dataId)) { 85 | where += " AND data_id LIKE ? "; 86 | paramList.add(dataId); 87 | } 88 | if (!StringUtils.isBlank(group)) { 89 | where += " AND group_id LIKE ? "; 90 | paramList.add(group); 91 | } 92 | 93 | if (!StringUtils.isBlank(tenantTmp)) { 94 | where += " AND tenant_id = ? "; 95 | paramList.add(tenantTmp); 96 | } 97 | 98 | if (!StringUtils.isBlank(appName)) { 99 | where += " AND app_name = ? "; 100 | paramList.add(appName); 101 | } 102 | if (startTime != null) { 103 | where += " AND gmt_modified >=? "; 104 | paramList.add(startTime); 105 | } 106 | if (endTime != null) { 107 | where += " AND gmt_modified <=? "; 108 | paramList.add(endTime); 109 | } 110 | return new MapperResult( 111 | sqlFetchRows + where + " AND id > " + context.getWhereParameter(FieldConstant.LAST_MAX_ID) 112 | + " ORDER BY id ASC" + " LIMIT " + context.getPageSize(), 113 | paramList); 114 | } 115 | 116 | @Override 117 | public MapperResult listGroupKeyMd5ByPageFetchRows(MapperContext context) { 118 | String sql = "SELECT t.id,data_id,group_id,tenant_id,app_name,md5,type,gmt_modified,encrypted_data_key FROM " 119 | + "( SELECT id FROM config_info ORDER BY id LIMIT " + context.getPageSize() + " offset " 120 | + context.getStartRow() + " ) g, config_info t WHERE g.id = t.id"; 121 | return new MapperResult(sql, Collections.emptyList()); 122 | } 123 | 124 | @Override 125 | public MapperResult findConfigInfoBaseLikeFetchRows(MapperContext context) { 126 | final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); 127 | final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); 128 | final String content = (String) context.getWhereParameter(FieldConstant.CONTENT); 129 | 130 | final String sqlFetchRows = "SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE "; 131 | String where = " 1=1 AND tenant_id='" + NamespaceUtil.getNamespaceDefaultId() + "' "; 132 | 133 | List paramList = new ArrayList<>(); 134 | 135 | if (!StringUtils.isBlank(dataId)) { 136 | where += " AND data_id LIKE ? "; 137 | paramList.add(dataId); 138 | } 139 | if (!StringUtils.isBlank(group)) { 140 | where += " AND group_id LIKE "; 141 | paramList.add(group); 142 | } 143 | if (!StringUtils.isBlank(content)) { 144 | where += " AND content LIKE ? "; 145 | paramList.add(content); 146 | } 147 | return new MapperResult( 148 | sqlFetchRows + where + " LIMIT " + context.getPageSize() + " offset " + context.getStartRow(), 149 | paramList); 150 | } 151 | 152 | @Override 153 | public MapperResult findConfigInfo4PageFetchRows(MapperContext context) { 154 | final String tenant = (String) context.getWhereParameter(FieldConstant.TENANT_ID); 155 | final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); 156 | final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); 157 | final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME); 158 | final String content = (String) context.getWhereParameter(FieldConstant.CONTENT); 159 | 160 | List paramList = new ArrayList<>(); 161 | 162 | final String sql = "SELECT id,data_id,group_id,tenant_id,app_name,content,type,encrypted_data_key FROM config_info"; 163 | StringBuilder where = new StringBuilder(" WHERE "); 164 | where.append(" tenant_id=? "); 165 | paramList.add(tenant); 166 | if (StringUtils.isNotBlank(dataId)) { 167 | where.append(" AND data_id=? "); 168 | paramList.add(dataId); 169 | } 170 | if (StringUtils.isNotBlank(group)) { 171 | where.append(" AND group_id=? "); 172 | paramList.add(group); 173 | } 174 | if (StringUtils.isNotBlank(appName)) { 175 | where.append(" AND app_name=? "); 176 | paramList.add(appName); 177 | } 178 | if (!StringUtils.isBlank(content)) { 179 | where.append(" AND content LIKE ? "); 180 | paramList.add(content); 181 | } 182 | return new MapperResult(sql + where + " LIMIT " + context.getPageSize() + " offset " + context.getStartRow(), 183 | paramList); 184 | } 185 | 186 | @Override 187 | public MapperResult findConfigInfoBaseByGroupFetchRows(MapperContext context) { 188 | String sql = "SELECT id,data_id,group_id,content FROM config_info WHERE group_id=? AND tenant_id=?" + " LIMIT " 189 | + context.getPageSize() + " offset " + context.getStartRow(); 190 | return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.GROUP_ID), 191 | context.getWhereParameter(FieldConstant.TENANT_ID))); 192 | } 193 | 194 | @Override 195 | public MapperResult findConfigInfoLike4PageFetchRows(MapperContext context) { 196 | final String tenant = (String) context.getWhereParameter(FieldConstant.TENANT_ID); 197 | final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); 198 | final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); 199 | final String appName = (String) context.getWhereParameter(FieldConstant.APP_NAME); 200 | final String content = (String) context.getWhereParameter(FieldConstant.CONTENT); 201 | 202 | List paramList = new ArrayList<>(); 203 | 204 | final String sqlFetchRows = "SELECT id,data_id,group_id,tenant_id,app_name,content,encrypted_data_key FROM config_info"; 205 | StringBuilder where = new StringBuilder(" WHERE "); 206 | where.append(" tenant_id LIKE ? "); 207 | paramList.add(tenant); 208 | 209 | if (!StringUtils.isBlank(dataId)) { 210 | where.append(" AND data_id LIKE ? "); 211 | paramList.add(dataId); 212 | 213 | } 214 | if (!StringUtils.isBlank(group)) { 215 | where.append(" AND group_id LIKE ? "); 216 | paramList.add(group); 217 | } 218 | if (!StringUtils.isBlank(appName)) { 219 | where.append(" AND app_name = ? "); 220 | paramList.add(appName); 221 | } 222 | if (!StringUtils.isBlank(content)) { 223 | where.append(" AND content LIKE ? "); 224 | paramList.add(content); 225 | } 226 | return new MapperResult( 227 | sqlFetchRows + where + " LIMIT " + context.getPageSize() + " offset " + context.getStartRow(), 228 | paramList); 229 | } 230 | 231 | @Override 232 | public MapperResult findAllConfigInfoFetchRows(MapperContext context) { 233 | String sql = "SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5 " 234 | + " FROM ( SELECT id FROM config_info WHERE tenant_id LIKE ? ORDER BY id LIMIT ? offset ? )" 235 | + " g, config_info t WHERE g.id = t.id "; 236 | return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.TENANT_ID), 237 | context.getPageSize(), context.getStartRow())); 238 | } 239 | 240 | @Override 241 | public String getDataSource() { 242 | return DataSourceConstant.POSTGRESQL; 243 | } 244 | 245 | @Override 246 | public String insert(List columns) { 247 | return super.insert(columns); 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.5.3 9 | 10 | 11 | com.pig4cloud.plugin 12 | nacos-datasource-plugin-postgresql 13 | 0.0.7 14 | nacos-datasource-plugin-pg 15 | nacos-datasource-plugin-pg 16 | https://pig4cloud.com 17 | 18 | 19 | The ApacheSoftware License, Version 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0.txt 21 | repo 22 | 23 | 24 | 25 | 26 | 3.0.2 27 | 0.0.32 28 | 3.8.1 29 | 1.8 30 | 1.8 31 | 2.2.5 32 | 1.21 33 | 34 | 35 | 36 | 37 | lengleng 38 | wangiegie@gmail.com 39 | 40 | 41 | 42 | 43 | master 44 | https://gitee.wang/lengleng/pig 45 | https://pigx.top 46 | https://gitee.wang/lengleng/pig.git 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter 54 | 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | true 60 | 61 | 62 | 63 | com.alibaba.nacos 64 | nacos-datasource-plugin 65 | ${nacos.version} 66 | 67 | 68 | 69 | com.alibaba.nacos 70 | nacos-common 71 | ${nacos.version} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | io.spring.javaformat 81 | spring-javaformat-maven-plugin 82 | ${spring.checkstyle.plugin} 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-compiler-plugin 88 | ${maven.compiler.version} 89 | 90 | ${maven.compiler.target} 91 | ${maven.compiler.source} 92 | UTF-8 93 | true 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | snapshot 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-source-plugin 108 | 2.2.1 109 | 110 | 111 | package 112 | 113 | jar-no-fork 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-javadoc-plugin 122 | 3.3.2 123 | 124 | private 125 | true 126 | UTF-8 127 | UTF-8 128 | UTF-8 129 | none 130 | false 131 | 132 | 133 | 134 | package 135 | 136 | jar 137 | 138 | 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-gpg-plugin 145 | 3.0.1 146 | 147 | 148 | sign-artifacts 149 | verify 150 | 151 | sign 152 | 153 | 154 | 155 | 156 | 157 | --pinentry-mode 158 | loopback 159 | 160 | 161 | 162 | 163 | org.sonatype.plugins 164 | nexus-staging-maven-plugin 165 | 1.6.13 166 | true 167 | 168 | sonatype 169 | https://oss.sonatype.org/ 170 | true 171 | 172 | 173 | 174 | 175 | 176 | 177 | sonatype 178 | 179 | https://oss.sonatype.org/content/repositories/snapshots/ 180 | 181 | 182 | 183 | 184 | 185 | release 186 | 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-source-plugin 192 | 2.2.1 193 | 194 | 195 | package 196 | 197 | jar-no-fork 198 | 199 | 200 | 201 | 202 | 203 | 204 | org.apache.maven.plugins 205 | maven-javadoc-plugin 206 | 3.3.2 207 | 208 | private 209 | true 210 | UTF-8 211 | UTF-8 212 | UTF-8 213 | none 214 | false 215 | 216 | 217 | 218 | package 219 | 220 | jar 221 | 222 | 223 | 224 | 225 | 226 | 227 | org.apache.maven.plugins 228 | maven-gpg-plugin 229 | 3.0.1 230 | 231 | 232 | sign-artifacts 233 | verify 234 | 235 | sign 236 | 237 | 238 | 239 | 240 | 241 | --pinentry-mode 242 | loopback 243 | 244 | 245 | 246 | 247 | org.sonatype.plugins 248 | nexus-staging-maven-plugin 249 | 1.6.13 250 | true 251 | 252 | sonatype 253 | https://oss.sonatype.org/ 254 | true 255 | 256 | 257 | 258 | 259 | 260 | 261 | sonatype 262 | 263 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | --------------------------------------------------------------------------------