From 404163f3919d09dcf90d8c25772f82403daae9d1 Mon Sep 17 00:00:00 2001 From: lexluo Date: Sat, 8 Jul 2023 15:00:03 +0800 Subject: [PATCH] [improvement][project] global refactor , code format , support llm , support fuzzy detect ,support query filter and so on. --- assembly/bin/build-standalone.sh | 23 + assembly/bin/start-all.sh | 2 +- assembly/bin/start-standalone.sh | 30 + auth/api/pom.xml | 4 +- .../api/authorization}/pojo/AuthGroup.java | 2 +- .../api/authorization}/pojo/AuthRule.java | 2 +- .../request/QueryAuthResReq.java | 5 + .../authorization/service/AuthService.java | 10 +- auth/authentication/pom.xml | 4 +- .../domain/dataobject/UserDOExample.java | 100 +- .../DefaultAuthenticationInterceptor.java | 3 - .../interceptor/InterceptorFactory.java | 2 +- auth/authorization/pom.xml | 4 +- .../application/AuthApplicationService.java | 118 - .../application/AuthServiceImpl.java | 134 +- .../authorization/rest/AuthController.java | 20 +- auth/pom.xml | 4 +- .../{service => component}/SchemaMapper.java | 2 +- .../{service => component}/SemanticLayer.java | 5 +- .../SemanticParser.java | 4 +- .../chat/api/component/SemanticQuery.java | 18 + .../supersonic/chat/api/pojo/QueryFilter.java | 16 + ...aElementCount.java => QueryMatchInfo.java} | 4 +- .../chat/api/pojo/SchemaElementMatch.java | 9 +- .../chat/api/pojo/SchemaMapInfo.java | 8 +- .../chat/api/pojo/SemanticParseInfo.java | 4 + .../chat/api/request/QueryContextReq.java | 10 +- .../chat/api/response/QueryResultResp.java | 2 +- .../chat/api/service/SemanticQuery.java | 29 - chat/core/pom.xml | 14 +- .../chat/application/ChatServiceImpl.java | 35 +- .../chat/application/ConfigServiceImpl.java | 104 +- .../chat/application/DomainEntityService.java | 42 +- .../chat/application/QueryServiceImpl.java | 81 +- .../application/RecommendServiceImpl.java | 8 +- .../chat/application/SearchServiceImpl.java | 25 +- .../knowledge/ApplicationStartedInit.java | 5 +- .../knowledge/DictApplicationService.java | 15 +- .../application/knowledge/NatureHelper.java | 7 +- .../knowledge/WordNatureService.java | 19 +- .../mapper/DatabaseSchemaMapper.java | 144 + .../application/mapper/HanlpSchemaMapper.java | 53 +- .../chat/application/mapper/MapperHelper.java | 83 + .../application/mapper/MatchStrategy.java | 31 +- .../application/mapper/QueryFilterMapper.java | 50 + .../mapper/QueryMatchStrategy.java | 98 +- .../mapper/SearchMatchStrategy.java | 9 +- .../parser/AggregateSemanticParser.java | 104 +- .../parser/{resolver => }/DomainResolver.java | 8 +- .../parser/DomainSemanticParser.java | 336 +- ...lver.java => HeuristicDomainResolver.java} | 226 +- .../application/parser/LLMSemanticParser.java | 148 + .../application/parser/ListFilterParser.java | 130 - .../parser/TimeSemanticParser.java | 78 +- .../resolver/AggregateTypeResolver.java | 14 - .../resolver/DomainSemanticQueryResolver.java | 45 - .../resolver/HeuristicDomainResolver.java | 50 - .../resolver/RegexAggregateTypeResolver.java | 55 - .../resolver/SemanticQueryResolver.java | 14 - .../application/query/BaseSemanticQuery.java | 123 - .../chat/application/query/EntityDetail.java | 46 +- .../application/query/EntityListFilter.java | 145 +- .../application/query/EntityListTopN.java | 44 +- .../application/query/EntityMetricFilter.java | 42 +- .../query/EntitySemanticQuery.java | 13 + .../query/HeuristicQuerySelector.java | 63 + .../application/query/LLMSemanticQuery.java | 71 + .../chat/application/query/MetricCompare.java | 64 +- .../chat/application/query/MetricDomain.java | 43 +- .../chat/application/query/MetricFilter.java | 49 +- .../chat/application/query/MetricGroupBy.java | 46 +- .../chat/application/query/MetricOrderBy.java | 43 +- .../query/MetricSemanticQuery.java | 13 + .../application/query/QueryMatchOption.java | 43 + .../chat/application/query/QueryMatcher.java | 113 + .../query/QueryModeElementOption.java | 43 - .../application/query/QueryModeOption.java | 307 -- .../chat/application/query/QuerySelector.java | 13 + .../application/query/RuleSemanticQuery.java | 87 + .../query/RuleSemanticQueryManager.java | 39 + .../query/SemanticQueryFactory.java | 46 - .../chat/domain/config/LLMConfig.java | 23 + .../domain/dataobject/ChatQueryDOExample.java | 90 +- .../chat/domain/dataobject/QueryDO.java | 6 +- .../chat/domain/pojo/chat/LLMReq.java | 11 + .../chat/domain/pojo/chat/LLMResp.java | 20 + .../chat/domain/pojo/chat/LLMSchema.java | 13 + .../domain/pojo/chat/PageQueryInfoReq.java | 12 +- .../domain/pojo/config/KnowledgeInfo.java | 3 + .../domain/repository/ChatRepository.java | 2 + .../chat/domain/service/ChatService.java | 3 + .../chat/domain/service/ConfigService.java | 2 + .../chat/domain/service/QueryService.java | 6 +- .../chat/domain/utils/ChatConfigUtils.java | 7 +- .../chat/domain/utils/ComponentFactory.java | 67 + .../chat/domain/utils/ContextHelper.java | 88 +- .../utils/DefaultMetricUtils.java} | 214 +- .../utils/DefaultSemanticInternalUtils.java | 8 +- .../chat/domain/utils/DictMetaUtils.java | 13 +- .../chat/domain/utils/DictQueryUtils.java | 12 +- .../chat/domain/utils/DslToSemanticInfo.java | 89 + .../domain/utils/SchemaInfoConverter.java | 40 + .../utils/SemanticSatisfactionChecker.java | 93 + .../infrastructure/mapper/ChatMapper.java | 2 + .../repository/ChatConfigRepositoryImpl.java | 4 +- .../repository/ChatContextRepositoryImpl.java | 10 + .../repository/ChatQueryRepositoryImpl.java | 12 +- .../repository/ChatRepositoryImpl.java | 5 + .../semantic/DefaultSemanticConfig.java | 35 + .../semantic/LocalSemanticLayerImpl.java | 181 + ...Impl.java => RemoteSemanticLayerImpl.java} | 103 +- .../chat/rest/ChatConfigController.java | 31 +- .../supersonic/chat/rest/ChatController.java | 20 +- .../chat/rest/ChatQueryController.java | 5 +- .../src/main/resources/mapper/ChatMapper.xml | 9 +- .../resources/mapper/ChatQueryDOMapper.java | 67 - .../parser/TimeSemanticParserTest.java | 23 +- .../AggregateSemanticParserTest.java | 38 + .../RegexAggregateTypeEnumResolverTest.java | 38 - .../domain/utils/DslToSemanticInfoTest.java | 28 + .../supersonic/chat/test/ChatBizLauncher.java | 6 +- .../chat/test/context/ContextTest.java | 7 +- .../test/context/MockBeansConfiguration.java | 97 +- .../test/context/QueryServiceImplTest.java | 177 + chat/core/src/test/resources/hanlp.properties | 2 +- .../collection/trie/bintrie/BaseNode.java | 65 +- .../hanlp/dictionary/CoreDictionary.java | 66 +- .../hankcs/hanlp/seg/WordBasedSegment.java | 8 +- .../application/online/BaseWordNature.java | 7 - .../application/online/DomainWordNature.java | 4 - .../infrastructure/nlp/HanlpHelper.java | 12 +- .../infrastructure/nlp/HdfsFileHelper.java | 1 + .../nlp/MultiCustomDictionary.java | 96 +- .../infrastructure/nlp/Suggester.java | 5 +- common/pom.xml | 6 + .../supersonic/common/constant/Constants.java | 1 + .../tencent/supersonic/common/nlp/ItemDO.java | 23 +- .../common/util/calcite/SqlParseUtils.java | 160 + .../common/util/calcite/SqlParserInfo.java | 17 + .../common/util/context/ThreadContext.java | 5 + docs/images/supersonic_components.png | Bin 224324 -> 370520 bytes launchers/chat/pom.xml | 6 +- .../main/resources/META-INF/spring.factories | 37 +- .../src/main/resources/application-local.yaml | 4 +- .../src/main/resources/db/chat-data-h2.sql | 2 +- launchers/common/pom.xml | 4 +- .../supersonic/config/RestTemplateConfig.java | 16 +- .../supersonic/config/SpringContextUtil.java | 8 +- launchers/pom.xml | 7 +- launchers/semantic/pom.xml | 4 +- .../src/main/resources/application-local.yaml | 2 +- .../main/resources/db/semantic-schema-h2.sql | 3 + .../main/resources/model/s2_time_stat.yaml | 22 - .../src/main/resources/model/s2_uv.yaml | 26 - launchers/standalone/pom.xml | 126 + launchers/standalone/src/main/bin/env.sh | 2 + launchers/standalone/src/main/bin/run.sh | 38 + launchers/standalone/src/main/bin/service.sh | 55 + .../supersonic/StandaloneLauncher.java | 20 + .../tencent/supersonic/db/MybatisConfig.java | 31 + .../main/resources/META-INF/spring.factories | 23 + .../src/main/resources/application-local.yaml | 28 + .../src/main/resources/application.yaml | 7 + .../src/main/resources/data/README.url | 2 + .../dictionary/CoreNatureDictionary.mini.txt | 3 + .../CoreNatureDictionary.ngram.mini.txt | 4 + .../data/dictionary/custom/DimValue_1_1.txt | 5 + .../data/dictionary/custom/DimValue_1_2.txt | 7 + .../data/dictionary/custom/DimValue_1_3.txt | 6 + .../data/dictionary/other/CharTable.txt | 4890 +++++++++++++++++ .../data/dictionary/other/TagPKU98.csv | 44 + .../src/main/resources/data/version.txt | 1 + .../src/main/resources/db/data-h2.sql | 1070 ++++ .../src/main/resources/db/schema-h2.sql | 341 ++ .../src/main/resources/hanlp.properties | 2 + .../src/main/resources/logback-spring.xml | 93 + .../tencent/supersonic/db/MybatisConfig.java | 31 + .../supersonic/test/QueryIntegrationTest.java | 371 ++ .../test/resources/META-INF/spring.factories | 23 + .../src/test/resources/application-local.yaml | 28 + .../src/test/resources/application.yaml | 7 + .../src/test/resources/data/README.url | 2 + .../dictionary/CoreNatureDictionary.mini.txt | 3 + .../CoreNatureDictionary.ngram.mini.txt | 4 + .../data/dictionary/custom/DimValue_1_1.txt | 5 + .../data/dictionary/custom/DimValue_1_2.txt | 7 + .../data/dictionary/custom/DimValue_1_3.txt | 6 + .../data/dictionary/other/CharTable.txt | 4890 +++++++++++++++++ .../data/dictionary/other/TagPKU98.csv | 44 + .../src/test/resources/data/version.txt | 1 + .../src/test/resources/db/data-h2.sql | 1070 ++++ .../src/test/resources/db/schema-h2.sql | 341 ++ .../src/test/resources/hanlp.properties | 2 + .../src/test/resources/logback-spring.xml | 93 + pom.xml | 2 +- .../api/core/enums/MetricTypeEnum.java | 12 +- .../semantic/api/core/enums/OperatorEnum.java | 4 +- .../api/core/enums/QueryTypeBackEnum.java | 16 +- .../api/core/enums/QueryTypeEnum.java | 8 +- .../api/core/enums/TimeDimensionEnum.java | 8 +- .../api/core/pojo/MetricTypeParams.java | 3 +- .../semantic/api/core/pojo/QueryColumn.java | 8 +- .../api/core/request/DatabaseReq.java | 6 +- .../api/core/request/DatasourceReq.java | 2 +- .../api/core/request/DimensionReq.java | 5 + .../api/core/request/MetricBaseReq.java | 2 + .../semantic/api/core/request/MetricReq.java | 19 + .../api/core/response/DimensionResp.java | 5 + .../api/core/response/DomainResp.java | 4 + .../api/core/response/MetricResp.java | 6 +- .../api/query/enums/FilterOperatorEnum.java | 10 +- .../semantic/api/query/pojo/Criterion.java | 42 +- .../semantic/api/query/pojo/Filter.java | 13 +- .../core/application/DatabaseServiceImpl.java | 37 +- .../application/DatasourceServiceImpl.java | 36 +- .../application/DimensionServiceImpl.java | 126 +- .../core/application/DomainServiceImpl.java | 49 +- .../core/application/MetricServiceImpl.java | 58 +- .../semantic/core/domain/DatabaseService.java | 5 + .../core/domain/DimensionService.java | 2 + .../semantic/core/domain/MetricService.java | 2 + .../engineadapter/ClickHouseAdaptor.java | 18 + .../adaptor/engineadapter/EngineAdaptor.java | 5 + .../adaptor/engineadapter/MysqlAdaptor.java | 19 +- .../domain/dataobject/DatabaseDOExample.java | 100 +- .../dataobject/DatasourceDOExample.java | 100 +- .../dataobject/DatasourceRelaDOExample.java | 100 +- .../dataobject/DictionaryDOExample.java | 100 +- .../dataobject/DictionaryTaskDOExample.java | 100 +- .../core/domain/dataobject/DimensionDO.java | 42 + .../domain/dataobject/DimensionDOExample.java | 245 +- .../domain/dataobject/DomainDOExample.java | 100 +- .../dataobject/DomainExtendDOExample.java | 100 +- .../core/domain/dataobject/MetricDO.java | 19 + .../domain/dataobject/MetricDOExample.java | 180 +- .../domain/dataobject/ViewInfoDOExample.java | 100 +- .../domain/manager/DatasourceYamlManager.java | 44 - .../domain/manager/DimensionYamlManager.java | 38 - .../domain/manager/MetricYamlManager.java | 36 - .../core/domain/manager/YamlManager.java | 66 - .../semantic/core/domain/pojo/Dimension.java | 7 +- .../core/domain/pojo/JdbcDataSource.java | 76 +- .../semantic/core/domain/pojo/Metric.java | 2 + .../repository/DimensionRepository.java | 2 + .../domain/repository/MetricRepository.java | 2 + .../domain/utils/DatasourceConverter.java | 16 +- .../core/domain/utils/DimensionConverter.java | 37 +- .../core/domain/utils/DomainConvert.java | 27 +- .../domain/utils/JdbcDataSourceUtils.java | 107 +- .../core/domain/utils/MetricConverter.java | 4 +- .../semantic/core/domain/utils/SqlUtils.java | 22 +- .../domain/utils/SysTimeDimensionBuilder.java | 2 +- .../mapper/DimensionDOMapper.java | 4 +- .../infrastructure/mapper/DomainDOMapper.java | 2 +- .../repository/DatasourceRepositoryImpl.java | 2 +- .../repository/DimensionRepositoryImpl.java | 11 +- .../repository/DomainRepositoryImpl.java | 3 + .../repository/MetricRepositoryImpl.java | 6 + .../core/rest/DatabaseController.java | 19 + .../core/rest/ViewInfoController.java | 2 +- .../resources/mapper/DimensionDOMapper.xml | 669 +-- .../main/resources/mapper/MetricDOMapper.xml | 630 ++- .../mapper/custom/DateInfoMapper.xml | 11 +- .../query/application/ParserServiceImpl.java | 5 +- .../query/application/QueryServiceImpl.java | 39 +- .../query/application/SchemaServiceImpl.java | 10 +- .../parser/SemanticSchemaManagerImpl.java | 101 +- .../semantic/query/domain/QueryService.java | 2 +- .../parser/convertor/Configuration.java | 11 +- .../domain/parser/convertor/sql/Renderer.java | 31 +- .../convertor/sql/node/AggFunctionNode.java | 14 +- .../convertor/sql/node/SemanticNode.java | 9 +- .../convertor/sql/render/JoinRender.java | 2 +- .../convertor/sql/render/SourceRender.java | 59 +- .../query/domain/parser/dsl/Dimension.java | 13 +- .../query/domain/parser/dsl/Metric.java | 10 +- .../domain/parser/dsl/SemanticModel.java | 1 + .../domain/parser/schema/DataSourceTable.java | 10 +- .../domain/parser/schema/SemanticSchema.java | 21 +- .../parser/schema/SemanticSqlDialect.java | 52 +- .../query/domain/pojo/ParserSvrResponse.java | 16 +- .../query/domain/utils/DataPermissionAOP.java | 38 +- .../query/domain/utils/DateUtils.java | 39 +- .../domain/utils/ParserCommandConverter.java | 8 +- .../query/domain/utils/QueryReqConverter.java | 72 + .../query/domain/utils/QueryStructUtils.java | 15 +- .../query/domain/utils/QueryUtils.java | 54 +- .../query/domain/utils/SqlGenerateUtils.java | 13 +- .../query/domain/utils/SqlParserUtils.java | 2 +- .../query/domain/utils/StatUtils.java | 3 +- .../semantic/query/rest/QueryController.java | 31 +- .../semantic/query/rest/SchemaController.java | 14 +- .../parser/SemanticParserServiceTest.java | 121 +- .../chat-sdk/rollup/rollup.umd.config.mjs | 2 +- .../src/components/ChatItem/style.less | 2 +- .../supersonic-fe/coding_build/build_prd.sh | 2 +- .../supersonic-fe/config/envConfig.ts | 4 +- webapp/packages/supersonic-fe/src/app.tsx | 31 + .../src/pages/SemanticModel/ChatSetting.tsx | 123 +- .../components/DataSourceBasicForm.tsx | 89 +- .../components/DataSourceCreateForm.tsx | 115 +- .../Datasource/components/SqlDetail.tsx | 60 +- .../Datasource/components/SqlSide.tsx | 2 - .../pages/SemanticModel/Datasource/data.d.ts | 16 + .../pages/SemanticModel/Datasource/service.ts | 12 - .../pages/SemanticModel/ProjectManager.tsx | 158 +- .../XflowJsonSchemaFormDrawerForm.tsx | 54 +- .../SemanticGraph/components/ToolTips.tsx | 2 +- .../SemanticModel/SemanticGraph/index.tsx | 3 +- .../SemanticModel/SemanticGraph/service.ts | 12 - .../components/ClassDataSourceTable.tsx | 71 +- .../components/ClassDataSourceTypeModal.tsx | 68 + .../components/ClassDimensionTable.tsx | 8 +- .../components/ClassMetricTable.tsx | 8 +- .../Database/DatabaseCreateForm.tsx | 35 +- .../components/Database/DatabaseSection.tsx | 14 +- .../components/DimensionInfoModal.tsx | 4 + .../Entity/DimensionMetricVisibleModal.tsx | 26 +- .../components/Entity/EntityCreateForm.tsx | 63 +- .../components/Entity/EntitySection.tsx | 39 +- .../components/Entity/MetricSettingForm.tsx | 10 +- .../SemanticModel/components/Entity/utils.ts | 28 + .../components/MetricInfoCreateForm.tsx | 3 + .../components/Permission/PermissionTable.tsx | 4 +- .../SemanticModel/components/ProjectList.tsx | 54 +- .../pages/SemanticModel/components/style.less | 137 +- .../src/pages/SemanticModel/data.d.ts | 82 + .../src/pages/SemanticModel/model.ts | 65 +- .../src/pages/SemanticModel/service.ts | 29 + 329 files changed, 21050 insertions(+), 5036 deletions(-) create mode 100755 assembly/bin/build-standalone.sh create mode 100755 assembly/bin/start-standalone.sh rename auth/{authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain => api/src/main/java/com/tencent/supersonic/auth/api/authorization}/pojo/AuthGroup.java (89%) rename auth/{authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain => api/src/main/java/com/tencent/supersonic/auth/api/authorization}/pojo/AuthRule.java (89%) delete mode 100644 auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthApplicationService.java rename chat/api/src/main/java/com/tencent/supersonic/chat/api/{service => component}/SchemaMapper.java (89%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/{service => component}/SemanticLayer.java (86%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/{service => component}/SemanticParser.java (78%) create mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java create mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryFilter.java rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/{SchemaElementCount.java => QueryMatchInfo.java} (61%) delete mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/DatabaseSchemaMapper.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MapperHelper.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryFilterMapper.java rename chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/{resolver => }/DomainResolver.java (59%) rename chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/{resolver/BaseDomainResolver.java => HeuristicDomainResolver.java} (57%) create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/LLMSemanticParser.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/ListFilterParser.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/AggregateTypeResolver.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainSemanticQueryResolver.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/HeuristicDomainResolver.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/RegexAggregateTypeResolver.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/SemanticQueryResolver.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/BaseSemanticQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntitySemanticQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/HeuristicQuerySelector.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/LLMSemanticQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricSemanticQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatchOption.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatcher.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeElementOption.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeOption.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QuerySelector.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQueryManager.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/application/query/SemanticQueryFactory.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/config/LLMConfig.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMReq.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMResp.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMSchema.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ComponentFactory.java rename chat/core/src/main/java/com/tencent/supersonic/chat/{application/parser/DefaultMetricSemanticParser.java => domain/utils/DefaultMetricUtils.java} (54%) create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfo.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SemanticSatisfactionChecker.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticConfig.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/LocalSemanticLayerImpl.java rename chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/{DefaultSemanticLayerImpl.java => RemoteSemanticLayerImpl.java} (72%) delete mode 100644 chat/core/src/main/resources/mapper/ChatQueryDOMapper.java create mode 100644 chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/AggregateSemanticParserTest.java delete mode 100644 chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/RegexAggregateTypeEnumResolverTest.java create mode 100644 chat/core/src/test/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfoTest.java create mode 100644 chat/core/src/test/java/com/tencent/supersonic/chat/test/context/QueryServiceImplTest.java create mode 100644 common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParseUtils.java create mode 100644 common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParserInfo.java delete mode 100644 launchers/semantic/src/main/resources/model/s2_time_stat.yaml delete mode 100644 launchers/semantic/src/main/resources/model/s2_uv.yaml create mode 100644 launchers/standalone/pom.xml create mode 100644 launchers/standalone/src/main/bin/env.sh create mode 100755 launchers/standalone/src/main/bin/run.sh create mode 100755 launchers/standalone/src/main/bin/service.sh create mode 100644 launchers/standalone/src/main/java/com/tencent/supersonic/StandaloneLauncher.java create mode 100644 launchers/standalone/src/main/java/com/tencent/supersonic/db/MybatisConfig.java create mode 100644 launchers/standalone/src/main/resources/META-INF/spring.factories create mode 100644 launchers/standalone/src/main/resources/application-local.yaml create mode 100644 launchers/standalone/src/main/resources/application.yaml create mode 100644 launchers/standalone/src/main/resources/data/README.url create mode 100644 launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.mini.txt create mode 100644 launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt create mode 100644 launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_1.txt create mode 100644 launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_2.txt create mode 100644 launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_3.txt create mode 100644 launchers/standalone/src/main/resources/data/dictionary/other/CharTable.txt create mode 100644 launchers/standalone/src/main/resources/data/dictionary/other/TagPKU98.csv create mode 100644 launchers/standalone/src/main/resources/data/version.txt create mode 100644 launchers/standalone/src/main/resources/db/data-h2.sql create mode 100644 launchers/standalone/src/main/resources/db/schema-h2.sql create mode 100644 launchers/standalone/src/main/resources/hanlp.properties create mode 100644 launchers/standalone/src/main/resources/logback-spring.xml create mode 100644 launchers/standalone/src/test/java/com/tencent/supersonic/db/MybatisConfig.java create mode 100644 launchers/standalone/src/test/java/com/tencent/supersonic/test/QueryIntegrationTest.java create mode 100644 launchers/standalone/src/test/resources/META-INF/spring.factories create mode 100644 launchers/standalone/src/test/resources/application-local.yaml create mode 100644 launchers/standalone/src/test/resources/application.yaml create mode 100644 launchers/standalone/src/test/resources/data/README.url create mode 100644 launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.mini.txt create mode 100644 launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt create mode 100644 launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_1.txt create mode 100644 launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_2.txt create mode 100644 launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_3.txt create mode 100644 launchers/standalone/src/test/resources/data/dictionary/other/CharTable.txt create mode 100644 launchers/standalone/src/test/resources/data/dictionary/other/TagPKU98.csv create mode 100644 launchers/standalone/src/test/resources/data/version.txt create mode 100644 launchers/standalone/src/test/resources/db/data-h2.sql create mode 100644 launchers/standalone/src/test/resources/db/schema-h2.sql create mode 100644 launchers/standalone/src/test/resources/hanlp.properties create mode 100644 launchers/standalone/src/test/resources/logback-spring.xml delete mode 100644 semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/YamlManager.java create mode 100644 semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryReqConverter.java create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/data.d.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/utils.ts diff --git a/assembly/bin/build-standalone.sh b/assembly/bin/build-standalone.sh new file mode 100755 index 000000000..efa2324e8 --- /dev/null +++ b/assembly/bin/build-standalone.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +sbinDir=$(cd "$(dirname "$0")"; pwd) +baseDir=$(readlink -f $sbinDir/../) +runtimeDir=$baseDir/runtime +buildDir=$baseDir/build + +cd $baseDir + +#1. build semantic chat service +rm -fr ${buildDir}/*.tar.gz +rm -fr dist + +mvn -f $baseDir/../ clean package -DskipTests + +#2. move package to build +cp $baseDir/../launchers/standalone/target/*.tar.gz ${buildDir}/supersonic.tar.gz + +#3. build webapp +chmod +x $baseDir/../webapp/start-fe-prod.sh +cd ../webapp +sh ./start-fe-prod.sh +cp -fr ./supersonic-webapp.tar.gz ${buildDir}/ diff --git a/assembly/bin/start-all.sh b/assembly/bin/start-all.sh index 534c0094b..dc7c5abb5 100755 --- a/assembly/bin/start-all.sh +++ b/assembly/bin/start-all.sh @@ -2,7 +2,7 @@ sbinDir=$(cd "$(dirname "$0")"; pwd) baseDir=$(readlink -f $sbinDir/../) -runtimeDir=$baseDir/runtime +runtimeDir=$baseDir/../runtime buildDir=$baseDir/build cd $baseDir diff --git a/assembly/bin/start-standalone.sh b/assembly/bin/start-standalone.sh new file mode 100755 index 000000000..e0a180eda --- /dev/null +++ b/assembly/bin/start-standalone.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +sbinDir=$(cd "$(dirname "$0")"; pwd) +baseDir=$(readlink -f $sbinDir/../) +runtimeDir=$baseDir/../runtime +buildDir=$baseDir/build + +cd $baseDir + +#1. clear file +mkdir -p ${runtimeDir} +rm -fr ${runtimeDir}/* + +#2. package lib + +tar -zxvf ${buildDir}/supersonic.tar.gz -C ${runtimeDir} + +mv ${runtimeDir}/launchers-standalone-* ${runtimeDir}/supersonic-standalone + +tar -zxvf ${buildDir}/supersonic-webapp.tar.gz -C ${buildDir} + +mkdir -p ${runtimeDir}/supersonic-standalone/webapp + +cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-standalone/webapp + +rm -fr ${buildDir}/supersonic-webapp + +#3. start service +sh ${runtimeDir}/supersonic-standalone/bin/service.sh restart + diff --git a/auth/api/pom.xml b/auth/api/pom.xml index 11a8f9f79..c3f97fb87 100644 --- a/auth/api/pom.xml +++ b/auth/api/pom.xml @@ -1,6 +1,6 @@ - com.tencent.supersonic diff --git a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain/pojo/AuthGroup.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/pojo/AuthGroup.java similarity index 89% rename from auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain/pojo/AuthGroup.java rename to auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/pojo/AuthGroup.java index c6aa819cd..2a9a053c1 100644 --- a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain/pojo/AuthGroup.java +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/pojo/AuthGroup.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authorization.domain.pojo; +package com.tencent.supersonic.auth.api.authorization.pojo; import java.util.List; import lombok.Data; diff --git a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain/pojo/AuthRule.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/pojo/AuthRule.java similarity index 89% rename from auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain/pojo/AuthRule.java rename to auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/pojo/AuthRule.java index 735e73c4a..b5adafa67 100644 --- a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/domain/pojo/AuthRule.java +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/pojo/AuthRule.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authorization.domain.pojo; +package com.tencent.supersonic.auth.api.authorization.pojo; import java.beans.Transient; import java.util.ArrayList; diff --git a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/request/QueryAuthResReq.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/request/QueryAuthResReq.java index dc1f6c6da..bf77f2e0d 100644 --- a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/request/QueryAuthResReq.java +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/request/QueryAuthResReq.java @@ -1,6 +1,7 @@ package com.tencent.supersonic.auth.api.authorization.request; import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes; +import java.util.ArrayList; import java.util.List; import lombok.Data; @@ -11,6 +12,10 @@ import lombok.ToString; public class QueryAuthResReq { private String user; + + private List departmentIds = new ArrayList<>(); + private List resources; + private String domainId; } diff --git a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/service/AuthService.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/service/AuthService.java index b6e1991c0..a3256c86d 100644 --- a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/service/AuthService.java +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authorization/service/AuthService.java @@ -1,10 +1,18 @@ package com.tencent.supersonic.auth.api.authorization.service; +import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup; import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq; import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp; import javax.servlet.http.HttpServletRequest; +import java.util.List; public interface AuthService { - AuthorizedResourceResp queryAuthorizedResources(HttpServletRequest request, QueryAuthResReq req); + List queryAuthGroups(String domainId, Integer groupId); + + void updateAuthGroup(AuthGroup group); + + void removeAuthGroup(AuthGroup group); + + AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request); } diff --git a/auth/authentication/pom.xml b/auth/authentication/pom.xml index 77afc102b..091e1f1de 100644 --- a/auth/authentication/pom.xml +++ b/auth/authentication/pom.xml @@ -1,6 +1,6 @@ - auth diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java index 2272afe73..522407eff 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java @@ -37,13 +37,6 @@ public class UserDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -54,8 +47,8 @@ public class UserDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -65,6 +58,13 @@ public class UserDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -116,13 +116,6 @@ public class UserDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -133,8 +126,8 @@ public class UserDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -144,6 +137,13 @@ public class UserDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_user null */ @@ -561,38 +561,6 @@ public class UserDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -628,5 +596,37 @@ public class UserDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java index fe769c5a3..b245ece49 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java @@ -14,14 +14,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; -@Component @Slf4j public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor { - @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws AccessException { diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java index b9e4203ef..a67d5caec 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java @@ -22,7 +22,7 @@ public class InterceptorFactory implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { for (AuthenticationInterceptor authenticationInterceptor : authenticationInterceptors) { registry.addInterceptor(authenticationInterceptor).addPathPatterns("/**") - .excludePathPatterns("/", "/webapp/**","/error"); + .excludePathPatterns("/", "/webapp/**", "/error"); } } diff --git a/auth/authorization/pom.xml b/auth/authorization/pom.xml index 692a63f43..78907dfd8 100644 --- a/auth/authorization/pom.xml +++ b/auth/authorization/pom.xml @@ -1,6 +1,6 @@ - auth diff --git a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthApplicationService.java b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthApplicationService.java deleted file mode 100644 index 149f0853d..000000000 --- a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthApplicationService.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.tencent.supersonic.auth.authorization.application; - -import com.google.common.base.Strings; -import com.google.gson.Gson; -import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes; -import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp; -import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter; -import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq; -import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp; -import com.tencent.supersonic.auth.authorization.domain.pojo.AuthGroup; -import com.tencent.supersonic.auth.authorization.domain.pojo.AuthRule; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -@Component -@Slf4j -public class AuthApplicationService { - - @Autowired - private JdbcTemplate jdbcTemplate; - - private List load() { - List rows = jdbcTemplate.queryForList("select config from s2_auth_groups", String.class); - Gson g = new Gson(); - return rows.stream().map(row -> g.fromJson(row, AuthGroup.class)).collect(Collectors.toList()); - } - - public List queryAuthGroups(String domainId, Integer groupId) { - return load().stream() - .filter(group -> (Objects.isNull(groupId) || groupId.equals(group.getGroupId())) - && domainId.equals(group.getDomainId())) - .collect(Collectors.toList()); - } - - public void updateAuthGroup(AuthGroup group) { - Gson g = new Gson(); - if (group.getGroupId() == null) { - int nextGroupId = 1; - String sql = "select max(group_id) as group_id from s2_auth_groups"; - Integer obj = jdbcTemplate.queryForObject(sql, Integer.class); - if (obj != null) { - nextGroupId = obj + 1; - } - group.setGroupId(nextGroupId); - jdbcTemplate.update("insert into s2_auth_groups (group_id, config) values (?, ?);", nextGroupId, - g.toJson(group)); - } else { - jdbcTemplate.update("update s2_auth_groups set config = ? where group_id = ?;", g.toJson(group), - group.getGroupId()); - } - } - - public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request) { - List groups = load().stream(). - filter(group -> group.getAuthorizedUsers().contains(req.getUser()) && req.getDomainId() - .equals(group.getDomainId())). - collect(Collectors.toList()); - AuthorizedResourceResp resource = new AuthorizedResourceResp(); - Map> authGroupsByDomainId = groups.stream() - .collect(Collectors.groupingBy(AuthGroup::getDomainId)); - Map> reqAuthRes = req.getResources().stream() - .collect(Collectors.groupingBy(AuthRes::getDomainId)); - - for (String domainId : reqAuthRes.keySet()) { - List reqResourcesList = reqAuthRes.get(domainId); - AuthResGrp rg = new AuthResGrp(); - if (authGroupsByDomainId.containsKey(domainId)) { - List authGroups = authGroupsByDomainId.get(domainId); - for (AuthRes reqRes : reqResourcesList) { - for (AuthGroup authRuleGroup : authGroups) { - List authRules = authRuleGroup.getAuthRules(); - List allAuthItems = new ArrayList<>(); - authRules.stream().forEach(authRule -> allAuthItems.addAll(authRule.resourceNames())); - - if (allAuthItems.contains(reqRes.getName())) { - rg.getGroup().add(reqRes); - } - - } - } - } - if (Objects.nonNull(rg) && !CollectionUtils.isEmpty(rg.getGroup())) { - resource.getResources().add(rg); - } - } - - if (StringUtils.isNotEmpty(req.getDomainId())) { - List authGroups = authGroupsByDomainId.get(req.getDomainId()); - if (!CollectionUtils.isEmpty(authGroups)) { - for (AuthGroup group : authGroups) { - if (group.getDimensionFilters() != null - && group.getDimensionFilters().stream().anyMatch(expr -> !Strings.isNullOrEmpty(expr))) { - DimensionFilter df = new DimensionFilter(); - df.setDescription(group.getDimensionFilterDescription()); - df.setExpressions(group.getDimensionFilters()); - resource.getFilters().add(df); - } - } - } - } - - return resource; - } - - public void removeAuthGroup(AuthGroup group) { - jdbcTemplate.update("delete from s2_auth_groups where group_id = ?", group.getGroupId()); - } -} diff --git a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java index d63608b9d..9e7797b9f 100644 --- a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java +++ b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java @@ -1,24 +1,148 @@ package com.tencent.supersonic.auth.authorization.application; +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes; +import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp; +import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter; import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq; import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp; import com.tencent.supersonic.auth.api.authorization.service.AuthService; import javax.servlet.http.HttpServletRequest; + +import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup; +import com.tencent.supersonic.auth.api.authorization.pojo.AuthRule; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; @Service @Slf4j public class AuthServiceImpl implements AuthService { - private final AuthApplicationService authApplicationService; + private JdbcTemplate jdbcTemplate; - public AuthServiceImpl(AuthApplicationService authApplicationService) { - this.authApplicationService = authApplicationService; + public AuthServiceImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + private List load() { + List rows = jdbcTemplate.queryForList("select config from s2_auth_groups", String.class); + Gson g = new Gson(); + return rows.stream().map(row -> g.fromJson(row, AuthGroup.class)).collect(Collectors.toList()); } @Override - public AuthorizedResourceResp queryAuthorizedResources(HttpServletRequest request, QueryAuthResReq req) { - return authApplicationService.queryAuthorizedResources(req, request); + public List queryAuthGroups(String domainId, Integer groupId) { + return load().stream() + .filter(group -> (Objects.isNull(groupId) || groupId.equals(group.getGroupId())) + && domainId.equals(group.getDomainId())) + .collect(Collectors.toList()); } + + @Override + public void updateAuthGroup(AuthGroup group) { + Gson g = new Gson(); + if (group.getGroupId() == null) { + int nextGroupId = 1; + String sql = "select max(group_id) as group_id from s2_auth_groups"; + Integer obj = jdbcTemplate.queryForObject(sql, Integer.class); + if (obj != null) { + nextGroupId = obj + 1; + } + group.setGroupId(nextGroupId); + jdbcTemplate.update("insert into s2_auth_groups (group_id, config) values (?, ?);", nextGroupId, + g.toJson(group)); + } else { + jdbcTemplate.update("update s2_auth_groups set config = ? where group_id = ?;", g.toJson(group), + group.getGroupId()); + } + } + + @Override + public void removeAuthGroup(AuthGroup group) { + jdbcTemplate.update("delete from s2_auth_groups where group_id = ?", group.getGroupId()); + } + + + @Override + public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request) { + List groups = getAuthGroups(req); + AuthorizedResourceResp resource = new AuthorizedResourceResp(); + Map> authGroupsByDomainId = groups.stream() + .collect(Collectors.groupingBy(AuthGroup::getDomainId)); + Map> reqAuthRes = req.getResources().stream() + .collect(Collectors.groupingBy(AuthRes::getDomainId)); + + for (String domainId : reqAuthRes.keySet()) { + List reqResourcesList = reqAuthRes.get(domainId); + AuthResGrp rg = new AuthResGrp(); + if (authGroupsByDomainId.containsKey(domainId)) { + List authGroups = authGroupsByDomainId.get(domainId); + for (AuthRes reqRes : reqResourcesList) { + for (AuthGroup authRuleGroup : authGroups) { + List authRules = authRuleGroup.getAuthRules(); + List allAuthItems = new ArrayList<>(); + authRules.forEach(authRule -> allAuthItems.addAll(authRule.resourceNames())); + + if (allAuthItems.contains(reqRes.getName())) { + rg.getGroup().add(reqRes); + } + + } + } + } + if (!CollectionUtils.isEmpty(rg.getGroup())) { + resource.getResources().add(rg); + } + } + + if (StringUtils.isNotEmpty(req.getDomainId())) { + List authGroups = authGroupsByDomainId.get(req.getDomainId()); + if (!CollectionUtils.isEmpty(authGroups)) { + for (AuthGroup group : authGroups) { + if (group.getDimensionFilters() != null + && group.getDimensionFilters().stream().anyMatch(expr -> !Strings.isNullOrEmpty(expr))) { + DimensionFilter df = new DimensionFilter(); + df.setDescription(group.getDimensionFilterDescription()); + df.setExpressions(group.getDimensionFilters()); + resource.getFilters().add(df); + } + } + } + } + + return resource; + } + + private List getAuthGroups(QueryAuthResReq req) { + List groups = load().stream(). + filter(group -> { + if (!Objects.equals(group.getDomainId(), req.getDomainId())) { + return false; + } + if (!CollectionUtils.isEmpty(group.getAuthorizedUsers()) && group.getAuthorizedUsers() + .contains(req.getUser())) { + return true; + } + for (String deparmentId : req.getDepartmentIds()) { + if (!CollectionUtils.isEmpty(group.getAuthorizedDepartmentIds()) + && group.getAuthorizedDepartmentIds().contains(deparmentId)) { + return true; + } + } + return false; + }).collect(Collectors.toList()); + log.info("user:{} department:{} authGroups:{}", req.getUser(), req.getDepartmentIds(), groups); + return groups; + } + } diff --git a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/rest/AuthController.java b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/rest/AuthController.java index 378212412..780f5bbc3 100644 --- a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/rest/AuthController.java +++ b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/rest/AuthController.java @@ -2,8 +2,8 @@ package com.tencent.supersonic.auth.authorization.rest; import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq; import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp; -import com.tencent.supersonic.auth.authorization.application.AuthApplicationService; -import com.tencent.supersonic.auth.authorization.domain.pojo.AuthGroup; +import com.tencent.supersonic.auth.api.authorization.service.AuthService; +import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup; import java.util.List; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -19,16 +19,16 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j public class AuthController { - private final AuthApplicationService service; + private final AuthService authService; - public AuthController(AuthApplicationService service) { - this.service = service; + public AuthController(AuthService authService) { + this.authService = authService; } @GetMapping("/queryGroup") public List queryAuthGroup(@RequestParam("domainId") String domainId, @RequestParam(value = "groupId", required = false) Integer groupId) { - return service.queryAuthGroups(domainId, groupId); + return authService.queryAuthGroups(domainId, groupId); } /** @@ -37,12 +37,12 @@ public class AuthController { @PostMapping("/createGroup") public void newAuthGroup(@RequestBody AuthGroup group) { group.setGroupId(null); - service.updateAuthGroup(group); + authService.updateAuthGroup(group); } @PostMapping("/removeGroup") public void removeAuthGroup(@RequestBody AuthGroup group) { - service.removeAuthGroup(group); + authService.removeAuthGroup(group); } /** @@ -55,7 +55,7 @@ public class AuthController { if (group.getGroupId() == null || group.getGroupId() == 0) { throw new RuntimeException("groupId is empty"); } - service.updateAuthGroup(group); + authService.updateAuthGroup(group); } /** @@ -68,6 +68,6 @@ public class AuthController { @PostMapping("/queryAuthorizedRes") public AuthorizedResourceResp queryAuthorizedResources(@RequestBody QueryAuthResReq req, HttpServletRequest request) { - return service.queryAuthorizedResources(req, request); + return authService.queryAuthorizedResources(req, request); } } diff --git a/auth/pom.xml b/auth/pom.xml index 77dcfcab7..f3962ea15 100644 --- a/auth/pom.xml +++ b/auth/pom.xml @@ -1,6 +1,6 @@ - supersonic diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SchemaMapper.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SchemaMapper.java similarity index 89% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SchemaMapper.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SchemaMapper.java index 7c89eca4f..caf15515c 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SchemaMapper.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SchemaMapper.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.api.service; +package com.tencent.supersonic.chat.api.component; import com.tencent.supersonic.chat.api.request.QueryContextReq; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticLayer.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticLayer.java similarity index 86% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticLayer.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticLayer.java index 7f56e689a..e524d8189 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticLayer.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticLayer.java @@ -1,8 +1,9 @@ -package com.tencent.supersonic.chat.api.service; +package com.tencent.supersonic.chat.api.component; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; +import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; import java.util.List; @@ -23,6 +24,8 @@ public interface SemanticLayer { QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user); + QueryResultWithSchemaResp queryBySql(QuerySqlReq querySqlReq, User user); + DomainSchemaResp getDomainSchemaInfo(Long domain); List getDomainSchemaInfo(List ids); diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticParser.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticParser.java similarity index 78% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticParser.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticParser.java index deaf48b4c..218537502 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticParser.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticParser.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.api.service; +package com.tencent.supersonic.chat.api.component; import com.tencent.supersonic.chat.api.pojo.ChatContext; @@ -13,5 +13,5 @@ import com.tencent.supersonic.chat.api.request.QueryContextReq; */ public interface SemanticParser { - boolean parse(QueryContextReq queryContext, ChatContext chatCtx); + void parse(QueryContextReq queryContext, ChatContext chatContext); } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java new file mode 100644 index 000000000..88429f69e --- /dev/null +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java @@ -0,0 +1,18 @@ +package com.tencent.supersonic.chat.api.component; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.api.response.QueryResultResp; + +/** + * This class defines the contract for a semantic query that executes specific type of + * query based on the results of semantic parsing. + */ +public interface SemanticQuery { + + String getQueryMode(); + + QueryResultResp execute(User user); + + SemanticParseInfo getParseInfo(); +} diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryFilter.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryFilter.java new file mode 100644 index 000000000..c1bb4c4ac --- /dev/null +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryFilter.java @@ -0,0 +1,16 @@ +package com.tencent.supersonic.chat.api.pojo; + +import lombok.Data; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +public class QueryFilter { + + private List filters = new ArrayList<>(); + + private Map params = new HashMap<>(); + +} diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementCount.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryMatchInfo.java similarity index 61% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementCount.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryMatchInfo.java index 02932c77f..7e866f050 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementCount.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryMatchInfo.java @@ -3,8 +3,10 @@ package com.tencent.supersonic.chat.api.pojo; import lombok.Data; @Data -public class SchemaElementCount { +public class QueryMatchInfo { + SchemaElementType elementType; + String detectWord; private Integer count = 0; private double maxSimilarity; } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java index fd5f6cdf0..592ee3bbe 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java @@ -1,10 +1,16 @@ package com.tencent.supersonic.chat.api.pojo; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.ToString; @Data @ToString +@Builder +@AllArgsConstructor +@NoArgsConstructor public class SchemaElementMatch { SchemaElementType elementType; @@ -18,7 +24,4 @@ public class SchemaElementMatch { String word; Long frequency; - - public SchemaElementMatch() { - } } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaMapInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaMapInfo.java index d13c840d6..66f8c2ca3 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaMapInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaMapInfo.java @@ -21,12 +21,12 @@ public class SchemaMapInfo { return domainElementMatches; } - public void setMatchedElements(Integer domain, List elementMatches) { - domainElementMatches.put(domain, elementMatches); - } - public void setDomainElementMatches( Map> domainElementMatches) { this.domainElementMatches = domainElementMatches; } + + public void setMatchedElements(Integer domain, List elementMatches) { + domainElementMatches.put(domain, elementMatches); + } } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java index c7bd543e0..03adf3897 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java @@ -5,6 +5,7 @@ import com.tencent.supersonic.common.enums.AggregateTypeEnum; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.common.pojo.Order; import com.tencent.supersonic.common.pojo.SchemaItem; + import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; @@ -27,4 +28,7 @@ public class SemanticParseInfo { private DateConf dateInfo; private Long limit; private Boolean nativeQuery = false; + private Double bonus = 0d; + private List elementMatches = new ArrayList<>(); + private Object info; } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/request/QueryContextReq.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/request/QueryContextReq.java index 71037c1b6..87622822f 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/request/QueryContextReq.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/request/QueryContextReq.java @@ -1,11 +1,14 @@ package com.tencent.supersonic.chat.api.request; - import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.QueryFilter; import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import lombok.Data; +import java.util.ArrayList; +import java.util.List; + @Data public class QueryContextReq { @@ -13,7 +16,8 @@ public class QueryContextReq { private Integer chatId; private Integer domainId = 0; private User user; - private SemanticParseInfo parseInfo = new SemanticParseInfo(); + private QueryFilter queryFilter; + private List candidateQueries = new ArrayList<>(); private SchemaMapInfo mapInfo = new SchemaMapInfo(); private boolean saveAnswer = true; } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/response/QueryResultResp.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/response/QueryResultResp.java index fa70b13a8..6ed1fc587 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/response/QueryResultResp.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/response/QueryResultResp.java @@ -11,13 +11,13 @@ import lombok.Data; @Data public class QueryResultResp { + public EntityInfo entityInfo; private Long queryId; private String queryMode; private String querySql; private int queryState; private List queryColumns; private QueryAuthorization queryAuthorization; - public EntityInfo entityInfo; private SemanticParseInfo chatContext; private Object response; private List> queryResults; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticQuery.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticQuery.java deleted file mode 100644 index 5cb650cfb..000000000 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/service/SemanticQuery.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.tencent.supersonic.chat.api.service; - -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.response.QueryResultResp; - -import java.io.Serializable; -import java.util.List; - -/** - * This interface defines the contract for a semantic query that executes specific type of - * query based on the results of semantic parsing. - */ -public interface SemanticQuery extends Serializable { - - String getQueryMode(); - - QueryResultResp execute(QueryContextReq queryCtx, ChatContext chatCtx) throws Exception; - - SchemaElementCount match(List elementMatches, QueryContextReq queryCtx); - - void updateContext(QueryResultResp queryResponse, ChatContext chatCtx, QueryContextReq queryCtx); - - SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx); - -} diff --git a/chat/core/pom.xml b/chat/core/pom.xml index e7ba122ed..63369ba78 100644 --- a/chat/core/pom.xml +++ b/chat/core/pom.xml @@ -113,6 +113,12 @@ semantic-api ${project.version} + + + + + + com.tencent.supersonic semantic-query @@ -125,6 +131,12 @@ ${project.version} compile + + com.tencent.supersonic + semantic-query + ${project.version} + compile + - \ No newline at end of file + diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/ChatServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/ChatServiceImpl.java index 5663f3f9a..1ae1e4cbd 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/ChatServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/ChatServiceImpl.java @@ -18,20 +18,19 @@ import com.tencent.supersonic.chat.domain.service.ChatService; import java.text.SimpleDateFormat; import java.util.List; import java.util.Objects; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; @Service("ChatService") @Primary +@Slf4j public class ChatServiceImpl implements ChatService { private ChatContextRepository chatContextRepository; private ChatRepository chatRepository; private ChatQueryRepository chatQueryRepository; - private final Logger logger = LoggerFactory.getLogger(ChatService.class); - public ChatServiceImpl(ChatContextRepository chatContextRepository, ChatRepository chatRepository, ChatQueryRepository chatQueryRepository) { @@ -64,27 +63,32 @@ public class ChatServiceImpl implements ChatService { @Override public void updateContext(ChatContext chatCtx) { - logger.debug("save ChatContext {}", chatCtx); + log.debug("save ChatContext {}", chatCtx); chatContextRepository.updateContext(chatCtx); } + @Override + public void updateContext(ChatContext chatCtx, QueryContextReq queryCtx, SemanticParseInfo semanticParseInfo) { + chatCtx.setParseInfo(semanticParseInfo); + chatCtx.setQueryText(queryCtx.getQueryText()); + updateContext(chatCtx); + } + @Override public void switchContext(ChatContext chatCtx) { - logger.debug("switchContext ChatContext {}", chatCtx); + log.debug("switchContext ChatContext {}", chatCtx); chatCtx.setParseInfo(new SemanticParseInfo()); } @Override public Boolean addChat(User user, String chatName) { - SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String datetime = tempDate.format(new java.util.Date()); ChatDO intelligentConversionDO = new ChatDO(); intelligentConversionDO.setChatName(chatName); intelligentConversionDO.setCreator(user.getName()); - intelligentConversionDO.setCreateTime(datetime); + intelligentConversionDO.setCreateTime(getCurrentTime()); intelligentConversionDO.setIsDelete(0); - intelligentConversionDO.setLastTime(datetime); + intelligentConversionDO.setLastTime(getCurrentTime()); intelligentConversionDO.setLastQuestion("Hello, welcome to using supersonic"); intelligentConversionDO.setIsTop(0); return chatRepository.createChat(intelligentConversionDO); @@ -97,9 +101,7 @@ public class ChatServiceImpl implements ChatService { @Override public boolean updateChatName(Long chatId, String chatName, String userName) { - SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String lastTime = tempDate.format(new java.util.Date()); - return chatRepository.updateChatName(chatId, chatName, lastTime, userName); + return chatRepository.updateChatName(chatId, chatName, getCurrentTime(), userName); } @Override @@ -129,6 +131,8 @@ public class ChatServiceImpl implements ChatService { @Override public void addQuery(QueryResultResp queryResponse, QueryContextReq queryContext, ChatContext chatCtx) { chatQueryRepository.createChatQuery(queryResponse, queryContext, chatCtx); + chatRepository.updateLastQuestion(chatCtx.getChatId().longValue(), queryContext.getQueryText(), + getCurrentTime()); } @Override @@ -141,4 +145,9 @@ public class ChatServiceImpl implements ChatService { return chatQueryRepository.updateChatQuery(chatQueryDO); } + private String getCurrentTime() { + SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return tempDate.format(new java.util.Date()); + } + } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/ConfigServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/ConfigServiceImpl.java index 209f9b407..63ba2fa20 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/ConfigServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/ConfigServiceImpl.java @@ -2,9 +2,7 @@ package com.tencent.supersonic.chat.application; import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.service.SemanticLayer; -import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfig; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigBase; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigEditReq; @@ -14,14 +12,22 @@ import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric; import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo; import com.tencent.supersonic.chat.domain.pojo.config.ItemVisibilityInfo; +import com.tencent.supersonic.chat.domain.pojo.config.KnowledgeInfo; import com.tencent.supersonic.chat.domain.repository.ChatConfigRepository; import com.tencent.supersonic.chat.domain.service.ConfigService; import com.tencent.supersonic.chat.domain.utils.ChatConfigUtils; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; +import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; import com.tencent.supersonic.common.util.json.JsonUtil; +import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.DomainResp; +import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; @@ -35,15 +41,16 @@ import org.springframework.util.CollectionUtils; public class ConfigServiceImpl implements ConfigService { private final ChatConfigRepository chaConfigRepository; - private final SemanticLayer semanticLayer; private final ChatConfigUtils chatConfigUtils; + private final DefaultSemanticInternalUtils defaultSemanticUtils; + public ConfigServiceImpl(ChatConfigRepository chaConfigRepository, - @Lazy SemanticLayer semanticLayer, - ChatConfigUtils chatConfigUtils) { + ChatConfigUtils chatConfigUtils, + @Lazy DefaultSemanticInternalUtils defaultSemanticUtils) { this.chaConfigRepository = chaConfigRepository; - this.semanticLayer = semanticLayer; this.chatConfigUtils = chatConfigUtils; + this.defaultSemanticUtils = defaultSemanticUtils; } @Override @@ -101,19 +108,19 @@ public class ConfigServiceImpl implements ConfigService { } public EntityRichInfo fetchEntityDescByDomainId(Long domainId) { - + SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); ChatConfigInfo chaConfigDesc = chaConfigRepository.getConfigByDomainId(domainId); - return fetchEntityDescByConfig(chaConfigDesc); + DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId); + return fetchEntityDescByConfig(chaConfigDesc, domainSchemaDesc); } - public EntityRichInfo fetchEntityDescByConfig(ChatConfigInfo chatConfigDesc) { + public EntityRichInfo fetchEntityDescByConfig(ChatConfigInfo chatConfigDesc, DomainSchemaResp domain) { Long domainId = chatConfigDesc.getDomainId(); EntityRichInfo entityDesc = new EntityRichInfo(); if (Objects.isNull(chatConfigDesc) || Objects.isNull(chatConfigDesc.getEntity())) { log.info("domainId:{}, entityDesc info is null", domainId); return entityDesc; } - DomainSchemaResp domain = semanticLayer.getDomainSchemaInfo(domainId); entityDesc.setDomainId(domain.getId()); entityDesc.setDomainBizName(domain.getBizName()); @@ -128,13 +135,14 @@ public class ConfigServiceImpl implements ConfigService { public List fetchDefaultMetricDescByDomainId(Long domainId) { + SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId); - return fetchDefaultMetricDescByConfig(chatConfigDesc); + DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId); + return fetchDefaultMetricDescByConfig(chatConfigDesc, domainSchemaDesc); } - public List fetchDefaultMetricDescByConfig(ChatConfigInfo chatConfigDesc) { + public List fetchDefaultMetricDescByConfig(ChatConfigInfo chatConfigDesc, DomainSchemaResp domain) { Long domainId = chatConfigDesc.getDomainId(); - DomainSchemaResp domain = semanticLayer.getDomainSchemaInfo(domainId); List defaultMetricDescList = new ArrayList<>(); if (Objects.isNull(chatConfigDesc) || CollectionUtils.isEmpty(chatConfigDesc.getDefaultMetrics())) { log.info("domainId:{}, defaultMetricDescList info is null", domainId); @@ -158,14 +166,17 @@ public class ConfigServiceImpl implements ConfigService { } public ItemVisibilityInfo fetchVisibilityDescByDomainId(Long domainId) { + SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId); - return fetchVisibilityDescByConfig(chatConfigDesc); + DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId); + return fetchVisibilityDescByConfig(chatConfigDesc, domainSchemaDesc); } - private ItemVisibilityInfo fetchVisibilityDescByConfig(ChatConfigInfo chatConfigDesc) { + private ItemVisibilityInfo fetchVisibilityDescByConfig(ChatConfigInfo chatConfigDesc, + DomainSchemaResp domainSchemaDesc) { ItemVisibilityInfo itemVisibilityDesc = new ItemVisibilityInfo(); Long domainId = chatConfigDesc.getDomainId(); - DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId); + List dimIdAllList = chatConfigUtils.generateAllDimIdList(domainSchemaDesc); List metricIdAllList = chatConfigUtils.generateAllMetricIdList(domainSchemaDesc); @@ -194,18 +205,65 @@ public class ConfigServiceImpl implements ConfigService { @Override public ChatConfigRichInfo getConfigRichInfo(Long domainId) { + SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); ChatConfigRichInfo chaConfigRichDesc = new ChatConfigRichInfo(); ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId); + if (Objects.isNull(chatConfigDesc)) { + log.info("there is no chatConfigDesc for domainId:{}", domainId); + return chaConfigRichDesc; + } BeanUtils.copyProperties(chatConfigDesc, chaConfigRichDesc); - DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId); - chaConfigRichDesc.setBizName(domainSchemaDesc.getBizName()); - chaConfigRichDesc.setName(domainSchemaDesc.getName()); + DomainSchemaResp domainSchemaInfo = semanticLayer.getDomainSchemaInfo(domainId); + chaConfigRichDesc.setBizName(domainSchemaInfo.getBizName()); + chaConfigRichDesc.setName(domainSchemaInfo.getName()); - chaConfigRichDesc.setDefaultMetrics(fetchDefaultMetricDescByConfig(chatConfigDesc)); - chaConfigRichDesc.setVisibility(fetchVisibilityDescByConfig(chatConfigDesc)); - chaConfigRichDesc.setEntity(fetchEntityDescByConfig(chatConfigDesc)); + chaConfigRichDesc.setKnowledgeInfos( + fillKnowledgeBizName(chaConfigRichDesc.getKnowledgeInfos(), domainSchemaInfo)); + chaConfigRichDesc.setDefaultMetrics(fetchDefaultMetricDescByConfig(chatConfigDesc, domainSchemaInfo)); + chaConfigRichDesc.setVisibility(fetchVisibilityDescByConfig(chatConfigDesc, domainSchemaInfo)); + chaConfigRichDesc.setEntity(fetchEntityDescByConfig(chatConfigDesc, domainSchemaInfo)); return chaConfigRichDesc; } + + private List fillKnowledgeBizName(List knowledgeInfos, + DomainSchemaResp domainSchemaInfo) { + if (CollectionUtils.isEmpty(knowledgeInfos)) { + return new ArrayList<>(); + } + Map dimIdAndRespPair = domainSchemaInfo.getDimensions().stream() + .collect(Collectors.toMap(DimSchemaResp::getId, Function.identity())); + knowledgeInfos.stream().forEach(knowledgeInfo -> { + if (Objects.nonNull(knowledgeInfo)) { + DimSchemaResp dimSchemaResp = dimIdAndRespPair.get(knowledgeInfo.getItemId()); + if (Objects.nonNull(dimSchemaResp)) { + knowledgeInfo.setBizName(dimSchemaResp.getBizName()); + } + if (CollectionUtils.isEmpty(knowledgeInfo.getBlackList())) { + knowledgeInfo.setBlackList(new ArrayList<>()); + } + if (CollectionUtils.isEmpty(knowledgeInfo.getRuleList())) { + knowledgeInfo.setRuleList(new ArrayList<>()); + } + if (CollectionUtils.isEmpty(knowledgeInfo.getWhiteList())) { + knowledgeInfo.setWhiteList(new ArrayList<>()); + } + } + }); + return knowledgeInfos; + } + + @Override + public List getAllChatRichConfig() { + List chatConfigRichInfoList = new ArrayList<>(); + List domainRespList = defaultSemanticUtils.getDomainListForAdmin(); + domainRespList.stream().forEach(domainResp -> { + ChatConfigRichInfo chatConfigRichInfo = getConfigRichInfo(domainResp.getId()); + if (Objects.nonNull(chatConfigRichInfo)) { + chatConfigRichInfoList.add(chatConfigRichInfo); + } + }); + return chatConfigRichInfoList; + } } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/DomainEntityService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/DomainEntityService.java index 052b918c7..935bb9090 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/DomainEntityService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/DomainEntityService.java @@ -2,49 +2,44 @@ package com.tencent.supersonic.chat.application; import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.chat.api.pojo.DataInfo; import com.tencent.supersonic.chat.api.pojo.DomainInfo; import com.tencent.supersonic.chat.api.pojo.EntityInfo; import com.tencent.supersonic.chat.api.pojo.Filter; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticLayer; -import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; -import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.common.pojo.SchemaItem; +import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; +import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - @Service +@Slf4j public class DomainEntityService { - private final Logger logger = LoggerFactory.getLogger(DomainEntityService.class); - @Autowired - private SemanticLayer semanticLayer; + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); @Autowired private DefaultSemanticInternalUtils defaultSemanticUtils; - public EntityInfo getEntityInfo(QueryContextReq queryCtx, ChatContext chatCtx, User user) { - SemanticParseInfo parseInfo = queryCtx.getParseInfo(); - + public EntityInfo getEntityInfo(SemanticParseInfo parseInfo, User user) { if (parseInfo != null && parseInfo.getDomainId() > 0) { EntityInfo entityInfo = getEntityInfo(parseInfo.getDomainId()); if (parseInfo.getDimensionFilters().size() <= 0) { @@ -56,7 +51,8 @@ public class DomainEntityService { String domainInfoPrimaryName = entityInfo.getDomainInfo().getPrimaryEntityBizName(); String domainInfoId = ""; for (Filter chatFilter : parseInfo.getDimensionFilters()) { - if (chatFilter.getBizName().equals(domainInfoPrimaryName)) { + if (chatFilter != null && chatFilter.getBizName() != null && chatFilter.getBizName() + .equals(domainInfoPrimaryName)) { if (chatFilter.getOperator().equals(FilterOperatorEnum.EQUALS)) { domainInfoId = chatFilter.getValue().toString(); } @@ -72,7 +68,7 @@ public class DomainEntityService { return entityInfo; } catch (Exception e) { - logger.error("setMaintDomain error {}", e); + log.error("setMaintDomain error {}", e); } } } @@ -88,7 +84,7 @@ public class DomainEntityService { private EntityInfo getEntityInfo(EntityRichInfo entityDesc) { EntityInfo entityInfo = new EntityInfo(); - if (entityDesc != null) { + if (entityDesc != null && Objects.nonNull(entityDesc.getDomainId())) { DomainInfo domainInfo = new DomainInfo(); domainInfo.setItemId(Integer.valueOf(entityDesc.getDomainId().intValue())); domainInfo.setName(entityDesc.getDomainName()); @@ -148,7 +144,7 @@ public class DomainEntityService { queryResultWithColumns = semanticLayer.queryByStruct(SchemaInfoConverter.convertTo(semanticParseInfo), user); } catch (Exception e) { - logger.warn("setMainDomain queryByStruct error, e:", e); + log.warn("setMainDomain queryByStruct error, e:", e); } if (queryResultWithColumns != null) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/QueryServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/QueryServiceImpl.java index e9aed284f..46d2b8a2a 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/QueryServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/QueryServiceImpl.java @@ -2,52 +2,44 @@ package com.tencent.supersonic.chat.application; import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.component.*; import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; import com.tencent.supersonic.chat.api.response.QueryResultResp; -import com.tencent.supersonic.chat.api.service.SchemaMapper; -import com.tencent.supersonic.chat.api.service.SemanticLayer; -import com.tencent.supersonic.chat.api.service.SemanticParser; -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; -import com.tencent.supersonic.chat.application.query.SemanticQueryFactory; +import com.tencent.supersonic.chat.application.query.QuerySelector; import com.tencent.supersonic.chat.domain.pojo.chat.QueryData; -import com.tencent.supersonic.chat.domain.service.ChatService; +import com.tencent.supersonic.chat.domain.pojo.search.QueryState; import com.tencent.supersonic.chat.domain.service.QueryService; +import com.tencent.supersonic.chat.domain.service.ChatService; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; -import com.tencent.supersonic.common.util.json.JsonUtil; +import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @Service +@Component("chatQueryService") +@Primary +@Slf4j public class QueryServiceImpl implements QueryService { - private final Logger logger = LoggerFactory.getLogger(QueryServiceImpl.class); - private List schemaMappers; - private List semanticParsers; @Autowired private ChatService chatService; - @Autowired - private SemanticLayer semanticLayer; - public QueryServiceImpl() { - schemaMappers = SpringFactoriesLoader.loadFactories(SchemaMapper.class, - Thread.currentThread().getContextClassLoader()); - semanticParsers = SpringFactoriesLoader.loadFactories(SemanticParser.class, - Thread.currentThread().getContextClassLoader()); - } + private List schemaMappers = ComponentFactory.getSchemaMappers(); + private List semanticParsers = ComponentFactory.getSemanticParsers(); + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); + private QuerySelector querySelector = ComponentFactory.getQuerySelector(); + @Override public QueryResultResp executeQuery(QueryContextReq queryCtx) throws Exception { schemaMappers.stream().forEach(s -> s.map(queryCtx)); @@ -55,24 +47,29 @@ public class QueryServiceImpl implements QueryService { ChatContext chatCtx = chatService.getOrCreateContext(queryCtx.getChatId()); for (SemanticParser semanticParser : semanticParsers) { - logger.info("semanticParser processing:{}", JsonUtil.prettyToString(semanticParser)); - boolean isFinish = semanticParser.parse(queryCtx, chatCtx); - if (isFinish) { - logger.info("semanticParser is finish ,semanticParser:{}", semanticParser.getClass().getName()); - break; + log.info("semanticParser processing:[{}]", semanticParser.getClass().getName()); + semanticParser.parse(queryCtx, chatCtx); + } + + if (queryCtx.getCandidateQueries().size() > 0) { + log.info("pick before [{}]", queryCtx.getCandidateQueries().stream().collect( + Collectors.toList())); + SemanticQuery semanticQuery = querySelector.select(queryCtx.getCandidateQueries()); + log.info("pick after [{}]", semanticQuery); + + QueryResultResp queryResponse = semanticQuery.execute(queryCtx.getUser()); + if (queryResponse != null) { + // update chat context after a successful semantic query + if (queryCtx.isSaveAnswer() && queryResponse.getQueryState() == QueryState.NORMAL.getState()) { + chatService.updateContext(chatCtx, queryCtx, semanticQuery.getParseInfo()); + } + queryResponse.setChatContext(chatCtx.getParseInfo()); + chatService.addQuery(queryResponse, queryCtx, chatCtx); + return queryResponse; } } - // submit semantic query based on the result of semantic parsing - SemanticQuery query = SemanticQueryFactory.get(queryCtx.getParseInfo().getQueryMode()); - QueryResultResp queryResponse = query.execute(queryCtx, chatCtx); - - // update chat context after a successful semantic query - query.updateContext(queryResponse, chatCtx, queryCtx); - - chatService.addQuery(queryResponse, queryCtx, chatCtx); - - return queryResponse; + return null; } @Override @@ -82,7 +79,7 @@ public class QueryServiceImpl implements QueryService { } @Override - public QueryResultResp queryData(QueryData queryData, User user) throws Exception { + public QueryResultResp executeDirectQuery(QueryData queryData, User user) throws Exception { SemanticParseInfo semanticParseInfo = new SemanticParseInfo(); QueryResultResp queryResponse = new QueryResultResp(); BeanUtils.copyProperties(queryData, semanticParseInfo); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/RecommendServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/RecommendServiceImpl.java index 727ec651c..6044b08d0 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/RecommendServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/RecommendServiceImpl.java @@ -2,14 +2,15 @@ package com.tencent.supersonic.chat.application; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.chat.domain.pojo.chat.RecommendResponse; import com.tencent.supersonic.chat.domain.service.RecommendService; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Service; /*** @@ -18,8 +19,7 @@ import org.springframework.stereotype.Service; @Service public class RecommendServiceImpl implements RecommendService { - @Autowired - private SemanticLayer semanticLayer; + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); @Override public RecommendResponse recommend(QueryContextReq queryCtx) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/SearchServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/SearchServiceImpl.java index da7288f7a..f9361d1fa 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/SearchServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/SearchServiceImpl.java @@ -32,9 +32,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -43,9 +43,11 @@ import org.springframework.stereotype.Service; * search service impl */ @Service +@Slf4j public class SearchServiceImpl implements SearchService { - private static final Logger LOGGER = LoggerFactory.getLogger(SearchServiceImpl.class); + private static final int RESULT_SIZE = 10; + @Autowired private WordNatureService wordNatureService; @Autowired @@ -53,9 +55,6 @@ public class SearchServiceImpl implements SearchService { @Autowired private SearchMatchStrategy searchMatchStrategy; - private static final int RESULT_SIZE = 10; - - @Override public List search(QueryContextReq queryCtx) { String queryText = queryCtx.getQueryText(); @@ -67,7 +66,7 @@ public class SearchServiceImpl implements SearchService { // 2.detect by segment List originals = HanlpHelper.getSegment().seg(queryText.toLowerCase()).stream() .collect(Collectors.toList()); - Map> regTextMap = searchMatchStrategy.matchWithMatchText(queryText, originals, + Map> regTextMap = searchMatchStrategy.match(queryText, originals, queryCtx.getDomainId()); regTextMap.entrySet().stream().forEach(m -> HanlpHelper.transLetterOriginal(m.getValue())); // 3.get the most matching data @@ -77,14 +76,14 @@ public class SearchServiceImpl implements SearchService { .reduce((entry1, entry2) -> entry1.getKey().getDetectSegment().length() >= entry2.getKey().getDetectSegment().length() ? entry1 : entry2); - LOGGER.debug("mostSimilarSearchResult:{}", mostSimilarSearchResult); + log.debug("mostSimilarSearchResult:{}", mostSimilarSearchResult); // 4.optimize the results after the query if (!mostSimilarSearchResult.isPresent()) { - LOGGER.info("unable to find any information through search , queryCtx:{}", queryCtx); + log.info("unable to find any information through search , queryCtx:{}", queryCtx); return Lists.newArrayList(); } Map.Entry> searchTextEntry = mostSimilarSearchResult.get(); - LOGGER.info("searchTextEntry:{},queryCtx:{}", searchTextEntry, queryCtx); + log.info("searchTextEntry:{},queryCtx:{}", searchTextEntry, queryCtx); Set searchResults = new LinkedHashSet(); DomainInfoStat domainStat = NatureHelper.getDomainStat(originals); @@ -98,7 +97,7 @@ public class SearchServiceImpl implements SearchService { // 4.2 process based on dimension values MatchText matchText = searchTextEntry.getKey(); Map natureToNameMap = getNatureToNameMap(searchTextEntry, new HashSet<>(possibleDomains)); - LOGGER.debug("possibleDomains:{},natureToNameMap:{}", possibleDomains, natureToNameMap); + log.debug("possibleDomains:{},natureToNameMap:{}", possibleDomains, natureToNameMap); for (Map.Entry natureToNameEntry : natureToNameMap.entrySet()) { searchDimensionValue(metricsDb, domainToName, domainStat.getMetricDomainCount(), searchResults, @@ -120,7 +119,7 @@ public class SearchServiceImpl implements SearchService { Long contextDomain = chatService.getContextDomain(queryCtx.getChatId()); - LOGGER.debug("possibleDomains:{},domainStat:{},contextDomain:{}", possibleDomains, domainStat, contextDomain); + log.debug("possibleDomains:{},domainStat:{},contextDomain:{}", possibleDomains, domainStat, contextDomain); // If nothing is recognized or only metric are present, then add the contextDomain. if (nothingOrOnlyMetric(domainStat) && effectiveDomain(contextDomain)) { @@ -249,7 +248,7 @@ public class SearchServiceImpl implements SearchService { domainToName.get(domain), domain, semanticType)); } } - LOGGER.info("parseResult:{},dimensionMetricClassIds:{},possibleDomains:{}", mapResult, + log.info("parseResult:{},dimensionMetricClassIds:{},possibleDomains:{}", mapResult, dimensionMetricClassIds, possibleDomains); } return existMetric; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/knowledge/ApplicationStartedInit.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/knowledge/ApplicationStartedInit.java index fa1630538..517851ed0 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/knowledge/ApplicationStartedInit.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/knowledge/ApplicationStartedInit.java @@ -45,7 +45,7 @@ public class ApplicationStartedInit implements ApplicationListener wordNatures = wordNatureService.getAllWordNature(); @@ -55,6 +55,7 @@ public class ApplicationStartedInit implements ApplicationListener preWordNatures = new ArrayList<>(); private LoadingCache cache = CacheBuilder.newBuilder() @@ -39,14 +38,14 @@ public class WordNatureService { new CacheLoader() { @Override public DomainInfos load(String key) { - LOGGER.info("load getDomainSchemaInfo cache [{}]", key); + log.info("load getDomainSchemaInfo cache [{}]", key); return SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>())); } } ); public List getAllWordNature() { - + SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); DomainInfos domainInfos = SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>())); List natures = new ArrayList<>(); @@ -64,7 +63,7 @@ public class WordNatureService { private void addNatureToResult(NatureType value, List metas, List natures) { List natureList = WordNatureStrategyFactory.get(value).getWordNatureList(metas); - LOGGER.debug("nature type:{} , nature size:{}", value.name(), natureList.size()); + log.debug("nature type:{} , nature size:{}", value.name(), natureList.size()); natures.addAll(natureList); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/DatabaseSchemaMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/DatabaseSchemaMapper.java new file mode 100644 index 000000000..d9b5173b8 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/DatabaseSchemaMapper.java @@ -0,0 +1,144 @@ +package com.tencent.supersonic.chat.application.mapper; + +import com.hankcs.hanlp.seg.common.Term; +import com.tencent.supersonic.chat.api.component.SchemaMapper; +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; +import com.tencent.supersonic.chat.api.pojo.SchemaElementType; +import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import com.tencent.supersonic.chat.application.knowledge.WordNatureService; +import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos; +import com.tencent.supersonic.common.nlp.ItemDO; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.knowledge.infrastructure.nlp.HanlpHelper; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; + +@Slf4j +public class DatabaseSchemaMapper implements SchemaMapper { + + @Override + public void map(QueryContextReq queryContext) { + + log.debug("before db mapper,mapInfo:{}", queryContext.getMapInfo()); + + List terms = HanlpHelper.getSegment().seg(queryContext.getQueryText().toLowerCase()).stream() + .collect(Collectors.toList()); + + WordNatureService wordNatureService = ContextUtils.getBean(WordNatureService.class); + + DomainInfos domainInfos = wordNatureService.getCache().getUnchecked(""); + + detectAndAddToSchema(queryContext, terms, domainInfos.getDimensions(), + SchemaElementType.DIMENSION); + detectAndAddToSchema(queryContext, terms, domainInfos.getMetrics(), SchemaElementType.METRIC); + + log.debug("after db mapper,mapInfo:{}", queryContext.getMapInfo()); + } + + private void detectAndAddToSchema(QueryContextReq queryContext, List terms, List domains, + SchemaElementType schemaElementType) { + try { + String queryText = queryContext.getQueryText(); + + Map> domainResultSet = getResultSet(queryText, terms, domains); + + addToSchemaMapInfo(domainResultSet, queryContext.getMapInfo(), schemaElementType); + + } catch (Exception e) { + log.error("detectAndAddToSchema error", e); + } + } + + private Map> getResultSet(String queryText, List terms, List domains) { + + MapperHelper mapperHelper = ContextUtils.getBean(MapperHelper.class); + + Map> nameToItems = getNameToItems(domains); + + Map regOffsetToLength = terms.stream().sorted(Comparator.comparing(Term::length)) + .collect(Collectors.toMap(Term::getOffset, term -> term.word.length(), (value1, value2) -> value2)); + + Map> domainResultSet = new HashMap<>(); + for (Integer index = 0; index <= queryText.length() - 1; ) { + for (Integer i = index; i <= queryText.length(); ) { + i = mapperHelper.getStepIndex(regOffsetToLength, i); + if (i <= queryText.length()) { + String detectSegment = queryText.substring(index, i); + nameToItems.forEach( + (name, newItemDOs) -> { + if (name.contains(detectSegment) + && mapperHelper.getSimilarity(detectSegment, name) + >= mapperHelper.getMetricDimensionThresholdConfig()) { + Set preItemDOS = domainResultSet.putIfAbsent(detectSegment, newItemDOs); + if (Objects.nonNull(preItemDOS)) { + preItemDOS.addAll(newItemDOs); + } + } + } + ); + } + } + index = mapperHelper.getStepIndex(regOffsetToLength, index); + } + return domainResultSet; + } + + private Map> getNameToItems(List domains) { + return domains.stream() + .collect(Collectors.toMap(ItemDO::getName, a -> { + Set result = new HashSet<>(); + result.add(a); + return result; + }, (k1, k2) -> { + k1.addAll(k2); + return k1; + })); + } + + private void addToSchemaMapInfo(Map> mapResultRowSet, SchemaMapInfo schemaMap, + SchemaElementType schemaElementType) { + if (Objects.isNull(mapResultRowSet) || mapResultRowSet.size() <= 0) { + return; + } + MapperHelper mapperHelper = ContextUtils.getBean(MapperHelper.class); + + for (Map.Entry> entry : mapResultRowSet.entrySet()) { + String detectWord = entry.getKey(); + Set itemDOS = entry.getValue(); + for (ItemDO itemDO : itemDOS) { + + List elements = schemaMap.getMatchedElements(itemDO.getDomain()); + if (CollectionUtils.isEmpty(elements)) { + elements = new ArrayList<>(); + schemaMap.setMatchedElements(itemDO.getDomain(), elements); + } + Set regElementSet = elements.stream() + .filter(elementMatch -> schemaElementType.equals(elementMatch.getElementType())) + .map(elementMatch -> elementMatch.getElementID()) + .collect(Collectors.toSet()); + + if (regElementSet.contains(itemDO.getItemId())) { + continue; + } + SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder() + .elementID(itemDO.getItemId()).word(itemDO.getName()).frequency(10000L) + .elementType(schemaElementType).detectWord(detectWord) + .similarity(mapperHelper.getSimilarity(detectWord, itemDO.getName())) + .build(); + log.info("schemaElementType:{},add to schema, elementMatch {}", schemaElementType, schemaElementMatch); + elements.add(schemaElementMatch); + } + } + + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/HanlpSchemaMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/HanlpSchemaMapper.java index 392e604af..56fc6cb02 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/HanlpSchemaMapper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/HanlpSchemaMapper.java @@ -1,17 +1,17 @@ package com.tencent.supersonic.chat.application.mapper; import com.hankcs.hanlp.seg.common.Term; +import com.tencent.supersonic.chat.api.component.SchemaMapper; import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SchemaMapper; import com.tencent.supersonic.chat.application.knowledge.NatureHelper; +import com.tencent.supersonic.chat.domain.pojo.search.MatchText; import com.tencent.supersonic.chat.domain.utils.NatureConverter; import com.tencent.supersonic.common.nlp.MapResult; import com.tencent.supersonic.common.nlp.NatureType; import com.tencent.supersonic.common.util.context.ContextUtils; -import com.tencent.supersonic.common.util.json.JsonUtil; import com.tencent.supersonic.knowledge.application.online.BaseWordNature; import com.tencent.supersonic.knowledge.application.online.WordNatureStrategyFactory; import com.tencent.supersonic.knowledge.infrastructure.nlp.HanlpHelper; @@ -19,15 +19,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j public class HanlpSchemaMapper implements SchemaMapper { - private static final Logger LOGGER = LoggerFactory.getLogger(HanlpSchemaMapper.class); - @Override public void map(QueryContextReq queryContext) { @@ -35,22 +34,37 @@ public class HanlpSchemaMapper implements SchemaMapper { .collect(Collectors.toList()); terms.forEach( - item -> LOGGER.info("word:{},nature:{},frequency:{}", item.word, item.nature.toString(), + item -> log.info("word:{},nature:{},frequency:{}", item.word, item.nature.toString(), item.getFrequency()) ); QueryMatchStrategy matchStrategy = ContextUtils.getBean(QueryMatchStrategy.class); - List matches = matchStrategy.match(queryContext.getQueryText(), terms, queryContext.getDomainId()); + Map> matchResult = matchStrategy.match(queryContext.getQueryText(), terms, + queryContext.getDomainId()); + List matches = new ArrayList<>(); + if (Objects.nonNull(matchResult)) { + Optional> first = matchResult.entrySet().stream() + .filter(entry -> CollectionUtils.isNotEmpty(entry.getValue())) + .map(entry -> entry.getValue()).findFirst(); + if (first.isPresent()) { + matches = first.get(); + } + } HanlpHelper.transLetterOriginal(matches); - LOGGER.info("queryContext:{},matches:{}", queryContext, matches); + log.info("queryContext:{},matches:{}", queryContext, matches); - convertTermsToSchemaMapInfo(matches, queryContext.getMapInfo()); + convertTermsToSchemaMapInfo(matches, queryContext.getMapInfo(), terms); } - private void convertTermsToSchemaMapInfo(List mapResults, SchemaMapInfo schemaMap) { + private void convertTermsToSchemaMapInfo(List mapResults, SchemaMapInfo schemaMap, List terms) { if (CollectionUtils.isEmpty(mapResults)) { return; } + + Map wordNatureToFrequency = terms.stream().collect( + Collectors.toMap(entry -> entry.getWord() + entry.getNature(), + term -> Long.valueOf(term.getFrequency()), (value1, value2) -> value2)); + for (MapResult mapResult : mapResults) { for (String nature : mapResult.getNatures()) { Integer domain = NatureHelper.getDomain(nature); @@ -61,17 +75,18 @@ public class HanlpSchemaMapper implements SchemaMapper { if (Objects.isNull(elementType)) { continue; } - SchemaElementMatch schemaElementMatch = new SchemaElementMatch(); - schemaElementMatch.setElementType(elementType); BaseWordNature baseWordNature = WordNatureStrategyFactory.get(NatureType.getNatureType(nature)); Integer elementID = baseWordNature.getElementID(nature); - schemaElementMatch.setElementID(elementID); - Long frequency = baseWordNature.getFrequency(nature); - schemaElementMatch.setFrequency(frequency); - schemaElementMatch.setWord(mapResult.getName()); - schemaElementMatch.setSimilarity(mapResult.getSimilarity()); - schemaElementMatch.setDetectWord(mapResult.getDetectWord()); + Long frequency = wordNatureToFrequency.get(mapResult.getName() + nature); + SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder() + .elementType(elementType) + .elementID(elementID) + .frequency(frequency) + .word(mapResult.getName()) + .similarity(mapResult.getSimilarity()) + .detectWord(mapResult.getDetectWord()) + .build(); Map> domainElementMatches = schemaMap.getDomainElementMatches(); List schemaElementMatches = domainElementMatches.putIfAbsent(domain, diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MapperHelper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MapperHelper.java new file mode 100644 index 000000000..55309da3d --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MapperHelper.java @@ -0,0 +1,83 @@ +package com.tencent.supersonic.chat.application.mapper; + +import com.hankcs.hanlp.algorithm.EditDistance; +import com.tencent.supersonic.chat.application.knowledge.NatureHelper; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * Mapper helper + */ + +@Data +@Service +@Slf4j +public class MapperHelper { + + @Value("${one.detection.size:6}") + private Integer oneDetectionSize; + @Value("${one.detection.max.size:20}") + private Integer oneDetectionMaxSize; + @Value("${metric.dimension.threshold:0.3}") + private Double metricDimensionThresholdConfig; + @Value("${dimension.value.threshold:0.5}") + private Double dimensionValueThresholdConfig; + + public Integer getStepIndex(Map regOffsetToLength, Integer index) { + Integer subRegLength = regOffsetToLength.get(index); + if (Objects.nonNull(subRegLength)) { + index = index + subRegLength; + } else { + index++; + } + return index; + } + + public Integer getStepOffset(List termList, Integer index) { + for (int j = 0; j < termList.size() - 1; j++) { + if (termList.get(j) <= index && termList.get(j + 1) > index) { + return termList.get(j); + } + } + return index; + } + + public double getThresholdMatch(List natures) { + if (existDimensionValues(natures)) { + return dimensionValueThresholdConfig; + } + return metricDimensionThresholdConfig; + } + + /*** + * exist dimension values + * @param natures + * @return + */ + public boolean existDimensionValues(List natures) { + for (String nature : natures) { + if (NatureHelper.isDimensionValueClassId(nature)) { + return true; + } + } + return false; + } + + /*** + * get similarity + * @param detectSegment + * @param matchName + * @return + */ + public double getSimilarity(String detectSegment, String matchName) { + String detectSegmentLower = detectSegment == null ? null : detectSegment.toLowerCase(); + String matchNameLower = matchName == null ? null : matchName.toLowerCase(); + return 1 - (double) EditDistance.compute(detectSegmentLower, matchNameLower) / Math.max(matchName.length(), + detectSegment.length()); + } +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MatchStrategy.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MatchStrategy.java index 9e1348ae4..9b98f9d60 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MatchStrategy.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/MatchStrategy.java @@ -1,8 +1,6 @@ package com.tencent.supersonic.chat.application.mapper; -import com.hankcs.hanlp.algorithm.EditDistance; import com.hankcs.hanlp.seg.common.Term; -import com.tencent.supersonic.chat.application.knowledge.NatureHelper; import com.tencent.supersonic.chat.domain.pojo.search.MatchText; import com.tencent.supersonic.common.nlp.MapResult; import java.util.List; @@ -13,33 +11,6 @@ import java.util.Map; */ public interface MatchStrategy { - List match(String text, List terms, Integer detectDomainId); + Map> match(String text, List terms, Integer detectDomainId); - - Map> matchWithMatchText(String text, List originals, Integer detectDomainId); - - /*** - * exist dimension values - * @param natures - * @return - */ - default boolean existDimensionValues(List natures) { - for (String nature : natures) { - if (NatureHelper.isDimensionValueClassId(nature)) { - return true; - } - } - return false; - } - - /*** - * get similarity - * @param detectSegment - * @param matchName - * @return - */ - default double getSimilarity(String detectSegment, String matchName) { - return 1 - (double) EditDistance.compute(detectSegment, matchName) / Math.max(matchName.length(), - detectSegment.length()); - } } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryFilterMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryFilterMapper.java new file mode 100644 index 000000000..dccf8290b --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryFilterMapper.java @@ -0,0 +1,50 @@ +package com.tencent.supersonic.chat.application.mapper; + +import com.google.common.collect.Lists; +import com.tencent.supersonic.chat.api.component.SchemaMapper; +import com.tencent.supersonic.chat.api.pojo.*; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import java.util.List; + + +@Slf4j +public class QueryFilterMapper implements SchemaMapper { + + private Long FREQUENCY = 9999999L; + + private double SIMILARITY = 1.0; + + @Override + public void map(QueryContextReq queryContext) { + Integer domainId = queryContext.getDomainId(); + if (domainId == null || domainId <= 0 || queryContext.getQueryFilter() == null) { + return; + } + QueryFilter queryFilter = queryContext.getQueryFilter(); + SchemaMapInfo schemaMapInfo = queryContext.getMapInfo(); + List schemaElementMatches = schemaMapInfo.getMatchedElements(domainId); + convertFilterToSchemaMapInfo(queryFilter.getFilters(), schemaElementMatches); + } + + private void convertFilterToSchemaMapInfo(List filters, List schemaElementMatches) { + log.info("schemaElementMatches before queryFilerMapper:{}", schemaElementMatches); + if (CollectionUtils.isEmpty(schemaElementMatches)) { + schemaElementMatches = Lists.newArrayList(); + } + for (Filter filter : filters) { + SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder() + .elementType(SchemaElementType.VALUE) + .elementID(filter.getElementID().intValue()) + .frequency(FREQUENCY) + .word(String.valueOf(filter.getValue())) + .similarity(SIMILARITY) + .detectWord(filter.getName()) + .build(); + schemaElementMatches.add(schemaElementMatch); + } + log.info("schemaElementMatches after queryFilerMapper:{}", schemaElementMatches); + } + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryMatchStrategy.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryMatchStrategy.java index 11a689843..86a389a2f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryMatchStrategy.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/QueryMatchStrategy.java @@ -5,42 +5,34 @@ import com.tencent.supersonic.chat.domain.pojo.search.MatchText; import com.tencent.supersonic.common.nlp.MapResult; import com.tencent.supersonic.common.nlp.NatureType; import com.tencent.supersonic.knowledge.infrastructure.nlp.Suggester; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.compress.utils.Lists; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; /** * match strategy implement */ @Service +@Slf4j public class QueryMatchStrategy implements MatchStrategy { - private static final Logger LOGGER = LoggerFactory.getLogger(QueryMatchStrategy.class); - public static final double STEP = 0.1; - @Value("${one.detection.size:6}") - private Integer oneDetectionSize; - @Value("${one.detection.max.size:20}") - private Integer oneDetectionMaxSize; - @Value("${metric.dimension.threshold:0.3}") - private Double metricDimensionThresholdConfig; - @Value("${dimension.value.threshold:0.5}") - private Double dimensionValueThresholdConfig; + @Autowired + private MapperHelper mapperHelper; @Override - public List match(String text, List terms, Integer detectDomainId) { + public Map> match(String text, List terms, Integer detectDomainId) { if (CollectionUtils.isEmpty(terms) || StringUtils.isEmpty(text)) { return null; } @@ -50,17 +42,14 @@ public class QueryMatchStrategy implements MatchStrategy { List offsetList = terms.stream().sorted(Comparator.comparing(Term::getOffset)) .map(term -> term.getOffset()).collect(Collectors.toList()); - LOGGER.debug("retryCount:{},terms:{},regOffsetToLength:{},offsetList:{},detectDomainId:{}", terms, + log.debug("retryCount:{},terms:{},regOffsetToLength:{},offsetList:{},detectDomainId:{}", terms, regOffsetToLength, offsetList, detectDomainId); - return detect(text, regOffsetToLength, offsetList, detectDomainId); - } - - @Override - public Map> matchWithMatchText(String text, List originals, - Integer detectDomainId) { - - return null; + List detects = detect(text, regOffsetToLength, offsetList, detectDomainId); + Map> result = new HashMap<>(); + MatchText matchText = new MatchText(text, text); + result.put(matchText, detects); + return result; } private List detect(String text, Map regOffsetToLength, List offsetList, @@ -72,15 +61,15 @@ public class QueryMatchStrategy implements MatchStrategy { Set mapResultRowSet = new LinkedHashSet(); for (Integer i = index; i <= text.length(); ) { - int offset = getStepOffset(offsetList, index); - i = getStepIndex(regOffsetToLength, i); + int offset = mapperHelper.getStepOffset(offsetList, index); + i = mapperHelper.getStepIndex(regOffsetToLength, i); if (i <= text.length()) { List mapResults = detectByStep(text, detectDomainId, index, i, offset); mapResultRowSet.addAll(mapResults); } } - index = getStepIndex(regOffsetToLength, index); + index = mapperHelper.getStepIndex(regOffsetToLength, index); results.addAll(mapResultRowSet); } return results; @@ -88,11 +77,13 @@ public class QueryMatchStrategy implements MatchStrategy { private List detectByStep(String text, Integer detectDomainId, Integer index, Integer i, int offset) { String detectSegment = text.substring(index, i); + Integer oneDetectionSize = mapperHelper.getOneDetectionSize(); // step1. pre search - LinkedHashSet mapResults = Suggester.prefixSearch(detectSegment, oneDetectionMaxSize) + LinkedHashSet mapResults = Suggester.prefixSearch(detectSegment, + mapperHelper.getOneDetectionMaxSize()) .stream().collect(Collectors.toCollection(LinkedHashSet::new)); // step2. suffix search - LinkedHashSet suffixMapResults = Suggester.suffixSearch(detectSegment, oneDetectionMaxSize) + LinkedHashSet suffixMapResults = Suggester.suffixSearch(detectSegment, oneDetectionSize) .stream().collect(Collectors.toCollection(LinkedHashSet::new)); mapResults.addAll(suffixMapResults); @@ -105,7 +96,7 @@ public class QueryMatchStrategy implements MatchStrategy { .collect(Collectors.toCollection(LinkedHashSet::new)); // step4. filter by classId if (Objects.nonNull(detectDomainId) && detectDomainId > 0) { - LOGGER.debug("detectDomainId:{}, before parseResults:{}", mapResults); + log.debug("detectDomainId:{}, before parseResults:{}", mapResults); mapResults = mapResults.stream().map(entry -> { List natures = entry.getNatures().stream().filter( nature -> nature.startsWith(NatureType.NATURE_SPILT + detectDomainId) || (nature.startsWith( @@ -114,25 +105,27 @@ public class QueryMatchStrategy implements MatchStrategy { entry.setNatures(natures); return entry; }).collect(Collectors.toCollection(LinkedHashSet::new)); - LOGGER.info("after domainId parseResults:{}", mapResults); + log.info("after domainId parseResults:{}", mapResults); } // step5. filter by similarity mapResults = mapResults.stream() - .filter(term -> getSimilarity(detectSegment, term.getName()) >= getThresholdMatch(term.getNatures())) + .filter(term -> mapperHelper.getSimilarity(detectSegment, term.getName()) + >= mapperHelper.getThresholdMatch(term.getNatures())) .filter(term -> CollectionUtils.isNotEmpty(term.getNatures())) .collect(Collectors.toCollection(LinkedHashSet::new)); - LOGGER.debug("metricDimensionThreshold:{},dimensionValueThreshold:{},after isSimilarity parseResults:{}", + log.debug("metricDimensionThreshold:{},dimensionValueThreshold:{},after isSimilarity parseResults:{}", mapResults); mapResults = mapResults.stream().map(parseResult -> { parseResult.setOffset(offset); - parseResult.setSimilarity(getSimilarity(detectSegment, parseResult.getName())); + parseResult.setSimilarity(mapperHelper.getSimilarity(detectSegment, parseResult.getName())); return parseResult; }).collect(Collectors.toCollection(LinkedHashSet::new)); // step6. take only one dimension or 10 metric/dimension value per rond. - List dimensionMetrics = mapResults.stream().filter(entry -> existDimensionValues(entry.getNatures())) + List dimensionMetrics = mapResults.stream() + .filter(entry -> mapperHelper.existDimensionValues(entry.getNatures())) .collect(Collectors.toList()) .stream() .limit(1) @@ -144,31 +137,4 @@ public class QueryMatchStrategy implements MatchStrategy { return mapResults.stream().limit(oneDetectionSize).collect(Collectors.toList()); } } - - private Integer getStepIndex(Map regOffsetToLength, Integer index) { - Integer subRegLength = regOffsetToLength.get(index); - if (Objects.nonNull(subRegLength)) { - index = index + subRegLength; - } else { - index++; - } - return index; - } - - private Integer getStepOffset(List termList, Integer index) { - for (int j = 0; j < termList.size() - 1; j++) { - if (termList.get(j) <= index && termList.get(j + 1) > index) { - return termList.get(j); - } - } - return index; - } - - private double getThresholdMatch(List natures) { - if (existDimensionValues(natures)) { - return dimensionValueThresholdConfig; - } - return metricDimensionThresholdConfig; - } - } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/SearchMatchStrategy.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/SearchMatchStrategy.java index 8e7c57013..38c449a8e 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/SearchMatchStrategy.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/mapper/SearchMatchStrategy.java @@ -23,15 +23,8 @@ public class SearchMatchStrategy implements MatchStrategy { private static final int SEARCH_SIZE = 3; - @Override - public List match(String text, List terms, Integer detectDomainId) { - - return null; - } - - @Override - public Map> matchWithMatchText(String text, List originals, + public Map> match(String text, List originals, Integer detectDomainId) { Map regOffsetToLength = originals.stream() diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/AggregateSemanticParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/AggregateSemanticParser.java index 6a6c9d092..9a9940c45 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/AggregateSemanticParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/AggregateSemanticParser.java @@ -1,59 +1,82 @@ package com.tencent.supersonic.chat.application.parser; +import com.tencent.supersonic.chat.api.component.SemanticParser; +import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticParser; -import com.tencent.supersonic.chat.application.parser.resolver.AggregateTypeResolver; -import com.tencent.supersonic.chat.application.query.MetricCompare; -import com.tencent.supersonic.chat.application.query.MetricFilter; +import com.tencent.supersonic.chat.application.query.EntityListFilter; import com.tencent.supersonic.chat.application.query.MetricGroupBy; import com.tencent.supersonic.chat.application.query.MetricOrderBy; import com.tencent.supersonic.common.enums.AggregateTypeEnum; -import com.tencent.supersonic.common.pojo.SchemaItem; -import com.tencent.supersonic.common.util.context.ContextUtils; -import java.util.List; -import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; -@Component +@Slf4j public class AggregateSemanticParser implements SemanticParser { - private final Logger logger = LoggerFactory.getLogger(AggregateSemanticParser.class); public static final Integer TOPN_LIMIT = 1000; - private AggregateTypeResolver aggregateTypeResolver; + private static Map aggregateRegexMap = new HashMap<>(); + + static { + aggregateRegexMap.put(AggregateTypeEnum.MAX, Pattern.compile("(?i)(最大值|最大|max|峰值|最高|最多)")); + aggregateRegexMap.put(AggregateTypeEnum.MIN, Pattern.compile("(?i)(最小值|最小|min|最低|最少)")); + aggregateRegexMap.put(AggregateTypeEnum.SUM, Pattern.compile("(?i)(汇总|总和|sum)")); + aggregateRegexMap.put(AggregateTypeEnum.AVG, Pattern.compile("(?i)(平均值|日均|平均|avg)")); + aggregateRegexMap.put(AggregateTypeEnum.TOPN, Pattern.compile("(?i)(top)")); + aggregateRegexMap.put(AggregateTypeEnum.DISTINCT, Pattern.compile("(?i)(uv)")); + aggregateRegexMap.put(AggregateTypeEnum.COUNT, Pattern.compile("(?i)(总数|pv)")); + aggregateRegexMap.put(AggregateTypeEnum.NONE, Pattern.compile("(?i)(明细)")); + } + + public static AggregateTypeEnum resolveAggregateType(String queryText) { + + Map aggregateCount = new HashMap<>(aggregateRegexMap.size()); + for (Map.Entry entry : aggregateRegexMap.entrySet()) { + Matcher matcher = entry.getValue().matcher(queryText); + int count = 0; + while (matcher.find()) { + count++; + } + if (count > 0) { + aggregateCount.put(entry.getKey(), count); + } + } + + return aggregateCount.entrySet().stream().max(Map.Entry.comparingByValue()).map(entry -> entry.getKey()) + .orElse(null); + } @Override - public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) { - aggregateTypeResolver = ContextUtils.getBean(AggregateTypeResolver.class); + public void parse(QueryContextReq queryContext, ChatContext chatContext) { + AggregateTypeEnum aggregateType = resolveAggregateType(queryContext.getQueryText()); - AggregateTypeEnum aggregateType = aggregateTypeResolver.resolve(queryContext.getQueryText()); + for (SemanticQuery semanticQuery : queryContext.getCandidateQueries()) { + SemanticParseInfo semanticParse = semanticQuery.getParseInfo(); - SemanticParseInfo semanticParse = queryContext.getParseInfo(); - - Set metrics = semanticParse.getMetrics(); - - semanticParse.setNativeQuery(getNativeQuery(aggregateType, queryContext)); - - semanticParse.setAggType(aggregateType); - //semanticParse.setOrders(getOrder(aggregateType, metrics)); - semanticParse.setLimit(Long.valueOf(TOPN_LIMIT)); - resetQueryModeByAggregateType(queryContext, aggregateType); - return false; + semanticParse.setNativeQuery(getNativeQuery(aggregateType, semanticParse)); + semanticParse.setAggType(aggregateType); + if (Objects.isNull(semanticParse.getLimit()) || semanticParse.getLimit() <= 0) { + semanticParse.setLimit(Long.valueOf(TOPN_LIMIT)); + } + resetQueryModeByAggregateType(semanticParse, aggregateType); + } } /** * query mode reset by the AggregateType * - * @param queryContext + * @param parseInfo * @param aggregateType */ - private void resetQueryModeByAggregateType(QueryContextReq queryContext, AggregateTypeEnum aggregateType) { + private void resetQueryModeByAggregateType(SemanticParseInfo parseInfo, + AggregateTypeEnum aggregateType) { - SemanticParseInfo parseInfo = queryContext.getParseInfo(); String queryMode = parseInfo.getQueryMode(); if (MetricGroupBy.QUERY_MODE.equals(queryMode) || MetricGroupBy.QUERY_MODE.equals(queryMode)) { if (AggregateTypeEnum.MAX.equals(aggregateType) || AggregateTypeEnum.MIN.equals(aggregateType) @@ -62,23 +85,18 @@ public class AggregateSemanticParser implements SemanticParser { } else { parseInfo.setQueryMode(MetricGroupBy.QUERY_MODE); } + log.info("queryMode mode [{}]->[{}]", queryMode, parseInfo.getQueryMode()); } - if (MetricFilter.QUERY_MODE.equals(queryMode) || MetricCompare.QUERY_MODE.equals(queryMode)) { - if (aggregateTypeResolver.hasCompareIntentionalWords(queryContext.getQueryText())) { - parseInfo.setQueryMode(MetricCompare.QUERY_MODE); - } else { - parseInfo.setQueryMode(MetricFilter.QUERY_MODE); - } - } - logger.info("queryMode mode [{}]->[{}]", queryMode, parseInfo.getQueryMode()); } - private boolean getNativeQuery(AggregateTypeEnum aggregateType, QueryContextReq queryContext) { + private boolean getNativeQuery(AggregateTypeEnum aggregateType, SemanticParseInfo semanticParse) { if (AggregateTypeEnum.TOPN.equals(aggregateType)) { return true; } - return queryContext.getParseInfo().getNativeQuery(); + if (EntityListFilter.QUERY_MODE.equals(semanticParse.getQueryMode()) && (semanticParse.getMetrics() == null + || semanticParse.getMetrics().isEmpty())) { + return true; + } + return semanticParse.getNativeQuery(); } - - } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainResolver.java similarity index 59% rename from chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainResolver.java rename to chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainResolver.java index 9c32fcd38..b3ca4e295 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainResolver.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainResolver.java @@ -1,10 +1,12 @@ -package com.tencent.supersonic.chat.application.parser.resolver; +package com.tencent.supersonic.chat.application.parser; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticQuery; +import com.tencent.supersonic.chat.api.component.SemanticQuery; + import java.util.Map; public interface DomainResolver { @@ -12,6 +14,6 @@ public interface DomainResolver { Integer resolve(Map domainQueryModes, QueryContextReq queryCtx, ChatContext chatCtx, SchemaMapInfo schemaMap); - boolean isDomainSwitch(ChatContext chatCtx, QueryContextReq queryCtx); + boolean isDomainSwitch(ChatContext chatCtx, SemanticParseInfo semanticParseInfo); } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainSemanticParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainSemanticParser.java index 10d5db455..32cb52923 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainSemanticParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DomainSemanticParser.java @@ -1,125 +1,111 @@ package com.tencent.supersonic.chat.application.parser; -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticParser; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.*; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticLayer; -import com.tencent.supersonic.chat.api.service.SemanticParser; -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver; -import com.tencent.supersonic.chat.application.parser.resolver.SemanticQueryResolver; +import com.tencent.supersonic.chat.application.query.*; import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos; -import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; +import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; +import com.tencent.supersonic.chat.domain.utils.*; +import com.tencent.supersonic.common.pojo.SchemaItem; import com.tencent.supersonic.common.util.context.ContextUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; +import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; -@Component +@Slf4j public class DomainSemanticParser implements SemanticParser { - private final Logger logger = LoggerFactory.getLogger(DomainSemanticParser.class); - private List domainResolverList; - - private SemanticQueryResolver semanticQueryResolver; - - public DomainSemanticParser() { - domainResolverList = SpringFactoriesLoader.loadFactories(DomainResolver.class, - Thread.currentThread().getContextClassLoader()); - } + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); @Override - public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) { - DomainInfos domainInfosDb = SchemaInfoConverter.convert( - ContextUtils.getBean(SemanticLayer.class).getDomainSchemaInfo(new ArrayList<>())); + public void parse(QueryContextReq queryContext, ChatContext chatContext) { + DomainInfos domainInfosDb = SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>())); Map domainToName = domainInfosDb.getDomainToName(); - SchemaMapInfo mapInfo = queryContext.getMapInfo(); - SemanticParseInfo parseInfo = queryContext.getParseInfo(); - //domainResolver = ContextUtils.getBean(DomainResolver.class); - semanticQueryResolver = ContextUtils.getBean(SemanticQueryResolver.class); - - Map domainSemanticQuery = new HashMap<>(); - // Round 1: find all domains that can be resolved to any query mode - - for (Integer domain : mapInfo.getMatchedDomains()) { - List elementMatches = mapInfo.getMatchedElements(domain); - - SemanticQuery query = semanticQueryResolver.resolve(elementMatches, queryContext); - - if (Objects.nonNull(query)) { - domainSemanticQuery.put(domain, query); + // iterate all schemaElementMatches to resolve semantic query + for (Integer domainId : mapInfo.getMatchedDomains()) { + List elementMatches = mapInfo.getMatchedElements(domainId); + Map> queryMatches = resolveQuery(elementMatches, queryContext); + for (Map.Entry> match : queryMatches.entrySet()) { + addCandidateQuery(queryContext, chatContext, domainId.longValue(), + domainToName.get(domainId), match.getKey(), match.getValue()); } } - // only one domain is found, no need to rank - if (domainSemanticQuery.size() == 1) { - Optional> match = domainSemanticQuery.entrySet().stream().findFirst(); - if (match.isPresent()) { - logger.info("select by only one [{}:{}]", match.get().getKey(), match.get().getValue()); - parseInfo.setDomainId(Long.valueOf(match.get().getKey())); - parseInfo.setDomainName(domainToName.get(Integer.valueOf(match.get().getKey()))); - parseInfo.setQueryMode(match.get().getValue().getQueryMode()); - return false; - } - } else if (domainSemanticQuery.size() > 1) { - // will choose one by the domain select - Optional domainId = domainResolverList.stream() - .map(domainResolver -> domainResolver.resolve(domainSemanticQuery, queryContext, chatCtx, mapInfo)) - .filter(d -> d > 0).findFirst(); - if (domainId.isPresent() && domainId.get() > 0) { - for (Map.Entry match : domainSemanticQuery.entrySet()) { - if (match.getKey().equals(domainId.get())) { - logger.info("select by selectStrategy [{}:{}]", domainId.get(), match.getValue()); - parseInfo.setDomainId(Long.valueOf(match.getKey())); - parseInfo.setDomainName(domainToName.get(Integer.valueOf(match.getKey()))); - parseInfo.setQueryMode(match.getValue().getQueryMode()); - return false; + + // if no candidates have been found yet, count in chat context and try again + if (queryContext.getCandidateQueries().size() <= 0) { + if (chatContext.getParseInfo() != null && chatContext.getParseInfo().getDomainId() > 0) { + Integer chatDomainId = Integer.valueOf(chatContext.getParseInfo().getDomainId().intValue()); + if (mapInfo.getMatchedDomains().contains(chatDomainId)) { + List elementMatches = mapInfo.getMatchedElements(chatDomainId); + detectionContext(chatContext); + Map> queryMatches = tryParseByContext(elementMatches, + chatContext, queryContext); + for (Map.Entry> match : queryMatches.entrySet()) { + addCandidateQuery(queryContext, chatContext, chatDomainId.longValue(), + domainToName.get(chatDomainId), match.getKey(), match.getValue()); } } } } - // Round 2: no domains can be found yet, count in chat context - if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getDomainId() > 0) { - Integer chatDomain = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue()); - if (mapInfo.getMatchedDomains().contains(chatDomain) || CollectionUtils.isEmpty( - mapInfo.getMatchedDomains())) { - List elementMatches = mapInfo.getMatchedElements(chatDomain); - if (CollectionUtils.isEmpty(elementMatches)) { - parseInfo.setDomainId(Long.valueOf(chatDomain)); - parseInfo.setDomainName(domainToName.get(chatDomain)); - parseInfo.setQueryMode(chatCtx.getParseInfo().getQueryMode()); - return false; - } - - SemanticQuery query = tryParseByContext(elementMatches, chatCtx, queryContext); - if (Objects.nonNull(query)) { - logger.info("select by context count [{}:{}]", chatDomain, query); - parseInfo.setDomainId(Long.valueOf(chatDomain)); - parseInfo.setDomainName(domainToName.get(chatDomain)); - parseInfo.setQueryMode(query.getQueryMode()); - return false; - } - } - } - // Round 3: no domains can be found yet, count in default metric - return false; } + private void addCandidateQuery(QueryContextReq queryContext, ChatContext chatContext, + Long domainId, String domainName, + RuleSemanticQuery semanticQuery, List elementMatches) { + if (semanticQuery != null) { + fillParseInfo(semanticQuery, domainId, domainName, elementMatches); + // inherit from context + inheritContext(semanticQuery, chatContext); + // default metric, date, dimension + injectDefaultMetric(semanticQuery, queryContext, chatContext); + queryContext.getCandidateQueries().add(semanticQuery); + } + } + + protected void inheritContext(RuleSemanticQuery semanticQuery, ChatContext chatContext) { + // is domain switch + SemanticParseInfo semanticParse = semanticQuery.getParseInfo(); + DomainResolver domainResolver = ComponentFactory.getDomainResolver(); + if (!domainResolver.isDomainSwitch(chatContext, semanticParse)) { + semanticQuery.inheritContext(chatContext); + } + } + + protected void injectDefaultMetric(RuleSemanticQuery semanticQuery, QueryContextReq queryContext, + ChatContext chatContext) { + DefaultMetricUtils defaultMetricUtils = ContextUtils.getBean(DefaultMetricUtils.class); + defaultMetricUtils.injectDefaultMetric(semanticQuery.getParseInfo(), queryContext, chatContext); + } + + /** + * get the chatContext for the tryParseByContext + * + * @param chatContext + */ + protected void detectionContext(ChatContext chatContext) { + if (chatContext.getParseInfo() != null) { + SemanticParseInfo semanticParseInfo = chatContext.getParseInfo(); + if (semanticParseInfo.getQueryMode().equals(EntityDetail.QUERY_MODE)) { + // EntityDetail model will unset some items + semanticParseInfo.setDateInfo(null); + semanticParseInfo.setMetrics(new HashSet<>()); + semanticParseInfo.setDimensions(new HashSet<>()); + } + } + } /** * try to add ChatContext to SchemaElementMatch and look if match QueryMode @@ -128,8 +114,8 @@ public class DomainSemanticParser implements SemanticParser { * @param chatCtx * @return */ - private SemanticQuery tryParseByContext(List elementMatches, ChatContext chatCtx, - QueryContextReq searchCt) { + private Map> tryParseByContext(List elementMatches, + ChatContext chatCtx, QueryContextReq queryCtx) { if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getEntity() > 0) { Long entityCount = elementMatches.stream().filter(i -> SchemaElementType.ENTITY.equals(i.getElementType())) .count(); @@ -137,27 +123,29 @@ public class DomainSemanticParser implements SemanticParser { .count(); if (entityCount <= 0 && metricCount <= 0 && ContextHelper.hasEntityId(chatCtx)) { // try entity parse - SchemaElementMatch entityElementMatch = new SchemaElementMatch(); - entityElementMatch.setElementType(SchemaElementType.ENTITY); + SchemaElementMatch entityElementMatch = SchemaElementMatch.builder() + .elementType(SchemaElementType.ENTITY).build(); List newSchemaElementMatch = new ArrayList<>(); if (!CollectionUtils.isEmpty(elementMatches)) { newSchemaElementMatch.addAll(elementMatches); } newSchemaElementMatch.add(entityElementMatch); - SemanticQuery semanticQuery = doParseByContext(newSchemaElementMatch, chatCtx, searchCt); - if (Objects.nonNull(semanticQuery)) { - return semanticQuery; + Map> queryMatches = doParseByContext(newSchemaElementMatch, + chatCtx, queryCtx); + if (queryMatches.size() > 0) { + return queryMatches; } } } - return doParseByContext(elementMatches, chatCtx, searchCt); + return doParseByContext(elementMatches, chatCtx, queryCtx); } - private SemanticQuery doParseByContext(List elementMatches, ChatContext chatCtx, - QueryContextReq searchCt) { - SemanticParseInfo contextSemanticParseInfo = chatCtx.getParseInfo(); - if (contextSemanticParseInfo != null) { - List newSchemaElementMatch = new ArrayList<>(); + + private Map> doParseByContext(List elementMatches, + ChatContext chatCtx, QueryContextReq queryCtx) { + SemanticParseInfo contextSemanticParse = chatCtx.getParseInfo(); + if (contextSemanticParse != null) { + List newElementMatches = new ArrayList<>(); List> trySchemaElementTypes = new LinkedList<>(); // try DIMENSION+METRIC+VALUE // try DIMENSION+METRIC METRIC+VALUE DIMENSION+VALUE @@ -175,18 +163,122 @@ public class DomainSemanticParser implements SemanticParser { trySchemaElementTypes.add(new ArrayList<>(Arrays.asList(SchemaElementType.DIMENSION))); for (List schemaElementTypes : trySchemaElementTypes) { - newSchemaElementMatch.clear(); + newElementMatches.clear(); if (!CollectionUtils.isEmpty(elementMatches)) { - newSchemaElementMatch.addAll(elementMatches); + newElementMatches.addAll(elementMatches); } - ContextHelper.mergeContextSchemaElementMatch(newSchemaElementMatch, elementMatches, schemaElementTypes, - contextSemanticParseInfo); - SemanticQuery semanticQuery = semanticQueryResolver.resolve(newSchemaElementMatch, searchCt); - if (semanticQuery != null) { - return semanticQuery; + ContextHelper.mergeContextSchemaElementMatch(newElementMatches, elementMatches, schemaElementTypes, + contextSemanticParse); + Map> queryMatches = resolveQuery(newElementMatches, + queryCtx); + if (queryMatches.size() > 0) { + return queryMatches; } } } - return null; + return new HashMap<>(); } -} + + private Map> resolveQuery(List elementMatches, + QueryContextReq queryCtx) { + Map> matchMap = new HashMap<>(); + + for (RuleSemanticQuery semanticQuery : RuleSemanticQueryManager.getSemanticQueries()) { + List matches = semanticQuery.match(elementMatches, queryCtx); + + if (matches.size() > 0) { + log.info("resolve match [{}:{}] ", semanticQuery.getQueryMode(), matches.size()); + matchMap.put(RuleSemanticQueryManager.create(semanticQuery.getQueryMode()), matches); + } + } + + return matchMap; + } + + public void fillParseInfo(SemanticQuery query, Long domainId, String domainName, + List elementMatches) { + SemanticParseInfo parseInfo = query.getParseInfo(); + parseInfo.setDomainId(domainId); + parseInfo.setDomainName(domainName); + parseInfo.setQueryMode(query.getQueryMode()); + parseInfo.getElementMatches().addAll(elementMatches); + + DefaultSemanticInternalUtils defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class); + SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); + + DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(parseInfo.getDomainId()); + ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(parseInfo.getDomainId()); + Map dimensionDescMap = domainSchemaDesc.getDimensions().stream() + .collect(Collectors.toMap(DimSchemaResp::getId, Function.identity())); + Map metricDescMap = domainSchemaDesc.getMetrics().stream() + .collect(Collectors.toMap(MetricSchemaResp::getId, Function.identity())); + Map> dim2Values = new HashMap<>(); + + for (SchemaElementMatch schemaElementMatch : elementMatches) { + Long elementID = Long.valueOf(schemaElementMatch.getElementID()); + switch (schemaElementMatch.getElementType()) { + case ID: + case VALUE: + if (dimensionDescMap.containsKey(elementID)) { + if (dim2Values.containsKey(elementID)) { + dim2Values.get(elementID).add(schemaElementMatch); + } else { + dim2Values.put(elementID, new ArrayList<>(Arrays.asList(schemaElementMatch))); + } + } + break; + case DIMENSION: + DimSchemaResp dimensionDesc = dimensionDescMap.get(elementID); + if (dimensionDesc != null) { + SchemaItem dimensionParseInfo = new SchemaItem(); + dimensionParseInfo.setBizName(dimensionDesc.getBizName()); + dimensionParseInfo.setName(dimensionDesc.getName()); + dimensionParseInfo.setId(dimensionDesc.getId()); + parseInfo.getDimensions().add(dimensionParseInfo); + } + break; + case METRIC: + MetricSchemaResp metricDesc = metricDescMap.get(elementID); + if (metricDesc != null) { + SchemaItem metricItem = new SchemaItem(); + metricItem.setBizName(metricDesc.getBizName()); + metricItem.setName(metricDesc.getName()); + metricItem.setId(metricDesc.getId()); + metricItem.setCreatedAt(null); + metricItem.setUpdatedAt(null); + parseInfo.getMetrics().add(metricItem); + } + break; + default: + } + } + + if (!dim2Values.isEmpty()) { + for (Map.Entry> entry : dim2Values.entrySet()) { + DimSchemaResp dimensionDesc = dimensionDescMap.get(entry.getKey()); + if (entry.getValue().size() == 1) { + SchemaElementMatch schemaElementMatch = entry.getValue().get(0); + Filter dimensionFilter = new Filter(); + dimensionFilter.setValue(schemaElementMatch.getWord()); + dimensionFilter.setBizName(dimensionDesc.getBizName()); + dimensionFilter.setName(dimensionDesc.getName()); + dimensionFilter.setOperator(FilterOperatorEnum.EQUALS); + dimensionFilter.setElementID(Long.valueOf(schemaElementMatch.getElementID())); + parseInfo.getDimensionFilters().add(dimensionFilter); + ContextHelper.setEntityId(entry.getKey(), schemaElementMatch.getWord(), chaConfigRichDesc, + parseInfo); + } else { + Filter dimensionFilter = new Filter(); + List vals = new ArrayList<>(); + entry.getValue().stream().forEach(i -> vals.add(i.getWord())); + dimensionFilter.setValue(vals); + dimensionFilter.setBizName(dimensionDesc.getBizName()); + dimensionFilter.setName(dimensionDesc.getName()); + dimensionFilter.setOperator(FilterOperatorEnum.IN); + dimensionFilter.setElementID(entry.getKey()); + parseInfo.getDimensionFilters().add(dimensionFilter); + } + } + } + } +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/BaseDomainResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/HeuristicDomainResolver.java similarity index 57% rename from chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/BaseDomainResolver.java rename to chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/HeuristicDomainResolver.java index 62edf7521..1b781be17 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/BaseDomainResolver.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/HeuristicDomainResolver.java @@ -1,36 +1,117 @@ -package com.tencent.supersonic.chat.application.parser.resolver; +package com.tencent.supersonic.chat.application.parser; -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; +import com.tencent.supersonic.chat.api.pojo.*; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticQuery; +import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -@Slf4j -public abstract class BaseDomainResolver implements DomainResolver { +import java.util.*; +import java.util.stream.Collectors; - @Override - public boolean isDomainSwitch(ChatContext chatCtx, QueryContextReq searchCtx) { - Long contextDomain = chatCtx.getParseInfo().getDomainId(); - Long currentDomain = searchCtx.getParseInfo().getDomainId(); - boolean noSwitch = currentDomain == null || contextDomain == null || contextDomain.equals(currentDomain); - log.info("ChatContext isDomainSwitch [{}]", !noSwitch); - return !noSwitch; +@Slf4j +public class HeuristicDomainResolver implements DomainResolver { + + protected static Integer selectDomainBySchemaElementCount(Map domainQueryModes, + SchemaMapInfo schemaMap) { + Map domainTypeMap = getDomainTypeMap(schemaMap); + if (domainTypeMap.size() == 1) { + Integer domainSelect = domainTypeMap.entrySet().stream().collect(Collectors.toList()).get(0).getKey(); + if (domainQueryModes.containsKey(domainSelect)) { + log.info("selectDomain from domainTypeMap not order [{}]", domainSelect); + return domainSelect; + } + } else { + Map.Entry maxDomain = domainTypeMap.entrySet().stream() + .filter(entry -> domainQueryModes.containsKey(entry.getKey())) + .sorted(ContextHelper.DomainStatComparator).findFirst().orElse(null); + if (maxDomain != null) { + log.info("selectDomain from domainTypeMap order [{}]", maxDomain.getKey()); + return maxDomain.getKey(); + } + } + return 0; } - public abstract Integer selectDomain(Map domainQueryModes, QueryContextReq searchCtx, - ChatContext chatCtx, SchemaMapInfo schemaMap); + /** + * to check can switch domain if context exit domain + * + * @return false will use context domain, true will use other domain , maybe include context domain + */ + protected static boolean isAllowSwitch(Map domainQueryModes, SchemaMapInfo schemaMap, + ChatContext chatCtx, QueryContextReq searchCtx, Integer domainId) { + if (!Objects.nonNull(domainId) || domainId <= 0) { + return true; + } + // except content domain, calculate the number of types for each domain, if numbers<=1 will not switch + Map domainTypeMap = getDomainTypeMap(schemaMap); + log.info("isAllowSwitch domainTypeMap [{}]", domainTypeMap); + long otherDomainTypeNumBigOneCount = domainTypeMap.entrySet().stream() + .filter(entry -> domainQueryModes.containsKey(entry.getKey()) && !entry.getKey().equals(domainId)) + .filter(entry -> entry.getValue().getCount() > 1).count(); + if (otherDomainTypeNumBigOneCount >= 1) { + return true; + } + // if query text only contain time , will not switch + for (SemanticQuery semanticQuery : domainQueryModes.values()) { + SemanticParseInfo semanticParseInfo = semanticQuery.getParseInfo(); + if (semanticParseInfo == null) { + continue; + } + if (searchCtx.getQueryText() != null && semanticParseInfo.getDateInfo() != null) { + if (semanticParseInfo.getDateInfo().getText() != null) { + if (semanticParseInfo.getDateInfo().getText().equalsIgnoreCase(searchCtx.getQueryText())) { + log.info("timeParseResults is not null , can not switch context , timeParseResults:{},", + semanticParseInfo.getDateInfo()); + return false; + } + } + } + } + + // if context domain not in schemaMap , will switch + if (schemaMap.getMatchedElements(domainId) == null || schemaMap.getMatchedElements(domainId).size() <= 0) { + log.info("domainId not in schemaMap "); + return true; + } + // other will not switch + return false; + } + + public static Map getDomainTypeMap(SchemaMapInfo schemaMap) { + Map domainCount = new HashMap<>(); + for (Map.Entry> entry : schemaMap.getDomainElementMatches().entrySet()) { + List schemaElementMatches = schemaMap.getMatchedElements(entry.getKey()); + if (schemaElementMatches != null && schemaElementMatches.size() > 0) { + if (!domainCount.containsKey(entry.getKey())) { + domainCount.put(entry.getKey(), new QueryMatchInfo()); + } + QueryMatchInfo queryMatchInfo = domainCount.get(entry.getKey()); + Set schemaElementTypes = new HashSet<>(); + schemaElementMatches.stream() + .forEach(schemaElementMatch -> schemaElementTypes.add(schemaElementMatch.getElementType())); + SchemaElementMatch schemaElementMatchMax = schemaElementMatches.stream() + .sorted(ContextHelper.schemaElementMatchComparatorBySimilarity).findFirst().orElse(null); + if (schemaElementMatchMax != null) { + queryMatchInfo.setMaxSimilarity(schemaElementMatchMax.getSimilarity()); + } + queryMatchInfo.setCount(schemaElementTypes.size()); + + } + } + return domainCount; + } + + @Override + public boolean isDomainSwitch(ChatContext chatCtx, SemanticParseInfo semanticParseInfo) { + Long contextDomain = chatCtx.getParseInfo().getDomainId(); + Long currentDomain = semanticParseInfo.getDomainId(); + boolean noSwitch = + currentDomain == null || contextDomain == null || contextDomain.equals(currentDomain); + log.debug("ChatContext isDomainSwitch [{}] [{}]", + semanticParseInfo.getQueryMode(), !noSwitch); + return !noSwitch; + } @Override public Integer resolve(Map domainQueryModes, QueryContextReq searchCtx, @@ -44,88 +125,23 @@ public abstract class BaseDomainResolver implements DomainResolver { return selectDomainBySchemaElementCount(domainQueryModes, schemaMap); } - protected static Integer selectDomainBySchemaElementCount(Map domainQueryModes, + public Integer selectDomain(Map domainQueryModes, QueryContextReq searchCtx, + ChatContext chatCtx, SchemaMapInfo schemaMap) { - Map domainTypeMap = getDomainTypeMap(schemaMap); - if (domainTypeMap.size() == 1) { - Integer domainSelect = domainTypeMap.entrySet().stream().collect(Collectors.toList()).get(0).getKey(); - if (domainQueryModes.containsKey(domainSelect)) { - log.info("selectDomain from domainTypeMap not order [{}]", domainSelect); - return domainSelect; - } - } else { - Map.Entry maxDomain = domainTypeMap.entrySet().stream() - .filter(entry -> domainQueryModes.containsKey(entry.getKey())) - .sorted(ContextHelper.DomainStatComparator).findFirst().orElse(null); - if (maxDomain != null) { - log.info("selectDomain from domainTypeMap order [{}]", maxDomain.getKey()); - return maxDomain.getKey(); + // if QueryContext has domainId and in domainQueryModes + if (domainQueryModes.containsKey(searchCtx.getDomainId())) { + log.info("selectDomain from QueryContext [{}]", searchCtx.getDomainId()); + return searchCtx.getDomainId(); + } + // if ChatContext has domainId and in domainQueryModes + if (chatCtx.getParseInfo().getDomainId() > 0) { + Integer domainId = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue()); + if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, searchCtx, domainId)) { + log.info("selectDomain from ChatContext [{}]", domainId); + return domainId; } } + // default 0 return 0; } - - - /** - * to check can switch domain if context exit domain - * - * @return false will use context domain, true will use other domain , maybe include context domain - */ - protected static boolean isAllowSwitch(Map domainQueryModes, SchemaMapInfo schemaMap, - ChatContext chatCtx, QueryContextReq searchCtx, Integer domainId) { - if (!Objects.nonNull(domainId) || domainId <= 0) { - return true; - } - // except content domain, calculate the number of types for each domain, if numbers<=1 will not switch - Map domainTypeMap = getDomainTypeMap(schemaMap); - log.info("isAllowSwitch domainTypeMap [{}]", domainTypeMap); - long otherDomainTypeNumBigOneCount = domainTypeMap.entrySet().stream() - .filter(entry -> domainQueryModes.containsKey(entry.getKey()) && !entry.getKey().equals(domainId)) - .filter(entry -> entry.getValue().getCount() > 1).count(); - if (otherDomainTypeNumBigOneCount >= 1) { - return true; - } - // if query text only contain time , will not switch - if (searchCtx.getQueryText() != null && searchCtx.getParseInfo().getDateInfo() != null) { - if (searchCtx.getParseInfo().getDateInfo().getText() != null) { - if (searchCtx.getParseInfo().getDateInfo().getText().equalsIgnoreCase(searchCtx.getQueryText())) { - log.info("timeParseResults is not null , can not switch context , timeParseResults:{},", - searchCtx.getParseInfo().getDateInfo()); - return false; - } - } - } - // if context domain not in schemaMap , will switch - if (schemaMap.getMatchedElements(domainId) == null || schemaMap.getMatchedElements(domainId).size() <= 0) { - log.info("domainId not in schemaMap "); - return true; - } - // other will not switch - return false; - } - - protected static Map getDomainTypeMap(SchemaMapInfo schemaMap) { - Map domainCount = new HashMap<>(); - for (Map.Entry> entry : schemaMap.getDomainElementMatches().entrySet()) { - List schemaElementMatches = schemaMap.getMatchedElements(entry.getKey()); - if (schemaElementMatches != null && schemaElementMatches.size() > 0) { - if (!domainCount.containsKey(entry.getKey())) { - domainCount.put(entry.getKey(), new SchemaElementCount()); - } - SchemaElementCount schemaElementCount = domainCount.get(entry.getKey()); - Set schemaElementTypes = new HashSet<>(); - schemaElementMatches.stream() - .forEach(schemaElementMatch -> schemaElementTypes.add(schemaElementMatch.getElementType())); - SchemaElementMatch schemaElementMatchMax = schemaElementMatches.stream() - .sorted(ContextHelper.schemaElementMatchComparatorBySimilarity).findFirst().orElse(null); - if (schemaElementMatchMax != null) { - schemaElementCount.setMaxSimilarity(schemaElementMatchMax.getSimilarity()); - } - schemaElementCount.setCount(schemaElementTypes.size()); - - } - } - return domainCount; - } - -} +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/LLMSemanticParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/LLMSemanticParser.java new file mode 100644 index 000000000..ebb633a1d --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/LLMSemanticParser.java @@ -0,0 +1,148 @@ +package com.tencent.supersonic.chat.application.parser; + +import com.tencent.supersonic.chat.api.component.SemanticParser; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; +import com.tencent.supersonic.chat.api.pojo.SchemaElementType; +import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import com.tencent.supersonic.chat.application.knowledge.WordNatureService; +import com.tencent.supersonic.chat.application.query.LLMSemanticQuery; +import com.tencent.supersonic.chat.domain.config.LLMConfig; +import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos; +import com.tencent.supersonic.chat.domain.pojo.chat.LLMReq; +import com.tencent.supersonic.chat.domain.pojo.chat.LLMResp; +import com.tencent.supersonic.chat.domain.pojo.chat.LLMSchema; +import com.tencent.supersonic.chat.domain.utils.DslToSemanticInfo; +import com.tencent.supersonic.chat.domain.utils.SemanticSatisfactionChecker; +import com.tencent.supersonic.common.nlp.ItemDO; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.common.util.json.JsonUtil; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +@Slf4j +public class LLMSemanticParser implements SemanticParser { + + @Override + public void parse(QueryContextReq queryContext, ChatContext chatCtx) { + if (SemanticSatisfactionChecker.check(queryContext)) { + log.info("There is no need parse by llm , queryText:{}", queryContext.getQueryText()); + return; + } + + Integer domainId = getDomainId(queryContext, chatCtx); + LLMResp llmResp = requestLLM(queryContext, domainId); + if (Objects.isNull(llmResp)) { + return; + } + LLMSemanticQuery semanticQuery = new LLMSemanticQuery(); + SemanticParseInfo parseInfo = semanticQuery.getParseInfo(); + String sql = convertToSql(llmResp, parseInfo); + parseInfo.setDomainId(Long.valueOf(domainId)); + parseInfo.setBonus(queryContext.getQueryText().length() * 1.0); + parseInfo.setQueryMode(LLMSemanticQuery.QUERY_MODE); + parseInfo.setInfo(sql); + queryContext.getCandidateQueries().add(semanticQuery); + return; + } + + protected String convertToSql(LLMResp llmResp, SemanticParseInfo parseInfo) { + return DslToSemanticInfo.convert(parseInfo, llmResp); + } + + protected LLMResp requestLLM(QueryContextReq queryContext, Integer domainId) { + try { + final LLMConfig llmConfig = ContextUtils.getBean(LLMConfig.class); + + DomainInfos domainInfos = ContextUtils.getBean(WordNatureService.class).getCache().getUnchecked(""); + + Map domainIdToName = domainInfos.getDomains().stream() + .collect(Collectors.toMap(ItemDO::getDomain, a -> a.getName(), (k1, k2) -> k1)); + + Map itemIdToName = domainInfos.getDimensions().stream() + .filter(entry -> domainId.equals(entry.getDomain())) + .collect(Collectors.toMap(ItemDO::getItemId, ItemDO::getName, (value1, value2) -> value2)); + + String domainName = domainIdToName.get(domainId); + LLMReq llmReq = new LLMReq(); + llmReq.setQueryText(queryContext.getQueryText()); + + List matchedElements = queryContext.getMapInfo().getMatchedElements(domainId); + + List fieldNameList = matchedElements.stream() + .filter(schemaElementMatch -> + SchemaElementType.METRIC.equals(schemaElementMatch.getElementType()) || + SchemaElementType.DIMENSION.equals(schemaElementMatch.getElementType()) || + SchemaElementType.VALUE.equals(schemaElementMatch.getElementType())) + .map(schemaElementMatch -> { + if (!SchemaElementType.VALUE.equals(schemaElementMatch.getElementType())) { + return schemaElementMatch.getWord(); + } + return itemIdToName.get(schemaElementMatch.getElementID()); + }) + .filter(name -> StringUtils.isNotEmpty(name) && !name.contains("%")) + .collect(Collectors.toList()); + + LLMSchema llmSchema = new LLMSchema(); + llmSchema.setDomainName(domainName); + llmSchema.setFieldNameList(fieldNameList); + llmReq.setSchema(llmSchema); + + log.info("domainId:{},llmReq:{}", domainId, llmReq); + String questUrl = llmConfig.getUrl() + llmConfig.getQueryToSqlPath(); + + RestTemplate restTemplate = ContextUtils.getBean(RestTemplate.class); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(JsonUtil.toString(llmReq), headers); + + log.info("requestLLM request:{},entity:{}", questUrl, entity); + ResponseEntity responseEntity = restTemplate.exchange(questUrl, HttpMethod.POST, entity, + LLMResp.class); + + log.info("requestLLM result:{}", responseEntity); + return responseEntity.getBody(); + } catch (Exception e) { + log.error("requestLLM error", e); + } + return null; + } + + protected Integer getDomainId(QueryContextReq queryContext, ChatContext chatCtx) { + SchemaMapInfo mapInfo = queryContext.getMapInfo(); + Set matchedDomains = mapInfo.getMatchedDomains(); + Map domainQueryModes = new HashMap<>(); + for (Integer matchedDomain : matchedDomains) { + domainQueryModes.put(matchedDomain, new LLMSemanticQuery()); + } + List domainResolverList = SpringFactoriesLoader.loadFactories(DomainResolver.class, + Thread.currentThread().getContextClassLoader()); + Optional domainId = domainResolverList.stream() + .map(domainResolver -> domainResolver.resolve(domainQueryModes, queryContext, chatCtx, + queryContext.getMapInfo())).filter(d -> d > 0).findFirst(); + if (domainId.isPresent()) { + return domainId.get(); + } + return 0; + } +} + + diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/ListFilterParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/ListFilterParser.java deleted file mode 100644 index a3f7a1e4f..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/ListFilterParser.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.tencent.supersonic.chat.application.parser; - -import static com.tencent.supersonic.common.constant.Constants.DAY; - -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticParser; -import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; -import com.tencent.supersonic.chat.application.query.EntityListFilter; -import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; -import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo; -import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; -import com.tencent.supersonic.common.constant.Constants; -import com.tencent.supersonic.common.pojo.DateConf; -import com.tencent.supersonic.common.pojo.Order; -import com.tencent.supersonic.common.pojo.SchemaItem; -import com.tencent.supersonic.common.util.context.ContextUtils; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -@Component -public class ListFilterParser implements SemanticParser { - - - private DefaultSemanticInternalUtils defaultSemanticUtils; - - @Override - public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) { - defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class); - - String queryMode = queryContext.getParseInfo().getQueryMode(); - if (!EntityListFilter.QUERY_MODE.equals(queryMode)) { - return false; - } - this.fillDateEntityFilter(queryContext.getParseInfo()); - this.addEntityDetailAndOrderByMetric(queryContext, chatCtx); - this.dealNativeQuery(queryContext, true); - return true; - - } - - private void fillDateEntityFilter(SemanticParseInfo semanticParseInfo) { - DateConf dateInfo = new DateConf(); - dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); - dateInfo.setUnit(1); - dateInfo.setPeriod(DAY); - dateInfo.setText(String.format("近1天")); - semanticParseInfo.setDateInfo(dateInfo); - } - - private void addEntityDetailAndOrderByMetric(QueryContextReq queryContext, ChatContext chatCtx) { - if (queryContext.getParseInfo().getDomainId() > 0L) { - ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo( - queryContext.getParseInfo().getDomainId()); - if (chaConfigRichDesc != null) { - SemanticParseInfo semanticParseInfo = queryContext.getParseInfo(); - Set dimensions = new LinkedHashSet(); - Set primaryDimensions = this.addPrimaryDimension(chaConfigRichDesc.getEntity(), dimensions); - Set metrics = new LinkedHashSet(); - if (chaConfigRichDesc.getEntity() != null - && chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) { - chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream() - .forEach((m) -> metrics.add(this.getMetric(m))); - chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream() - .filter((m) -> !primaryDimensions.contains(m.getBizName())) - .forEach((m) -> dimensions.add(this.getDimension(m))); - } - - semanticParseInfo.setDimensions(dimensions); - semanticParseInfo.setMetrics(metrics); - Set orders = new LinkedHashSet(); - if (chaConfigRichDesc.getEntity() != null - && chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) { - chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream() - .forEach((metric) -> orders.add(new Order(metric.getBizName(), Constants.DESC_UPPER))); - } - - semanticParseInfo.setOrders(orders); - } - } - - } - - private Set addPrimaryDimension(EntityRichInfo entity, Set dimensions) { - Set primaryDimensions = new HashSet(); - if (!Objects.isNull(entity) && !CollectionUtils.isEmpty(entity.getEntityIds())) { - entity.getEntityIds().stream().forEach((dimSchemaDesc) -> { - SchemaItem dimension = new SchemaItem(); - BeanUtils.copyProperties(dimSchemaDesc, dimension); - dimensions.add(dimension); - primaryDimensions.add(dimSchemaDesc.getBizName()); - }); - return primaryDimensions; - } else { - return primaryDimensions; - } - } - - private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) { - SchemaItem queryMeta = new SchemaItem(); - queryMeta.setId(metricSchemaDesc.getId()); - queryMeta.setBizName(metricSchemaDesc.getBizName()); - queryMeta.setName(metricSchemaDesc.getName()); - return queryMeta; - } - - private SchemaItem getDimension(DimSchemaResp dimSchemaDesc) { - SchemaItem queryMeta = new SchemaItem(); - queryMeta.setId(dimSchemaDesc.getId()); - queryMeta.setBizName(dimSchemaDesc.getBizName()); - queryMeta.setName(dimSchemaDesc.getName()); - return queryMeta; - } - - private void dealNativeQuery(QueryContextReq queryContext, boolean isNativeQuery) { - if (Objects.nonNull(queryContext) && Objects.nonNull(queryContext.getParseInfo())) { - queryContext.getParseInfo().setNativeQuery(isNativeQuery); - } - - } -} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParser.java index 0d6bbd5a7..e63abf831 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParser.java @@ -1,22 +1,30 @@ package com.tencent.supersonic.chat.application.parser; +import com.tencent.supersonic.chat.api.component.SemanticParser; +import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticParser; +import com.tencent.supersonic.chat.application.query.MetricSemanticQuery; +import com.tencent.supersonic.chat.application.query.RuleSemanticQuery; +import com.tencent.supersonic.chat.application.query.RuleSemanticQueryManager; import com.tencent.supersonic.common.constant.Constants; import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.common.pojo.SchemaItem; +import com.tencent.supersonic.semantic.api.core.enums.TimeDimensionEnum; import java.time.LocalDate; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.util.Strings; -import org.springframework.stereotype.Component; -@Component public class TimeSemanticParser implements SemanticParser { - public TimeSemanticParser() { - } + private static final Pattern recentPeriodPattern = Pattern.compile( + ".*(?(近|过去)((?\\d+)|(?[一二三四五六七八九十百千万亿]+))个?(?[天周月年])).*"); private int zhNumParse(String zhNumStr) { Stack stack = new Stack<>(); @@ -42,11 +50,8 @@ public class TimeSemanticParser implements SemanticParser { return stack.stream().mapToInt(s -> s).sum(); } - private static final Pattern recentPeriodPattern = Pattern.compile( - ".*(?(近|过去)((?\\d+)|(?[一二三四五六七八九十百千万亿]+))个?(?[天周月年])).*"); - @Override - public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) { + public void parse(QueryContextReq queryContext, ChatContext chatContext) { Matcher m = recentPeriodPattern.matcher(queryContext.getQueryText()); if (m.matches()) { int num = 0; @@ -61,18 +66,19 @@ public class TimeSemanticParser implements SemanticParser { DateConf info = new DateConf(); String zhPeriod = m.group("zhPeriod"); int days; + info.setPeriod(Constants.DAY); switch (zhPeriod) { case "周": days = 7; - info.setPeriod(Constants.WEEK); + //info.setPeriod(Constants.WEEK); break; case "月": days = 30; - info.setPeriod(Constants.MONTH); + //info.setPeriod(Constants.MONTH); break; case "年": days = 365; - info.setPeriod(Constants.YEAR); + //info.setPeriod(Constants.YEAR); break; default: days = 1; @@ -87,9 +93,53 @@ public class TimeSemanticParser implements SemanticParser { info.setText(text); info.setStartDate(LocalDate.now().minusDays(days).toString()); info.setUnit(days); - queryContext.getParseInfo().setDateInfo(info); + //queryContext.getParseInfo().setDateInfo(info); + for (SemanticQuery query : queryContext.getCandidateQueries()) { + if (query instanceof MetricSemanticQuery) { + query.getParseInfo().setDateInfo(info); + } + } + doParseOnlyTime(queryContext, chatContext, info); + } + } + } + + protected void doParseOnlyTime(QueryContextReq queryContext, ChatContext chatContext, DateConf info) { + if (!queryContext.getCandidateQueries().isEmpty() || chatContext.getParseInfo() == null || Objects.isNull( + info.getText())) { + return; + } + if (info.getText().equals(queryContext.getQueryText()) && queryContext.getMapInfo().getDomainElementMatches() + .isEmpty() + ) { + if (Objects.nonNull(chatContext.getParseInfo().getQueryMode()) && Objects.nonNull( + chatContext.getParseInfo().getDomainId()) && chatContext.getParseInfo().getDomainId() > 0) { + if (Objects.nonNull(chatContext.getParseInfo().getDateInfo()) && !chatContext.getParseInfo() + .getDateInfo().getPeriod().equals(info.getPeriod())) { + if (!CollectionUtils.isEmpty(chatContext.getParseInfo().getDimensions())) { + String dateField = TimeDimensionEnum.DAY.getName(); + if (Constants.MONTH.equals(chatContext.getParseInfo().getDateInfo().getPeriod())) { + dateField = TimeDimensionEnum.MONTH.getName(); + } + if (Constants.WEEK.equals(chatContext.getParseInfo().getDateInfo().getPeriod())) { + dateField = TimeDimensionEnum.WEEK.getName(); + } + Set dimensions = new HashSet<>(); + for (SchemaItem schemaItem : chatContext.getParseInfo().getDimensions()) { + if (schemaItem.getBizName().equals(dateField)) { + continue; + } + dimensions.add(schemaItem); + } + chatContext.getParseInfo().setDimensions(dimensions); + } + } + chatContext.getParseInfo().setDateInfo(info); + RuleSemanticQuery semanticQuery = RuleSemanticQueryManager.create( + chatContext.getParseInfo().getQueryMode()); + semanticQuery.setParseInfo(chatContext.getParseInfo()); + queryContext.getCandidateQueries().add(semanticQuery); } } - return false; } } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/AggregateTypeResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/AggregateTypeResolver.java deleted file mode 100644 index 3f2b6a3e2..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/AggregateTypeResolver.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.tencent.supersonic.chat.application.parser.resolver; - -import com.tencent.supersonic.common.enums.AggregateTypeEnum; - -/*** - * aggregate parser - */ -public interface AggregateTypeResolver { - - AggregateTypeEnum resolve(String queryText); - - boolean hasCompareIntentionalWords(String queryText); - -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainSemanticQueryResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainSemanticQueryResolver.java deleted file mode 100644 index 61a505c7a..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/DomainSemanticQueryResolver.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.tencent.supersonic.chat.application.parser.resolver; - -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import com.tencent.supersonic.chat.application.query.SemanticQueryFactory; -import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Component -public class DomainSemanticQueryResolver implements SemanticQueryResolver { - - private static final Logger LOGGER = LoggerFactory.getLogger(DomainSemanticQueryResolver.class); - - @Override - public SemanticQuery resolve(List elementMatches, QueryContextReq queryCtx) { - Map matchMap = new HashMap<>(); - - for (SemanticQuery semanticQuery : SemanticQueryFactory.getSemanticQueries()) { - - SchemaElementCount match = semanticQuery.match(elementMatches, queryCtx); - - if (match != null && match.getCount() > 0 && match.getMaxSimilarity() > 0) { - LOGGER.info("resolve match [{}:{}] ", semanticQuery.getQueryMode(), match); - matchMap.put(semanticQuery, match); - } - - } - // get the similarity max - Map.Entry matchMax = matchMap.entrySet().stream() - .sorted(ContextHelper.SemanticQueryStatComparator).findFirst().orElse(null); - if (matchMax != null) { - LOGGER.info("resolve max [{}:{}] ", matchMax.getKey().getQueryMode(), matchMax.getValue()); - return matchMax.getKey(); - } - return null; - } -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/HeuristicDomainResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/HeuristicDomainResolver.java deleted file mode 100644 index 12ec57684..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/HeuristicDomainResolver.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.tencent.supersonic.chat.application.parser.resolver; - -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - - -@Service("DomainResolver") -@Slf4j -public class HeuristicDomainResolver extends BaseDomainResolver { - - @Override - public Integer selectDomain(Map domainQueryModes, QueryContextReq searchCtx, - ChatContext chatCtx, - SchemaMapInfo schemaMap) { - // if QueryContext has domainId and in domainQueryModes - if (domainQueryModes.containsKey(searchCtx.getDomainId())) { - log.info("selectDomain from QueryContext [{}]", searchCtx.getDomainId()); - return searchCtx.getDomainId(); - } - // if ChatContext has domainId and in domainQueryModes - if (chatCtx.getParseInfo().getDomainId() > 0) { - Integer domainId = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue()); - if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, searchCtx, domainId)) { - log.info("selectDomain from ChatContext [{}]", domainId); - return domainId; - } - } - // default 0 - return 0; - } -} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/RegexAggregateTypeResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/RegexAggregateTypeResolver.java deleted file mode 100644 index 80a5d7647..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/RegexAggregateTypeResolver.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.tencent.supersonic.chat.application.parser.resolver; - -import com.tencent.supersonic.common.enums.AggregateTypeEnum; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -@Primary -public class RegexAggregateTypeResolver implements AggregateTypeResolver { - - private static Map aggregateRegexMap = new HashMap<>(); - private static Pattern compareIntentionalWord = Pattern.compile("(?i)(比较|对比)"); - - static { - aggregateRegexMap.put(AggregateTypeEnum.MAX, Pattern.compile("(?i)(最大值|最大|max|峰值|最高)")); - aggregateRegexMap.put(AggregateTypeEnum.MIN, Pattern.compile("(?i)(最小值|最小|min|最低)")); - aggregateRegexMap.put(AggregateTypeEnum.SUM, Pattern.compile("(?i)(汇总|总和|sum)")); - aggregateRegexMap.put(AggregateTypeEnum.AVG, Pattern.compile("(?i)(平均值|日均|平均|avg)")); - aggregateRegexMap.put(AggregateTypeEnum.TOPN, Pattern.compile("(?i)(top)")); - aggregateRegexMap.put(AggregateTypeEnum.DISTINCT, Pattern.compile("(?i)(uv)")); - aggregateRegexMap.put(AggregateTypeEnum.COUNT, Pattern.compile("(?i)(总数|pv)")); - aggregateRegexMap.put(AggregateTypeEnum.NONE, Pattern.compile("(?i)(明细)")); - } - - @Override - public AggregateTypeEnum resolve(String text) { - - Map aggregateCount = new HashMap<>(aggregateRegexMap.size()); - for (Entry entry : aggregateRegexMap.entrySet()) { - Matcher matcher = entry.getValue().matcher(text); - int count = 0; - while (matcher.find()) { - count++; - } - if (count > 0) { - aggregateCount.put(entry.getKey(), count); - } - } - return aggregateCount.entrySet().stream().max(Map.Entry.comparingByValue()).map(entry -> entry.getKey()) - .orElse(null); - } - - @Override - public boolean hasCompareIntentionalWords(String queryText) { - return compareIntentionalWord.matcher(queryText).find(); - } - -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/SemanticQueryResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/SemanticQueryResolver.java deleted file mode 100644 index 908937f2a..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/resolver/SemanticQueryResolver.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.tencent.supersonic.chat.application.parser.resolver; - -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import java.util.List; - -/** - * Base interface for resolving query mode. - */ -public interface SemanticQueryResolver { - - SemanticQuery resolve(List elementMatches, QueryContextReq queryCtx); -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/BaseSemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/BaseSemanticQuery.java deleted file mode 100644 index 74c509a86..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/BaseSemanticQuery.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.tencent.supersonic.chat.application.query; - -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.EntityInfo; -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.response.QueryResultResp; -import com.tencent.supersonic.chat.api.service.SemanticLayer; -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn; -import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; -import com.tencent.supersonic.chat.application.DomainEntityService; -import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver; -import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; -import com.tencent.supersonic.chat.domain.pojo.search.QueryState; -import com.tencent.supersonic.chat.domain.service.ChatService; -import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; -import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; -import com.tencent.supersonic.common.util.context.ContextUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - - -public abstract class BaseSemanticQuery implements SemanticQuery { - - protected QueryModeOption queryModeOption = QueryModeOption.build(); - private static final Logger LOGGER = LoggerFactory.getLogger(BaseSemanticQuery.class); - - @Override - public QueryResultResp execute(QueryContextReq queryCtx, ChatContext chatCtx) { - - DomainResolver domainResolver = ContextUtils.getBean(DomainResolver.class); - ChatService chatService = ContextUtils.getBean(ChatService.class); - SemanticLayer semanticLayer = ContextUtils.getBean(SemanticLayer.class); - - SemanticParseInfo semanticParse = queryCtx.getParseInfo(); - - String queryMode = semanticParse.getQueryMode(); - - if (semanticParse.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) { - // reach here some error may happen - LOGGER.error("not find QueryMode"); - throw new RuntimeException("not find QueryMode"); - } - - supplyMetadata(semanticLayer, queryCtx); - - // is domain switch - if (domainResolver.isDomainSwitch(chatCtx, queryCtx)) { - chatService.switchContext(chatCtx); - } - // submit semantic query based on the result of semantic parsing - SemanticParseInfo semanticParseInfo = getContext(chatCtx, queryCtx); - QueryResultResp queryResponse = new QueryResultResp(); - QueryResultWithSchemaResp queryResult = semanticLayer.queryByStruct( - SchemaInfoConverter.convertTo(semanticParseInfo), queryCtx.getUser()); - if (queryResult != null) { - queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization()); - } - String sql = queryResult == null ? null : queryResult.getSql(); - List> resultList = queryResult == null ? new ArrayList<>() - : queryResult.getResultList(); - List columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns(); - queryResponse.setQuerySql(sql); - queryResponse.setQueryResults(resultList); - queryResponse.setQueryColumns(columns); - queryResponse.setQueryMode(queryMode); - - // add domain info - EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class) - .getEntityInfo(queryCtx, chatCtx, queryCtx.getUser()); - queryResponse.setEntityInfo(entityInfo); - return queryResponse; - - } - - private void supplyMetadata(SemanticLayer semanticLayer, QueryContextReq queryCtx) { - DefaultSemanticInternalUtils defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class); - - SchemaMapInfo mapInfo = queryCtx.getMapInfo(); - SemanticParseInfo semanticParse = queryCtx.getParseInfo(); - Long domain = semanticParse.getDomainId(); - List schemaElementMatches = mapInfo.getMatchedElements(domain.intValue()); - DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domain); - ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domain); - - // supply metadata - if (!CollectionUtils.isEmpty(schemaElementMatches)) { - this.queryModeOption.addQuerySemanticParseInfo(schemaElementMatches, domainSchemaDesc, - chaConfigRichDesc, semanticParse); - } - } - - @Override - public void updateContext(QueryResultResp queryResponse, ChatContext chatCtx, QueryContextReq queryCtx) { - if (queryCtx.isSaveAnswer() && queryResponse != null - && queryResponse.getQueryState() == QueryState.NORMAL.getState()) { - chatCtx.setParseInfo(getParseInfo(queryCtx, chatCtx)); - chatCtx.setQueryText(queryCtx.getQueryText()); - ContextUtils.getBean(ChatService.class).updateContext(chatCtx); - } - queryResponse.setChatContext(queryCtx.getParseInfo()); - } - - public abstract SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx); - - - @Override - public SchemaElementCount match(List elementMatches, QueryContextReq queryCtx) { - return queryModeOption.match(elementMatches, queryCtx); - } - -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityDetail.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityDetail.java index 78b0e62c0..db626d7af 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityDetail.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityDetail.java @@ -1,26 +1,22 @@ package com.tencent.supersonic.chat.application.query; -import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; -import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; -@Service -public class EntityDetail extends BaseSemanticQuery { +import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.domain.utils.ContextHelper; +import org.springframework.stereotype.Component; + +@Component +public class EntityDetail extends EntitySemanticQuery { public static String QUERY_MODE = "ENTITY_DETAIL"; public EntityDetail() { - queryModeOption.setAggregation(QueryModeElementOption.unused()); - queryModeOption.setDate(QueryModeElementOption.unused()); - queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, - 1); - queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setMetric(QueryModeElementOption.unused()); - queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_MOST, 1); - queryModeOption.setDomain(QueryModeElementOption.optional()); + super(); + queryMatcher.addOption(DIMENSION, REQUIRED, AT_LEAST, 1) + .addOption(VALUE, REQUIRED, AT_LEAST, 1); } @Override @@ -29,21 +25,9 @@ public class EntityDetail extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(), - semanticParseInfo.getDimensionFilters()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + ContextHelper.addIfEmpty(chatContext.getParseInfo().getDimensionFilters(), + parseInfo.getDimensionFilters()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListFilter.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListFilter.java index 06e3d3999..1bf46041f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListFilter.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListFilter.java @@ -1,26 +1,42 @@ package com.tencent.supersonic.chat.application.query; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.VALUE; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; +import static com.tencent.supersonic.common.constant.Constants.DAY; + import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; +import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; +import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; +import com.tencent.supersonic.common.constant.Constants; +import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.common.pojo.Order; +import com.tencent.supersonic.common.pojo.SchemaItem; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; -@Service -public class EntityListFilter extends BaseSemanticQuery { +@Slf4j +@Component +public class EntityListFilter extends EntitySemanticQuery { public static String QUERY_MODE = "ENTITY_LIST_FILTER"; private static Long entityListLimit = 200L; public EntityListFilter() { - queryModeOption.setAggregation(QueryModeElementOption.unused()); - queryModeOption.setDate(QueryModeElementOption.unused()); - queryModeOption.setDimension(QueryModeElementOption.unused()); - queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setMetric(QueryModeElementOption.unused()); - queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setDomain(QueryModeElementOption.optional()); + super(); + queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1); } @Override @@ -28,23 +44,98 @@ public class EntityListFilter extends BaseSemanticQuery { return QUERY_MODE; } - @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(), - semanticParseInfo.getDimensionFilters()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters()); - semanticParseInfo.setLimit(entityListLimit); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters()); + parseInfo.setLimit(entityListLimit); + this.fillDateEntityFilter(parseInfo); + this.addEntityDetailAndOrderByMetric(parseInfo); + this.dealNativeQuery(parseInfo, true); + } + + + private void fillDateEntityFilter(SemanticParseInfo semanticParseInfo) { + DateConf dateInfo = new DateConf(); + dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setUnit(1); + dateInfo.setPeriod(DAY); + dateInfo.setText(String.format("近1天")); + semanticParseInfo.setDateInfo(dateInfo); + } + + private void addEntityDetailAndOrderByMetric(SemanticParseInfo semanticParseInfo) { + if (semanticParseInfo.getDomainId() > 0L) { + DefaultSemanticInternalUtils defaultSemanticUtils = ContextUtils.getBean( + DefaultSemanticInternalUtils.class); + ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo( + semanticParseInfo.getDomainId()); + if (chaConfigRichDesc != null) { + //SemanticParseInfo semanticParseInfo = queryContext.getParseInfo(); + Set dimensions = new LinkedHashSet(); + Set primaryDimensions = this.addPrimaryDimension(chaConfigRichDesc.getEntity(), dimensions); + Set metrics = new LinkedHashSet(); + if (chaConfigRichDesc.getEntity() != null + && chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) { + chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream() + .forEach((m) -> metrics.add(this.getMetric(m))); + chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream() + .filter((m) -> !primaryDimensions.contains(m.getBizName())) + .forEach((m) -> dimensions.add(this.getDimension(m))); + } + + semanticParseInfo.setDimensions(dimensions); + semanticParseInfo.setMetrics(metrics); + Set orders = new LinkedHashSet(); + if (chaConfigRichDesc.getEntity() != null + && chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) { + chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream() + .forEach((metric) -> orders.add(new Order(metric.getBizName(), Constants.DESC_UPPER))); + } + + semanticParseInfo.setOrders(orders); + } + } + + } + + private Set addPrimaryDimension(EntityRichInfo entity, Set dimensions) { + Set primaryDimensions = new HashSet(); + if (!Objects.isNull(entity) && !CollectionUtils.isEmpty(entity.getEntityIds())) { + entity.getEntityIds().stream().forEach((dimSchemaDesc) -> { + SchemaItem dimension = new SchemaItem(); + BeanUtils.copyProperties(dimSchemaDesc, dimension); + dimensions.add(dimension); + primaryDimensions.add(dimSchemaDesc.getBizName()); + }); + return primaryDimensions; + } else { + return primaryDimensions; + } + } + + private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) { + SchemaItem queryMeta = new SchemaItem(); + queryMeta.setId(metricSchemaDesc.getId()); + queryMeta.setBizName(metricSchemaDesc.getBizName()); + queryMeta.setName(metricSchemaDesc.getName()); + return queryMeta; + } + + private SchemaItem getDimension(DimSchemaResp dimSchemaDesc) { + SchemaItem queryMeta = new SchemaItem(); + queryMeta.setId(dimSchemaDesc.getId()); + queryMeta.setBizName(dimSchemaDesc.getBizName()); + queryMeta.setName(dimSchemaDesc.getName()); + return queryMeta; + } + + private void dealNativeQuery(SemanticParseInfo semanticParseInfo, boolean isNativeQuery) { + if (Objects.nonNull(semanticParseInfo)) { + semanticParseInfo.setNativeQuery(isNativeQuery); + } + } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListTopN.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListTopN.java index 48fb8df14..4f7f9769c 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListTopN.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityListTopN.java @@ -1,27 +1,23 @@ package com.tencent.supersonic.chat.application.query; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; + import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service -public class EntityListTopN extends BaseSemanticQuery { +@Component +public class EntityListTopN extends EntitySemanticQuery { public static String QUERY_MODE = "ENTITY_LIST_TOPN"; - public EntityListTopN() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(QueryModeElementOption.unused()); - queryModeOption.setFilter(QueryModeElementOption.unused()); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setDomain(QueryModeElementOption.optional()); - queryModeOption.setSupportOrderBy(true); + super(); + queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1) + .setSupportOrderBy(true); } @Override @@ -30,22 +26,10 @@ public class EntityListTopN extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo); + ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityMetricFilter.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityMetricFilter.java index a42f2d94d..4139f83ea 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityMetricFilter.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntityMetricFilter.java @@ -1,26 +1,24 @@ package com.tencent.supersonic.chat.application.query; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.VALUE; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; + import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service -public class EntityMetricFilter extends BaseSemanticQuery { +@Component +public class EntityMetricFilter extends EntitySemanticQuery { public static String QUERY_MODE = "ENTITY_METRIC_FILTER"; - public EntityMetricFilter() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(QueryModeElementOption.unused()); - queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setDomain(QueryModeElementOption.optional()); + super(); + queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1) + .addOption(VALUE, REQUIRED, AT_LEAST, 1); } @Override @@ -29,21 +27,9 @@ public class EntityMetricFilter extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(), - semanticParseInfo.getDimensionFilters()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntitySemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntitySemanticQuery.java new file mode 100644 index 000000000..416af0244 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/EntitySemanticQuery.java @@ -0,0 +1,13 @@ +package com.tencent.supersonic.chat.application.query; + +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.ENTITY; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; + +public abstract class EntitySemanticQuery extends RuleSemanticQuery { + + public EntitySemanticQuery() { + super(); + queryMatcher.addOption(ENTITY, REQUIRED, AT_LEAST, 1); + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/HeuristicQuerySelector.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/HeuristicQuerySelector.java new file mode 100644 index 000000000..9e2ffd615 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/HeuristicQuerySelector.java @@ -0,0 +1,63 @@ +package com.tencent.supersonic.chat.application.query; + +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; +import com.tencent.supersonic.chat.api.pojo.SchemaElementType; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.common.constant.Constants; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HeuristicQuerySelector implements QuerySelector { + + @Override + public SemanticQuery select(List candidateQueries) { + double maxScore = 0; + SemanticQuery pickedQuery = null; + + for (SemanticQuery query : candidateQueries) { + if (query instanceof LLMSemanticQuery) { + log.info("force to use LLM if existed"); + return query; + } + + SemanticParseInfo semanticParse = query.getParseInfo(); + double score = computeScore(semanticParse); + if (score > maxScore) { + maxScore = score; + pickedQuery = query; + } + log.info("candidate query (domain={}, queryMode={}) with score={}", + semanticParse.getDomainName(), semanticParse.getQueryMode(), score); + } + + return pickedQuery; + } + + private double computeScore(SemanticParseInfo semanticParse) { + double score = 0; + + Map maxSimilarityMatch = new HashMap<>(); + for (SchemaElementMatch match : semanticParse.getElementMatches()) { + SchemaElementType type = match.getElementType(); + if (!maxSimilarityMatch.containsKey(type) || + match.getSimilarity() > maxSimilarityMatch.get(type).getSimilarity()) { + maxSimilarityMatch.put(type, match); + } + } + + for (SchemaElementMatch match : maxSimilarityMatch.values()) { + score += + Optional.ofNullable(match.getDetectWord()).orElse(Constants.EMPTY).length() * match.getSimilarity(); + } + + // bonus is a special construct to control the final score + score += semanticParse.getBonus(); + + return score; + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/LLMSemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/LLMSemanticQuery.java new file mode 100644 index 000000000..d7a7fbbdc --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/LLMSemanticQuery.java @@ -0,0 +1,71 @@ +package com.tencent.supersonic.chat.application.query; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.EntityInfo; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.api.response.QueryResultResp; +import com.tencent.supersonic.chat.application.DomainEntityService; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; +import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn; +import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +public class LLMSemanticQuery implements SemanticQuery { + + public static String QUERY_MODE = "DSL"; + + private SemanticParseInfo semanticParse = new SemanticParseInfo(); + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); + + @Override + public String getQueryMode() { + return QUERY_MODE; + } + + @Override + public QueryResultResp execute(User user) { + String queryMode = semanticParse.getQueryMode(); + + if (semanticParse.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) { + // reach here some error may happen + log.error("not find QueryMode"); + throw new RuntimeException("not find QueryMode"); + } + QueryResultResp queryResponse = new QueryResultResp(); + QueryResultWithSchemaResp queryResult = semanticLayer.queryBySql( + SchemaInfoConverter.convertToQuerySqlReq(semanticParse), user); + + if (queryResult != null) { + queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization()); + } + String sql = queryResult == null ? null : queryResult.getSql(); + List> resultList = queryResult == null ? new ArrayList<>() + : queryResult.getResultList(); + List columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns(); + queryResponse.setQuerySql(sql); + queryResponse.setQueryResults(resultList); + queryResponse.setQueryColumns(columns); + queryResponse.setQueryMode(queryMode); + + // add domain info + EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class) + .getEntityInfo(semanticParse, user); + queryResponse.setEntityInfo(entityInfo); + return queryResponse; + } + + @Override + public SemanticParseInfo getParseInfo() { + return semanticParse; + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricCompare.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricCompare.java index 96d7189be..3a215dd18 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricCompare.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricCompare.java @@ -1,39 +1,43 @@ package com.tencent.supersonic.chat.application.query; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.ENTITY; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.VALUE; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_MOST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.OPTIONAL; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; + import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.Filter; +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; import com.tencent.supersonic.common.pojo.SchemaItem; import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service @Slf4j -public class MetricCompare extends BaseSemanticQuery { +@Component +public class MetricCompare extends MetricSemanticQuery { public static String QUERY_MODE = "METRIC_COMPARE"; - + public static Pattern intentWordPattern = Pattern.compile("(?i)(比较|对比)"); public MetricCompare() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(QueryModeElementOption.unused()); - queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(QueryModeElementOption.unused()); - queryModeOption.setDomain(QueryModeElementOption.optional()); - queryModeOption.setSupportCompare(true); - queryModeOption.setSupportOrderBy(true); + super(); + queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 2) + .addOption(ENTITY, OPTIONAL, AT_MOST, 1); + + queryMatcher.setSupportCompare(true); + queryMatcher.setSupportOrderBy(true); } @Override @@ -42,26 +46,22 @@ public class MetricCompare extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCt) { - SemanticParseInfo semanticParseInfo = chatCt.getParseInfo(); - ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(queryCtx.getParseInfo().getDimensionFilters(), - semanticParseInfo.getDimensionFilters()); - ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; + public List match(List candidateElementMatches, QueryContextReq queryCtx) { + if (intentWordPattern.matcher(queryCtx.getQueryText()).find()) { + return super.match(candidateElementMatches, queryCtx); + } else { + return new ArrayList<>(); + } } @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - mergeAppend(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters()); - addCompareDimension(semanticParseInfo); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo); + ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics()); + mergeAppend(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters()); + addCompareDimension(parseInfo); + parseInfo.setBonus(2 * 1.0); } private void addCompareDimension(SemanticParseInfo semanticParseInfo) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricDomain.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricDomain.java index f90325a31..ed38701dc 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricDomain.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricDomain.java @@ -1,26 +1,23 @@ package com.tencent.supersonic.chat.application.query; - import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service -public class MetricDomain extends BaseSemanticQuery { +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*; + +@Component +public class MetricDomain extends MetricSemanticQuery { public static String QUERY_MODE = "METRIC_DOMAIN"; public MetricDomain() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(QueryModeElementOption.unused()); - queryModeOption.setFilter(QueryModeElementOption.unused()); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(QueryModeElementOption.unused()); - queryModeOption.setDomain(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); + super(); + queryMatcher.addOption(DOMAIN, REQUIRED, AT_LEAST, 1); } @Override @@ -29,22 +26,10 @@ public class MetricDomain extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo); + ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricFilter.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricFilter.java index c0e813fc6..a1ccd7975 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricFilter.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricFilter.java @@ -1,25 +1,25 @@ package com.tencent.supersonic.chat.application.query; import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service -public class MetricFilter extends BaseSemanticQuery { +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*; + +@Component +public class MetricFilter extends MetricSemanticQuery { public static String QUERY_MODE = "METRIC_FILTER"; public MetricFilter() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(QueryModeElementOption.unused()); - queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(QueryModeElementOption.unused()); - queryModeOption.setDomain(QueryModeElementOption.optional()); + super(); + queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1) + .addOption(ENTITY, OPTIONAL, AT_MOST, 1); + } @Override @@ -28,25 +28,10 @@ public class MetricFilter extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(), - semanticParseInfo.getDimensionFilters()); - ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo); + ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics()); + ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricGroupBy.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricGroupBy.java index 4b5a5dd95..b65909e64 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricGroupBy.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricGroupBy.java @@ -2,26 +2,21 @@ package com.tencent.supersonic.chat.application.query; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service -public class MetricGroupBy extends BaseSemanticQuery { +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*; + +@Component +public class MetricGroupBy extends MetricSemanticQuery { public static String QUERY_MODE = "METRIC_GROUPBY"; - public MetricGroupBy() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, - 1); - queryModeOption.setFilter(QueryModeElementOption.unused()); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(QueryModeElementOption.unused()); - queryModeOption.setDomain(QueryModeElementOption.optional()); + super(); + queryMatcher.addOption(DIMENSION, REQUIRED, AT_LEAST, 1); } @Override @@ -30,24 +25,11 @@ public class MetricGroupBy extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.updateList(queryCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo); + ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics()); + ContextHelper.addIfEmpty(chatParseInfo.getDimensions(), parseInfo.getDimensions()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricOrderBy.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricOrderBy.java index 5ce8f9b21..9eb671d21 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricOrderBy.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricOrderBy.java @@ -1,27 +1,18 @@ package com.tencent.supersonic.chat.application.query; import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service -public class MetricOrderBy extends BaseSemanticQuery { +@Component +public class MetricOrderBy extends RuleSemanticQuery { public static String QUERY_MODE = "METRIC_ORDERBY"; public MetricOrderBy() { - queryModeOption.setAggregation(QueryModeElementOption.optional()); - queryModeOption.setDate(QueryModeElementOption.optional()); - queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, - 1); - queryModeOption.setFilter(QueryModeElementOption.optional()); - queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1); - queryModeOption.setEntity(QueryModeElementOption.unused()); - queryModeOption.setDomain(QueryModeElementOption.optional()); - queryModeOption.setSupportOrderBy(true); + super(); } @Override @@ -30,24 +21,12 @@ public class MetricOrderBy extends BaseSemanticQuery { } @Override - public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) { - SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo(); - ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo); - ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.updateList(queryCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions()); - ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo); - return semanticParseInfo; - } - - @Override - public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) { - SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo(); - ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics()); - ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions()); - return semanticParseInfo; + public void inheritContext(ChatContext chatContext) { + SemanticParseInfo chatParseInfo = chatContext.getParseInfo(); + ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo); + ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics()); + ContextHelper.addIfEmpty(chatParseInfo.getDimensions(), parseInfo.getDimensions()); + ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters()); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricSemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricSemanticQuery.java new file mode 100644 index 000000000..95797c2d7 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/MetricSemanticQuery.java @@ -0,0 +1,13 @@ +package com.tencent.supersonic.chat.application.query; + +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC; +import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST; +import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED; + +public abstract class MetricSemanticQuery extends RuleSemanticQuery { + + public MetricSemanticQuery() { + super(); + queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1); + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatchOption.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatchOption.java new file mode 100644 index 000000000..e740a0c44 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatchOption.java @@ -0,0 +1,43 @@ +package com.tencent.supersonic.chat.application.query; + +import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; +import lombok.Data; + +@Data +public class QueryMatchOption { + + private SchemaElementOption schemaElementOption; + private RequireNumberType requireNumberType; + private Integer requireNumber; + + public static QueryMatchOption build(SchemaElementOption schemaElementOption, + RequireNumberType requireNumberType, Integer requireNumber) { + QueryMatchOption queryMatchOption = new QueryMatchOption(); + queryMatchOption.requireNumber = requireNumber; + queryMatchOption.requireNumberType = requireNumberType; + queryMatchOption.schemaElementOption = schemaElementOption; + return queryMatchOption; + } + + public static QueryMatchOption optional() { + QueryMatchOption queryMatchOption = new QueryMatchOption(); + queryMatchOption.setSchemaElementOption(SchemaElementOption.OPTIONAL); + queryMatchOption.setRequireNumber(0); + queryMatchOption.setRequireNumberType(RequireNumberType.AT_LEAST); + return queryMatchOption; + } + + public static QueryMatchOption unused() { + QueryMatchOption queryMatchOption = new QueryMatchOption(); + queryMatchOption.setSchemaElementOption(SchemaElementOption.UNUSED); + queryMatchOption.setRequireNumber(0); + queryMatchOption.setRequireNumberType(RequireNumberType.EQUAL); + return queryMatchOption; + } + + public enum RequireNumberType { + AT_MOST, AT_LEAST, EQUAL + } + + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatcher.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatcher.java new file mode 100644 index 000000000..53ecea73b --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryMatcher.java @@ -0,0 +1,113 @@ +package com.tencent.supersonic.chat.application.query; + +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; +import com.tencent.supersonic.chat.api.pojo.SchemaElementType; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; +import com.tencent.supersonic.common.enums.AggregateTypeEnum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.tencent.supersonic.common.pojo.SchemaItem; +import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import lombok.Data; +import lombok.ToString; + +@Data +@ToString +public class QueryMatcher { + + private HashMap elementOptionMap = new HashMap<>(); + private boolean supportCompare; + private boolean supportOrderBy; + private List orderByTypes = Arrays.asList(AggregateTypeEnum.MAX, AggregateTypeEnum.MIN, + AggregateTypeEnum.TOPN); + + public QueryMatcher() { + for (SchemaElementType type : SchemaElementType.values()) { + if (type.equals(SchemaElementType.DOMAIN)) { + elementOptionMap.put(type, QueryMatchOption.optional()); + } else { + elementOptionMap.put(type, QueryMatchOption.unused()); + } + } + } + + public QueryMatcher addOption(SchemaElementType type, SchemaElementOption option, + QueryMatchOption.RequireNumberType requireNumberType, Integer requireNumber) { + elementOptionMap.put(type, QueryMatchOption.build(option, requireNumberType, requireNumber)); + return this; + } + + /** + * Match schema element with current query according to the options. + * + * @param candidateElementMatches + * @return a list of all matched schema elements, + * empty list if no matches can be found + */ + public List match(List candidateElementMatches) { + List elementMatches = new ArrayList<>(); + + HashMap schemaElementTypeCount = new HashMap<>(); + for (SchemaElementMatch schemaElementMatch : candidateElementMatches) { + SchemaElementType schemaElementType = schemaElementMatch.getElementType(); + if (schemaElementTypeCount.containsKey(schemaElementType)) { + schemaElementTypeCount.put(schemaElementType, schemaElementTypeCount.get(schemaElementType) + 1); + } else { + schemaElementTypeCount.put(schemaElementType, 1); + } + } + + // check if current query options are satisfied, return immediately if not + for (Map.Entry e : elementOptionMap.entrySet()) { + SchemaElementType elementType = e.getKey(); + QueryMatchOption elementOption = e.getValue(); + if (!isMatch(elementOption, getCount(schemaElementTypeCount, elementType))) { + return new ArrayList<>(); + } + } + + // add element match if its element type is not declared as unused + for (SchemaElementMatch elementMatch : candidateElementMatches) { + QueryMatchOption elementOption = elementOptionMap.get(elementMatch.getElementType()); + if (Objects.nonNull(elementOption) && !elementOption.getSchemaElementOption() + .equals(SchemaElementOption.UNUSED)) { + elementMatches.add(elementMatch); + } + } + + return elementMatches; + } + + private int getCount(HashMap schemaElementTypeCount, + SchemaElementType schemaElementType) { + if (schemaElementTypeCount.containsKey(schemaElementType)) { + return schemaElementTypeCount.get(schemaElementType); + } + return 0; + } + + private boolean isMatch(QueryMatchOption queryMatchOption, int count) { + // check if required but empty + if (queryMatchOption.getSchemaElementOption().equals(SchemaElementOption.REQUIRED) && count <= 0) { + return false; + } + if (queryMatchOption.getRequireNumberType().equals(QueryMatchOption.RequireNumberType.AT_LEAST) + && count < queryMatchOption.getRequireNumber()) { + return false; + } + if (queryMatchOption.getRequireNumberType().equals(QueryMatchOption.RequireNumberType.AT_MOST) + && count > queryMatchOption.getRequireNumber()) { + return false; + } + + return true; + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeElementOption.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeElementOption.java deleted file mode 100644 index 2d0a07ec9..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeElementOption.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.tencent.supersonic.chat.application.query; - -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; -import lombok.Data; - -@Data -public class QueryModeElementOption { - - public enum RequireNumberType { - AT_MOST, AT_LEAST, EQUAL - } - - private SchemaElementOption schemaElementOption; - private RequireNumberType requireNumberType; - private Integer requireNumber; - - public static QueryModeElementOption build(SchemaElementOption schemaElementOption, - RequireNumberType requireNumberType, Integer requireNumber) { - QueryModeElementOption queryModeElementOption = new QueryModeElementOption(); - queryModeElementOption.requireNumber = requireNumber; - queryModeElementOption.requireNumberType = requireNumberType; - queryModeElementOption.schemaElementOption = schemaElementOption; - return queryModeElementOption; - } - - public static QueryModeElementOption optional() { - QueryModeElementOption queryModeElementOption = new QueryModeElementOption(); - queryModeElementOption.setSchemaElementOption(SchemaElementOption.OPTIONAL); - queryModeElementOption.setRequireNumber(0); - queryModeElementOption.setRequireNumberType(RequireNumberType.AT_LEAST); - return queryModeElementOption; - } - - public static QueryModeElementOption unused() { - QueryModeElementOption queryModeElementOption = new QueryModeElementOption(); - queryModeElementOption.setSchemaElementOption(SchemaElementOption.UNUSED); - queryModeElementOption.setRequireNumber(0); - queryModeElementOption.setRequireNumberType(RequireNumberType.EQUAL); - return queryModeElementOption; - } - - -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeOption.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeOption.java deleted file mode 100644 index 043a817ca..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QueryModeOption.java +++ /dev/null @@ -1,307 +0,0 @@ -package com.tencent.supersonic.chat.application.query; - -import com.tencent.supersonic.chat.api.pojo.Filter; -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; -import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; -import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; -import com.tencent.supersonic.chat.application.parser.resolver.AggregateTypeResolver; -import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption; -import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; -import com.tencent.supersonic.chat.domain.utils.ContextHelper; -import com.tencent.supersonic.common.enums.AggregateTypeEnum; -import com.tencent.supersonic.common.pojo.SchemaItem; -import com.tencent.supersonic.common.util.context.ContextUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import lombok.Data; -import lombok.ToString; - -@Data -@ToString -public class QueryModeOption { - - private QueryModeElementOption domain; - private QueryModeElementOption entity; - private QueryModeElementOption metric; - private QueryModeElementOption dimension; - private QueryModeElementOption filter; - private QueryModeElementOption date; - - private QueryModeElementOption aggregation; - private boolean supportCompare = false; - private boolean supportOrderBy = false; - List orderByTypes = Arrays.asList(AggregateTypeEnum.MAX, AggregateTypeEnum.MIN, - AggregateTypeEnum.TOPN); - - public static QueryModeOption build() { - return new QueryModeOption(); - } - - public QueryModeOption setDimension(SchemaElementOption dimension, - QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) { - this.dimension = QueryModeElementOption.build(dimension, requireNumberType, requireNumber); - return this; - } - - public QueryModeOption setDomain(SchemaElementOption domain, - QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) { - this.domain = QueryModeElementOption.build(domain, requireNumberType, requireNumber); - return this; - } - - public QueryModeOption setEntity(SchemaElementOption entity, - QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) { - this.entity = QueryModeElementOption.build(entity, requireNumberType, requireNumber); - return this; - } - - public QueryModeOption setFilter(SchemaElementOption filter, - QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) { - this.filter = QueryModeElementOption.build(filter, requireNumberType, requireNumber); - return this; - } - - public QueryModeOption setMetric(SchemaElementOption metric, - QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) { - this.metric = QueryModeElementOption.build(metric, requireNumberType, requireNumber); - return this; - } - - /** - * add query semantic parse info - * @param schemaElementMatches - * @param domainSchemaDesc - * @param chaConfigRichDesc - * @param semanticParseInfo - */ - public void addQuerySemanticParseInfo(List schemaElementMatches, - DomainSchemaResp domainSchemaDesc, ChatConfigRichInfo chaConfigRichDesc, - SemanticParseInfo semanticParseInfo) { - Map dimensionDescMap = domainSchemaDesc.getDimensions().stream() - .collect(Collectors.toMap(DimSchemaResp::getId, Function.identity())); - Map metricDescMap = domainSchemaDesc.getMetrics().stream() - .collect(Collectors.toMap(MetricSchemaResp::getId, Function.identity())); - - Map> values = getLinkSchemaElementMatch(schemaElementMatches, dimensionDescMap, - semanticParseInfo, metricDescMap); - if (!values.isEmpty()) { - for (Map.Entry> entry : values.entrySet()) { - DimSchemaResp dimensionDesc = dimensionDescMap.get(entry.getKey()); - if (entry.getValue().size() == 1) { - SchemaElementMatch schemaElementMatch = entry.getValue().get(0); - Filter chatFilter = new Filter(); - chatFilter.setValue(schemaElementMatch.getWord()); - chatFilter.setBizName(dimensionDesc.getBizName()); - chatFilter.setName(dimensionDesc.getName()); - chatFilter.setOperator(FilterOperatorEnum.EQUALS); - chatFilter.setElementID(Long.valueOf(schemaElementMatch.getElementID())); - semanticParseInfo.getDimensionFilters().add(chatFilter); - ContextHelper.setEntityId(entry.getKey(), schemaElementMatch.getWord(), chaConfigRichDesc, - semanticParseInfo); - } else { - Filter chatFilter = new Filter(); - List vals = new ArrayList<>(); - entry.getValue().stream().forEach(i -> vals.add(i.getWord())); - chatFilter.setValue(vals); - chatFilter.setBizName(dimensionDesc.getBizName()); - chatFilter.setName(dimensionDesc.getName()); - chatFilter.setOperator(FilterOperatorEnum.IN); - chatFilter.setElementID(entry.getKey()); - semanticParseInfo.getDimensionFilters().add(chatFilter); - } - } - } - } - - private Map> getLinkSchemaElementMatch(List schemaElementMatches, - Map dimensionDescMap, SemanticParseInfo semanticParseInfo, - Map metricDescMap) { - Map> values = new HashMap<>(); - for (SchemaElementMatch schemaElementMatch : schemaElementMatches) { - Long elementID = Long.valueOf(schemaElementMatch.getElementID()); - switch (schemaElementMatch.getElementType()) { - case DATE: - case DOMAIN: - case ENTITY: - break; - case ID: - case VALUE: - case DIMENSION: - if (dimensionDescMap.containsKey(elementID)) { - DimSchemaResp dimensionDesc = dimensionDescMap.get(elementID); - SchemaItem dimensionParseInfo = new SchemaItem(); - dimensionParseInfo.setBizName(dimensionDesc.getBizName()); - dimensionParseInfo.setName(dimensionDesc.getName()); - dimensionParseInfo.setId(dimensionDesc.getId()); - if (!dimension.getSchemaElementOption().equals(SchemaElementOption.UNUSED) - && schemaElementMatch.getElementType().equals(SchemaElementType.DIMENSION)) { - semanticParseInfo.getDimensions().add(dimensionParseInfo); - } - if (!filter.getSchemaElementOption().equals(SchemaElementOption.UNUSED) && ( - schemaElementMatch.getElementType().equals(SchemaElementType.VALUE) - || schemaElementMatch.getElementType().equals(SchemaElementType.ID))) { - if (values.containsKey(elementID)) { - values.get(elementID).add(schemaElementMatch); - } else { - values.put(elementID, new ArrayList<>(Arrays.asList(schemaElementMatch))); - } - } - } - break; - case METRIC: - if (!metric.getSchemaElementOption().equals(SchemaElementOption.UNUSED)) { - if (metricDescMap.containsKey(elementID)) { - MetricSchemaResp metricDesc = metricDescMap.get(elementID); - SchemaItem metric = new SchemaItem(); - metric.setBizName(metricDesc.getBizName()); - metric.setName(metricDesc.getName()); - metric.setId(metricDesc.getId()); - semanticParseInfo.getMetrics().add(metric); - } - } - break; - default: - } - } - return values; - } - - /** - * math - * @param elementMatches - * @param queryCtx - * @return - */ - public SchemaElementCount match(List elementMatches, QueryContextReq queryCtx) { - AggregateTypeResolver aggregateTypeResolver = ContextUtils.getBean(AggregateTypeResolver.class); - - boolean isCompareType = aggregateTypeResolver.hasCompareIntentionalWords(queryCtx.getQueryText()); - boolean isOrderByType = orderByTypes.contains(aggregateTypeResolver.resolve(queryCtx.getQueryText())); - - if ((isOrderByType && !supportOrderBy) || (isCompareType && !supportCompare)) { - return new SchemaElementCount(); - } - - SchemaElementCount schemaElementCount = new SchemaElementCount(); - schemaElementCount.setCount(0); - schemaElementCount.setMaxSimilarity(0); - HashMap schemaElementTypeCount = new HashMap<>(); - for (SchemaElementMatch schemaElementMatch : elementMatches) { - SchemaElementType schemaElementType = schemaElementMatch.getElementType(); - if (schemaElementTypeCount.containsKey(schemaElementType)) { - schemaElementTypeCount.put(schemaElementType, schemaElementTypeCount.get(schemaElementType) + 1); - } else { - schemaElementTypeCount.put(schemaElementType, 1); - } - } - // test each element - if (!isMatch(domain, getCount(schemaElementTypeCount, SchemaElementType.DOMAIN))) { - return schemaElementCount; - } - if (!isMatch(dimension, getCount(schemaElementTypeCount, SchemaElementType.DIMENSION))) { - return schemaElementCount; - } - if (!isMatch(metric, getCount(schemaElementTypeCount, SchemaElementType.METRIC))) { - return schemaElementCount; - } - int filterCount = getCount(schemaElementTypeCount, SchemaElementType.VALUE) + getCount(schemaElementTypeCount, - SchemaElementType.ID); - if (!isMatch(filter, filterCount)) { - return schemaElementCount; - } - if (!isMatch(entity, getCount(schemaElementTypeCount, SchemaElementType.ENTITY))) { - return schemaElementCount; - } - if (!isMatch(date, getCount(schemaElementTypeCount, SchemaElementType.DATE))) { - return schemaElementCount; - } - // count the max similarity - double similarity = 0; - Set schemaElementTypeSet = new HashSet<>(); - for (SchemaElementMatch schemaElementMatch : elementMatches) { - double schemaElementMatchSimilarity = getSimilarity(schemaElementMatch); - if (schemaElementMatchSimilarity > similarity) { - similarity = schemaElementMatchSimilarity; - } - schemaElementTypeSet.add(schemaElementMatch.getElementType()); - } - schemaElementCount.setCount(schemaElementTypeSet.size()); - schemaElementCount.setMaxSimilarity(similarity); - return schemaElementCount; - } - - private int getCount(HashMap schemaElementTypeCount, - SchemaElementType schemaElementType) { - if (schemaElementTypeCount.containsKey(schemaElementType)) { - return schemaElementTypeCount.get(schemaElementType); - } - return 0; - } - - private double getSimilarity(SchemaElementMatch schemaElementMatch) { - switch (schemaElementMatch.getElementType()) { - case DATE: - return getSimilarity(date, schemaElementMatch.getSimilarity()); - case DOMAIN: - return getSimilarity(domain, schemaElementMatch.getSimilarity()); - case ENTITY: - return getSimilarity(entity, schemaElementMatch.getSimilarity()); - case DIMENSION: - return getSimilarity(dimension, schemaElementMatch.getSimilarity()); - case METRIC: - return getSimilarity(metric, schemaElementMatch.getSimilarity()); - case ID: - case VALUE: - return getSimilarity(filter, schemaElementMatch.getSimilarity()); - default: - return 0; - } - } - - private double getSimilarity(QueryModeElementOption queryModeElementOption, double similarity) { - if (queryModeElementOption.getSchemaElementOption().equals(SchemaElementOption.REQUIRED)) { - return similarity; - } - return 0; - } - - private boolean isMatch(QueryModeElementOption queryModeElementOption, int count) { - // first find if unused but not empty - if (queryModeElementOption.getSchemaElementOption().equals(SchemaElementOption.UNUSED) && count > 0) { - return false; - } - // find if required but empty - if (queryModeElementOption.getSchemaElementOption().equals(SchemaElementOption.REQUIRED) && count <= 0) { - return false; - } - // find if count no satisfy - if (queryModeElementOption.getRequireNumberType().equals(QueryModeElementOption.RequireNumberType.EQUAL) - && queryModeElementOption.getRequireNumber() != count) { - return false; - } - if (queryModeElementOption.getRequireNumberType().equals(QueryModeElementOption.RequireNumberType.AT_LEAST) - && count < queryModeElementOption.getRequireNumber()) { - return false; - } - if (queryModeElementOption.getRequireNumberType().equals(QueryModeElementOption.RequireNumberType.AT_MOST) - && count > queryModeElementOption.getRequireNumber()) { - return false; - } - // here default satisfy - return true; - } - -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QuerySelector.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QuerySelector.java new file mode 100644 index 000000000..e84b43586 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/QuerySelector.java @@ -0,0 +1,13 @@ +package com.tencent.supersonic.chat.application.query; + +import com.tencent.supersonic.chat.api.component.SemanticQuery; + +import java.util.List; + +/** + * This interface defines the contract for a selector that picks the most suitable semantic query. + **/ +public interface QuerySelector { + + SemanticQuery select(List candidateQueries); +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQuery.java new file mode 100644 index 000000000..6583ad91e --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQuery.java @@ -0,0 +1,87 @@ +package com.tencent.supersonic.chat.application.query; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.*; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import com.tencent.supersonic.chat.api.response.QueryResultResp; +import com.tencent.supersonic.chat.application.DomainEntityService; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; +import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn; +import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; +import java.util.*; + +@Slf4j +@ToString +public abstract class RuleSemanticQuery implements SemanticQuery, Serializable { + + protected SemanticParseInfo parseInfo = new SemanticParseInfo(); + protected List schemaElementMatches = new ArrayList<>(); + protected QueryMatcher queryMatcher = new QueryMatcher(); + protected SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); + + public RuleSemanticQuery() { + RuleSemanticQueryManager.register(this); + } + + public List match(List candidateElementMatches, QueryContextReq queryCtx) { + return queryMatcher.match(candidateElementMatches); + } + + public abstract void inheritContext(ChatContext chatContext); + + @Override + public QueryResultResp execute(User user) { + String queryMode = parseInfo.getQueryMode(); + + if (parseInfo.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) { + // reach here some error may happen + log.error("not find QueryMode"); + throw new RuntimeException("not find QueryMode"); + } + + List semanticQueryModes = RuleSemanticQueryManager.getSemanticQueryModes(); + if (!semanticQueryModes.contains(parseInfo.getQueryMode())) { + return null; + } + + QueryResultResp queryResponse = new QueryResultResp(); + QueryResultWithSchemaResp queryResult = semanticLayer.queryByStruct( + SchemaInfoConverter.convertTo(parseInfo), user); + + if (queryResult != null) { + queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization()); + } + String sql = queryResult == null ? null : queryResult.getSql(); + List> resultList = queryResult == null ? new ArrayList<>() + : queryResult.getResultList(); + List columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns(); + queryResponse.setQuerySql(sql); + queryResponse.setQueryResults(resultList); + queryResponse.setQueryColumns(columns); + queryResponse.setQueryMode(queryMode); + + // add domain info + EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class) + .getEntityInfo(parseInfo, user); + queryResponse.setEntityInfo(entityInfo); + return queryResponse; + } + + @Override + public SemanticParseInfo getParseInfo() { + return parseInfo; + } + + public void setParseInfo(SemanticParseInfo parseInfo) { + this.parseInfo = parseInfo; + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQueryManager.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQueryManager.java new file mode 100644 index 000000000..74c608dec --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/RuleSemanticQueryManager.java @@ -0,0 +1,39 @@ +package com.tencent.supersonic.chat.application.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * RuleSemanticQueryManager + */ +public class RuleSemanticQueryManager { + + private static Map semanticQueryMap = new ConcurrentHashMap<>(); + + public static RuleSemanticQuery create(String queryMode) { + RuleSemanticQuery semanticQuery = semanticQueryMap.get(queryMode); + if (Objects.isNull(semanticQuery)) { + throw new RuntimeException("no supported queryMode :" + queryMode); + } + try { + return semanticQuery.getClass().getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException("no supported queryMode :" + queryMode); + } + } + + public static void register(RuleSemanticQuery query) { + semanticQueryMap.put(query.getQueryMode(), query); + } + + public static List getSemanticQueries() { + return new ArrayList<>(semanticQueryMap.values()); + } + + public static List getSemanticQueryModes() { + return new ArrayList<>(semanticQueryMap.keySet()); + } +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/SemanticQueryFactory.java b/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/SemanticQueryFactory.java deleted file mode 100644 index b9f798c49..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/query/SemanticQueryFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.tencent.supersonic.chat.application.query; - -import com.tencent.supersonic.chat.api.service.SemanticQuery; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.CollectionUtils; - -/** - * SemanticQueryFactory - */ -public class SemanticQueryFactory { - - private static Map strategyFactory = new ConcurrentHashMap<>(); - - private static List semanticQueries; - - - public static SemanticQuery get(String queryMode) { - if (CollectionUtils.isEmpty(strategyFactory)) { - init(); - } - - SemanticQuery semanticQuery = strategyFactory.get(queryMode); - if (Objects.isNull(semanticQuery)) { - throw new RuntimeException("not support queryMode :" + queryMode); - } - return semanticQuery; - } - - private static void init() { - for (SemanticQuery semanticQuery : getSemanticQueries()) { - strategyFactory.put(semanticQuery.getQueryMode(), semanticQuery); - } - } - - public static List getSemanticQueries() { - if (CollectionUtils.isEmpty(semanticQueries)) { - semanticQueries = SpringFactoriesLoader.loadFactories(SemanticQuery.class, - Thread.currentThread().getContextClassLoader()); - } - return semanticQueries; - } -} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/config/LLMConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/config/LLMConfig.java new file mode 100644 index 000000000..2a2310036 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/config/LLMConfig.java @@ -0,0 +1,23 @@ +package com.tencent.supersonic.chat.domain.config; + + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class LLMConfig { + + + @Value("${llm.url:}") + private String url; + + @Value("${query2sql.path:query2sql}") + private String queryToSqlPath; + + + @Value("${query2sql.endpoint:}") + private String endpoint; + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/ChatQueryDOExample.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/ChatQueryDOExample.java index 4db464747..99d6cd109 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/ChatQueryDOExample.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/ChatQueryDOExample.java @@ -5,6 +5,7 @@ import java.util.Date; import java.util.List; public class ChatQueryDOExample { + protected String orderByClause; protected boolean distinct; protected List oredCriteria; @@ -15,23 +16,22 @@ public class ChatQueryDOExample { oredCriteria = new ArrayList(); } - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - - public String getOrderByClause() { return orderByClause; } - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } public boolean isDistinct() { return distinct; } + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + public List getOredCriteria() { return oredCriteria; } @@ -65,22 +65,22 @@ public class ChatQueryDOExample { distinct = false; } - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - public Integer getLimitStart() { return limitStart; } - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } public Integer getLimitEnd() { return limitEnd; } + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + protected abstract static class GeneratedCriteria { protected List criteria; @@ -589,38 +589,6 @@ public class ChatQueryDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -656,5 +624,37 @@ public class ChatQueryDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/QueryDO.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/QueryDO.java index d930b8680..4564714b0 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/QueryDO.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/dataobject/QueryDO.java @@ -5,6 +5,9 @@ import lombok.Data; @Data public class QueryDO { + public String aggregator = "trend"; + public String startTime; + public String endTime; private long id; private long questionId; private String createTime; @@ -19,10 +22,7 @@ public class QueryDO { private int isDeleted; private String module; private long chatId; - public String aggregator = "trend"; private int topNum; - public String startTime; - public String endTime; private String querySql; private Object queryColumn; private Object entityInfo; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMReq.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMReq.java new file mode 100644 index 000000000..a656d69c9 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMReq.java @@ -0,0 +1,11 @@ +package com.tencent.supersonic.chat.domain.pojo.chat; + +import lombok.Data; + +@Data +public class LLMReq { + + private String queryText; + private LLMSchema schema; + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMResp.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMResp.java new file mode 100644 index 000000000..b750161bf --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMResp.java @@ -0,0 +1,20 @@ +package com.tencent.supersonic.chat.domain.pojo.chat; + +import java.util.List; +import lombok.Data; + +@Data +public class LLMResp { + + private String query; + + private String domainName; + + private String sqlOutput; + + private List fields; + + private String schemaLinkingOutput; + + private String schemaLinkStr; +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMSchema.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMSchema.java new file mode 100644 index 000000000..a94141bcc --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/LLMSchema.java @@ -0,0 +1,13 @@ +package com.tencent.supersonic.chat.domain.pojo.chat; + +import java.util.List; +import lombok.Data; + +@Data +public class LLMSchema { + + private String domainName; + + private List fieldNameList; + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/PageQueryInfoReq.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/PageQueryInfoReq.java index 0d0df60a9..a6b092ae1 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/PageQueryInfoReq.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/chat/PageQueryInfoReq.java @@ -15,20 +15,20 @@ public class PageQueryInfoReq { return pageSize; } - public int getCurrent() { - return current; + public void setPageSize(int pageSize) { + this.pageSize = pageSize; } - public String getUserName() { - return userName; + public int getCurrent() { + return current; } public void setCurrent(int current) { this.current = current; } - public void setPageSize(int pageSize) { - this.pageSize = pageSize; + public String getUserName() { + return userName; } public void setUserName(String userName) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/config/KnowledgeInfo.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/config/KnowledgeInfo.java index 393d2f0fb..5ccd0c1dd 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/config/KnowledgeInfo.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/pojo/config/KnowledgeInfo.java @@ -1,8 +1,10 @@ package com.tencent.supersonic.chat.domain.pojo.config; import com.tencent.supersonic.common.enums.TypeEnums; + import java.util.List; import javax.validation.constraints.NotNull; + import lombok.Data; /** @@ -17,6 +19,7 @@ public class KnowledgeInfo { */ private Long itemId; + private String bizName; /** * type: IntentionTypeEnum * temporarily only supports dimension-related information diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/repository/ChatRepository.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/repository/ChatRepository.java index a46859306..da1990ed0 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/repository/ChatRepository.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/repository/ChatRepository.java @@ -12,6 +12,8 @@ public interface ChatRepository { Boolean updateChatName(Long chatId, String chatName, String lastTime, String creator); + Boolean updateLastQuestion(Long chatId, String lastQuestion, String lastTime); + Boolean updateConversionIsTop(Long chatId, int isTop); boolean updateFeedback(QueryDO queryDO); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ChatService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ChatService.java index 79dd8ceb3..531232822 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ChatService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ChatService.java @@ -3,6 +3,7 @@ package com.tencent.supersonic.chat.domain.service; import com.github.pagehelper.PageInfo; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; import com.tencent.supersonic.chat.api.response.QueryResultResp; import com.tencent.supersonic.chat.domain.dataobject.ChatDO; @@ -24,6 +25,8 @@ public interface ChatService { public void updateContext(ChatContext chatCtx); + public void updateContext(ChatContext chatCtx, QueryContextReq queryCtx, SemanticParseInfo semanticParseInfo); + public void switchContext(ChatContext chatCtx); public Boolean addChat(User user, String chatName); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ConfigService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ConfigService.java index 64acd2dd1..4a313fea8 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ConfigService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/ConfigService.java @@ -18,4 +18,6 @@ public interface ConfigService { List search(ChatConfigFilter filter, User user); ChatConfigRichInfo getConfigRichInfo(Long domainId); + + List getAllChatRichConfig(); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/QueryService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/QueryService.java index e9c0af105..f80be7fb7 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/QueryService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/service/QueryService.java @@ -11,9 +11,9 @@ import com.tencent.supersonic.chat.domain.pojo.chat.QueryData; */ public interface QueryService { - public QueryResultResp executeQuery(QueryContextReq queryCtx) throws Exception; + QueryResultResp executeQuery(QueryContextReq queryCtx) throws Exception; - public SemanticParseInfo queryContext(QueryContextReq queryCtx); + SemanticParseInfo queryContext(QueryContextReq queryCtx); - QueryResultResp queryData(QueryData queryData, User user) throws Exception; + QueryResultResp executeDirectQuery(QueryData queryData, User user) throws Exception; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ChatConfigUtils.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ChatConfigUtils.java index c3517d08a..f79114003 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ChatConfigUtils.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ChatConfigUtils.java @@ -88,7 +88,7 @@ public class ChatConfigUtils { } public EntityInternalDetail generateEntityDetailData(EntityDetailData detailData, - DomainSchemaResp domainSchemaDesc) { + DomainSchemaResp domainSchemaDesc) { EntityInternalDetail entityInternalDetailDesc = new EntityInternalDetail(); if (Objects.isNull(detailData)) { return entityInternalDetailDesc; @@ -100,7 +100,7 @@ public class ChatConfigUtils { } public Map generateMetricIdAndDescPair(List metricIds, - DomainSchemaResp domainSchemaDesc) { + DomainSchemaResp domainSchemaDesc) { Map metricIdAndDescPair = new HashMap<>(); List metricDescList = generateMetricDesc(metricIds, domainSchemaDesc); @@ -118,6 +118,9 @@ public class ChatConfigUtils { } public List generateAllMetricIdList(DomainSchemaResp domainSchemaDesc) { + if (Objects.isNull(domainSchemaDesc) || CollectionUtils.isEmpty(domainSchemaDesc.getMetrics())) { + return new ArrayList<>(); + } Map> metricIdAndDescPair = domainSchemaDesc.getMetrics() .stream().collect(Collectors.groupingBy(MetricSchemaResp::getId)); return new ArrayList<>(metricIdAndDescPair.keySet()); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ComponentFactory.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ComponentFactory.java new file mode 100644 index 000000000..6fcdcb4ed --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ComponentFactory.java @@ -0,0 +1,67 @@ +package com.tencent.supersonic.chat.domain.utils; + +import com.tencent.supersonic.chat.api.component.SchemaMapper; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticParser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.tencent.supersonic.chat.application.parser.DomainResolver; +import com.tencent.supersonic.chat.application.query.QuerySelector; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.core.io.support.SpringFactoriesLoader; + +public class ComponentFactory { + + private static List schemaMappers = new ArrayList<>(); + private static List semanticParsers = new ArrayList<>(); + private static SemanticLayer semanticLayer; + private static QuerySelector querySelector; + private static DomainResolver domainResolver; + + public static List getSchemaMappers() { + return CollectionUtils.isEmpty(schemaMappers) ? init(SchemaMapper.class, schemaMappers) : schemaMappers; + } + + public static List getSemanticParsers() { + return CollectionUtils.isEmpty(semanticParsers) ? init(SemanticParser.class, semanticParsers) : semanticParsers; + } + + public static SemanticLayer getSemanticLayer() { + if (Objects.isNull(semanticLayer)) { + semanticLayer = init(SemanticLayer.class); + } + return semanticLayer; + } + + public static void setSemanticLayer(SemanticLayer layer) { + semanticLayer = layer; + } + + public static QuerySelector getQuerySelector() { + if (Objects.isNull(querySelector)) { + querySelector = init(QuerySelector.class); + } + return querySelector; + } + + public static DomainResolver getDomainResolver() { + if (Objects.isNull(domainResolver)) { + domainResolver = init(DomainResolver.class); + } + return domainResolver; + } + + private static List init(Class factoryType, List list) { + list.addAll(SpringFactoriesLoader.loadFactories(factoryType, + Thread.currentThread().getContextClassLoader())); + return list; + } + + private static T init(Class factoryType) { + return SpringFactoriesLoader.loadFactories(factoryType, + Thread.currentThread().getContextClassLoader()).get(0); + } +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ContextHelper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ContextHelper.java index 9ceeb84fd..2d647c245 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ContextHelper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/ContextHelper.java @@ -2,11 +2,11 @@ package com.tencent.supersonic.chat.domain.utils; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.Filter; -import com.tencent.supersonic.chat.api.pojo.SchemaElementCount; +import com.tencent.supersonic.chat.api.pojo.QueryMatchInfo; import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.service.SemanticQuery; +import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; import com.tencent.supersonic.common.pojo.SchemaItem; @@ -22,6 +22,21 @@ import java.util.Optional; public class ContextHelper { + public static Comparator> DomainStatComparator + = (o1, o2) -> domainSchemaElementCountComparator(o1.getValue(), o2.getValue()); + public static Comparator> SemanticQueryStatComparator + = (o1, o2) -> domainSchemaElementCountComparator(o1.getValue(), o2.getValue()); + /** + * similarity desc + */ + public static Comparator schemaElementMatchComparatorBySimilarity + = new Comparator() { + @Override + public int compare(SchemaElementMatch o1, SchemaElementMatch o2) { + return (int) ((o2.getSimilarity() - o1.getSimilarity()) * 100); + } + }; + public static void updateDomain(SemanticParseInfo from, SemanticParseInfo to) { if (from != null && from.getDomainId() != null) { to.setDomainId(from.getDomainId()); @@ -65,6 +80,15 @@ public class ContextHelper { } } + public static void updateDomainIfEmpty(SemanticParseInfo from, SemanticParseInfo to) { + if (from != null && from.getDomainId() != null && to.getDomainId() == null) { + to.setDomainId(from.getDomainId()); + } + + if (from != null && from.getDomainName() != null && to.getDomainName() == null) { + to.setDomainName(from.getDomainName()); + } + } /** * add from to list if list is empty and from is not empty @@ -105,7 +129,7 @@ public class ContextHelper { /** * count desc > similarity desc */ - public static int domainSchemaElementCountComparator(SchemaElementCount o1, SchemaElementCount o2) { + public static int domainSchemaElementCountComparator(QueryMatchInfo o1, QueryMatchInfo o2) { int difference = o1.getCount() - o2.getCount(); if (difference == 0) { return (int) ((o1.getMaxSimilarity() - o2.getMaxSimilarity()) * 100); @@ -113,22 +137,6 @@ public class ContextHelper { return difference; } - public static Comparator> DomainStatComparator - = (o1, o2) -> domainSchemaElementCountComparator(o1.getValue(), o2.getValue()); - - public static Comparator> SemanticQueryStatComparator - = (o1, o2) -> domainSchemaElementCountComparator(o1.getValue(), o2.getValue()); - /** - * similarity desc - */ - public static Comparator schemaElementMatchComparatorBySimilarity - = new Comparator() { - @Override - public int compare(SchemaElementMatch o1, SchemaElementMatch o2) { - return (int) ((o2.getSimilarity() - o1.getSimilarity()) * 100); - } - }; - public static void setEntityId(Long dimensionId, String value, ChatConfigRichInfo chaConfigRichDesc, SemanticParseInfo semanticParseInfo) { if (chaConfigRichDesc != null && chaConfigRichDesc.getEntity() != null) { @@ -152,35 +160,42 @@ public class ContextHelper { * @param toSchemaElementMatch * @param elementMatches * @param schemaElementTypes - * @param contextSemanticParseInfo + * @param contextSemanticParse */ public static void mergeContextSchemaElementMatch(List toSchemaElementMatch, List elementMatches, List schemaElementTypes, - SemanticParseInfo contextSemanticParseInfo) { + SemanticParseInfo contextSemanticParse) { + + SchemaElementMatch domainMatch = SchemaElementMatch.builder() + .elementType(SchemaElementType.DOMAIN) + .elementID(contextSemanticParse.getDomainId().intValue()) + .similarity(1.0) + .word(contextSemanticParse.getDomainName()) + .detectWord(contextSemanticParse.getDomainName()) + .build(); + toSchemaElementMatch.add(domainMatch); + for (SchemaElementType schemaElementType : schemaElementTypes) { switch (schemaElementType) { case DIMENSION: - if (contextSemanticParseInfo.getDimensions() != null - && contextSemanticParseInfo.getDimensions().size() > 0) { - for (SchemaItem dimension : contextSemanticParseInfo.getDimensions()) { + if (contextSemanticParse.getDimensions().size() > 0) { + for (SchemaItem dimension : contextSemanticParse.getDimensions()) { addSchemaElementMatch(toSchemaElementMatch, elementMatches, SchemaElementType.DIMENSION, dimension); } } break; case METRIC: - if (contextSemanticParseInfo.getMetrics() != null - && contextSemanticParseInfo.getMetrics().size() > 0) { - for (SchemaItem metric : contextSemanticParseInfo.getMetrics()) { + if (contextSemanticParse.getMetrics().size() > 0) { + for (SchemaItem metric : contextSemanticParse.getMetrics()) { addSchemaElementMatch(toSchemaElementMatch, elementMatches, SchemaElementType.METRIC, metric); } } break; case VALUE: - if (contextSemanticParseInfo.getDimensionFilters() != null - && contextSemanticParseInfo.getDimensionFilters().size() > 0) { - for (Filter chatFilter : contextSemanticParseInfo.getDimensionFilters()) { + if (contextSemanticParse.getDimensionFilters().size() > 0) { + for (Filter chatFilter : contextSemanticParse.getDimensionFilters()) { if (!isInSchemaElementMatchList(elementMatches, SchemaElementType.VALUE, chatFilter.getValue().toString())) { toSchemaElementMatch.add( @@ -228,13 +243,12 @@ public class ContextHelper { private static SchemaElementMatch getSchemaElementMatchByContext(int id, String word, SchemaElementType schemaElementType) { - SchemaElementMatch schemaElementMatch = new SchemaElementMatch(); - schemaElementMatch.setElementID(id); - schemaElementMatch.setElementType(schemaElementType); - schemaElementMatch.setWord(word); - // todo default similarity - schemaElementMatch.setSimilarity(0.5); - return schemaElementMatch; + return SchemaElementMatch.builder() + .elementID(id) + .elementType(schemaElementType) + .word(word) + .similarity(0.5) + .build(); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DefaultMetricSemanticParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultMetricUtils.java similarity index 54% rename from chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DefaultMetricSemanticParser.java rename to chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultMetricUtils.java index 2d8036ecf..24d6d8530 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/application/parser/DefaultMetricSemanticParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultMetricUtils.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.application.parser; +package com.tencent.supersonic.chat.domain.utils; import static java.time.LocalDate.now; @@ -8,22 +8,21 @@ import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.chat.api.service.SemanticParser; -import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver; +import com.tencent.supersonic.chat.application.parser.DomainResolver; import com.tencent.supersonic.chat.application.query.EntityDetail; import com.tencent.supersonic.chat.application.query.EntityListFilter; +import com.tencent.supersonic.chat.application.query.EntityListTopN; import com.tencent.supersonic.chat.application.query.EntityMetricFilter; import com.tencent.supersonic.chat.application.query.MetricDomain; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo; import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric; import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo; -import com.tencent.supersonic.chat.domain.service.ChatService; -import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.common.pojo.SchemaItem; import com.tencent.supersonic.common.util.context.ContextUtils; import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -31,99 +30,129 @@ import java.util.Objects; import java.util.Set; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @Slf4j @Component -public class DefaultMetricSemanticParser implements SemanticParser { +public class DefaultMetricUtils { - private final Logger logger = LoggerFactory.getLogger(DefaultMetricSemanticParser.class); - private DomainResolver selectStrategy; - private ChatService chatService; - private DefaultSemanticInternalUtils defaultSemanticUtils; - - @Override - public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) { - selectStrategy = ContextUtils.getBean(DomainResolver.class); - chatService = ContextUtils.getBean(ChatService.class); - defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class); - String queryMode = queryContext.getParseInfo().getQueryMode(); + /** + * supplementary default metric date dimension + */ + public void injectDefaultMetric(SemanticParseInfo semanticParseInfo, QueryContextReq queryContext, + ChatContext chatContext) { + String queryMode = semanticParseInfo.getQueryMode(); + ChatConfigRichInfo chaConfigRichDesc = null; if (StringUtils.isNotEmpty(queryMode)) { - // QueryMode Selected + if (semanticParseInfo == null) { + return; + } if (!EntityListFilter.QUERY_MODE.equals(queryMode)) { + boolean isFillThemeDefaultMetricLogic = false; + boolean isAddEntityDetailDimensionMetric = false; Integer domainId = queryContext.getDomainId().intValue(); - List matchedElements = queryContext.getMapInfo().getMatchedElements(domainId); if (!CollectionUtils.isEmpty(matchedElements)) { long metricCount = matchedElements.stream() .filter(schemaElementMatch -> schemaElementMatch.getElementType() .equals(SchemaElementType.METRIC)).count(); if (metricCount <= 0) { - if (chatCtx.getParseInfo() == null - || chatCtx.getParseInfo().getMetrics() == null - || chatCtx.getParseInfo().getMetrics().size() <= 0) { - logger.info("fillThemeDefaultMetricLogic"); - fillThemeDefaultMetricLogic(queryContext.getParseInfo()); + if (chatContext.getParseInfo() == null + || chatContext.getParseInfo().getMetrics() == null + || chatContext.getParseInfo().getMetrics().size() <= 0) { + log.info("fillThemeDefaultMetricLogic"); + isFillThemeDefaultMetricLogic = true; } } + } else { + log.info("fillThemeDefaultMetricLogic for empty matchedElements "); + isFillThemeDefaultMetricLogic = true; } - fillDateDomain(chatCtx, queryContext); + if (EntityDetail.QUERY_MODE.equals(queryMode) || EntityMetricFilter.QUERY_MODE.equals(queryMode)) { + isAddEntityDetailDimensionMetric = true; + dealNativeQuery(semanticParseInfo, queryContext, true); + } + + if (isFillThemeDefaultMetricLogic) { + if (chaConfigRichDesc == null) { + chaConfigRichDesc = getChatConfigRichInfo(semanticParseInfo.getDomainId()); + } + fillThemeDefaultMetricLogic(semanticParseInfo, chaConfigRichDesc, queryContext); + } + if (isAddEntityDetailDimensionMetric) { + if (chaConfigRichDesc == null) { + chaConfigRichDesc = getChatConfigRichInfo(semanticParseInfo.getDomainId()); + } + addEntityDetailDimensionMetric(semanticParseInfo, chaConfigRichDesc, queryContext, chatContext); + } + fillDateDomain(semanticParseInfo, chatContext, chaConfigRichDesc, queryContext); } - } - defaultQueryMode(queryContext, chatCtx); - - if (EntityDetail.QUERY_MODE.equals(queryMode) || EntityMetricFilter.QUERY_MODE.equals(queryMode)) { - addEntityDetailDimensionMetric(queryContext, chatCtx); - dealNativeQuery(queryContext, true); - } - - return false; - } - - private void dealNativeQuery(QueryContextReq queryContext, boolean isNativeQuery) { - if (Objects.nonNull(queryContext) && Objects.nonNull(queryContext.getParseInfo())) { - queryContext.getParseInfo().setNativeQuery(isNativeQuery); + defaultQueryMode(semanticParseInfo, queryContext, chatContext); + addEntityTopDimension(semanticParseInfo, chaConfigRichDesc); } } - private Set addPrimaryDimension(EntityRichInfo entity, List dimensions) { + public void dealNativeQuery(SemanticParseInfo semanticParseInfo, QueryContextReq queryContext, + boolean isNativeQuery) { + if (Objects.nonNull(queryContext) && Objects.nonNull(semanticParseInfo)) { + semanticParseInfo.setNativeQuery(isNativeQuery); + } + } + + public Set addPrimaryDimension(EntityRichInfo entity, List dimensions) { Set primaryDimensions = new HashSet<>(); if (Objects.isNull(entity) || CollectionUtils.isEmpty(entity.getEntityIds())) { return primaryDimensions; } entity.getEntityIds().stream().forEach(dimSchemaDesc -> { SchemaItem dimension = new SchemaItem(); - BeanUtils.copyProperties(dimSchemaDesc, dimension); + //BeanUtils.copyProperties(dimSchemaDesc, dimension); + dimension.setName(dimSchemaDesc.getName()); + dimension.setBizName(dimSchemaDesc.getBizName()); + dimension.setId(dimSchemaDesc.getId()); dimensions.add(dimension); primaryDimensions.add(dimSchemaDesc.getBizName()); }); return primaryDimensions; } - protected void addEntityDetailDimensionMetric(QueryContextReq queryContext, ChatContext chatCtx) { - if (queryContext.getParseInfo().getDomainId() > 0) { - Long domainId = queryContext.getParseInfo().getDomainId(); - ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domainId); + public void addEntityTopDimension(SemanticParseInfo semanticParseInfo, ChatConfigRichInfo chaConfigRichDesc) { + if (!semanticParseInfo.getQueryMode().equals(EntityListTopN.QUERY_MODE) || !semanticParseInfo.getDimensions() + .isEmpty()) { + return; + } + if (semanticParseInfo.getDomainId() > 0) { + Long domainId = semanticParseInfo.getDomainId(); + if (chaConfigRichDesc == null) { + chaConfigRichDesc = getChatConfigRichInfo(domainId); + } + if (chaConfigRichDesc != null && chaConfigRichDesc.getEntity() != null) { + List dimensions = new ArrayList<>(); + addPrimaryDimension(chaConfigRichDesc.getEntity(), dimensions); + semanticParseInfo.setDimensions(new HashSet<>(dimensions)); + semanticParseInfo.setLimit(1L); + } + } + } + + public void addEntityDetailDimensionMetric(SemanticParseInfo semanticParseInfo, + ChatConfigRichInfo chaConfigRichDesc, QueryContextReq queryContext, + ChatContext chatCtx) { + if (semanticParseInfo.getDomainId() > 0) { + Long domainId = semanticParseInfo.getDomainId(); + if (chaConfigRichDesc != null) { if (chaConfigRichDesc.getEntity() == null || chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() == null) { return; } - SemanticParseInfo semanticParseInfo = queryContext.getParseInfo(); - Set metrics = new LinkedHashSet(); - chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream() - .forEach(m -> metrics.add(getMetric(m))); - semanticParseInfo.setMetrics(metrics); List schemaElementMatches = queryContext.getMapInfo() .getMatchedElements(domainId.intValue()); if (CollectionUtils.isEmpty(schemaElementMatches) || schemaElementMatches.stream() .filter(s -> SchemaElementType.DIMENSION.equals(s.getElementType())).count() <= 0) { - logger.info("addEntityDetailDimensionMetric catch"); + log.info("addEntityDetailDimensionMetric catch"); Set dimensions = new LinkedHashSet(); chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream() .forEach(m -> dimensions.add(getDimension(m))); @@ -134,10 +163,10 @@ public class DefaultMetricSemanticParser implements SemanticParser { } } - protected void defaultQueryMode(QueryContextReq queryContext, ChatContext chatCtx) { + public void defaultQueryMode(SemanticParseInfo semanticParseInfo, QueryContextReq queryContext, + ChatContext chatCtx) { SchemaMapInfo schemaMap = queryContext.getMapInfo(); - SemanticParseInfo parseInfo = queryContext.getParseInfo(); - if (StringUtils.isEmpty(parseInfo.getQueryMode())) { + if (StringUtils.isEmpty(semanticParseInfo.getQueryMode())) { if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getDomainId() > 0) { // Long domain = chatCtx.getParseInfo().getDomainId(); @@ -154,9 +183,9 @@ public class DefaultMetricSemanticParser implements SemanticParser { .filter(e -> e.getElementType().equals(SchemaElementType.METRIC)).count(); if (filterNUm > 0 && dimensionNUm > 0 && metricrNUm > 0) { // default as entity detail queryMode - logger.info("defaultQueryMode [{}]", EntityDetail.QUERY_MODE); - parseInfo.setQueryMode(EntityDetail.QUERY_MODE); - parseInfo.setDomainId(domain); + log.info("defaultQueryMode [{}]", EntityDetail.QUERY_MODE); + semanticParseInfo.setQueryMode(EntityDetail.QUERY_MODE); + semanticParseInfo.setDomainId(domain); return; } Long entityNUm = elementMatches.stream() @@ -165,21 +194,21 @@ public class DefaultMetricSemanticParser implements SemanticParser { // default as metric domain if (metricrNUm > 0 || MetricDomain.QUERY_MODE.equals(queryMode)) { // default as entity detail queryMode - logger.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE); - parseInfo.setQueryMode(MetricDomain.QUERY_MODE); - parseInfo.setDomainId(domain); + log.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE); + semanticParseInfo.setQueryMode(MetricDomain.QUERY_MODE); + semanticParseInfo.setDomainId(domain); return; } } } - if (CollectionUtils.isEmpty(schemaMap.getMatchedDomains()) && parseInfo != null - && parseInfo.getDateInfo() != null) { + if (CollectionUtils.isEmpty(schemaMap.getMatchedDomains()) && semanticParseInfo != null + && semanticParseInfo.getDateInfo() != null) { // only query time if (MetricDomain.QUERY_MODE.equals(queryMode)) { // METRIC_DOMAIN context - logger.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE); - parseInfo.setQueryMode(MetricDomain.QUERY_MODE); - parseInfo.setDomainId(domain); + log.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE); + semanticParseInfo.setQueryMode(MetricDomain.QUERY_MODE); + semanticParseInfo.setDomainId(domain); return; } } @@ -188,12 +217,14 @@ public class DefaultMetricSemanticParser implements SemanticParser { } - private void fillDateDomain(ChatContext chatCtx, QueryContextReq queryContext) { - SemanticParseInfo parseInfo = queryContext.getParseInfo(); + public void fillDateDomain(SemanticParseInfo parseInfo, ChatContext chatCtx, ChatConfigRichInfo chaConfigRichDesc, + QueryContextReq queryContext) { + //SemanticParseInfo parseInfo = queryContext.getParseInfo(); if (parseInfo == null || parseInfo.getDateInfo() == null) { + DomainResolver selectStrategy = ComponentFactory.getDomainResolver(); boolean isUpdateTime = false; - if (selectStrategy.isDomainSwitch(chatCtx, queryContext)) { + if (selectStrategy.isDomainSwitch(chatCtx, parseInfo)) { isUpdateTime = true; } if (chatCtx.getParseInfo() == null @@ -201,23 +232,31 @@ public class DefaultMetricSemanticParser implements SemanticParser { isUpdateTime = true; } if (isUpdateTime && parseInfo != null && parseInfo.getDomainId() > 0) { - logger.info("fillThemeDefaultTime"); - fillThemeDefaultTime(parseInfo.getDomainId(), parseInfo); + fillThemeDefaultTime(parseInfo.getDomainId(), chaConfigRichDesc, parseInfo); } } } - private void fillThemeDefaultMetricLogic(SemanticParseInfo semanticParseInfo) { - ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo( - semanticParseInfo.getDomainId()); + public void fillThemeDefaultMetricLogic(SemanticParseInfo semanticParseInfo, ChatConfigRichInfo chaConfigRichDesc, + QueryContextReq queryContext) { + //SemanticParseInfo semanticParseInfo = queryContext.getParseInfo(); if (Objects.isNull(chaConfigRichDesc) || CollectionUtils.isEmpty(chaConfigRichDesc.getDefaultMetrics())) { log.info("there is no defaultMetricIds info"); return; } - if (CollectionUtils.isEmpty(semanticParseInfo.getMetrics()) && CollectionUtils.isEmpty( - semanticParseInfo.getDimensions())) { + if (queryContext.getMapInfo() == null || !queryContext.getMapInfo().getMatchedDomains() + .contains(chaConfigRichDesc.getDomainId().intValue())) { + return; + } + List schemaElementMatches = queryContext.getMapInfo() + .getMatchedElements(chaConfigRichDesc.getDomainId().intValue()); + long metricNum = schemaElementMatches.stream().filter(e -> e.getElementType().equals(SchemaElementType.METRIC)) + .count(); + long dimensionNum = schemaElementMatches.stream() + .filter(e -> e.getElementType().equals(SchemaElementType.DIMENSION)).count(); + if (metricNum <= 0 && dimensionNum <= 0) { Set metrics = new LinkedHashSet(); chaConfigRichDesc.getDefaultMetrics().stream().forEach(metric -> { SchemaItem metricTmp = new SchemaItem(); @@ -240,9 +279,15 @@ public class DefaultMetricSemanticParser implements SemanticParser { } - private void fillThemeDefaultTime(Long domain, SemanticParseInfo semanticParseInfo) { - ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo( - semanticParseInfo.getDomainId()); + public void fillThemeDefaultTime(Long domain, ChatConfigRichInfo chaConfigRichDesc, + SemanticParseInfo semanticParseInfo) { + if (!Objects.isNull(semanticParseInfo.getDateInfo()) && !Objects.isNull( + semanticParseInfo.getDateInfo().getDateMode())) { + return; + } + if (chaConfigRichDesc == null) { + chaConfigRichDesc = getChatConfigRichInfo(semanticParseInfo.getDomainId()); + } if (!Objects.isNull(chaConfigRichDesc) && !CollectionUtils.isEmpty(chaConfigRichDesc.getDefaultMetrics())) { DefaultMetric defaultMetricInfo = chaConfigRichDesc.getDefaultMetrics().get(0); DateConf dateInfo = new DateConf(); @@ -252,9 +297,16 @@ public class DefaultMetricSemanticParser implements SemanticParser { dateInfo.setEndDate(now().minusDays(1).toString()); dateInfo.setPeriod(defaultMetricInfo.getPeriod()); semanticParseInfo.setDateInfo(dateInfo); + log.info("fillThemeDefaultTime"); } } + public ChatConfigRichInfo getChatConfigRichInfo(Long domain) { + DefaultSemanticInternalUtils defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class); + ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domain); + return chaConfigRichDesc; + } + private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) { SchemaItem queryMeta = new SchemaItem(); queryMeta.setId(metricSchemaDesc.getId()); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultSemanticInternalUtils.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultSemanticInternalUtils.java index ca9f6f407..aa163dffc 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultSemanticInternalUtils.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DefaultSemanticInternalUtils.java @@ -19,10 +19,12 @@ import com.tencent.supersonic.common.result.ReturnCode; import com.tencent.supersonic.common.util.context.S2ThreadContext; import com.tencent.supersonic.common.util.context.ThreadContext; import com.tencent.supersonic.common.util.json.JsonUtil; + import java.net.URI; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; + import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.BeanUtils; @@ -139,13 +141,13 @@ public class DefaultSemanticInternalUtils { headers.set(authenticationConfig.getTokenHttpHeaderKey(), threadContext.getToken()); } } else { - log.info("threadContext is null:{}", Objects.isNull(threadContext)); + log.debug("threadContext is null:{}", Objects.isNull(threadContext)); } } - public List getDomainListForUser(User user) { + public List getDomainListForAdmin() { Object domainDescListObject = fetchHttpResult(semanticUrl + fetchDomainListPath, null, HttpMethod.GET); - List domainDescList = (List) domainDescListObject; + List domainDescList = JsonUtil.toList(JsonUtil.toString(domainDescListObject), DomainResp.class); return domainDescList; } } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictMetaUtils.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictMetaUtils.java index 9590908aa..919a7b14e 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictMetaUtils.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictMetaUtils.java @@ -3,7 +3,7 @@ package com.tencent.supersonic.chat.domain.utils; import static com.tencent.supersonic.common.constant.Constants.DAY; import static com.tencent.supersonic.common.constant.Constants.UNDERLINE; -import com.tencent.supersonic.chat.api.service.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.chat.domain.dataobject.DimValueDO; @@ -32,15 +32,12 @@ import org.springframework.util.CollectionUtils; @Component public class DictMetaUtils { + private final DefaultSemanticInternalUtils defaultSemanticUtils; @Value("${model.internal.metric.suffix:internal_cnt}") private String internalMetricNameSuffix; + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); - private final SemanticLayer semanticLayer; - private final DefaultSemanticInternalUtils defaultSemanticUtils; - - public DictMetaUtils(SemanticLayer semanticLayer, - DefaultSemanticInternalUtils defaultSemanticUtils) { - this.semanticLayer = semanticLayer; + public DictMetaUtils(DefaultSemanticInternalUtils defaultSemanticUtils) { this.defaultSemanticUtils = defaultSemanticUtils; } @@ -131,7 +128,7 @@ public class DictMetaUtils { } private void fillDimValueDOList(List dimValueDOList, Long domainId, - Map dimIdAndDescPair) { + Map dimIdAndDescPair) { ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domainId); if (Objects.nonNull(chaConfigRichDesc)) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictQueryUtils.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictQueryUtils.java index d1f72fafe..2b6c2610f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictQueryUtils.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DictQueryUtils.java @@ -6,7 +6,7 @@ import static com.tencent.supersonic.common.constant.Constants.COMMA; import static com.tencent.supersonic.common.constant.Constants.UNDERLINE_DOUBLE; import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.service.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn; import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; @@ -19,6 +19,7 @@ import com.tencent.supersonic.common.enums.AggOperatorEnum; import com.tencent.supersonic.common.pojo.Aggregator; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.common.pojo.Order; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -26,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; + import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Value; @@ -36,9 +38,8 @@ import org.springframework.util.CollectionUtils; @Component public class DictQueryUtils { - private final SemanticLayer semanticLayer; Long frequencyMax = 99999999L; - + private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); @Value("${dimension.multi.value.split:#}") private String dimMultiValueSplit; @@ -48,11 +49,6 @@ public class DictQueryUtils { @Value("${dimension.max.limit:3000000}") private Long dimMaxLimit; - public DictQueryUtils(SemanticLayer semanticLayer) { - this.semanticLayer = semanticLayer; - } - - public List fetchDimValueSingle(Long domainId, DefaultMetric defaultMetricDesc, Dim4Dict dim4Dict, User user) { List data = new ArrayList<>(); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfo.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfo.java new file mode 100644 index 000000000..ee35e2864 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfo.java @@ -0,0 +1,89 @@ +package com.tencent.supersonic.chat.domain.utils; + +import static java.time.LocalDate.now; + +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.application.knowledge.WordNatureService; +import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos; +import com.tencent.supersonic.chat.domain.pojo.chat.LLMResp; +import com.tencent.supersonic.common.nlp.ItemDO; +import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.common.util.calcite.SqlParseUtils; +import com.tencent.supersonic.common.util.calcite.SqlParserInfo; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.semantic.api.core.enums.TimeDimensionEnum; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DslToSemanticInfo { + + public static final String SUB_TABLE = " ( select * from t_{0} where {1} >= ''{2}'' and {1} <= ''{3}'' ) as t_sub_{0}"; + private static final Logger LOG = LoggerFactory.getLogger(DslToSemanticInfo.class); + + public static String convert(SemanticParseInfo parseInfo, LLMResp llmResp) { + + String sqlOutput = llmResp.getSqlOutput(); + String domainName = llmResp.getDomainName(); + + DomainInfos domainInfos = ContextUtils.getBean(WordNatureService.class).getCache().getUnchecked(""); + + SqlParserInfo sqlParseInfo = SqlParseUtils.getSqlParseInfo(sqlOutput); + String tableName = sqlParseInfo.getTableName(); + if (StringUtils.isEmpty(domainName)) { + domainName = tableName; + } + + List allFields = sqlParseInfo.getAllFields(); + Map domainNameToId = domainInfos.getDomains().stream() + .collect(Collectors.toMap(ItemDO::getName, a -> a.getDomain(), (k1, k2) -> k1)); + + Integer domainId = domainNameToId.get(domainName); + LOG.info("sqlParseInfo:{} ,domainName:{},domainId:{}", sqlParseInfo, domainName, domainId); + + List fieldList = domainInfos.getMetrics(); + fieldList.addAll(domainInfos.getDimensions()); + Map fieldToBizName = getMapInfo(domainId, fieldList); + + for (String fieldName : allFields) { + String fieldBizName = fieldToBizName.get(fieldName); + if (StringUtils.isNotEmpty(fieldBizName)) { + sqlOutput = sqlOutput.replaceAll(fieldName, fieldBizName); + } + } + DateConf dateInfo = new DateConf(); + if (Objects.nonNull(parseInfo) && Objects.nonNull(parseInfo.getDateInfo())) { + dateInfo = parseInfo.getDateInfo(); + } else { + String startDate = now().plusDays(-4).toString(); + String endDate = now().plusDays(-4).toString(); + dateInfo.setStartDate(startDate); + dateInfo.setEndDate(endDate); + } + + String startDate = dateInfo.getStartDate(); + String endDate = dateInfo.getEndDate(); + String period = dateInfo.getPeriod(); + TimeDimensionEnum timeDimension = TimeDimensionEnum.valueOf(period); + String dayField = timeDimension.getName(); + + // add dayno + String subTable = MessageFormat.format(SUB_TABLE, domainId, dayField, startDate, endDate); + String querySql = sqlOutput.replaceAll(tableName, subTable); + + LOG.info("querySql:{},sqlOutput:{},dateInfo:{}", querySql, sqlOutput, dateInfo); + return querySql; + } + + private static Map getMapInfo(Integer domainId, List metrics) { + return metrics.stream().filter(entry -> entry.getDomain().equals(domainId)) + .collect(Collectors.toMap(ItemDO::getName, a -> a.getBizName(), (k1, k2) -> k1)); + } + + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SchemaInfoConverter.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SchemaInfoConverter.java index facfaef98..a6b013cbe 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SchemaInfoConverter.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SchemaInfoConverter.java @@ -20,15 +20,19 @@ import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; import com.tencent.supersonic.semantic.api.query.pojo.Filter; +import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; +import org.springframework.beans.BeanUtils; import org.springframework.util.CollectionUtils; public class SchemaInfoConverter { @@ -66,6 +70,23 @@ public class SchemaInfoConverter { return queryStructCmd; } + + /*** + * convert to QuerySqlReq + * @param parseInfo + * @return + */ + public static QuerySqlReq convertToQuerySqlReq(SemanticParseInfo parseInfo) { + QuerySqlReq querySqlReq = new QuerySqlReq(); + Object info = parseInfo.getInfo(); + if (Objects.nonNull(info)) { + querySqlReq.setSql(info.toString()); + } + querySqlReq.setDomainId(parseInfo.getDomainId()); + return querySqlReq; + } + + private static List getAggregatorByMetric(Set metrics, AggregateTypeEnum aggregateType) { List aggregators = new ArrayList<>(); String agg = (aggregateType == null || aggregateType.equals(AggregateTypeEnum.NONE)) ? "" @@ -131,6 +152,7 @@ public class SchemaInfoConverter { domainDO.setName(domainSchemaDesc.getName()); domainDO.setItemId(domain); result.getDomains().add(domainDO); + domainDO.setBizName(domainSchemaDesc.getBizName()); // entity List entityNames = domainSchemaDesc.getEntityNames(); if (!CollectionUtils.isEmpty(entityNames)) { @@ -149,7 +171,16 @@ public class SchemaInfoConverter { metricDO.setName(metric.getName()); metricDO.setItemId(Math.toIntExact(metric.getId())); metricDO.setUseCnt(metric.getUseCnt()); + metricDO.setBizName(metric.getBizName()); result.getMetrics().add(metricDO); + + String metricAlias = metric.getAlias(); + if (StringUtils.isNotEmpty(metricAlias)) { + ItemDO aliasMetricDO = new ItemDO(); + BeanUtils.copyProperties(metricDO, aliasMetricDO); + aliasMetricDO.setName(metricAlias); + result.getMetrics().add(aliasMetricDO); + } } // dimension for (DimSchemaResp dimension : domainSchemaDesc.getDimensions()) { @@ -158,7 +189,16 @@ public class SchemaInfoConverter { dimensionDO.setName(dimension.getName()); dimensionDO.setItemId(Math.toIntExact(dimension.getId())); dimensionDO.setUseCnt(dimension.getUseCnt()); + dimensionDO.setBizName(dimension.getBizName()); result.getDimensions().add(dimensionDO); + + String dimensionAlias = dimension.getAlias(); + if (StringUtils.isNotEmpty(dimensionAlias)) { + ItemDO aliasDimensionDO = new ItemDO(); + BeanUtils.copyProperties(dimensionDO, aliasDimensionDO); + aliasDimensionDO.setName(dimensionAlias); + result.getDimensions().add(aliasDimensionDO); + } } } return result; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SemanticSatisfactionChecker.java b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SemanticSatisfactionChecker.java new file mode 100644 index 000000000..336eb7556 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/domain/utils/SemanticSatisfactionChecker.java @@ -0,0 +1,93 @@ +package com.tencent.supersonic.chat.domain.utils; + + +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.Filter; +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; +import com.tencent.supersonic.chat.api.pojo.SchemaElementType; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import com.tencent.supersonic.common.pojo.SchemaItem; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * utils to check current parse info is enough to query result + */ +@Slf4j +public class SemanticSatisfactionChecker { + + private static final double THRESHOLD = 0.8; + + // check all the parse info in candidate + public static boolean check(QueryContextReq queryCtx) { + for (SemanticQuery query : queryCtx.getCandidateQueries()) { + SemanticParseInfo semanticParseInfo = query.getParseInfo(); + Long domainId = semanticParseInfo.getDomainId(); + List schemaElementMatches = queryCtx.getMapInfo() + .getMatchedElements(domainId.intValue()); + if (check(queryCtx.getQueryText(), semanticParseInfo, schemaElementMatches)) { + return true; + } + } + return false; + } + + //check single parse info + private static boolean check(String text, SemanticParseInfo semanticParseInfo, + List schemaElementMatches) { + if (CollectionUtils.isEmpty(schemaElementMatches)) { + return false; + } + List detectWords = Lists.newArrayList(); + Map detectWordMap = schemaElementMatches.stream() + .collect(Collectors.toMap(SchemaElementMatch::getElementID, SchemaElementMatch::getDetectWord, + (id1, id2) -> id1)); + // get detect word in text by element id in semantic layer + Long domainId = semanticParseInfo.getDomainId(); + if (domainId != null && domainId > 0) { + for (SchemaElementMatch schemaElementMatch : schemaElementMatches) { + if (SchemaElementType.DOMAIN.equals(schemaElementMatch.getElementType())) { + detectWords.add(schemaElementMatch.getDetectWord()); + } + } + } + + for (Filter filter : semanticParseInfo.getDimensionFilters()) { + detectWords.add( + detectWordMap.getOrDefault(Optional.ofNullable(filter.getElementID()).orElse(0L).intValue(), "")); + } + for (SchemaItem schemaItem : semanticParseInfo.getMetrics()) { + detectWords.add( + detectWordMap.getOrDefault(Optional.ofNullable(schemaItem.getId()).orElse(0L).intValue(), "")); + // only first metric + break; + } + for (SchemaItem schemaItem : semanticParseInfo.getDimensions()) { + detectWords.add( + detectWordMap.getOrDefault(Optional.ofNullable(schemaItem.getId()).orElse(0L).intValue(), "")); + // only first dimension + break; + } + //compare the length between detect words and query text + String detectWordsDistinct = StringUtils.join(new HashSet<>(detectWords), ""); + int detectWordsLength = detectWordsDistinct.length(); + int queryTextLength = text.length(); + double degree = detectWordsLength * 1.0 / queryTextLength; + if (degree > THRESHOLD) { + log.info("queryMode:{} has satisfied semantic check, degree:{}, detectWords:{}, parse info:{}", + semanticParseInfo.getQueryMode(), degree, detectWordsDistinct, semanticParseInfo); + return true; + } + return false; + } + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/mapper/ChatMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/mapper/ChatMapper.java index 1c04a07ba..5c7627158 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/mapper/ChatMapper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/mapper/ChatMapper.java @@ -16,6 +16,8 @@ public interface ChatMapper { Boolean updateChatName(Long chatId, String chatName, String lastTime, String creator); + Boolean updateLastQuestion(Long chatId, String lastQuestion, String lastTime); + Boolean updateConversionIsTop(Long chatId, int isTop); boolean updateFeedback(QueryDO queryDO); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatConfigRepositoryImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatConfigRepositoryImpl.java index e823466c9..4bb9ac28d 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatConfigRepositoryImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatConfigRepositoryImpl.java @@ -12,10 +12,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.springframework.beans.BeanUtils; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Repository; import org.springframework.util.CollectionUtils; @Repository +@Primary public class ChatConfigRepositoryImpl implements ChatConfigRepository { private final ChatConfigUtils chatConfigUtils; @@ -65,4 +67,4 @@ public class ChatConfigRepositoryImpl implements ChatConfigRepository { return chatConfigUtils.chatConfigDO2Descriptor(chaConfigPO); } -} \ No newline at end of file +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatContextRepositoryImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatContextRepositoryImpl.java index f4d70647f..54ab46a83 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatContextRepositoryImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatContextRepositoryImpl.java @@ -7,6 +7,7 @@ import com.tencent.supersonic.chat.domain.dataobject.ChatContextDO; import com.tencent.supersonic.chat.domain.repository.ChatContextRepository; import com.tencent.supersonic.chat.infrastructure.mapper.ChatContextMapper; import com.tencent.supersonic.common.util.json.JsonUtil; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Repository; @@ -14,6 +15,7 @@ import org.springframework.stereotype.Repository; @Primary public class ChatContextRepositoryImpl implements ChatContextRepository { + @Autowired(required = false) private final ChatContextMapper chatContextMapper; public ChatContextRepositoryImpl(ChatContextMapper chatContextMapper) { @@ -62,6 +64,14 @@ public class ChatContextRepositoryImpl implements ChatContextRepository { chatContextDO.setUser(chatContext.getUser()); if (chatContext.getParseInfo() != null) { Gson g = new Gson(); + chatContext.getParseInfo().getDimensions().stream().forEach(d -> { + d.setUpdatedAt(null); + d.setCreatedAt(null); + }); + chatContext.getParseInfo().getMetrics().stream().forEach(d -> { + d.setUpdatedAt(null); + d.setCreatedAt(null); + }); chatContextDO.setSemanticParse(g.toJson(chatContext.getParseInfo())); } return chatContextDO; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatQueryRepositoryImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatQueryRepositoryImpl.java index f8743b543..7b1e273d3 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatQueryRepositoryImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatQueryRepositoryImpl.java @@ -14,6 +14,7 @@ import com.tencent.supersonic.chat.domain.repository.ChatQueryRepository; import com.tencent.supersonic.chat.infrastructure.mapper.ChatQueryDOMapper; import com.tencent.supersonic.common.util.json.JsonUtil; import com.tencent.supersonic.common.util.mybatis.PageUtils; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -36,7 +37,7 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { @Override public PageInfo getChatQuery(PageQueryInfoReq pageQueryInfoCommend, long chatId) { ChatQueryDOExample example = new ChatQueryDOExample(); - example.setOrderByClause("question_id"); + example.setOrderByClause("question_id desc"); Criteria criteria = example.createCriteria(); criteria.andChatIdEqualTo(chatId); criteria.andUserNameEqualTo(pageQueryInfoCommend.getUserName()); @@ -47,7 +48,9 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { PageInfo chatQueryVOPageInfo = PageUtils.pageInfo2PageInfoVo(pageInfo); chatQueryVOPageInfo.setList( - pageInfo.getList().stream().map(chatQueryDO -> convertTo(chatQueryDO)).collect(Collectors.toList())); + pageInfo.getList().stream().map(this::convertTo) + .sorted(Comparator.comparingInt(o -> o.getQuestionId().intValue())) + .collect(Collectors.toList())); return chatQueryVOPageInfo; } @@ -55,6 +58,7 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { ChatQueryVO chatQueryVO = new ChatQueryVO(); BeanUtils.copyProperties(chatQueryDO, chatQueryVO); QueryResultResp queryResponse = JsonUtil.toObject(chatQueryDO.getQueryResponse(), QueryResultResp.class); + queryResponse.setQueryId(chatQueryDO.getQuestionId()); chatQueryVO.setQueryResponse(queryResponse); return chatQueryVO; } @@ -68,7 +72,9 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { chatQueryDO.setQueryState(queryResponse.getQueryState()); chatQueryDO.setQueryText(queryContext.getQueryText()); chatQueryDO.setQueryResponse(JsonUtil.toString(queryResponse)); - Long queryId = Long.valueOf(chatQueryDOMapper.insert(chatQueryDO)); + chatQueryDOMapper.insert(chatQueryDO); + ChatQueryDO lastChatQuery = getLastChatQuery(queryContext.getChatId()); + Long queryId = lastChatQuery.getQuestionId(); queryResponse.setQueryId(queryId); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatRepositoryImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatRepositoryImpl.java index 30bed3b03..e40d2ff85 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatRepositoryImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/repository/ChatRepositoryImpl.java @@ -36,6 +36,11 @@ public class ChatRepositoryImpl implements ChatRepository { return chatMapper.updateChatName(chatId, chatName, lastTime, creator); } + @Override + public Boolean updateLastQuestion(Long chatId, String lastQuestion, String lastTime) { + return chatMapper.updateLastQuestion(chatId, lastQuestion, lastTime); + } + @Override public Boolean updateConversionIsTop(Long chatId, int isTop) { return chatMapper.updateConversionIsTop(chatId, isTop); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticConfig.java new file mode 100644 index 000000000..af86760a3 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticConfig.java @@ -0,0 +1,35 @@ +package com.tencent.supersonic.chat.infrastructure.semantic; + +import com.tencent.supersonic.chat.application.ConfigServiceImpl; +import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; +import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +@Data +public class DefaultSemanticConfig { + + @Value("${semantic.url.prefix:http://localhost:8081}") + private String semanticUrl; + + @Value("${searchByStruct.path:/api/semantic/query/struct}") + private String searchByStructPath; + + @Value("${searchByStruct.path:/api/semantic/query/sql}") + private String searchBySqlPath; + + @Value("${fetchDomainSchemaPath.path:/api/semantic/schema}") + private String fetchDomainSchemaPath; + + @Autowired + private DefaultSemanticInternalUtils defaultSemanticInternalUtils; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ConfigServiceImpl chaConfigService; +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/LocalSemanticLayerImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/LocalSemanticLayerImpl.java new file mode 100644 index 000000000..dc7322450 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/LocalSemanticLayerImpl.java @@ -0,0 +1,181 @@ +package com.tencent.supersonic.chat.infrastructure.semantic; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigInfo; +import com.tencent.supersonic.chat.domain.pojo.config.ItemVisibility; +import com.tencent.supersonic.common.util.context.ContextUtils; +import com.tencent.supersonic.common.util.json.JsonUtil; +import com.tencent.supersonic.semantic.api.core.request.DomainSchemaFilterReq; +import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; +import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; +import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; +import com.tencent.supersonic.semantic.query.domain.SchemaService; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import com.tencent.supersonic.semantic.query.domain.QueryService; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Slf4j +public class LocalSemanticLayerImpl implements SemanticLayer { + + private static final Cache> domainSchemaCache = + CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build(); + private SchemaService schemaService; + + @Override + public QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user) { + deletionDuplicated(queryStructReq); + onlyQueryFirstMetric(queryStructReq); + try { + QueryService queryService = ContextUtils.getBean(QueryService.class); + QueryResultWithSchemaResp queryResultWithSchemaResp = queryService.queryByStruct(queryStructReq, user); + return queryResultWithSchemaResp; + } catch (Exception e) { + log.info("queryByStruct has an exception:{}", e.toString()); + } + return null; + } + + @Override + public QueryResultWithSchemaResp queryBySql(QuerySqlReq querySqlReq, User user) { + try { + QueryService queryService = ContextUtils.getBean(QueryService.class); + Object object = queryService.queryBySql(querySqlReq, user); + QueryResultWithSchemaResp queryResultWithSchemaResp = JsonUtil.toObject(JsonUtil.toString(object), + QueryResultWithSchemaResp.class); + return queryResultWithSchemaResp; + } catch (Exception e) { + log.info("queryByStruct has an exception:{}", e.toString()); + } + return null; + } + + public List fetchDomainSchemaAll(List ids) { + + DomainSchemaFilterReq filter = new DomainSchemaFilterReq(); + filter.setDomainIds(ids); + User user = new User(1L, "admin", "admin", "admin@email"); + schemaService = ContextUtils.getBean(SchemaService.class); + return schemaService.fetchDomainSchema(filter, user); + } + + + @SneakyThrows + public List fetchDomainSchema(List ids) { +// return domainSchemaCache.get(String.valueOf(ids), () -> { +// List data = fetchDomainSchemaAll(ids); +// fillEntityNameAndFilterBlackElement(data); +// return data; +// }); + + List data = fetchDomainSchemaAll(ids); + fillEntityNameAndFilterBlackElement(data); + return data; + } + + @Override + public DomainSchemaResp getDomainSchemaInfo(Long domain) { + List ids = new ArrayList<>(); + ids.add(domain); + List domainSchemaResps = fetchDomainSchema(ids); + if (!CollectionUtils.isEmpty(domainSchemaResps)) { + Optional domainSchemaResp = domainSchemaResps.stream() + .filter(d -> d.getId().equals(domain)).findFirst(); + if (domainSchemaResp.isPresent()) { + DomainSchemaResp domainSchema = domainSchemaResp.get(); + return domainSchema; + } + } + return null; + } + + @Override + public List getDomainSchemaInfo(List ids) { + return fetchDomainSchema(ids); + } + + public DomainSchemaResp fillEntityNameAndFilterBlackElement(DomainSchemaResp domainSchemaResp) { + if (Objects.isNull(domainSchemaResp) || Objects.isNull(domainSchemaResp.getId())) { + return domainSchemaResp; + } + ChatConfigInfo chaConfigInfo = getConfigBaseInfo(domainSchemaResp.getId()); + + // fill entity names + fillEntityNamesInfo(domainSchemaResp, chaConfigInfo); + + // filter black element + filterBlackDim(domainSchemaResp, chaConfigInfo); + filterBlackMetric(domainSchemaResp, chaConfigInfo); + return domainSchemaResp; + } + + public void fillEntityNameAndFilterBlackElement(List domainSchemaRespList) { + if (!CollectionUtils.isEmpty(domainSchemaRespList)) { + domainSchemaRespList.stream() + .forEach(domainSchemaResp -> fillEntityNameAndFilterBlackElement(domainSchemaResp)); + } + } + + private void filterBlackMetric(DomainSchemaResp domainSchemaResp, ChatConfigInfo chaConfigInfo) { + ItemVisibility visibility = chaConfigInfo.getVisibility(); + if (Objects.nonNull(chaConfigInfo) && Objects.nonNull(visibility) + && !CollectionUtils.isEmpty(visibility.getBlackMetricIdList()) + && !CollectionUtils.isEmpty(domainSchemaResp.getMetrics())) { + List metric4Chat = domainSchemaResp.getMetrics().stream() + .filter(metric -> !visibility.getBlackMetricIdList().contains(metric.getId())) + .collect(Collectors.toList()); + domainSchemaResp.setMetrics(metric4Chat); + } + } + + private void filterBlackDim(DomainSchemaResp domainSchemaResp, ChatConfigInfo chatConfigInfo) { + ItemVisibility visibility = chatConfigInfo.getVisibility(); + if (Objects.nonNull(chatConfigInfo) && Objects.nonNull(visibility) + && !CollectionUtils.isEmpty(visibility.getBlackDimIdList()) + && !CollectionUtils.isEmpty(domainSchemaResp.getDimensions())) { + List dim4Chat = domainSchemaResp.getDimensions().stream() + .filter(dim -> !visibility.getBlackDimIdList().contains(dim.getId())) + .collect(Collectors.toList()); + domainSchemaResp.setDimensions(dim4Chat); + } + } + + private void fillEntityNamesInfo(DomainSchemaResp domainSchemaResp, ChatConfigInfo chatConfigInfo) { + if (Objects.nonNull(chatConfigInfo) && Objects.nonNull(chatConfigInfo.getEntity()) + && !CollectionUtils.isEmpty(chatConfigInfo.getEntity().getNames())) { + domainSchemaResp.setEntityNames(chatConfigInfo.getEntity().getNames()); + } + } + + private void deletionDuplicated(QueryStructReq queryStructReq) { + if (!CollectionUtils.isEmpty(queryStructReq.getGroups()) && queryStructReq.getGroups().size() > 1) { + Set groups = new HashSet<>(); + groups.addAll(queryStructReq.getGroups()); + queryStructReq.getGroups().clear(); + queryStructReq.getGroups().addAll(groups); + } + } + + private void onlyQueryFirstMetric(QueryStructReq queryStructReq) { + if (!CollectionUtils.isEmpty(queryStructReq.getAggregators()) && queryStructReq.getAggregators().size() > 1) { + log.info("multi metric in aggregators:{} , only query first one", queryStructReq.getAggregators()); + queryStructReq.setAggregators(queryStructReq.getAggregators().subList(0, 1)); + } + } + + public ChatConfigInfo getConfigBaseInfo(Long domain) { + DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); + return defaultSemanticConfig.getChaConfigService().fetchConfigByDomainId(domain); + } + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticLayerImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/RemoteSemanticLayerImpl.java similarity index 72% rename from chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticLayerImpl.java rename to chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/RemoteSemanticLayerImpl.java index 3a1c48e84..c7b0a687c 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/DefaultSemanticLayerImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/infrastructure/semantic/RemoteSemanticLayerImpl.java @@ -3,17 +3,20 @@ package com.tencent.supersonic.chat.infrastructure.semantic; import static com.tencent.supersonic.common.constant.Constants.TRUE_LOWER; import com.alibaba.fastjson.JSON; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.gson.Gson; import com.tencent.supersonic.auth.api.authentication.constant.UserConstants; import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.service.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.common.util.context.ContextUtils; import com.tencent.supersonic.semantic.api.core.request.DomainSchemaFilterReq; import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; +import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; -import com.tencent.supersonic.chat.application.ConfigServiceImpl; import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigInfo; import com.tencent.supersonic.chat.domain.pojo.config.ItemVisibility; import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils; @@ -27,70 +30,62 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -@Service -public class DefaultSemanticLayerImpl implements SemanticLayer { - - private final Logger logger = LoggerFactory.getLogger(DefaultSemanticLayerImpl.class); - - @Value("${semantic.url.prefix:http://localhost:8081}") - private String semanticUrl; - - @Value("${searchByStruct.path:/api/semantic/query/struct}") - private String searchByStructPath; - - @Value("${fetchDomainSchemaPath.path:/api/semantic/schema}") - private String fetchDomainSchemaPath; - - @Autowired - private DefaultSemanticInternalUtils defaultSemanticInternalUtils; +@Slf4j +public class RemoteSemanticLayerImpl implements SemanticLayer { + private static final Cache> domainSchemaCache = + CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build(); private ParameterizedTypeReference> structTypeRef = new ParameterizedTypeReference>() { }; - @Autowired - private RestTemplate restTemplate; - - @Autowired - private ConfigServiceImpl chaConfigService; - @Override public QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user) { deletionDuplicated(queryStructReq); onlyQueryFirstMetric(queryStructReq); - return searchByStruct(semanticUrl + searchByStructPath, queryStructReq); + DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); + return searchByRestTemplate( + defaultSemanticConfig.getSemanticUrl() + defaultSemanticConfig.getSearchByStructPath(), + new Gson().toJson(queryStructReq)); } - public QueryResultWithSchemaResp searchByStruct(String url, QueryStructReq queryStructReq) { + @Override + public QueryResultWithSchemaResp queryBySql(QuerySqlReq querySqlReq, User user) { + DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); + return searchByRestTemplate(defaultSemanticConfig.getSemanticUrl() + defaultSemanticConfig.getSearchBySqlPath(), + new Gson().toJson(querySqlReq)); + } + + public QueryResultWithSchemaResp searchByRestTemplate(String url, String jsonReq) { + DefaultSemanticInternalUtils defaultSemanticInternalUtils = ContextUtils.getBean( + DefaultSemanticInternalUtils.class); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); defaultSemanticInternalUtils.fillToken(headers); URI requestUrl = UriComponentsBuilder.fromHttpUrl(url).build().encode().toUri(); - Gson gson = new Gson(); - HttpEntity entity = new HttpEntity<>(gson.toJson(queryStructReq), headers); - logger.info("searchByStruct {}", entity.getBody()); + HttpEntity entity = new HttpEntity<>(jsonReq, headers); + log.info("url:{},searchByRestTemplate:{}", url, entity.getBody()); ResultData responseBody; try { - ResponseEntity> responseEntity = restTemplate.exchange(requestUrl, - HttpMethod.POST, entity, structTypeRef); + DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); + ResponseEntity> responseEntity = defaultSemanticConfig.getRestTemplate() + .exchange(requestUrl, + HttpMethod.POST, entity, structTypeRef); responseBody = responseEntity.getBody(); - logger.debug("ApiResponse responseBody:{}", responseBody); + log.info("ApiResponse responseBody:{}", responseBody); QueryResultWithSchemaResp semanticQuery = new QueryResultWithSchemaResp(); if (ReturnCode.SUCCESS.getCode() == responseBody.getCode()) { QueryResultWithSchemaResp data = responseBody.getData(); @@ -101,17 +96,23 @@ public class DefaultSemanticLayerImpl implements SemanticLayer { return semanticQuery; } } catch (Exception e) { - throw new RuntimeException("search semantic struct interface error", e); + throw new RuntimeException("search semantic interface error,url:" + url, e); } throw new CommonException(responseBody.getCode(), responseBody.getMsg()); } public List fetchDomainSchemaAll(List ids) { + DefaultSemanticInternalUtils defaultSemanticInternalUtils = ContextUtils.getBean( + DefaultSemanticInternalUtils.class); HttpHeaders headers = new HttpHeaders(); headers.set(UserConstants.INTERNAL, TRUE_LOWER); headers.setContentType(MediaType.APPLICATION_JSON); defaultSemanticInternalUtils.fillToken(headers); - URI requestUrl = UriComponentsBuilder.fromHttpUrl(semanticUrl + fetchDomainSchemaPath).build().encode().toUri(); + DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); + + URI requestUrl = UriComponentsBuilder.fromHttpUrl( + defaultSemanticConfig.getSemanticUrl() + defaultSemanticConfig.getFetchDomainSchemaPath()).build() + .encode().toUri(); DomainSchemaFilterReq filter = new DomainSchemaFilterReq(); filter.setDomainIds(ids); ParameterizedTypeReference>> responseTypeRef = @@ -119,11 +120,13 @@ public class DefaultSemanticLayerImpl implements SemanticLayer { }; HttpEntity entity = new HttpEntity<>(JSON.toJSONString(filter), headers); + try { - ResponseEntity>> responseEntity = restTemplate.exchange(requestUrl, - HttpMethod.POST, entity, responseTypeRef); + ResponseEntity>> responseEntity = defaultSemanticConfig.getRestTemplate() + .exchange(requestUrl, + HttpMethod.POST, entity, responseTypeRef); ResultData> responseBody = responseEntity.getBody(); - logger.debug("ApiResponse responseBody:{}", responseBody); + log.debug("ApiResponse responseBody:{}", responseBody); if (ReturnCode.SUCCESS.getCode() == responseBody.getCode()) { List data = responseBody.getData(); return data; @@ -135,10 +138,13 @@ public class DefaultSemanticLayerImpl implements SemanticLayer { } + @SneakyThrows public List fetchDomainSchema(List ids) { - List data = fetchDomainSchemaAll(ids); - fillEntityNameAndFilterBlackElement(data); - return data; + return domainSchemaCache.get(String.valueOf(ids), () -> { + List data = fetchDomainSchemaAll(ids); + fillEntityNameAndFilterBlackElement(data); + return data; + }); } @Override @@ -226,13 +232,14 @@ public class DefaultSemanticLayerImpl implements SemanticLayer { private void onlyQueryFirstMetric(QueryStructReq queryStructReq) { if (!CollectionUtils.isEmpty(queryStructReq.getAggregators()) && queryStructReq.getAggregators().size() > 1) { - logger.info("multi metric in aggregators:{} , only query first one", queryStructReq.getAggregators()); + log.info("multi metric in aggregators:{} , only query first one", queryStructReq.getAggregators()); queryStructReq.setAggregators(queryStructReq.getAggregators().subList(0, 1)); } } public ChatConfigInfo getConfigBaseInfo(Long domain) { - return chaConfigService.fetchConfigByDomainId(domain); + DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); + return defaultSemanticConfig.getChaConfigService().fetchConfigByDomainId(domain); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java index 1852c1d6e..a3e568857 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java @@ -43,16 +43,16 @@ public class ChatConfigController { @PostMapping public Long addChatConfig(@RequestBody ChatConfigBase extendBaseCmd, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return configService.addConfig(extendBaseCmd, user); } @PutMapping public Long editDomainExtend(@RequestBody ChatConfigEditReq extendEditCmd, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return configService.editConfig(extendEditCmd, user); } @@ -60,8 +60,8 @@ public class ChatConfigController { @PostMapping("/search") public List search(@RequestBody ChatConfigFilter filter, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return configService.search(filter, user); } @@ -72,6 +72,11 @@ public class ChatConfigController { return configService.getConfigRichInfo(domainId); } + @GetMapping("/richDesc/all") + public List getAllChatRichConfig() { + return configService.getAllChatRichConfig(); + } + /** * get domain list @@ -79,24 +84,22 @@ public class ChatConfigController { * @param */ @GetMapping("/domainList") - public List getDomainList(HttpServletRequest request, - HttpServletResponse response) { - User user = UserHolder.findUser(request, response); - return defaultSemanticUtils.getDomainListForUser(user); + public List getDomainList() { + return defaultSemanticUtils.getDomainListForAdmin(); } @PostMapping("/dimension/page") public PageInfo queryDimension(@RequestBody PageDimensionReq pageDimensionCmd, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return defaultSemanticUtils.queryDimensionPage(pageDimensionCmd, user); } @PostMapping("/metric/page") public PageInfo queryMetric(@RequestBody PageMetricReq pageMetrricCmd, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return defaultSemanticUtils.queryMetricPage(pageMetrricCmd, user); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java index 249f2d1b4..f56131f90 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java @@ -28,7 +28,7 @@ public class ChatController { } @PostMapping("/save") - public Boolean save(@RequestParam(value = "chatName", required = true) String chatName, + public Boolean save(@RequestParam(value = "chatName") String chatName, HttpServletRequest request, HttpServletResponse response) { return chatService.addChat(UserHolder.findUser(request, response), chatName); } @@ -40,36 +40,36 @@ public class ChatController { } @PostMapping("/delete") - public Boolean deleteConversion(@RequestParam(value = "chatId", required = true) long chatId, + public Boolean deleteConversion(@RequestParam(value = "chatId") long chatId, HttpServletRequest request, HttpServletResponse response) { String userName = UserHolder.findUser(request, response).getName(); return chatService.deleteChat(chatId, userName); } @PostMapping("/updateChatName") - public Boolean updateConversionName(@RequestParam(value = "chatId", required = true) Long chatId, - @RequestParam(value = "chatName", required = true) String chatName, + public Boolean updateConversionName(@RequestParam(value = "chatId") Long chatId, + @RequestParam(value = "chatName") String chatName, HttpServletRequest request, HttpServletResponse response) { String userName = UserHolder.findUser(request, response).getName(); return chatService.updateChatName(chatId, chatName, userName); } @PostMapping("/updateQAFeedback") - public Boolean updateQAFeedback(@RequestParam(value = "id", required = true) Integer id, - @RequestParam(value = "score", required = true) Integer score, - @RequestParam(value = "feedback", required = true) String feedback) { + public Boolean updateQAFeedback(@RequestParam(value = "id") Integer id, + @RequestParam(value = "score") Integer score, + @RequestParam(value = "feedback", required = false) String feedback) { return chatService.updateFeedback(id, score, feedback); } @PostMapping("/updateChatIsTop") - public Boolean updateConversionIsTop(@RequestParam(value = "chatId", required = true) Long chatId, - @RequestParam(value = "isTop", required = true) int isTop) { + public Boolean updateConversionIsTop(@RequestParam(value = "chatId") Long chatId, + @RequestParam(value = "isTop") int isTop) { return chatService.updateChatIsTop(chatId, isTop); } @PostMapping("/pageQueryInfo") public PageInfo pageQueryInfo(@RequestBody PageQueryInfoReq pageQueryInfoCommend, - @RequestParam(value = "chatId", required = true) long chatId, + @RequestParam(value = "chatId") long chatId, HttpServletRequest request, HttpServletResponse response) { pageQueryInfoCommend.setUserName(UserHolder.findUser(request, response).getName()); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java index e237ac104..9ffd97e18 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java @@ -9,6 +9,7 @@ import com.tencent.supersonic.chat.domain.service.SearchService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -22,7 +23,9 @@ import org.springframework.web.bind.annotation.RestController; public class ChatQueryController { @Autowired + @Qualifier("chatQueryService") private QueryService queryService; + @Autowired private SearchService searchService; @@ -51,7 +54,7 @@ public class ChatQueryController { @PostMapping("queryData") public Object queryData(@RequestBody QueryData queryData, HttpServletRequest request, HttpServletResponse response) throws Exception { - return queryService.queryData(queryData, UserHolder.findUser(request, response)); + return queryService.executeDirectQuery(queryData, UserHolder.findUser(request, response)); } } diff --git a/chat/core/src/main/resources/mapper/ChatMapper.xml b/chat/core/src/main/resources/mapper/ChatMapper.xml index f6dd7e1ab..b2f737d72 100644 --- a/chat/core/src/main/resources/mapper/ChatMapper.xml +++ b/chat/core/src/main/resources/mapper/ChatMapper.xml @@ -38,6 +38,13 @@ and creator = #{creator} + + update s2_chat + set last_question = #{lastQuestion}, + last_time = #{lastTime} + where chat_id = #{chatId} + + insert into s2_chat (chat_name, create_time, last_time, creator, last_question, is_delete, is_top) @@ -80,7 +87,7 @@ update s2_chat_query set score=#{score}, feedback=#{feedback} - where id = #{id} + where question_id = #{id} selectByExampleWithBLOBs(ChatQueryDOExample example); - - /** - * - * @mbg.generated - */ - List selectByExample(ChatQueryDOExample example); - - /** - * - * @mbg.generated - */ - ChatQueryDO selectByPrimaryKey(Long id); - - /** - * - * @mbg.generated - */ - int updateByPrimaryKeySelective(ChatQueryDO record); - - /** - * - * @mbg.generated - */ - int updateByPrimaryKeyWithBLOBs(ChatQueryDO record); - - /** - * - * @mbg.generated - */ - int updateByPrimaryKey(ChatQueryDO record); -} \ No newline at end of file diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParserTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParserTest.java index 73e7ab656..14b67ec23 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParserTest.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeSemanticParserTest.java @@ -3,12 +3,24 @@ package com.tencent.supersonic.chat.application.parser; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.chat.api.request.QueryContextReq; -import com.tencent.supersonic.common.pojo.DateConf; import org.junit.jupiter.api.Test; class TimeSemanticParserTest { +// private HeuristicQuerySelector voteStrategy = new HeuristicQuerySelector() { +// @Override +// public void init(List semanticParsers) { +// List queryMode = new ArrayList<>(Arrays.asList(EntityDetail.QUERY_MODE)); +// for(SemanticParser semanticParser : semanticParsers) { +// if(semanticParser.getName().equals(TimeSemanticParser.PARSER_MODE)) { +// semanticParser.getQueryModes().clear(); +// semanticParser.getQueryModes().addAll(queryMode); +// } +// } +// } +// }; + @Test void parse() { TimeSemanticParser timeSemanticParser = new TimeSemanticParser(); @@ -16,12 +28,15 @@ class TimeSemanticParserTest { QueryContextReq queryContext = new QueryContextReq(); ChatContext chatCtx = new ChatContext(); SchemaMapInfo schemaMap = new SchemaMapInfo(); + queryContext.setQueryText("supersonic最近30天访问次数"); + //voteStrategy.init(new ArrayList<>(Arrays.asList(timeSemanticParser))); + timeSemanticParser.parse(queryContext, chatCtx); - boolean parse = timeSemanticParser.parse(queryContext, chatCtx); - - DateConf dateInfo = queryContext.getParseInfo().getDateInfo(); + //DateConf dateInfo = queryContext.getParseInfo(timeSemanticParser.getQueryModes().get(0)) + // .getDateInfo(); + //System.out.println(dateInfo); } } \ No newline at end of file diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/AggregateSemanticParserTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/AggregateSemanticParserTest.java new file mode 100644 index 000000000..cc996969f --- /dev/null +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/AggregateSemanticParserTest.java @@ -0,0 +1,38 @@ +package com.tencent.supersonic.chat.application.parser.aggregate; + +import static org.junit.Assert.assertEquals; + +import com.tencent.supersonic.chat.application.parser.AggregateSemanticParser; +import com.tencent.supersonic.common.enums.AggregateTypeEnum; +import org.junit.jupiter.api.Test; + +class AggregateSemanticParserTest { + + @Test + void getAggregateParser() { + AggregateSemanticParser aggregateParser = new AggregateSemanticParser(); + AggregateTypeEnum aggregateType = aggregateParser.resolveAggregateType("supsersonic产品访问次数最大值"); + assertEquals(aggregateType, AggregateTypeEnum.MAX); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品pv"); + assertEquals(aggregateType, AggregateTypeEnum.COUNT); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品uv"); + assertEquals(aggregateType, AggregateTypeEnum.DISTINCT); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品访问次数最大值"); + assertEquals(aggregateType, AggregateTypeEnum.MAX); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品访问次数最小值"); + assertEquals(aggregateType, AggregateTypeEnum.MIN); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品访问次数平均值"); + assertEquals(aggregateType, AggregateTypeEnum.AVG); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品访问次数topN"); + assertEquals(aggregateType, AggregateTypeEnum.TOPN); + + aggregateType = aggregateParser.resolveAggregateType("supsersonic产品访问次数汇总"); + assertEquals(aggregateType, AggregateTypeEnum.SUM); + } +} \ No newline at end of file diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/RegexAggregateTypeEnumResolverTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/RegexAggregateTypeEnumResolverTest.java deleted file mode 100644 index ef97c085e..000000000 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/aggregate/RegexAggregateTypeEnumResolverTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.tencent.supersonic.chat.application.parser.aggregate; - -import static org.junit.Assert.assertEquals; - -import com.tencent.supersonic.chat.application.parser.resolver.RegexAggregateTypeResolver; -import com.tencent.supersonic.common.enums.AggregateTypeEnum; -import org.junit.jupiter.api.Test; - -class RegexAggregateTypeEnumResolverTest { - - @Test - void getAggregateParser() { - RegexAggregateTypeResolver regexAggregateParser = new RegexAggregateTypeResolver(); - AggregateTypeEnum aggregateType = regexAggregateParser.resolve("supsersonic产品访问次数最大值"); - assertEquals(aggregateType, AggregateTypeEnum.MAX); - - aggregateType = regexAggregateParser.resolve("supsersonic产品pv"); - assertEquals(aggregateType, AggregateTypeEnum.COUNT); - - aggregateType = regexAggregateParser.resolve("supsersonic产品uv"); - assertEquals(aggregateType, AggregateTypeEnum.DISTINCT); - - aggregateType = regexAggregateParser.resolve("supsersonic产品访问次数最大值"); - assertEquals(aggregateType, AggregateTypeEnum.MAX); - - aggregateType = regexAggregateParser.resolve("supsersonic产品访问次数最小值"); - assertEquals(aggregateType, AggregateTypeEnum.MIN); - - aggregateType = regexAggregateParser.resolve("supsersonic产品访问次数平均值"); - assertEquals(aggregateType, AggregateTypeEnum.AVG); - - aggregateType = regexAggregateParser.resolve("supsersonic产品访问次数topN"); - assertEquals(aggregateType, AggregateTypeEnum.TOPN); - - aggregateType = regexAggregateParser.resolve("supsersonic产品访问次数汇总"); - assertEquals(aggregateType, AggregateTypeEnum.SUM); - } -} \ No newline at end of file diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfoTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfoTest.java new file mode 100644 index 000000000..ce2b98da4 --- /dev/null +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/domain/utils/DslToSemanticInfoTest.java @@ -0,0 +1,28 @@ +package com.tencent.supersonic.chat.domain.utils; + +import com.tencent.supersonic.semantic.api.core.enums.TimeDimensionEnum; +import java.text.MessageFormat; +import org.junit.Assert; +import org.junit.jupiter.api.Test; + +/** + * @author lex luo + * @date 2023/6/29 16:05 + */ +class DslToSemanticInfoTest { + + @Test + void search() { + + Integer domainId = 1; + String dayField = TimeDimensionEnum.DAY.getName(); + String startDate = "2023-04-01"; + String endDate = "2023-06-01"; + + String format = MessageFormat.format(DslToSemanticInfo.SUB_TABLE, domainId, dayField, startDate, endDate); + + Assert.assertEquals(format, + " ( select * from t_1 where sys_imp_date >= '2023-04-01' and sys_imp_date <= '2023-06-01' ) as t_sub_1"); + + } +} \ No newline at end of file diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/test/ChatBizLauncher.java b/chat/core/src/test/java/com/tencent/supersonic/chat/test/ChatBizLauncher.java index 9602f9f16..7279ed301 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/test/ChatBizLauncher.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/test/ChatBizLauncher.java @@ -6,11 +6,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -@SpringBootApplication(scanBasePackages = {"com.tencent.supersonic"} +@SpringBootApplication(scanBasePackages = {"com.tencent.supersonic.chat"} // , exclude = {DataSourceAutoConfiguration.class} ) -@ComponentScan("com.tencent.supersonic") -@MapperScan("com.tencent.supersonic") +@ComponentScan("com.tencent.supersonic.chat") +@MapperScan("com.tencent.supersonic.chat") public class ChatBizLauncher { public static void main(String[] args) { diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/ContextTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/ContextTest.java index 209ffb358..5ca6ebceb 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/ContextTest.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/ContextTest.java @@ -1,14 +1,16 @@ package com.tencent.supersonic.chat.test.context; +import com.tencent.supersonic.chat.domain.utils.ComponentFactory; import com.tencent.supersonic.chat.infrastructure.mapper.ChatContextMapper; import com.tencent.supersonic.chat.infrastructure.repository.ChatContextRepositoryImpl; -import com.tencent.supersonic.chat.infrastructure.semantic.DefaultSemanticLayerImpl; +import com.tencent.supersonic.chat.infrastructure.semantic.RemoteSemanticLayerImpl; import com.tencent.supersonic.chat.test.ChatBizLauncher; import com.tencent.supersonic.semantic.core.domain.DimensionService; import com.tencent.supersonic.semantic.core.domain.DomainService; import com.tencent.supersonic.semantic.core.domain.MetricService; import com.tencent.supersonic.semantic.query.domain.QueryService; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; @@ -23,7 +25,8 @@ import org.springframework.web.client.RestTemplate; @MockBean(DomainService.class) @MockBean(ChatContextMapper.class) @MockBean(RestTemplate.class) -@MockBean(DefaultSemanticLayerImpl.class) +@MockBean(RemoteSemanticLayerImpl.class) +@MockBean(ComponentFactory.class) //@MybatisTest //@AutoConfigureMybatis //@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java index ee6c23432..cd8404f12 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java @@ -5,7 +5,8 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.service.SemanticLayer; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.domain.service.QueryService; import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.core.response.DimensionResp; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; @@ -20,7 +21,6 @@ import com.tencent.supersonic.chat.domain.pojo.config.EntityInternalDetail; import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo; import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos; import com.tencent.supersonic.chat.domain.service.ChatService; -import com.tencent.supersonic.chat.domain.service.QueryService; import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter; import com.tencent.supersonic.chat.infrastructure.mapper.ChatContextMapper; import com.tencent.supersonic.chat.infrastructure.repository.ChatContextRepositoryImpl; @@ -39,51 +39,6 @@ import org.springframework.web.client.RestTemplate; @Configuration public class MockBeansConfiguration { - @Bean - public ChatContextRepositoryImpl getChatContextRepository() { - return Mockito.mock(ChatContextRepositoryImpl.class); - } - - @Bean - public QueryService getQueryService() { - return Mockito.mock(QueryService.class); - } - - @Bean - public DimensionService getDimensionService() { - return Mockito.mock(DimensionService.class); - } - - @Bean - public MetricService getMetricService() { - return Mockito.mock(MetricService.class); - } - - @Bean - public DomainService getDomainService() { - return Mockito.mock(DomainService.class); - } - - @Bean - public ChatContextMapper getChatContextMapper() { - return Mockito.mock(ChatContextMapper.class); - } - - @Bean - public ConfigServiceImpl getDomainExtendService() { - return Mockito.mock(ConfigServiceImpl.class); - } - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } -// @Bean -// public SemanticLayer getSemanticService() { -// return Mockito.mock(HttpSemanticServiceImpl.class); -// } - - public static void getOrCreateContextMock(ChatService chatService) { ChatContext context = new ChatContext(); context.setChatId(1); @@ -142,8 +97,6 @@ public class MockBeansConfiguration { when(configService.fetchConfigByDomainId(anyLong())).thenReturn(chaConfigDesc); } - //queryDimensionDescs - public static void dimensionDescBuild(DimensionService dimensionService, List dimensionDescs) { when(dimensionService.getDimensions(anyList())).thenReturn(dimensionDescs); } @@ -167,4 +120,50 @@ public class MockBeansConfiguration { dimensionDesc.setBizName(bizName); return dimensionDesc; } + + @Bean + public ChatContextRepositoryImpl getChatContextRepository() { + return Mockito.mock(ChatContextRepositoryImpl.class); + } +// @Bean +// public SemanticLayer getSemanticService() { +// return Mockito.mock(HttpSemanticServiceImpl.class); +// } + + @Bean + public QueryService getQueryService() { + return Mockito.mock(QueryService.class); + } + + @Bean + public DimensionService getDimensionService() { + return Mockito.mock(DimensionService.class); + } + + @Bean + public MetricService getMetricService() { + return Mockito.mock(MetricService.class); + } + + //queryDimensionDescs + + @Bean + public DomainService getDomainService() { + return Mockito.mock(DomainService.class); + } + + @Bean + public ChatContextMapper getChatContextMapper() { + return Mockito.mock(ChatContextMapper.class); + } + + @Bean + public ConfigServiceImpl getDomainExtendService() { + return Mockito.mock(ConfigServiceImpl.class); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/QueryServiceImplTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/QueryServiceImplTest.java new file mode 100644 index 000000000..9506aa6f4 --- /dev/null +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/QueryServiceImplTest.java @@ -0,0 +1,177 @@ +package com.tencent.supersonic.chat.test.context; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import com.tencent.supersonic.chat.application.mapper.HanlpSchemaMapper; +import com.tencent.supersonic.chat.application.mapper.QueryMatchStrategy; +import com.tencent.supersonic.chat.application.parser.DomainSemanticParser; +import com.tencent.supersonic.chat.domain.service.ChatService; +import com.tencent.supersonic.chat.test.ChatBizLauncher; +import com.tencent.supersonic.knowledge.infrastructure.nlp.Suggester; +import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ChatBizLauncher.class) +public class QueryServiceImplTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(QueryServiceImplTest.class); + +// @MockBean +// private QueryService queryService; + + //private SemanticLayer semanticLayer = mock(SemanticLayer.class); + +// @MockBean +// private DefaultSemanticLayerImpl semanticLayer; + //SemanticLayer + @Autowired + public QueryMatchStrategy queryMatchStrategy; + @Autowired(required = false) + public Suggester suggester; + @MockBean + private SemanticLayer semanticLayer;//= ComponentFactory.getSemanticLayer(); + +// @Before +// public void setUp() { +// //List getDomainSchemaInfo(List ids) +// List domainSchemaRespList=MockUtils.getChatContext(); +// Mockito.when(semanticLayer.getDomainSchemaInfo(Mockito.anyList())).thenReturn(domainSchemaRespList); +// } + @MockBean + private DomainSemanticParser domainSemanticParser; + @MockBean + private HanlpSchemaMapper hanlpSchemaMapper; + @MockBean + private ChatService chatService; + +//// @Autowired(required = false) +//// private ChatService chatService; + + //private SemanticLayer semanticLayer ; + + @Test + public void test() throws Exception { + QueryContextReq queryContextReq = getQueryContextReq("超音数访问次数"); + //hanlpSchemaMapper.map(queryContextReq); +// List terms = HanlpHelper.getSegment().seg(queryContextReq.getQueryText().toLowerCase()).stream() +// .collect(Collectors.toList()); +// LOGGER.info("terms::::{}",terms); +// MockUtils.putSuggester(); +// List matches = queryMatchStrategy.match(queryContextReq.getQueryText(), terms, queryContextReq.getDomainId()); +// HanlpHelper.transLetterOriginal(matches); +// HanlpSchemaMapperHelper.convertTermsToSchemaMapInfo(matches, queryContextReq.getMapInfo()); + HanlpSchemaMapper hanlpSchemaMapper = new HanlpSchemaMapper(); + hanlpSchemaMapper.map(queryContextReq); + //QueryContextReq queryContextReq=MockUtils.getQueryContextReq("METRIC_FILTER"); + LOGGER.info("QueryContextReq::::{}", queryContextReq.getMapInfo().getMatchedDomains()); + LOGGER.info("QueryContextReq::::{}", queryContextReq.getMapInfo().getDomainElementMatches()); + LOGGER.info("QueryContextReq::::{}", queryContextReq); + +// //chatService=new ChatServiceImpl(); +// ChatContext chatCtx = chatService.getOrCreateContext(queryContextReq.getChatId()); +// if (chatCtx == null) { +// chatCtx=new ChatContext(); +// chatCtx.setChatId(queryContextReq.getChatId()); +// } +// LOGGER.info("chatService::::{}",chatService); +// LOGGER.info("ChatContext::::{}",chatCtx); + ChatContext chatCtx = new ChatContext();//MockUtils.getChatContext1(); + //semanticLayer = ComponentFactory.getSemanticLayer(); +// DomainSemanticParser domainSemanticParser=new DomainSemanticParser(); +// domainSemanticParser.parse(queryContextReq,chatCtx); + + //DomainSemanticParser + + //domainSemanticParser=new DomainSemanticParser(); + LOGGER.info("domainSemanticParser::::{}", domainSemanticParser); + //SemanticLayer semanticLayer= mock(SemanticLayer.class); + //List domainSchemaRespList=MockUtils.getChatContext(); + +// //SemanticLayer semanticLayer = mock(SemanticLayer.class); +// when(semanticLayer.getDomainSchemaInfo(Mockito.anyList())).thenReturn(domainSchemaRespList); +// domainSemanticParser.parse(queryContextReq,chatCtx); +// LOGGER.info("QueryContextReq::::{}",queryContextReq); +// TimeSemanticParser timeSemanticParser=new TimeSemanticParser(); +// timeSemanticParser.parse(queryContextReq,chatCtx); +// AggregateSemanticParser aggregateSemanticParser=new AggregateSemanticParser(); +// aggregateSemanticParser.parse(queryContextReq,chatCtx); +// //PickStrategy pickStrategy = ComponentFactory.getPickStrategy(); +// LOGGER.info("pickStrategy::::{}",pickStrategy); +// pickStrategy=new ScoreBasedPickStrategy(); +// SemanticParseInfo semanticParse = pickStrategy.pick(queryContextReq, chatCtx); +// LOGGER.info("semanticParse::::{}",semanticParse); + //SemanticQueryExecutorHelper semanticQueryExecutorHelper=new SemanticQueryExecutorHelper(); + //semanticQueryExecutorHelper.execute(queryContextReq.getParseInfo(),queryContextReq.getUser()); +// LOGGER.info("queryContextReq::::{}",queryContextReq.getMapInfo().getMatchedDomains()); +// LOGGER.info("queryContextReq::::{}",queryContextReq.getMapInfo().getDomainElementMatches()); +// LOGGER.info("queryContextReq::::{}",queryContextReq.getCandidateParseInfos()); +// LOGGER.info("queryContextReq::::{}",queryContextReq.getDomainId()); + +// List queryExecutors = ComponentFactory.getQueryExecutors(); +// QueryResultResp queryResponse=new QueryResultResp(); +// for (QueryExecutor executor : queryExecutors) { +// queryResponse = executor.execute(semanticParse, queryContextReq.getUser()); +// if (queryResponse != null) { +// // update chat context after a successful semantic query +// if (queryContextReq.isSaveAnswer() && queryResponse.getQueryState() == QueryState.NORMAL.getState()) { +// chatService.updateContext(chatCtx, queryContextReq, semanticParse); +// } +// queryResponse.setChatContext(chatCtx.getParseInfo()); +// chatService.addQuery(queryResponse, queryContextReq, chatCtx); +// break; +// } +// } + + //assertThat(found.getName()).isEqualTo(name); + } + + public QueryContextReq getQueryContextReq(String query) { + QueryContextReq queryContextReq = new QueryContextReq(); + queryContextReq.setQueryText(query);//"alice的访问次数" + queryContextReq.setChatId(1); + queryContextReq.setUser(new User(1L, "admin", "admin", "admin@email")); + return queryContextReq; + } + + @TestConfiguration + static class EmployeeServiceImplTestContextConfiguration { + + @Bean + public QueryMatchStrategy queryMatchStrategyService() { + return new QueryMatchStrategy(); + } +// @Bean +// public SemanticLayer querySemanticLayer() { +// return new DefaultSemanticLayerImpl(); +// } + //@Bean + //public DomainSemanticParser queryDomainSemanticParser() { + // return new DomainSemanticParser(); + //} + +// @Bean +// public RestTemplate getRestTemplate(){ +// return new RestTemplate(); +// } +// +// @Bean +// public DefaultSemanticInternalUtils getUtils(){ +// return new DefaultSemanticInternalUtils(); +// } + + } +} diff --git a/chat/core/src/test/resources/hanlp.properties b/chat/core/src/test/resources/hanlp.properties index 19f09d02f..84fada8d0 100644 --- a/chat/core/src/test/resources/hanlp.properties +++ b/chat/core/src/test/resources/hanlp.properties @@ -1 +1 @@ -CustomDictionaryPath=data/dictionary/custom/23_285.txt \ No newline at end of file +CustomDictionaryPath=data/dictionary/custom/DimValue_1_2.txt diff --git a/chat/knowledge/src/main/java/com/hankcs/hanlp/collection/trie/bintrie/BaseNode.java b/chat/knowledge/src/main/java/com/hankcs/hanlp/collection/trie/bintrie/BaseNode.java index ec978fe09..895634c1e 100644 --- a/chat/knowledge/src/main/java/com/hankcs/hanlp/collection/trie/bintrie/BaseNode.java +++ b/chat/knowledge/src/main/java/com/hankcs/hanlp/collection/trie/bintrie/BaseNode.java @@ -19,6 +19,7 @@ public abstract class BaseNode implements Comparable { * 状态数组,方便读取的时候用 */ static final Status[] ARRAY_STATUS = Status.values(); + public String prefix = null; /** * 子节点 */ @@ -36,8 +37,6 @@ public abstract class BaseNode implements Comparable { */ protected V value; - public String prefix = null; - public BaseNode transition(String path, int begin) { BaseNode cur = this; for (int i = begin; i < path.length(); ++i) { @@ -231,37 +230,6 @@ public abstract class BaseNode implements Comparable { } } - public enum Status { - /** - * 未指定,用于删除词条 - */ - UNDEFINED_0, - /** - * 不是词语的结尾 - */ - NOT_WORD_1, - /** - * 是个词语的结尾,并且还可以继续 - */ - WORD_MIDDLE_2, - /** - * 是个词语的结尾,并且没有继续 - */ - WORD_END_3, - } - - public class TrieEntry extends AbstractMap.SimpleEntry implements Comparable { - - public TrieEntry(String key, V value) { - super(key, value); - } - - @Override - public int compareTo(TrieEntry o) { - return getKey().compareTo(String.valueOf(o.getKey())); - } - } - @Override public String toString() { return "BaseNode{" @@ -316,4 +284,35 @@ public abstract class BaseNode implements Comparable { } } + public enum Status { + /** + * 未指定,用于删除词条 + */ + UNDEFINED_0, + /** + * 不是词语的结尾 + */ + NOT_WORD_1, + /** + * 是个词语的结尾,并且还可以继续 + */ + WORD_MIDDLE_2, + /** + * 是个词语的结尾,并且没有继续 + */ + WORD_END_3, + } + + public class TrieEntry extends AbstractMap.SimpleEntry implements Comparable { + + public TrieEntry(String key, V value) { + super(key, value); + } + + @Override + public int compareTo(TrieEntry o) { + return getKey().compareTo(String.valueOf(o.getKey())); + } + } + } diff --git a/chat/knowledge/src/main/java/com/hankcs/hanlp/dictionary/CoreDictionary.java b/chat/knowledge/src/main/java/com/hankcs/hanlp/dictionary/CoreDictionary.java index e97c9f903..fecc6cb1e 100644 --- a/chat/knowledge/src/main/java/com/hankcs/hanlp/dictionary/CoreDictionary.java +++ b/chat/knowledge/src/main/java/com/hankcs/hanlp/dictionary/CoreDictionary.java @@ -26,9 +26,16 @@ import java.util.TreeMap; */ public class CoreDictionary { - public static DoubleArrayTrie trie = new DoubleArrayTrie(); - public static final String PATH = HanLP.Config.CoreDictionaryPath; + public static DoubleArrayTrie trie = new DoubleArrayTrie(); + // 一些特殊的WORD_ID + public static final int NR_WORD_ID = getWordID(Predefine.TAG_PEOPLE); + public static final int NS_WORD_ID = getWordID(Predefine.TAG_PLACE); + public static final int NT_WORD_ID = getWordID(Predefine.TAG_GROUP); + public static final int T_WORD_ID = getWordID(Predefine.TAG_TIME); + public static final int X_WORD_ID = getWordID(Predefine.TAG_CLUSTER); + public static final int M_WORD_ID = getWordID(Predefine.TAG_NUMBER); + public static final int NX_WORD_ID = getWordID(Predefine.TAG_PROPER); // 自动加载词典 static { @@ -40,15 +47,6 @@ public class CoreDictionary { } } - // 一些特殊的WORD_ID - public static final int NR_WORD_ID = getWordID(Predefine.TAG_PEOPLE); - public static final int NS_WORD_ID = getWordID(Predefine.TAG_PLACE); - public static final int NT_WORD_ID = getWordID(Predefine.TAG_GROUP); - public static final int T_WORD_ID = getWordID(Predefine.TAG_TIME); - public static final int X_WORD_ID = getWordID(Predefine.TAG_CLUSTER); - public static final int M_WORD_ID = getWordID(Predefine.TAG_NUMBER); - public static final int NX_WORD_ID = getWordID(Predefine.TAG_PROPER); - private static boolean load(String path) { logger.info("核心词典开始加载:" + path); if (loadDat(path)) { @@ -200,6 +198,29 @@ public class CoreDictionary { return trie.get(key) != null; } + /** + * 获取词语的ID + * + * @param a 词语 + * @return ID, 如果不存在, 则返回-1 + */ + public static int getWordID(String a) { + return CoreDictionary.trie.exactMatchSearch(a); + } + + /** + * 热更新核心词典
+ * 集群环境(或其他IOAdapter)需要自行删除缓存文件 + * + * @return 是否成功 + */ + public static boolean reload() { + String path = CoreDictionary.PATH; + IOUtil.deleteFile(path + Predefine.BIN_EXT); + + return load(path); + } + /** * 核心词典中的词属性 */ @@ -366,28 +387,5 @@ public class CoreDictionary { } } } - - /** - * 获取词语的ID - * - * @param a 词语 - * @return ID, 如果不存在, 则返回-1 - */ - public static int getWordID(String a) { - return CoreDictionary.trie.exactMatchSearch(a); - } - - /** - * 热更新核心词典
- * 集群环境(或其他IOAdapter)需要自行删除缓存文件 - * - * @return 是否成功 - */ - public static boolean reload() { - String path = CoreDictionary.PATH; - IOUtil.deleteFile(path + Predefine.BIN_EXT); - - return load(path); - } } diff --git a/chat/knowledge/src/main/java/com/hankcs/hanlp/seg/WordBasedSegment.java b/chat/knowledge/src/main/java/com/hankcs/hanlp/seg/WordBasedSegment.java index 47204ec23..b467abba3 100644 --- a/chat/knowledge/src/main/java/com/hankcs/hanlp/seg/WordBasedSegment.java +++ b/chat/knowledge/src/main/java/com/hankcs/hanlp/seg/WordBasedSegment.java @@ -236,6 +236,10 @@ public abstract class WordBasedSegment extends Segment { } } + protected static void speechTagging(List vertexList) { + Viterbi.compute(vertexList, CoreDictionaryTransformMatrixDictionary.transformMatrixDictionary); + } + protected void generateWordNet(final WordNet wordNetStorage) { final char[] charArray = wordNetStorage.charArray; DoubleArrayTrie.Searcher searcher = CoreDictionary.trie.getSearcher(charArray, 0); @@ -322,10 +326,6 @@ public abstract class WordBasedSegment extends Segment { return termList; } - protected static void speechTagging(List vertexList) { - Viterbi.compute(vertexList, CoreDictionaryTransformMatrixDictionary.transformMatrixDictionary); - } - protected void addTerms(List terms, Vertex vertex, int offset) { for (int i = 0; i < vertex.attribute.nature.length; i++) { Term term = new Term(vertex.realWord, vertex.attribute.nature[i]); diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/BaseWordNature.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/BaseWordNature.java index 09873cfd2..fe5de093c 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/BaseWordNature.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/BaseWordNature.java @@ -49,11 +49,4 @@ public abstract class BaseWordNature { return 0; } - public Long getFrequency(String nature) { - String[] split = nature.split(NatureType.NATURE_SPILT); - if (split.length >= 3) { - return Long.valueOf(split[2]); - } - return 0L; - } } diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/DomainWordNature.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/DomainWordNature.java index 8583e64e7..d76234985 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/DomainWordNature.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/application/online/DomainWordNature.java @@ -27,8 +27,4 @@ public class DomainWordNature extends BaseWordNature { return result; } - @Override - public Long getFrequency(String nature) { - return 0L; - } } diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HanlpHelper.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HanlpHelper.java index 8b22766f6..f5962d852 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HanlpHelper.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HanlpHelper.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; import org.springframework.util.ResourceUtils; /** @@ -21,13 +22,13 @@ import org.springframework.util.ResourceUtils; */ public class HanlpHelper { - private static final Logger LOGGER = LoggerFactory.getLogger(HanlpHelper.class); public static final String FILE_SPILT = "/"; public static final String SPACE_SPILT = "#"; - private static volatile Segment segment; - public static volatile DynamicCustomDictionary CustomDictionary; public static final String DICT_MAIN_FILE_NAME = "CustomDictionary.txt"; public static final String DICT_CLASS = "classes"; + private static final Logger LOGGER = LoggerFactory.getLogger(HanlpHelper.class); + public static volatile DynamicCustomDictionary CustomDictionary; + private static volatile Segment segment; static { // reset hanlp config @@ -152,11 +153,14 @@ public class HanlpHelper { } public static boolean addToCustomDictionary(WordNature wordNature) { - LOGGER.debug("wordNature:{}", wordNature); + LOGGER.info("wordNature:{}", wordNature); return getDynamicCustomDictionary().insert(wordNature.getWord(), wordNature.getNatureWithFrequency()); } public static void transLetterOriginal(List mapResults) { + if (CollectionUtils.isEmpty(mapResults)) { + return; + } for (MapResult mapResult : mapResults) { if (MultiCustomDictionary.isLowerLetter(mapResult.getName())) { if (CustomDictionary.contains(mapResult.getName())) { diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HdfsFileHelper.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HdfsFileHelper.java index a7d2cea16..48863fbf5 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HdfsFileHelper.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/HdfsFileHelper.java @@ -53,6 +53,7 @@ public class HdfsFileHelper { /** * reset path + * * @param customDictionary * @throws IOException */ diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/MultiCustomDictionary.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/MultiCustomDictionary.java index 6c31b5c6b..2a678cf98 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/MultiCustomDictionary.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/MultiCustomDictionary.java @@ -42,20 +42,6 @@ public class MultiCustomDictionary extends DynamicCustomDictionary { super(path); } - public boolean load(String... path) { - this.path = path; - long start = System.currentTimeMillis(); - if (!this.loadMainDictionary(path[0])) { - Predefine.logger.warning("自定义词典" + Arrays.toString(path) + "加载失败"); - return false; - } else { - Predefine.logger.info( - "自定义词典加载成功:" + this.dat.size() + "个词条,耗时" + (System.currentTimeMillis() - start) + "ms"); - this.path = path; - return true; - } - } - /*** * load dictionary * @param path @@ -139,10 +125,6 @@ public class MultiCustomDictionary extends DynamicCustomDictionary { } } - public boolean loadMainDictionary(String mainPath) { - return loadMainDictionary(mainPath, this.path, this.dat, true, addToSuggesterTrie); - } - /*** * load main dictionary * @param mainPath @@ -291,6 +273,53 @@ public class MultiCustomDictionary extends DynamicCustomDictionary { } } + public static boolean isLetters(String str) { + char[] chars = str.toCharArray(); + if (chars.length <= 1) { + return false; + } + for (int i = 0; i < chars.length; i++) { + if ((chars[i] >= 'A' && chars[i] <= 'Z')) { + return true; + } + } + return false; + } + + public static boolean isLowerLetter(String str) { + char[] chars = str.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if ((chars[i] >= 'a' && chars[i] <= 'z')) { + return true; + } + } + return false; + } + + public static String getWordBySpace(String word) { + if (word.contains(HanlpHelper.SPACE_SPILT)) { + return word.replace(HanlpHelper.SPACE_SPILT, " "); + } + return word; + } + + public boolean load(String... path) { + this.path = path; + long start = System.currentTimeMillis(); + if (!this.loadMainDictionary(path[0])) { + Predefine.logger.warning("自定义词典" + Arrays.toString(path) + "加载失败"); + return false; + } else { + Predefine.logger.info( + "自定义词典加载成功:" + this.dat.size() + "个词条,耗时" + (System.currentTimeMillis() - start) + "ms"); + this.path = path; + return true; + } + } + + public boolean loadMainDictionary(String mainPath) { + return loadMainDictionary(mainPath, this.path, this.dat, true, addToSuggesterTrie); + } public boolean reload() { if (this.path != null && this.path.length != 0) { @@ -344,35 +373,4 @@ public class MultiCustomDictionary extends DynamicCustomDictionary { return true; } } - - - public static boolean isLetters(String str) { - char[] chars = str.toCharArray(); - if (chars.length <= 1) { - return false; - } - for (int i = 0; i < chars.length; i++) { - if ((chars[i] >= 'A' && chars[i] <= 'Z')) { - return true; - } - } - return false; - } - - public static boolean isLowerLetter(String str) { - char[] chars = str.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if ((chars[i] >= 'a' && chars[i] <= 'z')) { - return true; - } - } - return false; - } - - public static String getWordBySpace(String word) { - if (word.contains(HanlpHelper.SPACE_SPILT)) { - return word.replace(HanlpHelper.SPACE_SPILT, " "); - } - return word; - } } \ No newline at end of file diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/Suggester.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/Suggester.java index 1fd7fea87..36b700ed0 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/Suggester.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/infrastructure/nlp/Suggester.java @@ -23,13 +23,12 @@ import org.springframework.util.CollectionUtils; @Service public class Suggester { + public static final int SEARCH_SIZE = 200; private static final Logger LOGGER = LoggerFactory.getLogger(Suggester.class); private static BinTrie> trie; private static BinTrie> suffixTrie; private static String localFileCache = ""; - public static final int SEARCH_SIZE = 200; - static { trie = new BinTrie<>(); suffixTrie = new BinTrie<>(); @@ -53,7 +52,7 @@ public class Suggester { return result.stream().map( entry -> { String name = entry.getKey().replace("#", " "); - return new MapResult(name, entry.getValue(),key); + return new MapResult(name, entry.getValue(), key); } ).sorted((a, b) -> -(b.getName().length() - a.getName().length())) .limit(SEARCH_SIZE) diff --git a/common/pom.xml b/common/pom.xml index 1c6d449ae..70397847c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -124,6 +124,12 @@ ${pagehelper.version} + + org.apache.calcite + calcite-core + ${calcite.version} + +
diff --git a/common/src/main/java/com/tencent/supersonic/common/constant/Constants.java b/common/src/main/java/com/tencent/supersonic/common/constant/Constants.java index e172e6def..5d06e987b 100644 --- a/common/src/main/java/com/tencent/supersonic/common/constant/Constants.java +++ b/common/src/main/java/com/tencent/supersonic/common/constant/Constants.java @@ -40,6 +40,7 @@ public class Constants { public static final String DAY = "DAY"; public static final String DAY_FORMAT = "yyyy-MM-dd"; + public static final String MONTH_FORMAT = "yyyy-MM"; public static final String DAY_FORMAT_INT = "YYYYMMDD"; public static final String MONTH = "MONTH"; public static final String WEEK = "WEEK"; diff --git a/common/src/main/java/com/tencent/supersonic/common/nlp/ItemDO.java b/common/src/main/java/com/tencent/supersonic/common/nlp/ItemDO.java index 8a7078e60..df47bb072 100644 --- a/common/src/main/java/com/tencent/supersonic/common/nlp/ItemDO.java +++ b/common/src/main/java/com/tencent/supersonic/common/nlp/ItemDO.java @@ -1,7 +1,7 @@ package com.tencent.supersonic.common.nlp; +import com.google.common.base.Objects; import java.io.Serializable; -import java.util.Objects; import lombok.Data; import lombok.Getter; import lombok.Setter; @@ -14,8 +14,18 @@ public class ItemDO implements Serializable { private Integer domain; private Integer itemId; private String name; + private String bizName; private Long useCnt = 0L; + public ItemDO() { + } + + public ItemDO(Integer domain, Integer itemId, String name, String bizName) { + this.domain = domain; + this.itemId = itemId; + this.name = name; + this.bizName = bizName; + } @Override public boolean equals(Object o) { @@ -26,7 +36,14 @@ public class ItemDO implements Serializable { return false; } ItemDO itemDO = (ItemDO) o; - return Objects.equals(domain, itemDO.domain) && Objects.equals(itemId, itemDO.itemId) - && Objects.equals(name, itemDO.name); + return Objects.equal(domain, itemDO.domain) && Objects.equal(itemId, + itemDO.itemId) && Objects.equal(name, itemDO.name) + && Objects.equal(bizName, itemDO.bizName) && Objects.equal( + useCnt, itemDO.useCnt); + } + + @Override + public int hashCode() { + return Objects.hashCode(domain, itemId, name, bizName, useCnt); } } diff --git a/common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParseUtils.java b/common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParseUtils.java new file mode 100644 index 000000000..a926d928c --- /dev/null +++ b/common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParseUtils.java @@ -0,0 +1,160 @@ +package com.tencent.supersonic.common.util.calcite; + +import java.util.List; +import java.util.stream.Collectors; +import org.apache.calcite.sql.SqlBasicCall; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOrderBy; +import org.apache.calcite.sql.SqlSelect; +import org.apache.calcite.sql.parser.SqlParseException; +import org.apache.calcite.sql.parser.SqlParser; + +/** + * sql parse utils + */ +public class SqlParseUtils { + + /** + * get sql parseInfo + * + * @param sql + * @return + */ + public static SqlParserInfo getSqlParseInfo(String sql) { + try { + SqlParser parser = SqlParser.create(sql); + SqlNode sqlNode = parser.parseQuery(); + SqlParserInfo sqlParserInfo = new SqlParserInfo(); + handlerSQL(sqlNode, sqlParserInfo); + + List collect = sqlParserInfo.getAllFields().stream().distinct().collect(Collectors.toList()); + + sqlParserInfo.setAllFields(collect); + return sqlParserInfo; + } catch (SqlParseException e) { + throw new RuntimeException("getSqlParseInfo", e); + } + } + + /** + * hanlder sql + * + * @param sqlNode + * @param sqlParserInfo + */ + public static void handlerSQL(SqlNode sqlNode, SqlParserInfo sqlParserInfo) { + SqlKind kind = sqlNode.getKind(); + + switch (kind) { + case SELECT: + handlerSelect(sqlNode, sqlParserInfo); + break; + case ORDER_BY: + handlerOrderBy(sqlNode, sqlParserInfo); + break; + } + } + + /** + * hanlder order by + * + * @param node + * @param sqlParserInfo + */ + private static void handlerOrderBy(SqlNode node, SqlParserInfo sqlParserInfo) { + SqlOrderBy sqlOrderBy = (SqlOrderBy) node; + SqlNode query = sqlOrderBy.query; + handlerSQL(query, sqlParserInfo); + SqlNodeList orderList = sqlOrderBy.orderList; + handlerField(orderList, sqlParserInfo); + } + + /** + * hanlder select + * + * @param select + * @param sqlParserInfo + */ + private static void handlerSelect(SqlNode select, SqlParserInfo sqlParserInfo) { + SqlSelect sqlSelect = (SqlSelect) select; + SqlNodeList selectList = sqlSelect.getSelectList(); + + selectList.getList().forEach(list -> { + handlerField(list, sqlParserInfo); + }); + String tableName = handlerFrom(sqlSelect.getFrom()); + sqlParserInfo.setTableName(tableName); + + if (sqlSelect.hasWhere()) { + handlerField(sqlSelect.getWhere(), sqlParserInfo); + } + if (sqlSelect.hasOrderBy()) { + handlerField(sqlSelect.getOrderList(), sqlParserInfo); + } + SqlNodeList group = sqlSelect.getGroup(); + if (group != null) { + group.forEach(groupField -> { + handlerField(groupField, sqlParserInfo); + }); + } + } + + /** + * hander from + * + * @param from + * @return + */ + private static String handlerFrom(SqlNode from) { + SqlKind kind = from.getKind(); + switch (kind) { + case IDENTIFIER: + SqlIdentifier sqlIdentifier = (SqlIdentifier) from; + return sqlIdentifier.getSimple(); + case AS: + SqlBasicCall sqlBasicCall = (SqlBasicCall) from; + SqlNode sqlNode = sqlBasicCall.getOperandList().get(0); + SqlSelect sqlSelect = (SqlSelect) sqlNode; + return handlerFrom(sqlSelect.getFrom()); + } + return ""; + } + + /** + * handler field + * + * @param field + * @param sqlParserInfo + */ + private static void handlerField(SqlNode field, SqlParserInfo sqlParserInfo) { + SqlKind kind = field.getKind(); + switch (kind) { + case AS: + List operandList1 = ((SqlBasicCall) field).getOperandList(); + SqlNode left_as = operandList1.get(0); + handlerField(left_as, sqlParserInfo); + break; + case IDENTIFIER: + SqlIdentifier sqlIdentifier = (SqlIdentifier) field; + sqlParserInfo.getAllFields().add(sqlIdentifier.getSimple()); + break; + default: + if (field instanceof SqlBasicCall) { + List operandList2 = ((SqlBasicCall) field).getOperandList(); + for (int i = 0; i < operandList2.size(); i++) { + handlerField(operandList2.get(i), sqlParserInfo); + } + } + if (field instanceof SqlNodeList) { + ((SqlNodeList) field).getList().forEach(node -> { + handlerField(node, sqlParserInfo); + }); + } + break; + } + } +} + diff --git a/common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParserInfo.java b/common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParserInfo.java new file mode 100644 index 000000000..cab347a03 --- /dev/null +++ b/common/src/main/java/com/tencent/supersonic/common/util/calcite/SqlParserInfo.java @@ -0,0 +1,17 @@ +package com.tencent.supersonic.common.util.calcite; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.ToString; + +@Data +@ToString +public class SqlParserInfo implements Serializable { + + private String tableName; + + private List allFields = new ArrayList<>(); + +} \ No newline at end of file diff --git a/common/src/main/java/com/tencent/supersonic/common/util/context/ThreadContext.java b/common/src/main/java/com/tencent/supersonic/common/util/context/ThreadContext.java index d54e831af..74b152401 100644 --- a/common/src/main/java/com/tencent/supersonic/common/util/context/ThreadContext.java +++ b/common/src/main/java/com/tencent/supersonic/common/util/context/ThreadContext.java @@ -4,6 +4,9 @@ package com.tencent.supersonic.common.util.context; import lombok.Builder; import lombok.Data; import lombok.ToString; +import java.util.Map; + +import java.util.Map; @Builder @ToString @@ -16,4 +19,6 @@ public class ThreadContext { private String token; + private Map extendInfo; + } \ No newline at end of file diff --git a/docs/images/supersonic_components.png b/docs/images/supersonic_components.png index 718f91818a7a69dabd76588d3a3ee07a7673d257..295f9eb0b5965da5a626324984dbd0b1a1581aae 100644 GIT binary patch literal 370520 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௾md6 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%0A~YaC2&7b&?&* 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%0D0#uHob|RKN0-{ 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

uQhPj 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

iucPjxe>@zKQo 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 diff --git a/launchers/chat/pom.xml b/launchers/chat/pom.xml index 970d20f01..3cc1d1875 100644 --- a/launchers/chat/pom.xml +++ b/launchers/chat/pom.xml @@ -1,6 +1,6 @@ - launchers @@ -100,4 +100,4 @@ - \ No newline at end of file + diff --git a/launchers/chat/src/main/resources/META-INF/spring.factories b/launchers/chat/src/main/resources/META-INF/spring.factories index b0497080c..19f37234b 100644 --- a/launchers/chat/src/main/resources/META-INF/spring.factories +++ b/launchers/chat/src/main/resources/META-INF/spring.factories @@ -1,32 +1,23 @@ -com.tencent.supersonic.chat.api.service.SchemaMapper=\ - com.tencent.supersonic.chat.application.mapper.HanlpSchemaMapper +com.tencent.supersonic.chat.api.component.SchemaMapper=\ + com.tencent.supersonic.chat.application.mapper.HanlpSchemaMapper, \ + com.tencent.supersonic.chat.application.mapper.DatabaseSchemaMapper, \ + com.tencent.supersonic.chat.application.mapper.QueryFilterMapper -com.tencent.supersonic.chat.application.parser.resolver.DomainResolver=\ - com.tencent.supersonic.chat.application.parser.resolver.HeuristicDomainResolver - -com.tencent.supersonic.chat.api.service.SemanticParser=\ - com.tencent.supersonic.chat.application.parser.TimeSemanticParser, \ +com.tencent.supersonic.chat.api.component.SemanticParser=\ com.tencent.supersonic.chat.application.parser.DomainSemanticParser, \ - com.tencent.supersonic.chat.application.parser.ListFilterParser, \ - com.tencent.supersonic.chat.application.parser.DefaultMetricSemanticParser, \ + com.tencent.supersonic.chat.application.parser.TimeSemanticParser, \ com.tencent.supersonic.chat.application.parser.AggregateSemanticParser +# com.tencent.supersonic.chat.application.parser.LLMSemanticParser -com.tencent.supersonic.chat.api.service.SemanticQuery=\ - com.tencent.supersonic.chat.application.query.EntityDetail, \ - com.tencent.supersonic.chat.application.query.EntityListTopN, \ - com.tencent.supersonic.chat.application.query.EntityMetricFilter, \ - com.tencent.supersonic.chat.application.query.EntityListFilter, \ - com.tencent.supersonic.chat.application.query.MetricCompare, \ - com.tencent.supersonic.chat.application.query.MetricDomain, \ - com.tencent.supersonic.chat.application.query.MetricFilter, \ - com.tencent.supersonic.chat.application.query.MetricGroupBy, \ - com.tencent.supersonic.chat.application.query.MetricOrderBy +com.tencent.supersonic.chat.api.component.SemanticLayer=\ + com.tencent.supersonic.chat.infrastructure.semantic.RemoteSemanticLayerImpl +com.tencent.supersonic.chat.application.query.QuerySelector=\ + com.tencent.supersonic.chat.application.query.HeuristicQuerySelector -com.tencent.supersonic.chat.api.service.SemanticLayer=\ - com.tencent.supersonic.chat.infrastructure.semantic.DefaultSemanticLayerImpl - +com.tencent.supersonic.chat.application.parser.DomainResolver=\ + com.tencent.supersonic.chat.application.parser.HeuristicDomainResolver com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ - com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor \ No newline at end of file + com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor diff --git a/launchers/chat/src/main/resources/application-local.yaml b/launchers/chat/src/main/resources/application-local.yaml index 1c28fd136..121adbce5 100644 --- a/launchers/chat/src/main/resources/application-local.yaml +++ b/launchers/chat/src/main/resources/application-local.yaml @@ -16,10 +16,10 @@ server: port: 9080 authentication: - enable: true + enable: false exclude: path: /api/auth/user/register,/api/auth/user/login semantic: url: - prefix: http://127.0.0.1:9081 \ No newline at end of file + prefix: http://127.0.0.1:9081 diff --git a/launchers/chat/src/main/resources/db/chat-data-h2.sql b/launchers/chat/src/main/resources/db/chat-data-h2.sql index 48d7d332c..f88969f27 100644 --- a/launchers/chat/src/main/resources/db/chat-data-h2.sql +++ b/launchers/chat/src/main/resources/db/chat-data-h2.sql @@ -3,7 +3,7 @@ insert into s2_user (id, `name`, password, display_name, email) values (2, 'jack insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom','123456','tom','tom@xx.com'); insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); -insert into s2_chat_config (id, domain_id, default_metrics, visibility, entity_info, dictionary_info, created_at, updated_at, created_by, updated_by, status) values (2, 1, '[{"metricId":1,"unit":7,"period":"DAY"}]','{"blackDimIdList":[],"blackMetricIdList":[]}','{"entityIds":[2],"names":["用户","用户姓名"],"detailData":{"dimensionIds":[1,2],"metricIds":[2]}}','[{"itemId":1,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":2,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":3,"type":"DIMENSION","blackList":[],"isDictInfo":true}]','2023-05-24 18:00:00', '2023-05-25 11:00:00', 'admin', 'admin', 1 ); +insert into s2_chat_config (`id` ,`domain_id` ,`default_metrics`,`visibility`,`entity_info` ,`dictionary_info`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) values (1,1,'[{"metricId":1,"unit":7,"period":"DAY"}]','{"blackDimIdList":[],"blackMetricIdList":[]}','{"entityIds":[2],"names":["用户","用户姓名"],"detailData":{"dimensionIds":[1,2],"metricIds":[2]}}','[{"itemId":1,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":2,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":3,"type":"DIMENSION","blackList":[],"isDictInfo":true}]','2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (1, '超音数访问统计','2023-06-10 10:00:52.495','2023-06-10 10:00:52','admin','您好,欢迎使用内容智能小Q','0','0'); insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (2, '用户访问统计','2023-06-10 10:01:04.528','2023-06-10 10:01:04','admin','您好,欢迎使用内容智能小Q','0','0'); diff --git a/launchers/common/pom.xml b/launchers/common/pom.xml index 9e2c3a7ac..0863ac9c5 100644 --- a/launchers/common/pom.xml +++ b/launchers/common/pom.xml @@ -1,6 +1,6 @@ - launchers diff --git a/launchers/common/src/main/java/com/tencent/supersonic/config/RestTemplateConfig.java b/launchers/common/src/main/java/com/tencent/supersonic/config/RestTemplateConfig.java index c1446534f..7bc0d3d6f 100644 --- a/launchers/common/src/main/java/com/tencent/supersonic/config/RestTemplateConfig.java +++ b/launchers/common/src/main/java/com/tencent/supersonic/config/RestTemplateConfig.java @@ -1,7 +1,13 @@ package com.tencent.supersonic.config; +import java.nio.charset.StandardCharsets; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.LaxRedirectStrategy; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration @@ -9,6 +15,14 @@ public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { - return new RestTemplate(); + HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); + httpRequestFactory.setConnectionRequestTimeout(2000); + httpRequestFactory.setConnectTimeout(10000); + httpRequestFactory.setReadTimeout(7200000); + HttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build(); + httpRequestFactory.setHttpClient(httpClient); + RestTemplate restTemplate = new RestTemplate(httpRequestFactory); + restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + return restTemplate; } } diff --git a/launchers/common/src/main/java/com/tencent/supersonic/config/SpringContextUtil.java b/launchers/common/src/main/java/com/tencent/supersonic/config/SpringContextUtil.java index e8e6f6197..64e293132 100644 --- a/launchers/common/src/main/java/com/tencent/supersonic/config/SpringContextUtil.java +++ b/launchers/common/src/main/java/com/tencent/supersonic/config/SpringContextUtil.java @@ -10,15 +10,15 @@ public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.applicationContext = applicationContext; } - public static ApplicationContext getApplicationContext() { - return applicationContext; - } - public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } diff --git a/launchers/pom.xml b/launchers/pom.xml index ef38fc7ca..af879b7d9 100644 --- a/launchers/pom.xml +++ b/launchers/pom.xml @@ -1,6 +1,6 @@ - supersonic @@ -16,10 +16,11 @@ chat semantic common + standalone 8 8 - \ No newline at end of file + diff --git a/launchers/semantic/pom.xml b/launchers/semantic/pom.xml index 676fdeecc..f05a52ffd 100644 --- a/launchers/semantic/pom.xml +++ b/launchers/semantic/pom.xml @@ -1,6 +1,6 @@ - launchers diff --git a/launchers/semantic/src/main/resources/application-local.yaml b/launchers/semantic/src/main/resources/application-local.yaml index 4977d37c8..39e9d0715 100644 --- a/launchers/semantic/src/main/resources/application-local.yaml +++ b/launchers/semantic/src/main/resources/application-local.yaml @@ -18,6 +18,6 @@ spring: data: classpath:db/semantic-data-h2.sql authentication: - enable: true + enable: false exclude: path: /api/auth/user/register,/api/auth/user/login \ No newline at end of file diff --git a/launchers/semantic/src/main/resources/db/semantic-schema-h2.sql b/launchers/semantic/src/main/resources/db/semantic-schema-h2.sql index 649ad3132..4d484dee7 100644 --- a/launchers/semantic/src/main/resources/db/semantic-schema-h2.sql +++ b/launchers/semantic/src/main/resources/db/semantic-schema-h2.sql @@ -84,6 +84,7 @@ CREATE TABLE IF NOT EXISTS `s2_metric` ( `updated_by` varchar(100) NOT NULL , `data_format_type` varchar(50) DEFAULT NULL , `data_format` varchar(500) DEFAULT NULL, + `alias` varchar(500) DEFAULT NULL, PRIMARY KEY (`id`) ); COMMENT ON TABLE s2_metric IS 'metric information table'; @@ -106,6 +107,8 @@ CREATE TABLE IF NOT EXISTS `s2_dimension` ( `updated_at` TIMESTAMP NOT NULL , `updated_by` varchar(100) NOT NULL , `semantic_type` varchar(20) NOT NULL, -- semantic type: DATE, ID, CATEGORY + `alias` varchar(500) DEFAULT NULL, + `default_values` varchar(500) DEFAULT NULL, PRIMARY KEY (`id`) ); COMMENT ON TABLE s2_dimension IS 'dimension information table'; diff --git a/launchers/semantic/src/main/resources/model/s2_time_stat.yaml b/launchers/semantic/src/main/resources/model/s2_time_stat.yaml deleted file mode 100644 index 8d2916389..000000000 --- a/launchers/semantic/src/main/resources/model/s2_time_stat.yaml +++ /dev/null @@ -1,22 +0,0 @@ -data_source: - name: s2_stay_time_statis - sql_query: select imp_date,page,stay_hours from s2_stay_time_statis - identifiers: - - name: sys_imp_date - type: primary - expr: imp_date - - name: user_name - type: primary - dimensions: - - name: page - type: categorical - - name: imp_date - type: time - type_params: - is_primary: True - time_granularity: day - measures: - - name: stay_hours - agg: sum - expr: stay_hours - create_metric: True \ No newline at end of file diff --git a/launchers/semantic/src/main/resources/model/s2_uv.yaml b/launchers/semantic/src/main/resources/model/s2_uv.yaml deleted file mode 100644 index c67b88130..000000000 --- a/launchers/semantic/src/main/resources/model/s2_uv.yaml +++ /dev/null @@ -1,26 +0,0 @@ -data_source: - name: s2_pv_uv_statis - sql_query: select imp_date,user_name from s2_pv_uv_statis - identifiers: - - name: sys_imp_date - type: primary - expr: imp_date - - name: user_name - type: primary - dimensions: - - name: page - type: categorical - - name: sys_imp_date - type: time - type_params: - is_primary: True - time_granularity: day - measures: - - name: uv - agg: count_distinct - expr: user_name - create_metric: True - - name: pv - agg: sum - expr: 1 - create_metric: True \ No newline at end of file diff --git a/launchers/standalone/pom.xml b/launchers/standalone/pom.xml new file mode 100644 index 000000000..665faab73 --- /dev/null +++ b/launchers/standalone/pom.xml @@ -0,0 +1,126 @@ + + + + launchers + com.tencent.supersonic + ${revision} + + 4.0.0 + + launchers-standalone + + + 8 + 8 + com.tencent.supersonic.StandaloneLauncher + + + + com.tencent.supersonic + launchers-common + ${project.version} + + + + com.tencent.supersonic + chat-core + ${project.version} + + + + com.tencent.supersonic + auth-authorization + ${project.version} + + + + com.h2database + h2 + ${h2.version} + + + + javax.servlet.jsp + jsp-api + 2.0 + compile + + + com.tencent.supersonic + semantic-query + ${project.version} + + + + com.tencent.supersonic + semantic-core + ${project.version} + + + com.tencent.supersonic + auth-api + ${project.version} + + + junit + junit + test + + + + + + + local + + + com.h2database + h2 + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + *.* + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + false + + + ${start-class} + + + + ../../assembly/build/build.xml + + + + + make-assembly + package + + single + + + + + + + + diff --git a/launchers/standalone/src/main/bin/env.sh b/launchers/standalone/src/main/bin/env.sh new file mode 100644 index 000000000..969d3fe90 --- /dev/null +++ b/launchers/standalone/src/main/bin/env.sh @@ -0,0 +1,2 @@ +export APP_NAME=chat-service +export MAIN_CLASS=com.tencent.supersonic.Launcher diff --git a/launchers/standalone/src/main/bin/run.sh b/launchers/standalone/src/main/bin/run.sh new file mode 100755 index 000000000..00d517aea --- /dev/null +++ b/launchers/standalone/src/main/bin/run.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +binDir=$(cd "$(dirname "$0")"; pwd) +baseDir=$(readlink -f $binDir/../) +libDir=$baseDir/lib +confDir=$baseDir/conf +webDir=$baseDir/webapp + +source ${baseDir}/bin/env.sh + + +CLASSPATH="" +CLASSPATH=$CLASSPATH:$confDir + +for jarPath in $libDir/*.jar; do + CLASSPATH=$CLASSPATH:$jarPath +done + + + +export CLASSPATH +export LANG="zh_CN.UTF-8" + +cd $baseDir + +if [[ "$JAVA_HOME" == "" ]]; then + JAVA_HOME=$(ls /usr/jdk64/jdk* -d 2>/dev/null | xargs | awk '{print "'$APP_NAME'"}') +fi +export PATH=$JAVA_HOME/bin:$PATH + +command="-Dfile.encoding="UTF-8" -Duser.language="Zh" -Duser.region="CN" -Duser.timezone="GMT+08" -Xms1024m -Xmx2048m "$MAIN_CLASS + +mkdir -p $baseDir/logs +if [[ "$is_test" == "true" ]]; then + java -Dspring.profiles.active="dev" $command >/dev/null 2>$baseDir/logs/error.log & +else + java $command $baseDir >/dev/null 2>$baseDir/logs/error.log & +fi diff --git a/launchers/standalone/src/main/bin/service.sh b/launchers/standalone/src/main/bin/service.sh new file mode 100755 index 000000000..3176eb811 --- /dev/null +++ b/launchers/standalone/src/main/bin/service.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +binDir=$(cd "$(dirname "$0")"; pwd) +baseDir=$(readlink -f $binDir/../) +confDir=$baseDir/conf +source ${baseDir}/bin/env.sh + +commond=$1 + +function start() +{ + pid=$(ps aux | grep $MAIN_CLASS | grep -v grep |grep $baseDir | awk '{print "'$APP_NAME'"}') + if [[ "$pid" == "" ]]; then + logs=$baseDir/logs/service.sh.log + env DEPLOY=true $baseDir/bin/run.sh $MAIN_CLASS && echo "Process started, see logs/error with logs/error command" + return 0 + else + echo "Process (PID = $pid) is running." + return 1 + fi +} + +function stop() +{ + pid=$(ps aux | grep $MAIN_CLASS | grep -v grep|grep $baseDir| awk '{print $2}') + if [[ "$pid" == "" ]]; then + echo "Process is not running !" + return 1 + else + kill -9 $pid + echo "Process (PID = $pid) is killed !" + return 0 + fi +} + +case "$commond" in + start) + echo -e "Starting $APP_NAME" + start + ;; + stop) + echo -e "Stopping $APP_NAME" + stop + ;; + restart) + echo -e "Resetting $APP_NAME" + stop + start + ;; + *) + echo "Use command {start|stop|status|restart} to run." + exit 1 +esac + +exit 0 diff --git a/launchers/standalone/src/main/java/com/tencent/supersonic/StandaloneLauncher.java b/launchers/standalone/src/main/java/com/tencent/supersonic/StandaloneLauncher.java new file mode 100644 index 000000000..0356d5a94 --- /dev/null +++ b/launchers/standalone/src/main/java/com/tencent/supersonic/StandaloneLauncher.java @@ -0,0 +1,20 @@ +package com.tencent.supersonic; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * StandaloneLauncher + **/ +@SpringBootApplication(scanBasePackages = {"com.tencent.supersonic"}, + exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) +@EnableScheduling +public class StandaloneLauncher { + + public static void main(String[] args) { + SpringApplication.run(StandaloneLauncher.class, args); + } +} diff --git a/launchers/standalone/src/main/java/com/tencent/supersonic/db/MybatisConfig.java b/launchers/standalone/src/main/java/com/tencent/supersonic/db/MybatisConfig.java new file mode 100644 index 000000000..f3428741a --- /dev/null +++ b/launchers/standalone/src/main/java/com/tencent/supersonic/db/MybatisConfig.java @@ -0,0 +1,31 @@ +package com.tencent.supersonic.db; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import javax.sql.DataSource; + + +@Configuration +@MapperScan(value = "com.tencent.supersonic", annotationClass = Mapper.class) +public class MybatisConfig { + + private static final String MAPPER_LOCATION = "classpath*:mapper/**/*.xml"; + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + bean.setConfiguration(configuration); + bean.setDataSource(dataSource); + + bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION)); + return bean.getObject(); + } +} diff --git a/launchers/standalone/src/main/resources/META-INF/spring.factories b/launchers/standalone/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..573471a9f --- /dev/null +++ b/launchers/standalone/src/main/resources/META-INF/spring.factories @@ -0,0 +1,23 @@ +com.tencent.supersonic.chat.api.component.SchemaMapper=\ + com.tencent.supersonic.chat.application.mapper.HanlpSchemaMapper, \ + com.tencent.supersonic.chat.application.mapper.DatabaseSchemaMapper, \ + com.tencent.supersonic.chat.application.mapper.QueryFilterMapper + +com.tencent.supersonic.chat.api.component.SemanticParser=\ + com.tencent.supersonic.chat.application.parser.DomainSemanticParser, \ + com.tencent.supersonic.chat.application.parser.TimeSemanticParser, \ + com.tencent.supersonic.chat.application.parser.AggregateSemanticParser +# com.tencent.supersonic.chat.application.parser.LLMSemanticParser + + +com.tencent.supersonic.chat.api.component.SemanticLayer=\ + com.tencent.supersonic.chat.infrastructure.semantic.LocalSemanticLayerImpl + +com.tencent.supersonic.chat.application.query.QuerySelector=\ + com.tencent.supersonic.chat.application.query.HeuristicQuerySelector + +com.tencent.supersonic.chat.application.parser.DomainResolver=\ + com.tencent.supersonic.chat.application.parser.HeuristicDomainResolver + +com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ + com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor diff --git a/launchers/standalone/src/main/resources/application-local.yaml b/launchers/standalone/src/main/resources/application-local.yaml new file mode 100644 index 000000000..4c029c184 --- /dev/null +++ b/launchers/standalone/src/main/resources/application-local.yaml @@ -0,0 +1,28 @@ +spring: + h2: + console: + path: /h2-console/chat + # enabled web + enabled: true + datasource: + driver-class-name: org.h2.Driver + schema: classpath:db/schema-h2.sql + data: classpath:db/data-h2.sql + url: jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false + username: root + password: semantic + +server: + port: 9080 + +authentication: + enable: false + exclude: + path: /api/auth/user/register,/api/auth/user/login + +semantic: + url: + prefix: http://127.0.0.1:9081 + +mybatis: + mapper-locations=classpath:mappers/custom/*.xml,classpath*:/mappers/*.xml diff --git a/launchers/standalone/src/main/resources/application.yaml b/launchers/standalone/src/main/resources/application.yaml new file mode 100644 index 000000000..83b731d26 --- /dev/null +++ b/launchers/standalone/src/main/resources/application.yaml @@ -0,0 +1,7 @@ +spring: + profiles: + active: local + application: + name: chat +mybatis: + mapper-locations=classpath:mappers/custom/*.xml,classpath*:/mappers/*.xml diff --git a/launchers/standalone/src/main/resources/data/README.url b/launchers/standalone/src/main/resources/data/README.url new file mode 100644 index 000000000..e37374f27 --- /dev/null +++ b/launchers/standalone/src/main/resources/data/README.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://github.com/hankcs/HanLP/ diff --git a/launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.mini.txt b/launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.mini.txt new file mode 100644 index 000000000..6014daa6e --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.mini.txt @@ -0,0 +1,3 @@ +龚 nr 1 +龛 ng 1 +龛影 n 1 \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt b/launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt new file mode 100644 index 000000000..562f7f1cc --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt @@ -0,0 +1,4 @@ +买@水果 1 +然后@来 1 +我@遗忘 10 +遗忘@我 10 \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_1.txt b/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_1.txt new file mode 100644 index 000000000..c21ae2edb --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_1.txt @@ -0,0 +1,5 @@ +hr _1_1 876 +sales _1_1 872 +marketing _1_1 310 +strategy _1_1 360 +sales _1_1 500 \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_2.txt b/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_2.txt new file mode 100644 index 000000000..e0570f4b0 --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_2.txt @@ -0,0 +1,7 @@ +tom _1_2 52 +alice _1_2 47 +lucy _1_2 31 +dean _1_2 36 +john _1_2 50 +jack _1_2 38 +admin _1_2 70 \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_3.txt b/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_3.txt new file mode 100644 index 000000000..f519c23fb --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/custom/DimValue_1_3.txt @@ -0,0 +1,6 @@ +p1 _1_3 52 +p2 _1_3 47 +p3 _1_3 31 +p4 _1_3 36 +p5 _1_3 50 +p6 _1_3 38 \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/data/dictionary/other/CharTable.txt b/launchers/standalone/src/main/resources/data/dictionary/other/CharTable.txt new file mode 100644 index 000000000..a66a33e07 --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/other/CharTable.txt @@ -0,0 +1,4890 @@ += +A=a +B=b +C=c +D=d +E=e +F=f +G=g +H=h +I=i +J=j +K=k +L=l +M=m +N=n +O=o +P=p +Q=q +R=r +S=s +T=t +U=u +V=v +W=w +X=x +Y=y +Z=z +[=《 +]=》 +{=《 +}=》 + = +«=《 +»=》 +“=" +”=" +•=· +‹=《 +›=》 +①=一 +②=二 +③=三 +④=四 +⑤=五 +⑥=六 +⑦=七 +⑧=八 +⑨=九 +⑩=十 +〈=《 +〉=》 +「=“ +」=” +『=‘ +』=’ +【=《 +】=》 +〔=《 +〕=》 +〖=《 +〗=" +〝=" +〞=" +と=之 +ふ=子 +ル=儿 +ㄖ=日 +丟=丢 +両=两 +並=并 +丼=井 +乁=乙 +乗=乘 +乧=斗 +乷=沙 +乹=乾 +乾=干 +亀=龟 +亁=乾 +亂=乱 +亙=亘 +亝=斋 +亞=亚 +亯=享 +亱=夜 +亷=廉 +亾=亡 +仈=八 +仏=佛 +仛=托 +仩=上 +仯=秒 +仴=月 +仸=袄 +仹=丰 +仺=仓 +伕=夫 +伖=友 +伝=传 +伮=奴 +佀=侣 +佇=伫 +佋=召 +佔=占 +佘=畲 +佡=仙 +佪=徊 +佱=企 +佲=铭 +併=并 +佷=很 +佹=危 +佽=次 +侀=型 +來=来 +侇=姨 +侎=眯 +侓=律 +侖=仑 +侢=再 +侶=侣 +侷=局 +侹=廷 +俁=俣 +係=系 +俆=徐 +俔=伣 +俠=侠 +俢=修 +俥=伡 +俬=私 +俻=备 +俽=欣 +倀=伥 +倁=蜘 +倂=并 +倆=俩 +倈=俫 +倉=仓 +個=个 +倐=倏 +們=们 +倖=幸 +倞=京 +倣=仿 +倫=伦 +倳=事 +倶=具 +倸=睬 +倹=俭 +倻=椰 +倽=啥 +偅=重 +偉=伟 +偓=屋 +偘=侃 +偡=湛 +偢=秋 +偪=逼 +偲=思 +側=侧 +偵=侦 +偸=偷 +偺=咱 +偽=伪 +傑=杰 +傓=扇 +傖=伧 +傘=伞 +備=备 +傚=效 +傢=家 +傪=参 +傭=佣 +傯=偬 +傳=传 +傴=伛 +債=债 +傷=伤 +傹=镜 +傾=倾 +僂=偻 +僅=仅 +僉=佥 +僊=仙 +働=動 +僐=善 +僑=侨 +僒=窘 +僕=仆 +僜=澄 +僞=伪 +僟=机 +僥=侥 +僨=偾 +僮=童 +僱=雇 +價=价 +僽=愁 +儀=仪 +儁=俊 +儂=侬 +億=亿 +儈=侩 +儉=俭 +儌=侥 +儐=傧 +儔=俦 +儕=侪 +儘=尽 +償=偿 +優=优 +儭=亲 +儲=储 +儵=倏 +儷=俪 +儸=箩 +儹=攒 +儺=傩 +儻=傥 +儼=俨 +兇=凶 +兌=兑 +兎=兔 +兒=儿 +兗=兖 +兠=兜 +內=内 +兩=两 +兯=节 +兲=天 +兿=艺 +冄=冉 +冇=没 +冊=册 +冋=回 +冐=冒 +冨=富 +冩=写 +冪=幂 +冴=讶 +冺=泯 +凂=免 +凃=涂 +凅=固 +凈=净 +凊=清 +凍=冻 +凗=摧 +凘=斯 +凜=凛 +凞=熙 +凢=几 +凣=凡 +処=处 +凧=巾 +凱=凯 +凲=兼 +凴=凭 +凾=涵 +刄=刃 +刅=办 +刋=刊 +別=别 +刦=劫 +刧=劫 +刪=删 +刴=剁 +刼=劫 +剄=刭 +則=则 +剋=克 +剎=刹 +剏=创 +剗=刬 +剘=期 +剙=創 +剛=刚 +剝=剥 +剨=割 +剮=剐 +剳=劄 +剴=剀 +創=创 +剷=铲 +剹=戮 +剼=删 +劃=划 +劄=札 +劇=剧 +劈=噼 +劉=刘 +劊=刽 +劌=刿 +劍=剑 +劑=剂 +劒=剑 +劦=力 +効=效 +勁=劲 +勄=敏 +勅=敕 +勌=倦 +動=动 +勗=勖 +務=务 +勛=勋 +勝=胜 +勞=劳 +勠=戮 +勢=势 +勦=剿 +勧=劝 +勩=勚 +勱=劢 +勳=勋 +勵=励 +勸=劝 +勻=匀 +勼=九 +匄=亡 +匊=菊 +匋=掏 +匑=躬 +匢=勿 +匨=壮 +匬=愈 +匭=匦 +匯=汇 +匱=匮 +匲=奁 +匳=奁 +匴=算 +匵=椟 +匼=合 +匽=宴 +區=区 +卂=汛 +卆=杂 +協=协 +卙=甚 +卛=率 +卬=仰 +卲=邵 +卹=恤 +卻=却 +卽=即 +厊=芽 +厐=庞 +厔=室 +厗=辛 +厙=厍 +厛=听 +厞=匪 +厠=厕 +厡=原 +厤=历 +厫=廒 +厭=厌 +厯=历 +厰=厂 +厲=厉 +厴=厣 +厷=公 +厾=去 +參=参 +叄=叁 +収=收 +叒=双 +叚=假 +叜=叟 +叡=睿 +叢=丛 +叧=另 +叺=入 +吂=盲 +吒=咤 +吘=午 +吚=咿 +吢=吣 +吳=吴 +吶=呐 +吷=决 +吿=告 +呁=钧 +呂=吕 +呉=吴 +呌=叫 +呎=迟 +呞=司 +呪=咒 +呮=只 +呱=哌 +呴=句 +呺=号 +咊=和 +咑=打 +咓=瓦 +咗=左 +咜=它 +咟=百 +咮=珠 +咰=询 +咷=啕 +咼=呙 +哃=同 +哋=的 +哠=告 +員=员 +哢=咔 +哣=痘 +哬=呵 +哯=现 +哴=琅 +哾=悦 +唂=谷 +唄=呗 +唍=完 +唎=例 +唕=唣 +唘=启 +唚=吣 +唡=俩 +唥=冷 +唦=砂 +唫=金 +唰=刷 +唵=俺 +唶=锡 +唸=念 +唻=来 +唽=析 +啇=商 +啌=控 +啍=享 +啎=忤 +問=问 +啑=捷 +啓=启 +啗=啖 +啘=婉 +啚=鄙 +啝=和 +啞=哑 +啟=启 +啢=唡 +啣=衔 +啨=晴 +啩=挂 +啱=岩 +啴=单 +喎=㖞 +喐=郁 +喒=咱 +喖=枯 +喚=唤 +喥=度 +喦=岩 +喪=丧 +喫=吃 +喬=乔 +單=单 +喰=食 +喲=哟 +営=宫 +喼=急 +嗁=啼 +嗆=呛 +嗇=啬 +嗊=唝 +嗎=吗 +嗏=茶 +嗐=害 +嗗=骨 +嗚=呜 +嗛=谦 +嗩=唢 +嗬=呵 +嗱=拿 +嗶=哔 +嗹=莲 +嗻=遮 +嗼=摸 +嘂=叫 +嘄=鸣 +嘆=叹 +嘋=教 +嘍=喽 +嘓=啯 +嘔=呕 +嘖=啧 +嘗=尝 +嘙=婆 +嘚=得 +嘜=唛 +嘠=嘎 +嘢=野 +嘩=哗 +嘫=然 +嘮=唠 +嘯=啸 +嘰=叽 +嘵=哓 +嘸=呒 +嘽=啴 +噂=遵 +噅=咴 +噉=啖 +噐=器 +噑=嗥 +噓=嘘 +噖=琴 +噝=咝 +噠=哒 +噥=哝 +噦=哕 +噭=激 +噯=嗳 +噲=哙 +噴=喷 +噸=吨 +噹=当 +噺=新 +嚀=咛 +嚂=滥 +嚇=吓 +嚌=哜 +嚐=尝 +嚕=噜 +嚗=爆 +嚙=啮 +嚜=墨 +嚠=刘 +嚡=鞋 +嚤=蘑 +嚦=呖 +嚨=咙 +嚮=向 +嚲=亸 +嚳=喾 +嚴=严 +嚶=嘤 +囀=啭 +囁=嗫 +囂=嚣 +囅=冁 +囈=呓 +囉=啰 +囑=嘱 +囓=啮 +囗=口 +囘=回 +囙=因 +囥=亢 +囩=云 +囪=囱 +囬=回 +囮=化 +囯=国 +囸=正 +圅=函 +圇=囵 +國=国 +圍=围 +圎=园 +園=园 +圓=圆 +圖=图 +團=团 +圝=圞 +圤=扑 +圧=庄 +圱=升 +圵=止 +圷=吓 +坆=玫 +坧=石 +坰=垧 +坵=丘 +坿=附 +垇=坳 +垉=咆 +垐=茨 +垜=垛 +垨=守 +垯=达 +垳=行 +垵=埯 +垹=绑 +垿=序 +埐=侵 +埖=花 +埜=野 +埡=垭 +埥=请 +埰=采 +埱=叔 +埳=坎 +執=执 +堅=坚 +堊=垩 +堒=坤 +堔=深 +堖=垴 +堘=塍 +堝=埚 +堦=階 +堯=尧 +報=报 +場=场 +堷=音 +堿=碱 +塆=弯 +塊=块 +塋=茔 +塏=垲 +塒=埘 +塖=乘 +塗=涂 +塚=冢 +塟=葬 +塡=填 +塢=坞 +塤=埙 +塨=恭 +塮=谢 +塲=场 +塵=尘 +塹=堑 +塼=砖 +墊=埝 +墖=塔 +墘=乾 +墛=蔚 +墜=坠 +墪=墩 +墭=盛 +墮=堕 +墳=坟 +墵=坛 +墶=垯 +墻=墙 +墾=垦 +壃=僵 +壄=野 +壇=坛 +壋=垱 +壎=埙 +壓=压 +壖=堧 +壘=垒 +壙=圹 +壚=垆 +壜=坛 +壞=坏 +壟=垅 +壠=垅 +壢=坜 +壩=坝 +壪=塆 +壯=壮 +壺=壶 +壻=婿 +壼=壸 +壽=寿 +壿=蹲 +夀=寿 +夃=孕 +夅=降 +夌=菱 +夗=苑 +夘=卯 +夝=胜 +夠=够 +夢=梦 +夥=伙 +夰=介 +夲=本 +夾=夹 +奐=奂 +奧=奥 +奨=奖 +奩=奁 +奪=夺 +奬=奖 +奮=奋 +奷=奸 +奼=姹 +妀=改 +妏=文 +妑=芭 +妔=坑 +妕=钟 +妝=妆 +妠=呐 +妢=纷 +妬=妒 +妭=拨 +妱=招 +妳=你 +妶=弦 +妷=失 +妸=可 +妺=妹 +妽=申 +姃=征 +姄=民 +姉=姊 +姌=冉 +姍=姗 +姎=央 +姖=巨 +姙=妊 +姟=该 +姠=响 +姦=奸 +姧=歼 +姩=年 +姪=侄 +姵=佩 +姸=妍 +姺=先 +娕=束 +娖=促 +娛=娱 +娝=否 +娦=兵 +娪=语 +娫=延 +娬=武 +娯=娱 +娸=其 +娿=啊 +婁=娄 +婂=锦 +婄=赔 +婇=菜 +婑=矮 +婔=菲 +婖=添 +婜=娶 +婣=姻 +婤=稠 +婥=卓 +婦=妇 +婫=混 +婬=淫 +婭=娅 +婯=丽 +婸=扬 +婹=要 +婼=若 +媈=挥 +媌=苗 +媍=妇 +媔=面 +媗=喧 +媙=威 +媟=谍 +媠=惰 +媣=染 +媥=偏 +媦=胃 +媧=娲 +媨=酋 +媫=婕 +媯=妫 +媴=袁 +媷=辱 +媹=溜 +媼=媪 +媽=妈 +媿=愧 +嫃=真 +嫆=蓉 +嫊=素 +嫋=袅 +嫎=膀 +嫗=妪 +嫙=旋 +嫚=蔓 +嫝=康 +嫟=匿 +嫧=责 +嫰=嫩 +嫲=麻 +嫵=妩 +嫺=娴 +嫻=娴 +嫼=黑 +嫽=撩 +嫿=婳 +嬀=妫 +嬈=娆 +嬋=婵 +嬌=娇 +嬑=意 +嬘=遂 +嬙=嫱 +嬝=袅 +嬡=嫒 +嬤=嬷 +嬪=嫔 +嬭=奶 +嬰=婴 +嬶=鼻 +嬸=婶 +嬾=懒 +嬿=燕 +孃=娘 +孄=栏 +孌=娈 +孒=了 +孡=抬 +孧=幼 +孫=孙 +孶=孳 +學=学 +孼=孽 +孿=孪 +宂=冗 +宖=宏 +宮=宫 +宼=寇 +寀=采 +寃=冤 +寑=寝 +寕=宁 +寢=寝 +實=实 +寧=宁 +審=审 +寫=写 +寬=宽 +寲=疑 +寳=宝 +寵=宠 +寶=宝 +尅=克 +將=将 +專=专 +尋=寻 +對=对 +導=导 +尒=尔 +尙=尚 +尟=鲜 +尠=鲜 +尩=尪 +尫=尪 +尲=尴 +尷=尴 +屆=届 +屍=尸 +屓=屃 +屗=尾 +屙=疴 +屚=漏 +屛=屏 +屜=屉 +屟=屉 +屢=屡 +層=层 +屨=屦 +屬=属 +屭=屃 +屰=逆 +屾=山 +岅=坂 +岆=妖 +岠=拒 +岡=冈 +岥=坡 +岼=坪 +峝=峒 +峩=峨 +峫=邪 +峬=捕 +峮=裙 +峯=峰 +峴=岘 +島=岛 +峸=城 +峽=峡 +崈=宗 +崍=崃 +崐=昆 +崕=崖 +崗=岗 +崙=仑 +崠=岽 +崢=峥 +崣=萎 +崧=嵩 +崫=窟 +崬=岽 +崳=嵛 +崶=封 +崾=腰 +嵐=岚 +嵒=岩 +嵔=畏 +嵗=岁 +嵻=慷 +嵿=顶 +嶁=嵝 +嶃=崭 +嶄=崭 +嶇=岖 +嶒=曾 +嶔=嵚 +嶗=崂 +嶠=峤 +嶢=峣 +嶧=峄 +嶨=峃 +嶮=崄 +嶴=岙 +嶵=罪 +嶶=微 +嶸=嵘 +嶺=岭 +嶼=屿 +嶽=岳 +巋=岿 +巒=峦 +巔=巅 +巗=岩 +巛=川 +巟=荒 +巰=巯 +巵=卮 +巹=卺 +巿=市 +帀=匝 +帉=粉 +帋=纸 +帞=陌 +帥=帅 +師=师 +帬=裙 +帳=帐 +帶=带 +帹=接 +帾=赌 +幀=帧 +幃=帏 +幇=帮 +幈=屏 +幎=幂 +幑=徽 +幗=帼 +幘=帻 +幙=幕 +幚=帮 +幟=帜 +幣=币 +幫=帮 +幬=帱 +幷=并 +幹=干 +幺=么 +幾=几 +広=广 +庅=么 +庝=疼 +庫=库 +庻=庶 +庽=寓 +庿=庙 +廁=厕 +廂=厢 +廄=厩 +廈=厦 +廎=庼 +廐=厩 +廕=荫 +廚=厨 +廜=屠 +廝=厮 +廟=庙 +廠=厂 +廡=庑 +廢=废 +廣=广 +廩=廪 +廬=庐 +廰=厅 +廳=厅 +廵=巡 +廸=迪 +廹=迫 +廻=回 +廼=迺 +弌=壹 +弍=贰 +弒=弑 +弔=吊 +弖=弓 +弚=弟 +弜=弱 +弳=弪 +張=张 +強=强 +彅=简 +彆=别 +彈=弹 +彊=强 +彌=弥 +彎=弯 +彔=录 +彙=汇 +彜=彝 +彞=彝 +彠=彟 +彡=三 +彥=彦 +彫=雕 +彯=飘 +彵=他 +彶=及 +彽=低 +彿=佛 +徃=往 +後=后 +徑=径 +從=从 +徠=徕 +徣=借 +徦=假 +徧=遍 +復=复 +徬=彷 +徳=德 +徵=征 +徹=彻 +忊=订 +忬=舒 +忲=太 +忹=汪 +忼=杭 +怇=矩 +怉=饱 +怌=呸 +怓=努 +怞=油 +怭=必 +怱=匆 +怳=恍 +怵=憷 +怶=披 +怺=咏 +恆=恒 +恉=旨 +恏=好 +恠=怪 +恡=吝 +恥=耻 +悀=涌 +悅=悦 +悇=余 +悈=戒 +悊=哲 +悋=吝 +悘=医 +悙=亨 +悞=悮 +悡=梨 +悢=恨 +悤=匆 +悩=脑 +悪=恶 +悮=误 +悵=怅 +悶=闷 +悽=凄 +惓=倦 +惔=淡 +惡=恶 +惢=蕊 +惥=恿 +惪=德 +惱=恼 +惲=恽 +惷=蠢 +惻=恻 +愅=革 +愙=客 +愛=爱 +愜=惬 +愨=悫 +愬=诉 +愮=瑶 +愰=晃 +愴=怆 +愷=恺 +愺=草 +愽=博 +愾=忾 +慁=恩 +慂=恿 +慄=栗 +慇=殷 +態=态 +慍=愠 +慓=漂 +慘=惨 +慙=惭 +慚=惭 +慛=崔 +慟=恸 +慠=傲 +慣=惯 +慤=悫 +慥=造 +慦=救 +慪=怄 +慫=怂 +慬=懂 +慮=虑 +慲=瞒 +慳=悭 +慴=慑 +慶=庆 +慹=热 +慼=戚 +慽=戚 +慾=欲 +憂=忧 +憅=动 +憇=憩 +憉=彭 +憊=惫 +憐=怜 +憑=凭 +憒=愦 +憕=登 +憖=慭 +憚=惮 +憛=潭 +憜=堕 +憡=策 +憤=愤 +憫=悯 +憮=怃 +憲=宪 +憶=忆 +懃=勤 +懄=勤 +懆=操 +懇=恳 +應=应 +懌=怿 +懍=懔 +懞=蒙 +懟=怼 +懣=懑 +懨=恹 +懪=暴 +懲=惩 +懶=懒 +懷=怀 +懸=悬 +懺=忏 +懼=惧 +懽=欢 +懾=慑 +戀=恋 +戇=戆 +戉=钺 +戓=或 +戔=戋 +戞=戛 +戧=戗 +戨=歌 +戩=戬 +戰=战 +戱=戯 +戲=戏 +戶=户 +戹=厄 +戼=卯 +扂=店 +扆=衣 +扙=丈 +扜=迂 +扝=亏 +扡=扦 +扱=吸 +抝=拗 +抳=拟 +抴=曳 +拋=抛 +拑=钳 +拕=拖 +拚=拼 +拡=扩 +拤=掐 +拹=协 +拾=十 +挌=格 +挘=劣 +挩=捝 +挱=挲 +挵=弄 +挶=局 +挾=挟 +捄=救 +捊=浮 +捨=舍 +捫=扪 +捲=卷 +捳=岳 +掃=扫 +掄=抡 +掗=挜 +掙=挣 +掛=挂 +採=采 +掫=取 +掱=手 +掵=命 +掹=猛 +掽=碰 +揀=拣 +揅=研 +揌=塞 +揑=捏 +揗=循 +揙=编 +揚=扬 +換=换 +揫=揪 +揮=挥 +揵=健 +揷=插 +揹=背 +搆=构 +搇=揿 +搉=榷 +損=损 +搖=摇 +搗=捣 +搣=灭 +搤=扼 +搥=捶 +搧=扇 +搨=拓 +搯=掏 +搵=揾 +搶=抢 +搾=榨 +摀=捂 +摂=摄 +摃=扛 +摋=杀 +摑=掴 +摜=掼 +摟=搂 +摣=揸 +摤=爽 +摯=挚 +摳=抠 +摶=抟 +摺=折 +摻=掺 +摽=标 +撁=牵 +撃=击 +撈=捞 +撏=挦 +撐=撑 +撓=挠 +撚=捻 +撝=㧑 +撟=挢 +撢=掸 +撣=掸 +撥=拨 +撦=扯 +撧=撅 +撫=抚 +撲=扑 +撳=揿 +撴=蹾 +撻=挞 +撽=邀 +撾=挝 +撿=捡 +擁=拥 +擄=掳 +擇=择 +擊=击 +擋=挡 +擏=敬 +擓=㧟 +擔=担 +擕=携 +據=据 +擝=盟 +擠=挤 +擡=抬 +擣=捣 +擧=举 +擬=拟 +擯=摈 +擰=拧 +擱=搁 +擲=掷 +擴=扩 +擷=撷 +擺=摆 +擻=擞 +擼=撸 +擾=扰 +擿=摘 +攃=擦 +攄=摅 +攆=撵 +攋=赖 +攏=拢 +攔=拦 +攖=撄 +攙=搀 +攛=撺 +攜=携 +攝=摄 +攢=攒 +攣=挛 +攤=摊 +攩=挡 +攪=搅 +攬=揽 +攷=考 +敁=掂 +敂=叩 +敍=叙 +敎=教 +敗=败 +敘=叙 +敟=典 +敩=学 +敭=扬 +敵=敌 +數=数 +敺=驱 +斂=敛 +斃=毙 +斈=学 +斉=齐 +斕=斓 +斚=斝 +斬=斩 +斷=断 +於=于 +旂=旗 +旛=幡 +旣=既 +旤=祸 +旪=叶 +旹=时 +旾=春 +昇=升 +昋=吞 +昐=盼 +昜=杨 +昬=昏 +昻=昂 +時=时 +晉=晋 +晎=哄 +晝=昼 +晵=启 +晿=唱 +暀=往 +暈=晕 +暉=晖 +暎=映 +暒=星 +暘=旸 +暠=皓 +暡=翁 +暢=畅 +暫=暂 +暱=昵 +曂=黄 +曃=逮 +曄=晔 +曆=历 +曇=昙 +曉=晓 +曊=费 +曏=向 +曖=暧 +曟=晨 +曠=旷 +曡=叠 +曨=昽 +曬=晒 +曱=甲 +書=书 +朂=最 +會=会 +朓=跳 +朞=期 +朢=望 +朧=胧 +朩=木 +朮=术 +朳=扒 +朶=朵 +杇=圬 +杘=尿 +杧=忙 +東=东 +杴=锨 +杺=心 +枃=匀 +枈=柴 +枒=桠 +枱=台 +枴=拐 +枾=柿 +柂=拖 +柆=垃 +柈=伴 +柭=跋 +柵=栅 +柷=祝 +柸=杯 +柹=柿 +柺=拐 +査=查 +栁=柳 +栆=枣 +栔=契 +栞=刊 +栢=柏 +栤=冰 +栰=筏 +栱=供 +栺=指 +桒=桑 +桚=拶 +桭=振 +桮=杯 +桺=柳 +桿=杆 +梔=栀 +梘=枧 +梚=挽 +條=条 +梟=枭 +梲=棁 +梹=槟 +梽=志 +棃=梨 +棄=弃 +棈=精 +棊=棋 +棌=睬 +棑=排 +棖=枨 +棗=枣 +棟=栋 +棡= +棧=栈 +棩=渊 +棬=桊 +棲=栖 +棴=服 +棶=梾 +椀=碗 +椉=乘 +椏=桠 +椗=碇 +椘=楚 +椨=俯 +椮=渗 +椶=棕 +椾=笺 +楃=握 +楊=杨 +楓=枫 +楛=苦 +楨=桢 +楩=便 +業=业 +楱=奏 +楳=梅 +極=极 +楿=相 +榘=矩 +榦=干 +榪=杩 +榮=荣 +榲=榅 +榿=桤 +槀=槁 +槁=藁 +槃=盘 +構=构 +槍=枪 +槑=呆 +槓=杠 +槕=桌 +槤=梿 +槧=椠 +槨=椁 +槩=概 +槪=概 +槮=椮 +槳=桨 +槶=椢 +槹=槔 +槼=規 +樁=桩 +樂=乐 +樅=枞 +樐=橹 +樑=梁 +樓=楼 +標=标 +樝=楂 +樞=枢 +樣=样 +樭=基 +樸=朴 +樹=树 +樺=桦 +樿=椫 +橆=舞 +橈=桡 +橋=桥 +橓=瞬 +橖=棠 +橜=橛 +機=机 +橢=椭 +橤=蕊 +橫=横 +橰=槔 +橴=紫 +檁=檩 +檇=槜 +檉=柽 +檔=档 +檜=桧 +檝=楫 +檟=槚 +檢=检 +檣=樯 +檤=道 +檭=银 +檮=梼 +檯=台 +檳=槟 +檸=柠 +檻=槛 +檾=苘 +櫂=棹 +櫃=柜 +櫇=颇 +櫈=凳 +櫓=橹 +櫕=替 +櫚=榈 +櫛=栉 +櫝=椟 +櫞=橼 +櫟=栎 +櫥=橱 +櫧=槠 +櫨=栌 +櫪=枥 +櫫=橥 +櫬=榇 +櫱=蘖 +櫳=栊 +櫸=榉 +櫺=棂 +櫻=樱 +欄=栏 +欅=榉 +權=权 +欎=郁 +欏=椤 +欑=攒 +欒=栾 +欖=榄 +欞=棂 +欥=吹 +欵=款 +欽=钦 +歎=叹 +歐=欧 +歔=墟 +歗=啸 +歘=欻 +歛=敛 +歟=欤 +歡=欢 +歭=持 +歮=址 +歯=齿 +歲=岁 +歳=岁 +歴=历 +歷=历 +歸=归 +歿=殁 +殀=夭 +殗=淹 +殘=残 +殙=婚 +殞=殒 +殣=谨 +殤=殇 +殨=㱮 +殫=殚 +殭=僵 +殮=殓 +殯=殡 +殲=歼 +殸=声 +殺=杀 +殻=壳 +殼=壳 +毀=毁 +毃=敲 +毆=殴 +毇=毁 +毉=医 +毐=毒 +毘=毗 +毝=毛 +毣=笔 +毧=绒 +毿=毵 +氂=牦 +氈=毡 +氊=毡 +氌=氇 +氣=气 +氫=氢 +氬=氩 +氳=氲 +氷=冰 +氹=凼 +氾=犯 +氿=酒 +汃=趴 +汈=叼 +汋=勺 +汍=丸 +汎=帆 +汏=大 +汒=茫 +汘=纤 +汙=污 +汚=污 +汢=土 +汥=枝 +汮=均 +汸=坊 +決=决 +汻=许 +沋=优 +沍=冱 +沑=扭 +沒=没 +沕=吻 +沖=冲 +沬=抹 +沰=拓 +沴=珍 +沵=你 +沶=示 +況=况 +泂=炯 +泇=架 +泙=砰 +泚=此 +泝=溯 +泩=生 +泬=穴 +泹=担 +洀=舟 +洂=亦 +洃=灰 +洊=存 +洏=而 +洘=拷 +洝=按 +洣=迷 +洤=全 +洩=泄 +洭=眶 +洶=汹 +洸=光 +洺=名 +洿=夸 +浀=曲 +浄=净 +浉=狮 +浌=伐 +浐=产 +浕=尽 +浗=球 +浛=含 +浢=逗 +浧=逞 +浨=宋 +浭=更 +浵=彤 +浹=浃 +浽=馁 +涃=捆 +涇=泾 +涊=忍 +涍=哮 +涖=莅 +涜=壳 +涥=哼 +涭=授 +涳=空 +涴=碗 +涺=锯 +涻=社 +涼=凉 +涽=昏 +淉=果 +淍=碉 +淒=凄 +淓=芳 +淔=植 +淚=泪 +淛=浙 +淣=倪 +淥=渌 +淨=净 +淩=凌 +淪=沦 +淰=念 +淵=渊 +淶=涞 +淺=浅 +渀=奔 +渁=水 +渇=渴 +渉=涉 +渏=奇 +渓=溪 +渘=揉 +渙=涣 +減=减 +渞=首 +渟=停 +渢=沨 +渦=涡 +渧=蒂 +測=测 +渱=虹 +渶=英 +渻=省 +渾=浑 +湁=拾 +湈=煤 +湊=凑 +湌=餐 +湏=须 +湜=是 +湝=皆 +湞=浈 +湠=碳 +湢=福 +湣=愍 +湤=施 +湥=突 +湧=涌 +湯=汤 +湰=隆 +湴=碰 +湺=保 +湻=淳 +湼=涅 +溇=楼 +溈=沩 +溓=嫌 +溔=糕 +準=准 +溙=泰 +溚=搭 +溜=熘 +溝=沟 +溡=时 +溤=冯 +溫=温 +溮=浉 +溳=涢 +溼=湿 +溿=畔 +滃=嗡 +滄=沧 +滅=灭 +滈=高 +滌=涤 +滎=荥 +滒=哥 +滘=浩 +滙=汇 +滛=淫 +滬=沪 +滭=毕 +滮=彪 +滯=滞 +滲=渗 +滵=蜜 +滷=卤 +滸=浒 +滺=悠 +滻=浐 +滽=庸 +滾=磙 +滿=满 +漁=渔 +漅=巢 +漈=际 +漊=溇 +漑=概 +漚=沤 +漟=堂 +漢=汉 +漣=涟 +漨=逢 +漬=渍 +漲=涨 +漴=崇 +漵=溆 +漷=郭 +漸=渐 +漹=焉 +漻=廖 +漿=浆 +潁=颍 +潄=漱 +潅=罐 +潎=撇 +潐=焦 +潑=泼 +潒=橡 +潔=洁 +潗=集 +潙=沩 +潛=潜 +潠=噀 +潤=润 +潪=智 +潯=浔 +潰=溃 +潵=撒 +潶=嘿 +潷=滗 +潹=森 +潾=磷 +潿=涠 +澀=涩 +澁=涩 +澆=浇 +澇=涝 +澊=尊 +澕=华 +澗=涧 +澠=渑 +澢=挡 +澣=浣 +澤=泽 +澥=懈 +澦=滪 +澩=泶 +澭=雍 +澮=浍 +澱=淀 +澼=辟 +濁=浊 +濃=浓 +濆=愤 +濇=涩 +濐=暑 +濔=沵 +濕=湿 +濘=泞 +濜=浕 +濟=济 +濢=粹 +濤=涛 +濨=磁 +濫=漤 +濰=潍 +濱=滨 +濳=潜 +濶=阔 +濸=呛 +濺=溅 +濼=泺 +濾=滤 +瀅=滢 +瀆=渎 +瀉=泻 +瀋=渖 +瀍=缠 +瀏=浏 +瀕=濒 +瀘=泸 +瀜=融 +瀝=沥 +瀟=潇 +瀠=潆 +瀦=潴 +瀧=泷 +瀨=濑 +瀭=输 +瀲=潋 +瀻=戴 +瀾=澜 +瀿=繁 +灀=霜 +灃=沣 +灄=滠 +灋=法 +灑=洒 +灕=漓 +灘=滩 +灝=灏 +灠=漤 +灡=烂 +灢=囊 +灣=湾 +灤=滦 +灧=滟 +灨=赣 +灩=滟 +災=灾 +炡=政 +炤=照 +炪=出 +為=为 +烄=胶 +烏=乌 +烑=姚 +烖=灾 +烥=臣 +烮=列 +烱=炯 +烴=烃 +烸=梅 +烺=浪 +烾=炎 +焄=君 +焒=吕 +無=无 +煆=煅 +煇=辉 +煉=炼 +煑=煮 +煒=炜 +煕=熙 +煖=暖 +煗=暖 +煙=烟 +煠=炸 +煢=茕 +煥=焕 +煩=烦 +煬=炀 +煭=裂 +煱=锅 +煷=亮 +熅=煴 +熈=熙 +熋=熊 +熒=荧 +熖=焰 +熗=炝 +熱=热 +熲=颎 +熷=增 +熾=炽 +燁=烨 +燄=焰 +燈=灯 +燉=炖 +燐=磷 +燒=烧 +燙=烫 +燜=焖 +營=营 +燦=灿 +燭=烛 +燳=照 +燴=烩 +燻=熏 +燼=烬 +燾=焘 +燿=耀 +爊=熬 +爍=烁 +爐=炉 +爕=燮 +爗=烨 +爘=餐 +爛=烂 +爭=争 +爲=为 +爺=爷 +爾=尔 +爿=丬 +牀=床 +牆=墙 +牋=笺 +牎=窗 +牐=闸 +牓=榜 +牕=窗 +牘=牍 +牠=它 +牴=抵 +牸=字 +牽=牵 +犂=犁 +犇=牛 +犖=荦 +犛=牦 +犠=牺 +犢=犊 +犧=牺 +犼=吼 +狀=状 +狆=中 +狌=牲 +狔=泥 +狚=胆 +狣=挑 +狥=徇 +狪=洞 +狹=狭 +狽=狈 +猂=悍 +猙=狰 +猦=枫 +猨=猿 +猵=遍 +猶=犹 +猻=狲 +獁=犸 +獃=呆 +獄=狱 +獅=狮 +獊=沧 +獌=馒 +獎=奖 +獏=貘 +獓=敖 +獘=毙 +獙=敝 +獞=撞 +獣=兽 +獧=狷 +獨=独 +獪=狯 +獫=猃 +獮=狝 +獰=狞 +獲=获 +獵=猎 +獷=犷 +獸=兽 +獺=獭 +獻=献 +獼=猕 +玀=猡 +玅=妙 +玆=玄 +玙=与 +玞=夫 +玨=珏 +玪=玲 +珆=台 +珒=津 +珓=较 +珪=圭 +珮=佩 +珵=呈 +珶=梯 +珻=悔 +現=现 +琇=锈 +琎=进 +琓=玩 +琖=盏 +琱=雕 +琹=琴 +琺=珐 +琿=珲 +瑆=惺 +瑇=玳 +瑉=珉 +瑋=玮 +瑏=穿 +瑒=玚 +瑝=皇 +瑠=琉 +瑣=琐 +瑤=瑶 +瑩=莹 +瑪=玛 +瑫=滔 +瑯=琅 +瑱=填 +瑲=玱 +瑺=常 +璄=境 +璉=琏 +璔=憎 +璡=琎 +璢=琉 +璣=玑 +璦=瑷 +璪=澡 +璫=珰 +環=环 +璵=玙 +璸=瑸 +璽=玺 +璿=璇 +瓈=璃 +瓊=琼 +瓌=瑰 +瓏=珑 +瓔=璎 +瓚=瓒 +瓟=爬 +甁=瓶 +甆=瓷 +甌=瓯 +甎=砖 +甕=瓮 +甖=罂 +甛=甜 +甞=尝 +產=产 +産=产 +甦=苏 +甯=宁 +甴=由 +甶=田 +甽=圳 +畆=亩 +畊=耕 +畝=亩 +畡=垓 +畢=毕 +畣=答 +畧=略 +畨=番 +畩=依 +畫=画 +畮=亩 +異=异 +畱=留 +畵=画 +當=当 +畼=场 +疇=畴 +疉=叠 +疊=叠 +疍=蛋 +疎=疏 +疒=病 +疧=底 +疺=乏 +疿=痱 +痌=恫 +痐=蛔 +痙=痉 +痠=酸 +痩=瘦 +痲=痳 +痺=痹 +痽=准 +痾=疴 +瘂=痖 +瘉=愈 +瘋=疯 +瘍=疡 +瘓=痪 +瘖=喑 +瘚=撅 +瘞=瘗 +瘡=疮 +瘧=疟 +瘮=瘆 +瘲=疭 +瘺=瘘 +瘻=瘘 +療=疗 +癅=瘤 +癆=痨 +癇=痫 +癉=瘅 +癒=愈 +癘=疠 +癙=鼠 +癟=瘪 +癡=痴 +癢=痒 +癤=疖 +癥=症 +癧=疬 +癩=癞 +癬=癣 +癭=瘿 +癮=瘾 +癰=痈 +癱=瘫 +癲=癫 +癷=罕 +發=发 +皁=皂 +皃=貌 +皐=皋 +皒=俄 +皗=绸 +皚=皑 +皛=白 +皜=皓 +皰=疱 +皷=鼓 +皸=皲 +皺=皱 +盁=盈 +盃=杯 +盇=盍 +盉=盒 +盋=钵 +盌=碗 +盜=盗 +盞=盏 +盡=尽 +監=监 +盤=盘 +盧=卢 +盪=荡 +盬=监 +眀=明 +眎=视 +眏=映 +眡=视 +眥=眦 +眪=丙 +眫=胖 +眾=众 +睋=蛾 +睏=困 +睓=腆 +睜=睁 +睞=睐 +睠=眷 +睪=睾 +睲=腥 +瞇=眯 +瞐=晶 +瞖=翳 +瞘=眍 +瞜=䁖 +瞞=瞒 +瞮=澈 +瞶=瞆 +瞼=睑 +矁=瞅 +矓=眬 +矙=瞰 +矚=瞩 +矯=矫 +矴=碇 +矼=缸 +矽=硅 +砡=玉 +砲=炮 +砽=拥 +硏=研 +硜=硁 +硤=硖 +硧=桶 +硨=砗 +硯=砚 +碁=棋 +碕=埼 +碙=刚 +碩=硕 +碪=砧 +碭=砀 +碸=砜 +確=确 +碼=码 +磆=猾 +磍=瞎 +磎=溪 +磑=硙 +磚=砖 +磟=碌 +磠=硵 +磣=碜 +磧=碛 +磯=矶 +磽=硗 +礃=掌 +礄=硚 +礆=硷 +礎=础 +礙=碍 +礦=矿 +礪=砺 +礫=砾 +礬=矾 +礮=炮 +礱=砻 +礶=罐 +祐=右 +祕=秘 +祘=算 +祿=禄 +禍=祸 +禎=祯 +禕=祎 +禞=篙 +禡=祃 +禢=塌 +禦=御 +禩=祀 +禪=禅 +禮=礼 +禰=祢 +禱=祷 +禸=内 +禿=秃 +秇=执 +秈=籼 +秊=年 +秌=秋 +秏=耗 +秐=耘 +秔=粳 +秖=祇 +秝=禾 +秡=泼 +秥=拈 +秱=桐 +稅=税 +稈=秆 +稉=粳 +稜=棱 +稟=禀 +稤=掠 +稬=糯 +稭=秸 +種=种 +稱=称 +稲=蹈 +稵=滋 +稺=稚 +稾=稿 +穀=谷 +穅=糠 +穉=稚 +穌=稣 +積=积 +穎=颖 +穏=稳 +穠=秾 +穡=穑 +穢=秽 +穤=糯 +穨=颓 +穩=稳 +穫=获 +穭=稆 +穽=阱 +窂=牢 +窉=柄 +窓=窗 +窩=窝 +窪=洼 +窮=穷 +窯=窑 +窰=窑 +窵=窎 +窶=窭 +窷=聊 +窺=窥 +窻=窗 +窾=款 +竄=窜 +竅=窍 +竇=窦 +竈=灶 +竊=窃 +竒=奇 +竚=伫 +竝=并 +竢=俟 +竪=竖 +竲=蹭 +競=竞 +竾=篪 +笀=芒 +笁=工 +笗=冬 +笣=包 +笩=代 +笵=范 +筃=茵 +筆=笔 +筍=笋 +筎=茹 +筗=忠 +筞=策 +筧=笕 +筩=筒 +筭=算 +筯=箸 +筴=䇲 +筺=筐 +箁=菩 +箇=个 +箋=笺 +箌=倒 +箎=篪 +箏=筝 +箒=帚 +箘=菌 +箚=札 +箛=孤 +箠=垂 +箥=玻 +箶=胡 +箹=约 +節=节 +範=范 +築=筑 +篋=箧 +篔=筼 +篛=箬 +篜=蒸 +篠=筱 +篤=笃 +篨=除 +篩=筛 +篲=彗 +篳=筚 +簀=箦 +簃=移 +簆=筘 +簍=篓 +簑=蓑 +簒=篡 +簘=萧 +簞=箪 +簡=简 +簣=篑 +簫=箫 +簮=簪 +簰=牌 +簷=檐 +簹=筜 +簽=签 +簾=帘 +籃=篮 +籌=筹 +籐=藤 +籙=箓 +籛=篯 +籜=箨 +籟=籁 +籠=笼 +籤=签 +籨=奁 +籩=笾 +籪=簖 +籬=篱 +籮=箩 +籲=吁 +籿=村 +粀=杖 +粃=秕 +粄=饭 +粅=物 +粦=磷 +粧=妆 +粩=姥 +粵=粤 +糉=粽 +糊=煳 +糘=稼 +糝=糁 +糞=粪 +糧=粮 +糱=糵 +糲=粝 +糴=籴 +糵=孽 +糶=粜 +糸=纟 +糹=纟 +糼=攻 +糾=纠 +紀=纪 +紁=叉 +紂=纣 +紃=驯 +約=约 +紅=红 +紆=纡 +紇=纥 +紈=纨 +紉=纫 +紋=纹 +納=纳 +紐=纽 +紓=纾 +純=纯 +紕=纰 +紖=纼 +紗=纱 +紘=纮 +紙=纸 +級=级 +紛=纷 +紜=纭 +紝=纴 +紡=纺 +紥=扎 +紬=䌷 +紮=扎 +細=细 +紱=绂 +紲=绁 +紳=绅 +紵=纻 +紹=绍 +紺=绀 +紼=绋 +紿=绐 +絀=绌 +終=终 +絃=弦 +組=组 +絅=䌹 +絆=绊 +絎=绗 +絏=绁 +結=结 +絒=酬 +絕=绝 +絛=绦 +絝=绔 +絞=绞 +絡=络 +絢=绚 +給=给 +絨=绒 +絪=姻 +絰=绖 +統=统 +絲=丝 +絳=绛 +絶=绝 +絸=茧 +絹=绢 +綁=绑 +綃=绡 +綆=绠 +綈=绨 +綉=绣 +綌=绤 +綏=绥 +綑=捆 +經=经 +綜=综 +綞=缍 +綠=绿 +綢=绸 +綣=绻 +綫=线 +綬=绶 +維=维 +綯=绹 +綰=绾 +綱=纲 +網=网 +綳=绷 +綴=缀 +綵=彩 +綸=纶 +綹=绺 +綺=绮 +綻=绽 +綽=绰 +綾=绫 +綿=绵 +緄=绲 +緇=缁 +緊=紧 +緋=绯 +緐=繁 +緑=绿 +緒=绪 +緓=绬 +緔=绱 +緗=缃 +緘=缄 +緙=缂 +線=线 +緜=绵 +緝=缉 +緞=缎 +締=缔 +緡=缗 +緣=缘 +緥=褓 +緦=缌 +編=编 +緩=缓 +緬=缅 +緯=纬 +緱=缑 +緲=缈 +練=练 +緶=缏 +緹=缇 +緻=致 +縂=总 +縈=萦 +縉=缙 +縊=缢 +縋=缒 +縐=绉 +縑=缣 +縕=缊 +縗=缞 +縚=绦 +縛=缚 +縝=缜 +縞=缟 +縟=缛 +縣=县 +縧=绦 +縨=幌 +縫=缝 +縭=缡 +縮=缩 +縱=纵 +縲=缧 +縳=䌸 +縵=缦 +縶=絷 +縷=缕 +縹=缥 +總=总 +績=绩 +繃=绷 +繅=缫 +繆=缪 +繈=襁 +繒=缯 +織=织 +繕=缮 +繖=伞 +繚=缭 +繞=绕 +繡=绣 +繢=缋 +繦=襁 +繩=绳 +繪=绘 +繫=系 +繭=茧 +繮=缰 +繯=缳 +繰=缲 +繳=缴 +繸=䍁 +繹=绎 +繺=煞 +繼=继 +繽=缤 +繾=缱 +纇=颣 +纈=缬 +纊=纩 +續=续 +纍=累 +纏=缠 +纓=缨 +纖=纤 +纘=缵 +纜=缆 +缞=衰 +缽=钵 +缾=瓶 +罁=缸 +罇=樽 +罈=坛 +罌=罂 +罎=坛 +罏=垆 +罓=冈 +罞=茅 +罣=挂 +罰=罚 +罵=骂 +罷=罢 +罸=罚 +羅=罗 +羆=罴 +羈=羁 +羋=芈 +羕=漾 +羗=羌 +羙=美 +羢=绒 +羣=群 +羥=羟 +羨=羡 +義=义 +羱=源 +羴=膻 +羶=膻 +翄=翅 +習=习 +翫=玩 +翬=翚 +翶=翱 +翹=翘 +翺=翱 +翽=翙 +耈=耇 +耉=耇 +耡=锄 +耬=耧 +耮=耢 +聅=联 +聖=圣 +聙=睛 +聞=闻 +聯=联 +聰=聪 +聲=声 +聳=耸 +聵=聩 +聶=聂 +職=职 +聹=聍 +聼=听 +聽=听 +聾=聋 +肅=肃 +肎=肯 +肐=胳 +肔=池 +肗=汝 +肧=胚 +肬=疣 +肻=肯 +胒=尼 +胕=附 +胷=胸 +脃=脆 +脅=胁 +脇=胁 +脈=脉 +脊=嵴 +脕=晚 +脗=吻 +脛=胫 +脣=唇 +脫=脱 +脹=胀 +腁=胼 +腄=捶 +腎=肾 +腖=胨 +腡=脶 +腦=脑 +腫=肿 +腳=脚 +腷=逼 +腸=肠 +膁=肷 +膃=腽 +膆=嗉 +膓=肠 +膕=腘 +膚=肤 +膠=胶 +膤=雪 +膩=腻 +膽=胆 +膾=脍 +膿=脓 +臈=腊 +臉=脸 +臋=臀 +臍=脐 +臏=膑 +臒=癯 +臕=膘 +臘=腊 +臙=胭 +臚=胪 +臝=裸 +臟=脏 +臠=脔 +臢=臜 +臥=卧 +臨=临 +臯=皋 +臱=旁 +臸=至 +臺=台 +舃=舄 +與=与 +興=兴 +舉=举 +舊=旧 +舎=舍 +舖=铺 +舗=铺 +舘=馆 +舙=舌 +舝=辖 +舩=船 +艙=舱 +艢=樯 +艣=橹 +艤=舣 +艦=舰 +艪=橹 +艫=舻 +艱=艰 +艷=艳 +艸=艹 +芉=竿 +芐=下 +芢=仁 +芣=不 +芲=花 +芶=勾 +芻=刍 +苆=切 +苉=匹 +苎=苧 +苐=第 +苝=北 +苧=苎 +苸=呼 +苺=莓 +茐=葱 +茖=各 +茘=荔 +茲=兹 +茿=筑 +荅=答 +荊=荆 +荍=收 +荕=筋 +荖=老 +荝=则 +荢=宇 +荰=杜 +荳=豆 +荴=扶 +荶=吟 +荹=步 +荿=成 +莁=巫 +莂=别 +莇=助 +莈=没 +莊=庄 +莋=做 +莏=抄 +莑=蓬 +莕=荇 +莖=茎 +莢=荚 +莣=忘 +莧=苋 +莮=男 +菈=拉 +菋=味 +菓=果 +菗=抽 +菢=抱 +菦=近 +菭=治 +菮=庚 +華=华 +菴=庵 +菸=烟 +菿=到 +萇=苌 +萉=肥 +萊=莱 +萚=择 +萠=萌 +萣=定 +萪=科 +萫=香 +萬=万 +萭=万 +萯=负 +萲=萱 +萵=莴 +萷=削 +萹=篇 +萺=冒 +萿=活 +葀=括 +葁=姜 +葃=昨 +葉=叶 +葒=荭 +葓=洪 +葠=参 +葢=盖 +葤=荮 +葦=苇 +葮=锻 +葯=药 +葰=所 +葲=泉 +葷=荤 +葾=怨 +葿=眉 +蒄=冠 +蒓=莼 +蒔=莳 +蒛=缺 +蒝=愿 +蒞=莅 +蒢=滁 +蒤=途 +蒩=租 +蒼=苍 +蓀=荪 +蓅=流 +蓆=席 +蓈=榔 +蓋=盖 +蓒=轩 +蓕=桂 +蓙=座 +蓜=配 +蓡=參 +蓢=廊 +蓧=莜 +蓮=莲 +蓯=苁 +蓱=萍 +蓳=董 +蓴=莼 +蓷=推 +蓸=曹 +蓽=荜 +蔀=部 +蔂=累 +蔆=菱 +蔉=滚 +蔋=淑 +蔍=鹿 +蔎=设 +蔔=卜 +蔕=蒂 +蔘=参 +蔞=蒌 +蔠=终 +蔣=蒋 +蔥=葱 +蔦=茑 +蔭=荫 +蔵=藏 +蕁=荨 +蕅=藕 +蕆=蒇 +蕋=蕊 +蕎=荞 +蕏=猪 +蕐=哗 +蕒=荬 +蕓=芸 +蕔=报 +蕕=莸 +蕗=露 +蕘=荛 +蕚=萼 +蕜=悲 +蕝=绝 +蕢=蒉 +蕥=雅 +蕩=荡 +蕪=芜 +蕭=萧 +蕯=萨 +蕶=零 +蕷=蓣 +蕿=萱 +薀=蕰 +薈=荟 +薊=蓟 +薌=芗 +薑=姜 +薔=蔷 +薘=荙 +薟=莶 +薦=荐 +薩=萨 +薴=苧 +薵=筹 +薺=荠 +藂=聚 +藅=罚 +藍=蓝 +藎=荩 +藙=毅 +藝=艺 +藞=磊 +藥=药 +藪=薮 +藴=蕴 +藶=苈 +藷=薯 +藹=蔼 +藺=蔺 +藼=萱 +蘀=萚 +蘂=蕊 +蘄=蕲 +蘆=芦 +蘇=苏 +蘊=蕴 +蘋=苹 +蘐=萱 +蘓=苏 +蘚=藓 +蘞=蔹 +蘢=茏 +蘤=花 +蘭=兰 +蘶=巍 +蘺=蓠 +蘿=萝 +虆=蔂 +處=处 +虖=呼 +虛=虚 +虜=虏 +號=号 +虧=亏 +虯=虬 +虵=蛇 +蚘=蛔 +蚡=鼢 +蚦=蚺 +蛕=蛔 +蛧=网 +蛫=跪 +蛬=蚕 +蛺=蛱 +蛻=蜕 +蜆=蚬 +蜋=螂 +蜖=蛔 +蜨=蝶 +蜯=蚌 +蜺=霓 +蝂=版 +蝕=蚀 +蝟=猬 +蝦=虾 +蝨=虱 +蝯=猿 +蝱=虻 +蝸=蜗 +螄=蛳 +螎=融 +螘=蚁 +螙=蠹 +螞=蚂 +螡=蚊 +螢=萤 +螻=蝼 +螾=蚓 +螿=螀 +蟁=蚊 +蟄=蛰 +蟇=蟆 +蟈=蝈 +蟎=螨 +蟣=虮 +蟬=蝉 +蟯=蛲 +蟲=虫 +蟶=蛏 +蟻=蚁 +蠅=蝇 +蠆=虿 +蠍=蝎 +蠏=蟹 +蠐=蛴 +蠑=蝾 +蠒=茧 +蠔=蚝 +蠟=蜡 +蠣=蛎 +蠧=蠹 +蠨=蟏 +蠭=蜂 +蠱=蛊 +蠶=蚕 +蠻=蛮 +衂=衄 +衆=众 +衇=脉 +衒=炫 +術=术 +衕=同 +衖=弄 +衚=胡 +衛=卫 +衝=冲 +衞=卫 +衹=只 +衺=邪 +袉=鸵 +袑=绍 +袓=祖 +袞=衮 +袵=衽 +裊=袅 +裌=夹 +裏=里 +裑=身 +補=补 +裝=装 +裠=裙 +裡=里 +裩=裈 +製=制 +褃=裉 +複=复 +褌=裈 +褘=袆 +褭=袅 +褲=裤 +褳=裢 +褸=褛 +褻=亵 +襃=褒 +襆=幞 +襇=裥 +襉=裥 +襍=杂 +襏=袯 +襖=袄 +襝=裣 +襠=裆 +襤=褴 +襪=袜 +襬=摆 +襯=衬 +襲=袭 +襴=襕 +覀=西 +覇=霸 +覈=核 +覊=羁 +見=见 +覎=觃 +規=规 +覓=觅 +覔=觅 +視=视 +覘=觇 +覡=觋 +覥=觍 +覦=觎 +覩=睹 +親=亲 +覬=觊 +覯=觏 +覰=觑 +覲=觐 +覷=觑 +覺=觉 +覽=览 +覿=觌 +觀=观 +觔=斤 +觝=抵 +觧=解 +觴=觞 +觶=觯 +觸=触 +訁=讠 +訂=订 +訃=讣 +計=计 +訊=讯 +訌=讧 +討=讨 +訐=讦 +訒=讱 +訓=训 +訕=讪 +訖=讫 +託=讬 +記=记 +訛=讹 +訝=讶 +訟=讼 +訢=䜣 +訣=诀 +訤=驳 +訥=讷 +訩=讻 +訪=访 +訬=吵 +設=设 +許=许 +訴=诉 +訶=诃 +訷=伸 +診=诊 +註=注 +証=证 +詁=诂 +詆=诋 +詎=讵 +詐=诈 +詒=诒 +詔=诏 +評=评 +詖=诐 +詗=诇 +詘=诎 +詛=诅 +詞=词 +詠=咏 +詡=诩 +詢=询 +詣=诣 +試=试 +詧=察 +詩=诗 +詫=诧 +詬=诟 +詭=诡 +詮=诠 +詰=诘 +話=话 +該=该 +詳=详 +詵=诜 +詶=州 +詻=洛 +詼=诙 +詿=诖 +誄=诔 +誅=诛 +誆=诓 +誇=夸 +誌=志 +認=认 +誑=诳 +誒=诶 +誕=诞 +誖=悖 +誘=诱 +誚=诮 +語=语 +誠=诚 +誡=诫 +誣=诬 +誤=误 +誥=诰 +誦=诵 +誧=哺 +誨=诲 +說=说 +説=说 +誯=昌 +誰=谁 +課=课 +誶=谇 +誹=诽 +誼=谊 +誾=訚 +調=调 +諂=谄 +諄=谆 +諆=棋 +談=谈 +諉=诿 +請=请 +諍=诤 +諏=诹 +諐=愆 +諑=诼 +諒=谅 +論=论 +諗=谂 +諛=谀 +諜=谍 +諝=谞 +諞=谝 +諟=堤 +諠=喧 +諡=谥 +諢=诨 +諤=谔 +諦=谛 +諧=谐 +諨=幅 +諫=谏 +諭=谕 +諮=谘 +諱=讳 +諳=谙 +諴=诚 +諶=谌 +諷=讽 +諸=诸 +諺=谚 +諼=谖 +諾=诺 +謀=谋 +謁=谒 +謂=谓 +謄=誊 +謅=诌 +謊=谎 +謌=歌 +謍=誉 +謎=谜 +謐=谧 +謔=谑 +謖=谡 +謗=谤 +謙=谦 +謚=谥 +講=讲 +謝=谢 +謠=谣 +謡=谣 +謨=谟 +謩=谟 +謫=谪 +謬=谬 +謭=谫 +謳=讴 +謸=傲 +謹=谨 +謾=谩 +謿=嘲 +譁=哗 +譆=嘻 +證=证 +譌=讹 +譎=谲 +譏=讥 +譒=播 +譔=撰 +譖=谮 +識=识 +譙=谯 +譚=谭 +譜=谱 +譟=噪 +譫=谵 +譭=毁 +譯=译 +議=议 +譴=谴 +護=护 +譸=诪 +譽=誉 +譾=谫 +讀=读 +讁=谪 +讅=审 +變=变 +讋=詟 +讌=宴 +讎=雠 +讐=雠 +讒=谗 +讓=让 +讕=谰 +讖=谶 +讚=赞 +讜=谠 +讞=谳 +谉=审 +谘=咨 +豈=岂 +豎=竖 +豐=丰 +豓=艳 +豔=艳 +豞=狗 +豩=逐 +豬=猪 +豶=豮 +貍=狸 +貓=猫 +貛=獾 +貝=贝 +貞=贞 +貟=贠 +負=负 +財=财 +貢=贡 +貧=贫 +貨=货 +販=贩 +貪=贪 +貫=贯 +責=责 +貯=贮 +貰=贳 +貲=赀 +貳=贰 +貴=贵 +貶=贬 +買=买 +貸=贷 +貺=贶 +費=费 +貼=贴 +貽=贻 +貿=贸 +賀=贺 +賁=贲 +賂=赂 +賃=赁 +賄=贿 +賅=赅 +資=资 +賈=贾 +賉=恤 +賊=贼 +賍=赃 +賑=赈 +賒=赊 +賓=宾 +賕=赇 +賗=串 +賙=赒 +賚=赉 +賛=赞 +賜=赐 +賞=赏 +賠=赔 +賡=赓 +賢=贤 +賣=卖 +賤=贱 +賦=赋 +賧=赕 +質=质 +賫=赍 +賬=账 +賭=赌 +賴=赖 +賵=赗 +賷=赍 +賸=剩 +賺=赚 +賻=赙 +購=购 +賽=赛 +賾=赜 +贄=贽 +贅=赘 +贇=赟 +贈=赠 +贊=赞 +贋=赝 +贍=赡 +贏=赢 +贐=赆 +贑=贛 +贓=赃 +贔=赑 +贖=赎 +贗=赝 +贛=赣 +贜=赃 +赑=贝 +赬=赪 +赽=块 +趂=趁 +趉=走 +趐=翅 +趕=赶 +趙=赵 +趚=速 +趦=趑 +趧=题 +趨=趋 +趫=超 +趬=翘 +趭=瞧 +趲=趱 +跕=沾 +跡=迹 +跥=跺 +跩=拽 +跴=踩 +跿=陡 +踁=胫 +踐=践 +踡=蜷 +踭=争 +踰=逾 +踴=踊 +蹆=腿 +蹌=跄 +蹍=展 +蹏=蹄 +蹓=溜 +蹔=暂 +蹕=跸 +蹜=宿 +蹟=迹 +蹠=跖 +蹣=蹒 +蹤=踪 +蹧=糟 +蹵=蹴 +蹺=跷 +躂=跶 +躉=趸 +躊=踌 +躋=跻 +躍=跃 +躑=踯 +躒=跞 +躓=踬 +躕=蹰 +躚=跹 +躡=蹑 +躥=蹿 +躦=躜 +躪=躏 +躭=耽 +躱=躲 +躳=躬 +躶=裸 +軀=躯 +軆=体 +車=车 +軋=轧 +軌=轨 +軍=军 +軑=轪 +軒=轩 +軔=轫 +軛=轭 +軟=软 +軤=轷 +軫=轸 +軲=轱 +軸=轴 +軹=轵 +軺=轺 +軻=轲 +軼=轶 +軾=轼 +較=较 +輅=辂 +輇=辁 +輈=辀 +載=载 +輊=轾 +輒=辄 +輓=挽 +輔=辅 +輕=轻 +輙=辄 +輛=辆 +輜=辎 +輝=辉 +輞=辋 +輟=辍 +輥=辊 +輦=辇 +輩=辈 +輪=轮 +輬=辌 +輭=软 +輯=辑 +輳=辏 +輸=输 +輻=辐 +輾=辗 +輿=舆 +轀=辒 +轂=毂 +轄=辖 +轅=辕 +轆=辘 +轉=转 +轍=辙 +轎=轿 +轔=辚 +轟=轰 +轡=辔 +轢=轹 +轤=轳 +辠=罪 +辢=辣 +辤=辞 +辦=办 +辧=辨 +辭=辞 +辮=辫 +辯=辩 +農=农 +辳=农 +辴=冁 +迀=干 +迆=迤 +迉=尸 +迊=迎 +迋=逛 +迖=达 +迡=呢 +迣=世 +迯=逃 +迴=回 +迻=移 +逈=迥 +逕=迳 +這=这 +連=连 +逥=回 +逩=奔 +逬=迸 +週=周 +進=进 +逷=逖 +逺=远 +遉=侦 +遊=游 +運=运 +過=过 +達=达 +違=违 +遖=南 +遙=遥 +遜=逊 +遝=沓 +遞=递 +遠=远 +遡=溯 +遦=惯 +適=适 +遯=遁 +遲=迟 +遶=绕 +遷=迁 +選=选 +遺=遗 +遼=辽 +邁=迈 +還=还 +邇=迩 +邊=边 +邏=逻 +邐=逦 +郉=邢 +郟=郏 +郤=郄 +郰=邓 +郵=邮 +鄆=郓 +鄉=乡 +鄒=邹 +鄔=邬 +鄖=郧 +鄧=邓 +鄭=郑 +鄰=邻 +鄲=郸 +鄴=邺 +鄶=郐 +鄺=邝 +酇=酂 +酈=郦 +酔=醉 +酧=酬 +酨=栽 +醃=腌 +醕=醇 +醖=酝 +醜=丑 +醞=酝 +醣=糖 +醫=医 +醬=酱 +醯=酰 +醱=酦 +醻=酬 +醼=宴 +醿=醾 +釀=酿 +釁=衅 +釃=酾 +釅=酽 +釋=释 +釐=厘 +釒=钅 +釓=钆 +釔=钇 +釕=钌 +釗=钊 +釘=钉 +釙=钋 +針=针 +釡=斧 +釢=乃 +釣=钓 +釤=钐 +釦=扣 +釧=钏 +釩=钒 +釬=焊 +釭=肛 +釵=钗 +釷=钍 +釹=钕 +釺=钎 +釿=斤 +鈀=钯 +鈁=钫 +鈃=钘 +鈄=钭 +鈅=钥 +鈆=铅 +鈈=钚 +鈉=钠 +鈍=钝 +鈎=钩 +鈐=钤 +鈑=钣 +鈒=钑 +鈔=钞 +鈕=钮 +鈞=钧 +鈣=钙 +鈥=钬 +鈦=钛 +鈧=钪 +鈫=纹 +鈮=铌 +鈰=铈 +鈳=钶 +鈴=铃 +鈷=钴 +鈸=钹 +鈹=铍 +鈺=钰 +鈽=钸 +鈾=铀 +鈿=钿 +鉀=钾 +鉄=铁 +鉅=钜 +鉆=钻 +鉈=铊 +鉉=铉 +鉋=铇 +鉍=铋 +鉏=锄 +鉑=铂 +鉕=钷 +鉗=钳 +鉚=铆 +鉛=铅 +鉞=钺 +鉢=钵 +鉤=钩 +鉦=钲 +鉬=钼 +鉭=钽 +鉮=神 +鉲=卡 +鉶=铏 +鉸=铰 +鉺=铒 +鉻=铬 +鉽=式 +鉿=铪 +銀=银 +銃=铳 +銅=铜 +銍=铚 +銑=铣 +銓=铨 +銕=铁 +銖=铢 +銘=铭 +銚=铫 +銛=铦 +銜=衔 +銠=铑 +銣=铷 +銥=铱 +銦=铟 +銨=铵 +銩=铥 +銪=铕 +銫=铯 +銬=铐 +銰=艾 +銱=铞 +銲=焊 +銳=锐 +銷=销 +銹=锈 +銻=锑 +銼=锉 +鋁=铝 +鋂=镅 +鋃=锒 +鋅=锌 +鋇=钡 +鋌=铤 +鋏=铗 +鋒=锋 +鋖=妥 +鋙=铻 +鋝=锊 +鋟=锓 +鋣=铘 +鋤=锄 +鋥=锃 +鋦=锔 +鋨=锇 +鋩=铓 +鋪=铺 +鋭=锐 +鋮=铖 +鋯=锆 +鋰=锂 +鋱=铽 +鋶=锍 +鋸=锯 +鋼=钢 +鋽=掉 +錁=锞 +錄=录 +錆=锖 +錇=锫 +錈=锩 +錏=铔 +錐=锥 +錒=锕 +錕=锟 +錘=锤 +錙=锱 +錚=铮 +錛=锛 +錞=醇 +錟=锬 +錠=锭 +錡=锜 +錢=钱 +錦=锦 +錨=锚 +錩=锠 +錫=锡 +錮=锢 +錯=错 +録=录 +錳=锰 +錶=表 +錸=铼 +錼=镎 +鍀=锝 +鍁=锨 +鍃=锪 +鍆=钔 +鍇=锴 +鍈=锳 +鍋=锅 +鍍=镀 +鍔=锷 +鍘=铡 +鍚=钖 +鍛=锻 +鍠=锽 +鍤=锸 +鍥=锲 +鍩=锘 +鍫=锹 +鍬=锹 +鍰=锾 +鍳=鉴 +鍴=端 +鍵=键 +鍶=锶 +鍺=锗 +鍼=针 +鍾=锺 +鎂=镁 +鎄=锿 +鎅=界 +鎇=镅 +鎊=镑 +鎌=镰 +鎍=索 +鎔=镕 +鎖=锁 +鎗=枪 +鎘=镉 +鎚=锤 +鎛=镈 +鎟=桑 +鎡=镃 +鎢=钨 +鎣=蓥 +鎦=镏 +鎧=铠 +鎩=铩 +鎪=锼 +鎬=镐 +鎮=镇 +鎰=镒 +鎲=镋 +鎳=镍 +鎵=镓 +鎸=镌 +鎹=送 +鎻=锁 +鎿=镎 +鏃=镞 +鏇=镟 +鏈=链 +鏌=镆 +鏍=镙 +鏐=镠 +鏑=镝 +鏗=铿 +鏘=锵 +鏜=镗 +鏝=镘 +鏞=镛 +鏟=铲 +鏡=镜 +鏢=镖 +鏤=镂 +鏨=錾 +鏰=镚 +鏵=铧 +鏷=镤 +鏹=镪 +鏽=锈 +鐃=铙 +鐋=铴 +鐐=镣 +鐒=铹 +鐓=镦 +鐔=镡 +鐘=钟 +鐙=镫 +鐝=镢 +鐠=镨 +鐦=锎 +鐧=锏 +鐨=镄 +鐫=镌 +鐮=镰 +鐰=糙 +鐲=镯 +鐳=镭 +鐴=避 +鐵=铁 +鐶=镮 +鐸=铎 +鐺=铛 +鐿=镱 +鑀=锿 +鑄=铸 +鑊=镬 +鑌=镔 +鑑=鉴 +鑒=鉴 +鑔=镲 +鑕=锧 +鑚=钻 +鑛=矿 +鑞=镴 +鑠=铄 +鑣=镳 +鑤=刨 +鑥=镥 +鑭=镧 +鑰=钥 +鑱=镵 +鑲=镶 +鑵=罐 +鑷=镊 +鑹=镩 +鑼=锣 +鑽=钻 +鑾=銮 +鑿=凿 +钁=镢 +钂=镋 +钜=鉅 +铇=刨 +镚=崩 +镟=碹 +镵=馋 +長=长 +門=门 +閁=闪 +閂=闩 +閃=闪 +閄=门 +閆=闫 +閈=闬 +閉=闭 +開=开 +閌=闶 +閎=闳 +閏=闰 +閑=闲 +閒=闲 +間=间 +閔=闵 +閘=闸 +閙=闹 +閞=开 +閡=阂 +関=关 +閣=阁 +閥=阀 +閨=闺 +閩=闽 +閫=阃 +閬=阆 +閭=闾 +閱=阅 +閲=阅 +閶=阊 +閷=刹 +閹=阉 +閻=阎 +閼=阏 +閽=阍 +閾=阈 +閿=阌 +闃=阒 +闆=板 +闈=闱 +闊=阔 +闋=阕 +闌=阑 +闍=阇 +闐=阗 +闒=阘 +闓=闿 +闔=阖 +闕=阙 +闖=闯 +闚=窥 +關=关 +闝=嫖 +闞=阚 +闠=阓 +闡=阐 +闢=辟 +闤=阛 +闥=闼 +阣=吃 +阨=厄 +阪=坂 +阬=坑 +阯=址 +陏=隋 +陗=峭 +陘=陉 +陝=陕 +陣=阵 +陥=馅 +陰=阴 +陳=陈 +陸=陆 +険=险 +陻=堙 +陼=堵 +陽=阳 +隂=阴 +隄=堤 +隉=陧 +隊=队 +階=阶 +隕=陨 +隖=坞 +際=际 +隟=隙 +隢=饶 +隣=邻 +隨=随 +險=险 +隱=隐 +隴=陇 +隷=隶 +隸=隶 +隻=只 +雋=隽 +雖=虽 +雙=双 +雛=雏 +雜=杂 +雝=雍 +雞=鸡 +離=离 +難=难 +雲=云 +電=电 +霚=雾 +霛=灵 +霡=脉 +霢=霡 +霤=溜 +霧=雾 +霩=廓 +霽=霁 +靂=雳 +靃=霍 +靄=霭 +靆=叇 +靈=灵 +靉=叆 +靚=靓 +靜=静 +靣=面 +靦=腼 +靨=靥 +靭=韧 +靱=韧 +鞀=鼗 +鞉=鼗 +鞌=鞍 +鞏=巩 +鞝=绱 +鞵=鞋 +鞽=鞒 +韁=缰 +韃=鞑 +韈=袜 +韉=鞯 +韋=韦 +韌=韧 +韍=韨 +韓=韩 +韙=韪 +韜=韬 +韝=鞴 +韞=韫 +韤=袜 +韮=韭 +韻=韵 +響=响 +頁=页 +頂=顶 +頃=顷 +項=项 +順=顺 +頇=顸 +須=须 +頊=顼 +頌=颂 +頎=颀 +頏=颃 +預=预 +頑=顽 +頒=颁 +頓=顿 +頗=颇 +領=领 +頙=项 +頜=颌 +頟=额 +頡=颉 +頤=颐 +頦=颏 +頭=头 +頮=颒 +頰=颊 +頲=颋 +頴=颕 +頷=颔 +頸=颈 +頹=颓 +頻=频 +頼=赖 +頽=颓 +顆=颗 +顋=腮 +題=题 +額=额 +顎=颚 +顏=颜 +顒=颙 +顓=颛 +顔=颜 +願=愿 +顙=颡 +顛=颠 +類=类 +顢=颟 +顥=颢 +顦=憔 +顧=顾 +顫=颤 +顬=颥 +顯=显 +顰=颦 +顱=颅 +顳=颞 +顴=颧 +颕=颖 +風=风 +颩=风 +颭=飐 +颮=飑 +颯=飒 +颱=台 +颳=刮 +颶=飓 +颸=飔 +颺=飏 +颻=飖 +颼=飕 +飀=飗 +飃=飘 +飄=飘 +飆=飚 +飈=飚 +飚=飙 +飛=飞 +飜=翻 +飠=饣 +飢=饥 +飣=饤 +飤=饲 +飥=饦 +飩=饨 +飪=饪 +飫=饫 +飭=饬 +飮=饮 +飯=饭 +飱=飧 +飲=饮 +飴=饴 +飺=糍 +飼=饲 +飽=饱 +飾=饰 +飿=饳 +餀=哎 +餁=饪 +餃=饺 +餄=饸 +餅=饼 +餈=糍 +餉=饷 +養=养 +餌=饵 +餎=饹 +餏=饻 +餑=饽 +餒=馁 +餓=饿 +餕=馂 +餖=饾 +餘=馀 +餚=肴 +餛=馄 +餜=馃 +餞=饯 +餡=馅 +餧=喂 +館=馆 +餬=糊 +餱=糇 +餳=饧 +餵=喂 +餶=馉 +餷=馇 +餹=糖 +餺=馎 +餻=糕 +餼=饩 +餽=馈 +餾=馏 +餿=馊 +饁=馌 +饃=馍 +饅=馒 +饈=馐 +饉=馑 +饊=馓 +饋=馈 +饌=馔 +饍=膳 +饑=饥 +饒=饶 +饗=飨 +饜=餍 +饝=馍 +饞=馋 +饟=饷 +饢=馕 +饤=盯 +馀=余 +馬=马 +馭=驭 +馮=冯 +馱=驮 +馳=驰 +馴=驯 +馶=驶 +馹=驲 +馿=驴 +駁=驳 +駆=驱 +駈=驱 +駐=驻 +駑=驽 +駒=驹 +駔=驵 +駕=驾 +駘=骀 +駙=驸 +駛=驶 +駝=驼 +駞=驼 +駟=驷 +駡=骂 +駢=骈 +駦=藤 +駭=骇 +駮=驳 +駰=骃 +駱=骆 +駸=骎 +駿=骏 +騁=骋 +騂=骍 +騅=骓 +騌=骔 +騍=骒 +騎=骑 +騏=骐 +騐=验 +騒=骚 +験=验 +騖=骛 +騗=骗 +騙=骗 +騣=鬃 +騤=骙 +騧=䯄 +騫=骞 +騭=骘 +騮=骝 +騰=腾 +騶=驺 +騷=骚 +騸=骟 +騾=骡 +驀=蓦 +驁=骜 +驂=骖 +驃=骠 +驄=骢 +驅=驱 +驊=骅 +驌=骕 +驍=骁 +驏=骣 +驕=骄 +驗=验 +驘=骡 +驚=惊 +驛=驿 +驟=骤 +驢=驴 +驤=骧 +驥=骥 +驦=骦 +驪=骊 +驫=骉 +骉=马 +骔=鬃 +骯=肮 +骼=胳 +骾=鲠 +髈=膀 +髊=搓 +髏=髅 +髒=脏 +體=体 +髕=髌 +髖=髋 +髠=髡 +髣=仿 +髥=髯 +髩=鬓 +髮=发 +髴=佛 +鬀=剃 +鬁=疬 +鬂=鬓 +鬆=松 +鬉=鬃 +鬍=胡 +鬚=须 +鬢=鬓 +鬥=斗 +鬦=斗 +鬧=闹 +鬨=闹 +鬩=阋 +鬪=斗 +鬭=斗 +鬮=阄 +鬰=郁 +鬱=郁 +鬴=釜 +魊=蜮 +魎=魉 +魘=魇 +魚=鱼 +魛=鱽 +魢=鱾 +魨=鲀 +魯=鲁 +魴=鲂 +魷=鱿 +魺=鲄 +鮁=鲅 +鮃=鲆 +鮊=鲌 +鮋=鲉 +鮍=鲏 +鮎=鲇 +鮐=鲐 +鮑=鲍 +鮒=鲋 +鮓=鲊 +鮚=鲒 +鮜=鲘 +鮝=鲞 +鮞=鲕 +鮟=安 +鮦=鲖 +鮪=鲔 +鮫=鲛 +鮭=鲑 +鮮=鲜 +鮳=鲓 +鮶=鲪 +鮺=鲝 +鯀=鲧 +鯁=鲠 +鯇=鲩 +鯉=鲤 +鯊=鲨 +鯒=鲬 +鯔=鲻 +鯕=鲯 +鯖=鲭 +鯗=鲞 +鯛=鲷 +鯝=鲴 +鯡=鲱 +鯢=鲵 +鯤=鲲 +鯧=鲳 +鯨=鲸 +鯪=鲮 +鯫=鲰 +鯰=鲶 +鯴=鲺 +鯵=鲹 +鯷=鳀 +鯽=鲫 +鯿=鳊 +鰁=鳈 +鰂=鲗 +鰃=鳂 +鰈=鲽 +鰉=鳇 +鰌=鳅 +鰍=鳅 +鰏=鲾 +鰐=鳄 +鰒=鳆 +鰓=鳃 +鰛=鳁 +鰜=鳒 +鰟=鳑 +鰠=鳋 +鰣=鲥 +鰥=鳏 +鰨=鳎 +鰩=鳐 +鰭=鳍 +鰮=鳁 +鰱=鲢 +鰲=鳌 +鰳=鳓 +鰵=鳘 +鰷=鲦 +鰹=鲣 +鰺=鲹 +鰻=鳗 +鰼=鳛 +鰾=鳔 +鱂=鳉 +鱅=鳙 +鱈=鳕 +鱉=鳖 +鱏=鲟 +鱒=鳟 +鱓=鳝 +鱔=鳝 +鱖=鳜 +鱗=鳞 +鱘=鲟 +鱝=鲼 +鱟=鲎 +鱠=鲙 +鱣=鳣 +鱤=鳡 +鱧=鳢 +鱨=鲿 +鱭=鲚 +鱯=鳠 +鱷=鳄 +鱸=鲈 +鱺=鲡 +鱻=鲜 +鳥=鸟 +鳧=凫 +鳩=鸠 +鳬=凫 +鳯=凤 +鳲=鸤 +鳳=凤 +鳴=鸣 +鳶=鸢 +鳾=䴓 +鴆=鸩 +鴇=鸨 +鴈=雁 +鴉=鸦 +鴒=鸰 +鴕=鸵 +鴛=鸳 +鴝=鸲 +鴞=鸮 +鴟=鸱 +鴣=鸪 +鴦=鸯 +鴨=鸭 +鴬=鸴 +鴯=鸸 +鴰=鸹 +鴴=鸻 +鴷=䴕 +鴻=鸿 +鴿=鸽 +鵁=䴔 +鵂=鸺 +鵃=鸼 +鵐=鹀 +鵑=鹃 +鵒=鹆 +鵓=鹁 +鵜=鹈 +鵝=鹅 +鵞=鹅 +鵠=鹄 +鵡=鹉 +鵪=鹌 +鵬=鹏 +鵮=鹐 +鵯=鹎 +鵰=雕 +鵲=鹊 +鵶=鸦 +鵷=鹓 +鵾=鹍 +鶄=䴖 +鶇=鸫 +鶉=鹑 +鶊=鹒 +鶏=鸡 +鶓=鹋 +鶖=鹙 +鶘=鹕 +鶚=鹗 +鶡=鹖 +鶤=鹍 +鶥=鹛 +鶩=鹜 +鶪=䴗 +鶬=鸧 +鶯=莺 +鶲=鹟 +鶴=鹤 +鶹=鹠 +鶺=鹡 +鶻=鹘 +鶼=鹣 +鶿=鹚 +鷀=鹚 +鷁=鹢 +鷂=鹞 +鷄=鸡 +鷈=䴘 +鷊=鹝 +鷓=鹧 +鷖=鹥 +鷗=鸥 +鷙=鸷 +鷚=鹨 +鷥=鸶 +鷦=鹪 +鷫=鹔 +鷯=鹩 +鷰=燕 +鷲=鹫 +鷳=鹇 +鷴=鹇 +鷸=鹬 +鷹=鹰 +鷺=鹭 +鷽=鸴 +鷿=䴙 +鸇=鹯 +鸌=鹱 +鸎=莺 +鸏=鹲 +鸕=鸬 +鸘=鹴 +鸚=鹦 +鸛=鹳 +鸝=鹂 +鸞=鸾 +鹵=卤 +鹹=咸 +鹺=鹾 +鹻=碱 +鹼=硷 +鹽=盐 +麁=粗 +麅=狍 +麐=麟 +麕=麇 +麗=丽 +麞=獐 +麤=粗 +麥=麦 +麩=麸 +麪=面 +麵=面 +麼=么 +麽=么 +黃=黄 +黌=黉 +點=点 +黨=党 +黲=黪 +黴=霉 +黶=黡 +黷=黩 +黽=黾 +黿=鼋 +鼂=鼌 +鼃=蛙 +鼇=鳌 +鼈=鳖 +鼉=鼍 +鼔=鼓 +鼡=用 +鼦=貂 +鼴=鼹 +齇=齄 +齊=齐 +齋=斋 +齎=赍 +齏=齑 +齒=齿 +齔=龀 +齕=龁 +齗=龂 +齙=龅 +齜=龇 +齟=龃 +齠=龆 +齡=龄 +齦=龈 +齧=啮 +齩=咬 +齪=龊 +齬=龉 +齰=醋 +齲=龋 +齶=腭 +齷=龌 +龍=龙 +龎=厐 +龐=庞 +龔=龚 +龕=龛 +龜=龟 +龞=鳖 +龢=和 +︰=﹕ +︵=《 +︶=》 +︷=《 +︸=》 +︹=《 +︺=》 +︻=《 +︼=》 +︽=《 +︾=》 +︿=《 +﹀=》 +﹁=《 +﹂=》 +﹃=《 +﹄=》 +﹝=《 +﹞=》 +﹢=+ +﹤=《 +﹦== +﹩=$ +﹪=% +﹫=@ +!=! +?=? +/=/ +、=, +%=% +(=( +)=) +,=, +.=. +0=0 +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +A=a +B=b +C=c +D=d +E=e +F=f +G=g +H=h +I=i +J=j +K=k +L=l +M=m +N=n +O=o +P=p +Q=q +R=r +S=s +T=t +U=u +V=v +W=w +X=x +Y=y +Z=z +a=a +b=b +c=c +d=d +e=e +f=f +g=g +h=h +i=i +j=j +k=k +l=l +m=m +n=n +o=o +p=p +q=q +r=r +s=s +t=t +u=u +v=v +w=w +x=x +y=y +z=z +º=0 +¹=1 +²=2 +³=3 +⁴=4 +⁵=5 +⁶=6 +⁷=7 +⁸=8 +⁹=9 +₀=0 +₁=1 +₂=2 +₃=3 +₄=4 +₅=5 +₆=6 +₇=7 +₈=8 +₉=9 +ⁿ=n +:=: +"=" +#=# +$=$ +&=& +'=' +*=* ++=+ +-=- +;=; +<=《 +=== +>=》 +@=@ +[=《 +\=\ +]=》 +^=^ +_=_ +`=` +{=《 +|=| +}=》 +~=~ diff --git a/launchers/standalone/src/main/resources/data/dictionary/other/TagPKU98.csv b/launchers/standalone/src/main/resources/data/dictionary/other/TagPKU98.csv new file mode 100644 index 000000000..c9268b302 --- /dev/null +++ b/launchers/standalone/src/main/resources/data/dictionary/other/TagPKU98.csv @@ -0,0 +1,44 @@ +序号,代码,名称,帮助记忆的诠释,例子及注解 +1,Ag,形语素,形容词性语素。形容词代码为a,语素代码g前面置以A。,绿色/n 似/d 锦/Ag , +2,a,形容词,取英语形容词adjective的第1个字母,[重要/a 步伐/n]NP ,美丽/a ,看似/v 抽象/a , +3,ad,副形词,直接作状语的形容词。形容词代码a和副词代码d并在一起。,[积极/ad 谋求/v]V-ZZ ,幻象/n 易/ad 逝/Vg , +4,an,名形词,具有名词功能的形容词。形容词代码a和名词代码n并在一起。,[外交/n 和/c 安全/an]NP-BL , +5,Bg,区别语素,区别词性语素。区别词代码为b,语素代码g前面置以B。,赤/Ag 橙/Bg 黄/a 绿/a 青/a 蓝/a 紫/a , +6,b,区别词,取汉字“别”的声母。,女/b 司机/n, 金/b 手镯/n, 慢性/b 胃炎/n, 古/b 钱币/n, 副/b 主任/n, 总/b 公司/n单音节区别词和单音节名词或名语素组合,作为一个词,并标以名词词性n。 雄鸡/n, 雌象/n, 女魔/n, 古币/n少数“单音节区别词+双音节词”的结构作为一个词。总书记/n , +7,c,连词,取英语连词conjunction的第1个字母。,合作/vn 与/c 伙伴/n +8,Dg,副语素,副词性语素。副词代码为d,语素代码g前面置以D。,了解/v 甚/Dg 深/a ,煞/Dg 是/v 喜人/a , +9,d,副词,取adverb的第2个字母,因其第1个字母已用于形容词。,进一步/d 发展/v , +10,e,叹词,取英语叹词exclamation的第1个字母。,啊/e ,/w 那/r 金灿灿/z 的/u 麦穗/n , +11,f,方位词,取汉字“方”。,军人/n 的/u 眼睛/n 里/f 不/d 是/v 没有/v 风景/n , +12,h,前接成分,取英语head的第1个字母。,许多/m 非/h 主角/n 人物/n ,办事处/n 的/u “/w 准/h 政府/n ”/w 功能/n 不断/d 加强/v , +13,i,成语,取英语成语idiom的第1个字母。,一言一行/i ,义无反顾/i , +14,j,简称略语,取汉字“简”的声母。,[德/j 外长/n]NP ,文教/j , +15,k,后接成分,后接成分。,少年儿童/l 朋友/n 们/k ,身体/n 健康/a 者/k , +16,l,习用语,习用语尚未成为成语,有点“临时性”,取“临”的声母。,少年儿童/l 朋友/n 们/k ,落到实处/l , +17,Mg,数语素,数词性语素。数词代码为m,语素代码g前面置以M。,甲/Mg 减下/v 的/u 人/n 让/v 乙/Mg 背上/v ,凡/d “/w 寅/Mg 年/n ”/w 中/f 出生/v 的/u 人/n 生肖/n 都/d 属/v 虎/n , +18,m,数词,取英语numeral的第3个字母,n,u已有他用。,1.数量词组应切分为数词和量词。 三/m 个/q, 10/m 公斤/q, 一/m 盒/q 点心/n ,但少数数量词已是词典的登录单位,则不再切分。 一个/m , 一些/m ,2. 基数、序数、小数、分数、百分数一律不予切分,为一个切分单位,标注为 m 。一百二十三/m,20万/m, 123.54/m, 一个/m, 第一/m, 第三十五/m, 20%/m, 三分之二/m, 千分之三十/m, 几十/m 人/n, 十几万/m 元/q, 第一百零一/m 个/q ,3. 约数,前加副词、形容词或后加“来、多、左右”等助数词的应予分开。约/d 一百/m 多/m 万/m,仅/d 一百/m 个/q, 四十/m 来/m 个/q,二十/m 余/m 只/q, 十几/m 个/q,三十/m 左右/m ,两个数词相连的及“成百”、“上千”等则不予切分。五六/m 年/q, 七八/m 天/q,十七八/m 岁/q, 成百/m 学生/n,上千/m 人/n, 4.表序关系的“数+名”结构,应予切分。二/m 连/n , 三/m 部/n , +19,Ng,名语素,名词性语素。名词代码为n,语素代码g前面置以N。,出/v 过/u 两/m 天/q 差/Ng, 理/v 了/u 一/m 次/q 发/Ng, +20,n,名词,取英语名词noun的第1个字母。,(参见 动词--v)岗位/n , 城市/n , 机会/n ,她/r 是/v 责任/n 编辑/n , +21,nr,人名,名词代码n和“人(ren)”的声母并在一起。,1. 汉族人及与汉族起名方式相同的非汉族人的姓和名单独切分,并分别标注为nr。张/nr 仁伟/nr, 欧阳/nr 修/nr, 阮/nr 志雄/nr, 朴/nr 贞爱/nr汉族人除有单姓和复姓外,还有双姓,即有的女子出嫁后,在原来的姓上加上丈夫的姓。如:陈方安生。这种情况切分、标注为:陈/nr 方/nr 安生/nr;唐姜氏,切分、标注为:唐/nr 姜氏/nr。2. 姓名后的职务、职称或称呼要分开。江/nr 主席/n, 小平/nr 同志/n, 江/nr 总书记/n,张/nr 教授/n, 王/nr 部长/n, 陈/nr 老总/n, 李/nr 大娘/n, 刘/nr 阿姨/n, 龙/nr 姑姑/n3. 对人的简称、尊称等若为两个字,则合为一个切分单位,并标以nr。老张/nr, 大李/nr, 小郝/nr, 郭老/nr, 陈总/nr4. 明显带排行的亲属称谓要切分开,分不清楚的则不切开。三/m 哥/n, 大婶/n, 大/a 女儿/n, 大哥/n, 小弟/n, 老爸/n5. 一些著名作者的或不易区分姓和名的笔名通常作为一个切分单位。鲁迅/nr, 茅盾/nr, 巴金/nr, 三毛/nr, 琼瑶/nr, 白桦/nr6. 外国人或少数民族的译名(包括日本人的姓名)不予切分,标注为nr。克林顿/nr, 叶利钦/nr, 才旦卓玛/nr, 小林多喜二/nr, 北研二/nr,华盛顿/nr, 爱因斯坦/nr有些西方人的姓名中有小圆点,也不分开。卡尔·马克思/nr +22,ns,地名,名词代码n和处所词代码s并在一起。,(参见2。短语标记说明--NS)安徽/ns,深圳/ns,杭州/ns,拉萨/ns,哈尔滨/ns, 呼和浩特/ns, 乌鲁木齐/ns,长江/ns,黄海/ns,太平洋/ns, 泰山/ns, 华山/ns,亚洲/ns, 海南岛/ns,太湖/ns,白洋淀/ns, 俄罗斯/ns,哈萨克斯坦/ns,彼得堡/ns, 伏尔加格勒/ns 1. 国名不论长短,作为一个切分单位。中国/ns, 中华人民共和国/ns, 日本国/ns, 美利坚合众国/ns, 美国/ns2. 地名后有“省”、“市”、“县”、“区”、“乡”、“镇”、“村”、“旗”、“州”、“都”、“府”、“道”等单字的行政区划名称时,不切分开,作为一个切分单位。四川省/ns, 天津市/ns,景德镇/ns沙市市/ns, 牡丹江市/ns,正定县/ns,海淀区/ns, 通州区/ns,东升乡/ns, 双桥镇/ns 南化村/ns,华盛顿州/ns,俄亥俄州/ns,东京都/ns, 大阪府/ns,北海道/ns, 长野县/ns,开封府/ns,宣城县/ns3. 地名后的行政区划有两个以上的汉字,则将地名同行政区划名称切开,不过要将地名同行政区划名称用方括号括起来,并标以短语NS。[芜湖/ns 专区/n] NS,[宣城/ns 地区/n]ns,[内蒙古/ns 自治区/n]NS,[深圳/ns 特区/n]NS, [厦门/ns 经济/n 特区/n]NS, [香港/ns 特别/a 行政区/n]NS,[香港/ns 特区/n]NS, [华盛顿/ns 特区/n]NS,4. 地名后有表示地形地貌的一个字的普通名词,如“江、河、山、洋、海、岛、峰、湖”等,不予切分。鸭绿江/ns,亚马逊河/ns, 喜马拉雅山/ns, 珠穆朗玛峰/ns,地中海/ns,大西洋/ns,洞庭湖/ns, 塞普路斯岛/ns 5. 地名后接的表示地形地貌的普通名词若有两个以上汉字,则应切开。然后将地名同该普通名词标成短语NS。[台湾/ns 海峡/n]NS,[华北/ns 平原/n]NS,[帕米尔/ns 高原/n]NS, [南沙/ns 群岛/n]NS,[京东/ns 大/a 峡谷/n]NS [横断/b 山脉/n]NS6.地名后有表示自然区划的一个字的普通名词,如“ 街,路,道,巷,里,町,庄,村,弄,堡”等,不予切分。 中关村/ns,长安街/ns,学院路/ns, 景德镇/ns, 吴家堡/ns, 庞各庄/ns, 三元里/ns,彼得堡/ns, 北菜市巷/ns, 7.地名后接的表示自然区划的普通名词若有两个以上汉字,则应切开。然后将地名同自然区划名词标成短语NS。[米市/ns 大街/n]NS, [蒋家/nz 胡同/n]NS , [陶然亭/ns 公园/n]NS , 8. 大小地名相连时的标注方式为:北京市/ns 海淀区/ns 海淀镇/ns [南/f 大街/n]NS [蒋家/nz 胡同/n]NS 24/m 号/q , +23,nt,机构团体,“团”的声母为t,名词代码n和t并在一起。,(参见2。短语标记说明--NT)联合国/nt,中共中央/nt,国务院/nt, 北京大学/nt1.大多数团体、机构、组织的专有名称一般是短语型的,较长,且含有地名或人名等专名,再组合,标注为短语NT。[中国/ns 计算机/n 学会/n]NT, [香港/ns 钟表业/n 总会/n]NT, [烟台/ns 大学/n]NT, [香港/ns 理工大学/n]NT, [华东/ns 理工大学/n]NT,[合肥/ns 师范/n 学院/n]NT, [北京/ns 图书馆/n]NT, [富士通/nz 株式会社/n]NT, [香山/ns 植物园/n]NT, [安娜/nz 美容院/n]NT,[上海/ns 手表/n 厂/n]NT, [永和/nz 烧饼铺/n]NT,[北京/ns 国安/nz 队/n]NT,2. 对于在国际或中国范围内的知名的唯一的团体、机构、组织的名称即使前面没有专名,也标为nt或NT。联合国/nt,国务院/nt,外交部/nt, 财政部/nt,教育部/nt, 国防部/nt,[世界/n 贸易/n 组织/n]NT, [国家/n 教育/vn 委员会/n]NT,[信息/n 产业/n 部/n]NT,[全国/n 信息/n 技术/n 标准化/vn 委员会/n]NT,[全国/n 总/b 工会/n]NT,[全国/n 人民/n 代表/n 大会/n]NT,美国的“国务院”,其他国家的“外交部、财政部、教育部”,必须在其所属国的国名之后出现时,才联合标注为NT。[美国/ns 国务院/n]NT,[法国/ns 外交部/n]NT,[美/j 国会/n]NT,日本有些政府机构名称很特别,无论是否出现在“日本”国名之后都标为nt。[日本/ns 外务省/nt]NT,[日/j 通产省/nt]NT通产省/nt 3. 前后相连有上下位关系的团体机构组织名称的处理方式如下:[联合国/nt 教科文/j 组织/n]NT, [中国/ns 银行/n 北京/ns 分行/n]NT,[河北省/ns 正定县/ns 西平乐乡/ns 南化村/ns 党支部/n]NT, 当下位名称含有专名(如“北京/ns 分行/n”、“南化村/ns 党支部/n”、“昌平/ns 分校/n”)时,也可脱离前面的上位名称单独标注为NT。[中国/ns 银行/n]NT [北京/ns 分行/n]NT,北京大学/nt [昌平/ns 分校/n]NT,4. 团体、机构、组织名称中用圆括号加注简称时:[宝山/ns 钢铁/n (/w 宝钢/j )/w 总/b 公司/n]NT,[宝山/ns 钢铁/n 总/b 公司/n]NT,(/w 宝钢/j )/w +24,nx,外文字符,外文字符。,A/nx 公司/n ,B/nx 先生/n ,X/nx 君/Ng ,24/m K/nx 镀金/n ,C/nx 是/v 光速/n ,Windows98/nx ,PentiumIV/nx ,I LOVE THIS GAME/nx , +25,nz,其他专名,“专”的声母的第1个字母为z,名词代码n和z并在一起。,(参见2。短语标记说明--NZ)除人名、国名、地名、团体、机构、组织以外的其他专有名词都标以nz。满族/nz,俄罗斯族/nz,汉语/nz,罗马利亚语/nz, 捷克语/nz,中文/nz, 英文/nz, 满人/nz, 哈萨克人/nz, 诺贝尔奖/nz, 茅盾奖/nz, 1.包含专有名称(或简称)的交通线,标以nz;短语型的,标为NZ。津浦路/nz, 石太线/nz, [京/j 九/j 铁路/n]NZ, [京/j 津/j 高速/b 公路/n]NZ, 2. 历史上重要事件、运动等专有名称一般是短语型的,按短语型专有名称处理,标以NZ。[卢沟桥/ns 事件/n]NZ, [西安/ns 事变/n]NZ,[五四/t 运动/n]NZ, [明治/nz 维新/n]NZ,[甲午/t 战争/n]NZ,3.专有名称后接多音节的名词,如“语言”、“文学”、“文化”、“方式”、“精神”等,失去专指性,则应分开。欧洲/ns 语言/n, 法国/ns 文学/n, 西方/ns 文化/n, 贝多芬/nr 交响乐/n, 雷锋/nr 精神/n, 美国/ns 方式/n,日本/ns 料理/n, 宋朝/t 古董/n 4. 商标(包括专名及后接的“牌”、“型”等)是专指的,标以nz,但其后所接的商品仍标以普通名词n。康师傅/nr 方便面/n, 中华牌/nz 香烟/n, 牡丹III型/nz 电视机/n, 联想/nz 电脑/n, 鳄鱼/nz 衬衣/n, 耐克/nz 鞋/n5. 以序号命名的名称一般不认为是专有名称。2/m 号/q 国道/n ,十一/m 届/q 三中全会/j如果前面有专名,合起来作为短语型专名。[中国/ns 101/m 国道/n]NZ, [中共/j 十一/m 届/q 三中全会/j]NZ,6. 书、报、杂志、文档、报告、协议、合同等的名称通常有书名号加以标识,不作为专有名词。由于这些名字往往较长,名字本身按常规处理。《/w 宁波/ns 日报/n 》/w ,《/w 鲁迅/nr 全集/n 》/w,中华/nz 读书/vn 报/n, 杜甫/nr 诗选/n,少数书名、报刊名等专有名称,则不切分。红楼梦/nz, 人民日报/nz,儒林外史/nz 7. 当有些专名无法分辨它们是人名还是地名或机构名时,暂标以nz。[巴黎/ns 贝尔希/nz 体育馆/n]NT,其中“贝尔希”只好暂标为nz。 +26,o,拟声词,取英语拟声词onomatopoeia的第1个字母。,哈哈/o 一/m 笑/v ,装载机/n 隆隆/o 推进/v , +27,p,介词,取英语介词prepositional的第1个字母。,对/p 子孙后代/n 负责/v ,以/p 煤/n 养/v 农/Ng ,为/p 治理/v 荒山/n 服务/v , 把/p 青年/n 推/v 上/v 了/u 领导/vn 岗位/n , +28,q,量词,取英语quantity的第1个字母。,(参见数词m)首/m 批/q ,一/m 年/q , +29,Rg,代语素,代词性语素。代词代码为r,在语素的代码g前面置以R。,读者/n 就/d 是/v 这/r 两/m 棵/q 小树/n 扎根/v 于/p 斯/Rg 、/w 成长/v 于/p 斯/Rg 的/u 肥田/n 沃土/n , +30,r,代词,取英语代词pronoun的第2个字母,因p已用于介词。,单音节代词“本”、“每”、“各”、“诸”后接单音节名词时,和后接的单音节名词合为代词;当后接双音节名词时,应予切分。本报/r, 每人/r, 本社/r, 本/r 地区/n, 各/r 部门/n +31,s,处所词,取英语space的第1个字母。,家里/s 的/u 电脑/n 都/d 联通/v 了/u 国际/n 互联网/n ,西部/s 交通/n 咽喉/n , +32,Tg,时语素,时间词性语素。时间词代码为t,在语素的代码g前面置以T。,3日/t 晚/Tg 在/p 总统府/n 发表/v 声明/n ,尊重/v 现/Tg 执政/vn 当局/n 的/u 权威/n , +33,t,时间词,取英语time的第1个字母。,1. 年月日时分秒,按年、月、日、时、分、秒切分,标注为t 。1997年/t 3月/t 19日/t 下午/t 2时/t 18分/t若数字后无表示时间的“年、月、日、时、分、秒”等的标为数词m。1998/m 中文/n 信息/n 处理/vn 国际/n 会议/n 2. 历史朝代的名称虽然有专有名词的性质,仍标注为t。西周/t, 秦朝/t, 东汉/t, 南北朝/t, 清代/t“牛年、虎年”等一律不予切分,标注为:牛年/t, 虎年/t, 甲午年/t, 甲午/t 战争/n, 庚子/t 赔款/n, 戊戌/t 变法/n +34,u,助词,取英语助词auxiliary。,[[俄罗斯/ns 和/c 北约/j]NP-BL 之间/f [战略/n 伙伴/n 关系/n]NP 的/u 建立/vn]NP 填平/v 了/u [[欧洲/ns 安全/a 政治/n]NP 的/u 鸿沟/n]NP +35,Vg,动语素,动词性语素。动词代码为v。在语素的代码g前面置以V。,洗/v 了/u 一个/m 舒舒服服/z 的/u 澡/Vg +36,v,动词,取英语动词verb的第一个字母。,(参见 名词--n)[[[欧盟/j 扩大/v]S 的/u [历史性/n 决定/n]NP]NP 和/c [北约/j 开放/v]S]NP-BL [为/p [创建/v [一/m 种/q 新/a 的/u 欧洲/ns 安全/a 格局/n]NP]VP-SBI]PP-MD [奠定/v 了/u 基础/n]V-SBI ,, +37,vd,副动词,直接作状语的动词。动词和副词的代码并在一起。,形势/n 会/v 持续/vd 好转/v ,认为/v 是/v 电话局/n 收/v 错/vd 了/u 费/n , +38,vn,名动词,指具有名词功能的动词。动词和名词的代码并在一起。,引起/v 人们/n 的/u 关注/vn 和/c 思考/vn ,收费/vn 电话/n 的/u 号码/n , +39,w,标点符号,,”/w :/w +40,x,非语素字,非语素字只是一个符号,字母x通常用于代表未知数、符号。, +41,Yg,语气语素,语气词性语素。语气词代码为y。在语素的代码g前面置以Y。,唯/d 大力/d 者/k 能/v 致/v 之/u 耳/Yg +42,y,语气词,取汉字“语”的声母。,会/v 泄露/v 用户/n 隐私/n 吗/y ,又/d 何在/v 呢/y ? +43,z,状态词,取汉字“状”的声母的前一个字母。,取得/v 扎扎实实/z 的/u 突破性/n 进展/vn ,四季/n 常青/z 的/u 热带/n 树木/n ,短短/z 几/m 年/q 间, \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/data/version.txt b/launchers/standalone/src/main/resources/data/version.txt new file mode 100644 index 000000000..6a126f402 --- /dev/null +++ b/launchers/standalone/src/main/resources/data/version.txt @@ -0,0 +1 @@ +1.7.5 diff --git a/launchers/standalone/src/main/resources/db/data-h2.sql b/launchers/standalone/src/main/resources/db/data-h2.sql new file mode 100644 index 000000000..61413b07d --- /dev/null +++ b/launchers/standalone/src/main/resources/db/data-h2.sql @@ -0,0 +1,1070 @@ +-- chat data +insert into s2_user (id, `name`, password, display_name, email) values (1, 'admin','admin','admin','admin@xx.com'); +insert into s2_user (id, `name`, password, display_name, email) values (2, 'jack','123456','jack','jack@xx.com'); +insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom','123456','tom','tom@xx.com'); +insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); + +insert into s2_chat_config (`id` ,`domain_id` ,`default_metrics`,`visibility`,`entity_info` ,`dictionary_info`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) values (1,1,'[{"metricId":1,"unit":7,"period":"DAY"}]','{"blackDimIdList":[],"blackMetricIdList":[]}','{"entityIds":[2],"names":["用户","用户姓名"],"detailData":{"dimensionIds":[1,2],"metricIds":[2]}}','[{"itemId":1,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":2,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":3,"type":"DIMENSION","blackList":[],"isDictInfo":true}]','2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); + +insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (1, '超音数访问统计','2023-06-10 10:00:52.495','2023-06-10 10:00:52','admin','您好,欢迎使用内容智能小Q','0','0'); +insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (2, '用户访问统计','2023-06-10 10:01:04.528','2023-06-10 10:01:04','admin','您好,欢迎使用内容智能小Q','0','0'); + +insert into s2_chat_context (chat_id, modified_at , `user`, `query_text`, `semantic_parse` ,ext_data) VALUES(1, '2023-06-10 10:40:49.877', 'admin', '访问', '{"queryMode":"METRIC_ORDERBY","aggType":"NONE","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":3,"name":"访问人数","bizName":"uv","status":1,"sensitiveLevel":0},{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"nativeQuery":false}', 'admin'); +insert into s2_chat_context (chat_id, modified_at , `user`, `query_text`, `semantic_parse` ,ext_data) VALUES(2, '2023-06-10 10:42:02.184', 'null', '访问', '{"queryMode":"METRIC_ORDERBY","aggType":"NONE","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":3,"name":"访问人数","bizName":"uv","status":1,"sensitiveLevel":0},{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"nativeQuery":false}', 'null'); + +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(1, '2023-06-10 10:39:55.178', '超音数 访问次数', 'admin',0,1,'{"queryMode":"METRIC_DOMAIN","querySql":"SELECT `sys_imp_date` , `pv` FROM ( SELECT `sys_imp_date` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` FROM ( SELECT `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_f370` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' ) GROUP BY `sys_imp_date` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"}},"chatContext":{"queryMode":"METRIC_DOMAIN","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","pv":11},{"sys_imp_date":"2023-06-04","pv":14},{"sys_imp_date":"2023-06-05","pv":1},{"sys_imp_date":"2023-06-06","pv":19},{"sys_imp_date":"2023-06-07","pv":18},{"sys_imp_date":"2023-06-08","pv":24},{"sys_imp_date":"2023-06-09","pv":23}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(2, '2023-06-10 10:40:12.259', '按页面', 'admin',0,1,'{"queryMode":"METRIC_ORDERBY","querySql":"SELECT `sys_imp_date` , `page` , `pv` FROM ( SELECT `sys_imp_date` , `page` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` , `page` FROM ( SELECT `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` , `page` AS `page` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_ecf5` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' ) GROUP BY `sys_imp_date` , `page` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"页面","type":"VARCHAR","nameEn":"page","showType":"CATEGORY","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"}},"chatContext":{"queryMode":"METRIC_ORDERBY","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","page":"p1","pv":2},{"sys_imp_date":"2023-06-03","page":"p2","pv":3},{"sys_imp_date":"2023-06-03","page":"p3","pv":2},{"sys_imp_date":"2023-06-03","page":"p4","pv":1},{"sys_imp_date":"2023-06-03","page":"p5","pv":3},{"sys_imp_date":"2023-06-04","page":"p1","pv":3},{"sys_imp_date":"2023-06-04","page":"p2","pv":1},{"sys_imp_date":"2023-06-04","page":"p3","pv":2},{"sys_imp_date":"2023-06-04","page":"p4","pv":4},{"sys_imp_date":"2023-06-04","page":"p5","pv":4}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(3, '2023-06-10 10:40:49.877', '访问', 'admin',0,1,'{"queryMode":"METRIC_ORDERBY","querySql":"SELECT `sys_imp_date` , `page` , `uv` , `pv` FROM ( SELECT `sys_imp_date` , `page` , `s2_pv_uv_statis_uv` AS `uv` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT COUNT ( DISTINCT `s2_pv_uv_statis_uv` ) AS `s2_pv_uv_statis_uv` , SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` , `page` FROM ( SELECT `uv` AS `s2_pv_uv_statis_uv` , `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` , `page` AS `page` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_022c` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' ) GROUP BY `sys_imp_date` , `page` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"页面","type":"VARCHAR","nameEn":"page","showType":"CATEGORY","authorized":true},{"name":"访问人数","type":"BIGINT","nameEn":"uv","showType":"NUMBER","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"}},"chatContext":{"queryMode":"METRIC_ORDERBY","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":3,"name":"访问人数","bizName":"uv","status":1,"sensitiveLevel":0},{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","page":"p1","uv":2,"pv":2},{"sys_imp_date":"2023-06-03","page":"p2","uv":3,"pv":3},{"sys_imp_date":"2023-06-03","page":"p3","uv":2,"pv":2},{"sys_imp_date":"2023-06-03","page":"p4","uv":1,"pv":1},{"sys_imp_date":"2023-06-03","page":"p5","uv":3,"pv":3},{"sys_imp_date":"2023-06-04","page":"p1","uv":2,"pv":3},{"sys_imp_date":"2023-06-04","page":"p2","uv":1,"pv":1},{"sys_imp_date":"2023-06-04","page":"p3","uv":2,"pv":2},{"sys_imp_date":"2023-06-04","page":"p4","uv":3,"pv":4},{"sys_imp_date":"2023-06-04","page":"p5","uv":3,"pv":4}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(4, '2023-06-10 10:41:18.589','alice 访问次数','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `pv` FROM ( SELECT `sys_imp_date` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` FROM ( SELECT `user_name` , `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_b825` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","pv":2},{"sys_imp_date":"2023-06-04","pv":2},{"sys_imp_date":"2023-06-06","pv":2},{"sys_imp_date":"2023-06-07","pv":2},{"sys_imp_date":"2023-06-08","pv":5},{"sys_imp_date":"2023-06-09","pv":2}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(5, '2023-06-10 10:41:48.211','停留时长','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(6, '2023-06-10 10:42:02.184','访问','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); + + +-- semantic data +insert into s2_database (id, domain_id , `name`, description, `type` ,config ,created_at ,created_by ,updated_at ,updated_by) VALUES(1, 1, 'H2数据实例', '', 'h2', '{"password":"semantic","url":"jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false","userName":"root"}', '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin'); +insert into s2_datasource (id , domain_id, `name`, biz_name, description, database_id ,datasource_detail, created_at, created_by, updated_at, updated_by ) VALUES(1, 1, '停留时长统计', 's2_stay_time_statis', '停留时长统计', 1, '{"dimensions":[{"bizName":"imp_date","dateFormat":"yyyy-MM-dd","expr":"imp_date","isCreateDimension":0,"type":"time","typeParams":{"isPrimary":"true","timeGranularity":"day"}},{"bizName":"page","dateFormat":"yyyy-MM-dd","expr":"page","isCreateDimension":0,"type":"categorical"}],"identifiers":[{"bizName":"user_name","name":"用户名","type":"primary"}],"measures":[{"agg":"sum","bizName":"s2_stay_time_statis_stay_hours","expr":"stay_hours","isCreateMetric":1,"name":"停留时长"}],"queryType":"sql_query","sqlQuery":"SELECT imp_date, page,user_name,stay_hours FROM s2_stay_time_statis"}', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource (id , domain_id, `name`, biz_name, description, database_id ,datasource_detail, created_at, created_by, updated_at, updated_by ) VALUES(2, 1, 'PVUV统计', 's2_pv_uv_statis', 'PVUV统计', 1, '{"dimensions":[{"bizName":"imp_date","dateFormat":"yyyy-MM-dd","expr":"imp_date","isCreateDimension":0,"type":"time","typeParams":{"isPrimary":"true","timeGranularity":"day"}},{"bizName":"page","dateFormat":"yyyy-MM-dd","expr":"page","isCreateDimension":0,"type":"categorical"}],"identifiers":[{"bizName":"user_name","name":"用户名","type":"primary"}],"measures":[{"agg":"sum","bizName":"s2_pv_uv_statis_pv","expr":"pv","isCreateMetric":1,"name":"访问次数"},{"agg":"count_distinct","bizName":"s2_pv_uv_statis_uv","expr":"uv","isCreateMetric":1,"name":"访问人数"}],"queryType":"sql_query","sqlQuery":"SELECT imp_date, user_name,page,1 as pv, user_name as uv FROM s2_pv_uv_statis"}', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource (id , domain_id, `name`, biz_name, description, database_id ,datasource_detail, created_at, created_by, updated_at, updated_by ) VALUES(3, 1, '用户部门', 'user_department', '用户部门', 1, '{"dimensions":[{"bizName":"department","dateFormat":"yyyy-MM-dd","expr":"department","isCreateDimension":1,"name":"部门","type":"categorical"}],"identifiers":[{"bizName":"user_name","name":"用户名","type":"primary"}],"measures":[],"queryType":"sql_query","sqlQuery":"SELECT user_name,department FROM s2_user_department"}', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource_rela (id , domain_id, `datasource_from`, datasource_to, join_key, created_at, created_by, updated_at, updated_by ) VALUES(1, 1, 1, 2, 'user_name', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource_rela (id , domain_id, `datasource_from`, datasource_to, join_key, created_at, created_by, updated_at, updated_by ) VALUES(2, 1, 1, 3, 'user_name', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource_rela (id , domain_id, `datasource_from`, datasource_to, join_key, created_at, created_by, updated_at, updated_by ) VALUES(3, 1, 2, 3, 'user_name', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(1, 1, 3, '部门', 'department', '部门', 1, 0, 'categorical', NULL, 'department', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); +insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(2, 1, 1, '用户名', 'user_name', '用户名', 1, 0, 'primary', NULL, 'user_name', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); +insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(3, 1, 2, '页面', 'page', '页面', 1, 2, 'categorical', NULL, 'page', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); +insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(1, '超音数', 'supersonic', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); +insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(1, 1, '停留时长', 'stay_hours', '停留时长', 1, 2, 'expr', '{"expr":"s2_stay_time_statis_stay_hours","measures":[{"agg":"sum","expr":"stay_hours","isCreateMetric":1,"datasourceId":1,"bizName":"s2_stay_time_statis_stay_hours","name":"s2_stay_time_statis_stay_hours"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); +insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(2, 1, '访问次数', 'pv', '访问次数', 1, 0, 'expr', ' {"expr":"s2_pv_uv_statis_pv","measures":[{"agg":"sum","bizName":"s2_pv_uv_statis_pv","datasourceId":2,"expr":"pv","isCreateMetric":1,"name":"s2_pv_uv_statis_pv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); +insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(3, 1, '访问人数', 'uv', '访问人数', 1, 0, 'expr', ' {"expr":"s2_pv_uv_statis_uv","measures":[{"agg":"count_distinct","bizName":"s2_pv_uv_statis_uv","datasourceId":2,"expr":"uv","isCreateMetric":1,"name":"s2_pv_uv_statis_uv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); + +insert into s2_available_date_info(`item_id` ,`type` ,`date_format` ,`start_date` ,`end_date` ,`unavailable_date` ,`created_at` ,`created_by` ,`updated_at` ,`updated_by` ) +values (1, 'dimension', 'yyyy-MM-dd', DATEADD('DAY', -28, CURRENT_DATE()), DATEADD('DAY', -1, CURRENT_DATE()), '[]', '2023-06-01', 'admin', '2023-06-01', 'admin'); +insert into s2_available_date_info(`item_id` ,`type` ,`date_format` ,`start_date` ,`end_date` ,`unavailable_date` ,`created_at` ,`created_by` ,`updated_at` ,`updated_by` ) +values (2, 'dimension', 'yyyy-MM-dd', DATEADD('DAY', -28, CURRENT_DATE()), DATEADD('DAY', -1, CURRENT_DATE()), '[]', '2023-06-01', 'admin', '2023-06-01', 'admin'); +insert into s2_available_date_info(`item_id` ,`type` ,`date_format` ,`start_date` ,`end_date` ,`unavailable_date` ,`created_at` ,`created_by` ,`updated_at` ,`updated_by` ) +values (3, 'dimension', 'yyyy-MM-dd', DATEADD('DAY', -28, CURRENT_DATE()), DATEADD('DAY', -1, CURRENT_DATE()), '[]', '2023-06-01', 'admin', '2023-06-01', 'admin'); + +insert into s2_auth_groups (group_id, config) +values (1, '{"domainId":"1","name":"admin-permission","groupId":1,"authRules":[{"metrics":["stay_hours"],"dimensions":["page"]}],"dimensionFilters":[""],"dimensionFilterDescription":"授权admin 页面和停留时长权限","authorizedUsers":["admin"],"authorizedDepartmentIds":[]}'); +insert into s2_auth_groups (group_id, config) +values (2, '{"domainId":"1","name":"tom_sales_permission","groupId":2,"authRules":[{"metrics":["stay_hours"],"dimensions":["page"]}],"dimensionFilters":["department in (''sales'')"],"dimensionFilterDescription":"开通 tom sales部门权限", "authorizedUsers":["tom"],"authorizedDepartmentIds":[]}'); + + +-- insert into s2_user (id, `name`, password, display_name, email) values (1, 'admin','admin','admin','admin@xx.com'); +-- insert into s2_user (id, `name`, password, display_name, email) values (2, 'jack','123456','jack','jack@xx.com'); +-- insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom','123456','tom','tom@xx.com'); +-- insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); + +---demo data for semantic and chat +insert into s2_user_department (user_name, department) values ('jack','HR'); +insert into s2_user_department (user_name, department) values ('tom','sales'); +insert into s2_user_department (user_name, department) values ('lucy','marketing'); +insert into s2_user_department (user_name, department) values ('john','strategy'); +insert into s2_user_department (user_name, department) values ('alice','sales'); +insert into s2_user_department (user_name, department) values ('dean','marketing'); + + +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -5, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', 'p4'); + + + + + +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.7636857512911863', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', '0.17663327393462436', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.38943688941552057', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', '0.2715819955225307', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.9358210273119568', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.9364586435510802', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.9707723036513162', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', '0.8497763866782723', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.15504417761372413', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.9507563118298399', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.9746364180572994', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', '0.12869214941133378', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.3024970533288409', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.6639702099980812', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'lucy', '0.4929901454858626', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.06853040276026445', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.8488086078299616', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', '0.8589111177125592', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', '0.5576357066482228', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.8047888670006846', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.766944548494366', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.5280072184505449', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.9693343356046343', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', '0.12805203958456424', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', '0.16963603387027637', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.5901202956521101', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.12710364646712236', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.6346530909156196', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', '0.12461289103639872', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', '0.9863947334662437', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', '0.48899961064192987', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'alice', '0.5382796792688207', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', '0.3506568687014143', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.8633072449771709', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.13999135315363687', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', '0.07258740493845894', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', '0.5244413940436958', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.13258670732966138', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.6015982054464575', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', '0.05513158944480323', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.6707121735296985', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', '0.9330440339006469', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', '0.5630674323371607', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', '0.8720647566229917', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.8331899070546519', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', '0.6712876436249856', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', '0.6694409980332703', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', '0.3703307480606334', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.775368688472696', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.9151205443267096', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.09543108823305857', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', '0.7893992120771057', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', '0.5119923080070498', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', '0.49906724167974936', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.046258282700961884', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'dean', '0.44843595680103954', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', '0.7743935471689718', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.5855299615656824', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', '0.9412963512379853', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.8383247587082538', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', '0.14517876867236124', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.9327229861441061', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.19042326582894153', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.6029067818254513', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.21715964747214422', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', '0.34259842721045974', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.7064419016593382', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', '0.5725636566517865', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', '0.22332539583809208', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.8049036189055911', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', '0.6029674758974956', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.11884976360561716', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', '0.7124916829130662', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.5893693718556829', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.602073304496253', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.10491061160039927', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', '0.9006548872378379', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.8545144244288455', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', '0.16915384987875726', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.2271640700690446', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', '0.7807518577160636', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.8919859648888653', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', '0.1564450687270359', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.5840549187653847', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', '0.2213255596777869', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', '0.07868261880306426', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.07710010861455818', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.5131249730162654', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.5035035055368601', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.8996978291173905', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.057442290722216294', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.6443079066865616', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', '0.7398098480748726', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', '0.9835694815034591', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', '0.9879213445635557', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.4020136688147111', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', '0.6698797170128024', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.17325132416789113', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', '0.5784229486763606', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.9185978183932058', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.5474783153973963', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.9730731954700215', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', '0.5390873359288765', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', '0.20522241320887713', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', '0.4088233242325021', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.7608047695853417', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.2749731221085713', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.06154055374702494', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.460668002022406', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.4474746325306228', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.5761666885467472', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'dean', '0.33233441360339655', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', '0.7426534909874778', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', '0.5841437875889118', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.2818296500094526', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', '0.8670888843915217', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', '0.5249294365740248', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.5483356748008438', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', '0.7278566847412673', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.6779976902157362', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.09995341651736978', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', '0.4528538159233879', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.5870756885301056', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.9842091927290255', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.04580936015706816', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.8814678270145769', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', '0.06517379256096412', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.8769832364187129', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', '0.584562279025023', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.8102404090621375', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.11481653429176686', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.43422888918962554', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'lucy', '0.0684414272594508', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.976546463969412', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.617906858141431', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'jack', '0.08663740247579998', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', '0.7124944606691416', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'alice', '0.1321700521239627', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'jack', '0.3078946609431664', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.6149442855237194', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'alice', '0.5963801306980994', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', '0.6999542038973406', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.4599112653446624', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', '0.20300901401048832', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.39989705958717037', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.2486378364940327', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', '0.16880398079144077', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.73927288385526', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.8645283506689198', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', '0.3266940826759587', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.9195490073037541', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.9452523036658287', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.21269683438120535', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.7377502855387184', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.38981597634408716', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.7001799391999863', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.6616720024008785', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'dean', '0.497721735058096', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'jack', '0.22255613760959603', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.05247640233319417', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', '0.27237572107833363', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', '0.9529452406380252', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.28243045060463157', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', '0.17880444250082506', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.035050038002381156', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', '0.840803223728221', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.5318457377361356', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.9280332892460665', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', '0.752354382202208', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', '0.1866528331789219', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', '0.7016165545791373', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.4191547989960899', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', '0.7025516699007639', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.6160127317884274', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', '0.91223094958137', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.4383056089013998', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', '0.595750781166582', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'lucy', '0.9472349338730268', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', '0.0519104588842193', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.48043983034526205', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.14754707786497478', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', '0.36124288370035695', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', '0.21777919493494613', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.22637666702475057', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.9378215576942598', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.3309229261144562', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.7602880453727515', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.9470462487873785', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.6770215935547629', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.1586074803669385', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'lucy', '0.2754855564794071', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', '0.8355347738454384', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.7251813505573811', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'jack', '0.006606625589642534', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', '0.304832277753024', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.026368662837989554', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', '0.6855977520602776', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'tom', '0.8193746826441749', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.021179295102459972', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.1533849522536005', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.18893553542301778', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.39870999343833624', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.9985665103520182', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.6961441157700171', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.9861933923851885', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', '0.993076500099477', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.4320547269058953', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', '0.18441071030375877', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.1501504986117118', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.252021845734527', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', '0.24442701577183745', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.07563738855797564', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', '0.34247820646440985', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.9456979276862031', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.19494357263973816', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.9371493867882469', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.6136241316589367', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', '0.8922330760877784', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', '0.9001986074661864', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.4889702884422866', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.2689551234431401', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.5223573993758465', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', '0.05042295556527243', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', '0.2717147121880483', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', '0.7397093309370814', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', '0.157064341631733', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', '0.7213399784998017', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', '0.764081440588005', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.7514070600074144', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.611647412825278', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.6600796877195596', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', '0.8942204153751679', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.07398121085929721', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', '0.1652506990439564', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.5849759516111703', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.1672502732600889', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.7836135556233219', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', '0.26181269644936356', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', '0.6577275876355586', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.3067293364197956', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.8608288543866495', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.814283434116926', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'jack', '0.33993584425872936', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'john', '0.010812798859160089', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.5156558224263926', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'jack', '0.46320035330198406', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', '0.2651020283994786', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.42467241545664147', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.3695905136678498', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', '0.15269122123348644', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.6755688670583248', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'jack', '0.39064306179528907', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', '0.36479296691952023', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'lucy', '0.5069249157662691', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.4785315495532231', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', '0.7582526218052175', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.42064109605717914', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', '0.5587757581237022', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', '0.3561686564964428', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.7101688305173135', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.6518061375522985', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.7564485884156583', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.36531347293134464', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', '0.5201689359070235', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', '0.7138792929290383', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.9751003716333827', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.5281906318027629', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.6291356541485003', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', '0.1938712974807698', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', '0.6267850210775459', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.4469970592043767', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', '0.7690659124175409', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'jack', '0.13335067838090386', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'jack', '0.2966621725922035', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.5740481445089863', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.838028890036331', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', '0.8094354537628714', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', '0.5552924586108698', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.49150373927678315', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.7264346889377966', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.9292830287297702', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.3905616258240767', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.15912349648571666', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', '0.6030082006630102', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', '0.8712354035243679', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.7685306377211826', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.2869913942171415', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.7142615166855639', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.5625978475154423', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.13611601734791123', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.6977333962685311', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', '0.35140477709778295', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.8805119222967716', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.7014124236538637', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', '0.12759538003439375', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.7515403792213445', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', '0.03700239289885987', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', '0.31674618364630946', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.4491378834800146', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', '0.6742764131652571', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'lucy', '0.5286362221140248', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.007890326473113496', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', '0.8046560540950831', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.7198364371127147', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.7400546712169153', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.16859870460868698', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.8462852684569557', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.010211452005474353', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', '0.8617802368201087', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.21667479046797633', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.8667689615468714', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', '0.16140709875863557', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.16713368182304666', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'lucy', '0.8957484629768053', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'tom', '0.457835758220534', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.9435170960198477', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', '0.9699253608913104', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.2309897429566834', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.7879705066452681', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.20795869239817255', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', '0.4110352469382019', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', '0.4979592772533561', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', '0.18810865430947044', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'tom', '0.5001240246982048', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'jack', '0.08341934160029707', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.04812784841651041', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.4655982693269717', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', '0.8539357978460663', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.9649541785823592', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', '0.8243635648047365', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.929949719929735', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.055983276861168996', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.07845430274829746', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.28257674222099116', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.1578419214960578', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', '0.7853118484860825', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', '0.20790127125904156', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.8650538395535204', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.902116091225815', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', '0.48542770770171373', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.16725337150113984', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'lucy', '0.3157444453259486', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.565727220131555', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', '0.2531688065358064', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', '0.9191434620980499', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.9224628853942058', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', '0.3256288410730337', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.9709152566761661', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.9794173893522709', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.16582064407977237', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', '0.2652519246960059', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'alice', '0.04092489871261762', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.3020444893927522', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.4655412764350543', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', '0.9226436424888846', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.4707663393012884', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.3277970119243966', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', '0.4730675479071551', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', '0.10261940477901954', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', '0.4148892373198616', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.2877219827348403', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.16212409974675845', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.9567425121214822', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'lucy', '0.19795350030679149', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.6954199597749198', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', '0.32884293488801164', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', '0.4789917995407148', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', '0.0698927593996298', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.3352267723792438', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', '0.8085116661598726', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.17515060210353794', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.6006963088370202', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.8794167536704468', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.04091469320757368', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', '0.6709116812690366', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', '0.4850646101328463', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.547488212623346', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', '0.6301717145008927', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', '0.06123370093612068', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.2545600223228257', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', '0.28355287519210803', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.3231348374147818', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.4585172495754063', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.7893945285152268', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.6810596014794181', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.7136031244915907', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'jack', '0.259734039051829', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.7759518703827996', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.06288891046833589', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', '0.8242980461154241', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.36590300307021595', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.20254092528445444', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.5427356081880325', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.1467846603517391', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.8975527268892767', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', '0.3483541520806722', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.6922544855316723', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.3690185253006011', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.7564541265683148', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', '0.3634152133342695', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.33740378933701987', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.7942640738315301', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.7894896778233523', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.7153281477198108', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'tom', '0.5546359859065261', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', '0.7727157385809087', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.8707097754747494', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.3873936520764878', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.7590305068820566', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.512826935863365', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', '0.19120284727846926', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', '0.5382693105670825', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.826241649014955', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', '0.6133080470571559', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.6452862617544055', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', '0.3025772179023586', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '4.709864550322962E-4', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.024816355013726588', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', '0.8407500495605565', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.8420879584266481', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', '0.2719224735814776', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.8939712577294938', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', '0.8086189323362379', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', '0.6063415085381448', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.39783242658234674', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.6085577206028068', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.5154289424127074', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', '0.878436600887031', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.5577906295015223', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', '0.1143260282925247', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', '0.312756557275364', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.05548807854726956', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', '0.12140791431139175', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.23897628700410234', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.22223137342481392', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.12379891645900953', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', '0.33729146112854247', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.8816768640060831', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', '0.6301700633426532', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', '0.4566295223861714', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', '0.1777378523933678', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.8163769471165477', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.4380805149704541', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', '0.2987018822475964', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.6726495645391617', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.8394327461109705', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'dean', '0.820512945501936', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.1580105370757261', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.9961450897279505', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.6574891890500061', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.5201205570085158', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', '0.2445069633928285', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', '0.3155229654901067', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.3665971881269575', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.5544977915912215', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.15978771803015113', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.038128748344929186', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.49026304025118594', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.5166802080526571', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.22568230066042194', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.9888634109849955', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', '0.21022365182102054', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'john', '0.47052993358031114', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.25686122383263454', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.18929054223320718', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.7925339862375451', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', '0.12613308249498645', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.7381524971311578', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.08639585437319919', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.9519897106846164', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.33446548574801926', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.40667134603483324', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.17100718420628735', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.4445585525686886', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.47372916928883013', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.19826861093848824', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.13679268112019338', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.9805515708224516', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'dean', '0.4738376165601095', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.5739441073158964', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'alice', '0.8428505498030564', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', '0.32655416551155336', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.7055736367780644', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', '0.9621355090189875', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.9665339161730553', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.44309781869697995', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', '0.8651220802537761', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.6451892308277741', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.056797307451316725', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.6847604118085596', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.13428051757364667', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.9814797176951834', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', '0.7386074051153445', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.4825297824657663', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', '0.06608870508231235', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'lucy', '0.6278253028988848', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'alice', '0.6705580511822682', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', '0.8131712486302015', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.8124302447925607', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', '0.039935860913407284', 'p2'); diff --git a/launchers/standalone/src/main/resources/db/schema-h2.sql b/launchers/standalone/src/main/resources/db/schema-h2.sql new file mode 100644 index 000000000..e75454e90 --- /dev/null +++ b/launchers/standalone/src/main/resources/db/schema-h2.sql @@ -0,0 +1,341 @@ +-- chat tables +CREATE TABLE IF NOT EXISTS `s2_chat_context` +( + `chat_id` BIGINT NOT NULL , -- context chat id + `modified_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , -- row modify time + `user` varchar(64) DEFAULT NULL , -- row modify user + `query_text` LONGVARCHAR DEFAULT NULL , -- query text + `semantic_parse` LONGVARCHAR DEFAULT NULL , -- parse data + `ext_data` LONGVARCHAR DEFAULT NULL , -- extend data + PRIMARY KEY (`chat_id`) +); + +CREATE TABLE IF NOT EXISTS `s2_chat` +( + `chat_id` BIGINT auto_increment ,-- AUTO_INCREMENT, + `chat_name` varchar(100) DEFAULT NULL, + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , + `last_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , + `creator` varchar(30) DEFAULT NULL, + `last_question` varchar(200) DEFAULT NULL, + `is_delete` INT DEFAULT '0' COMMENT 'is deleted', + `is_top` INT DEFAULT '0' COMMENT 'is top', + PRIMARY KEY (`chat_id`) +) ; + + +CREATE TABLE `s2_chat_query` +( + `question_id` BIGINT NOT NULL AUTO_INCREMENT, + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `query_text` mediumtext, + `user_name` varchar(150) DEFAULT NULL COMMENT '', + `query_state` int(1) DEFAULT NULL, + `chat_id` BIGINT NOT NULL , -- context chat id + `query_response` mediumtext NOT NULL , + `score` int DEFAULT '0', + `feedback` varchar(1024) DEFAULT '', + PRIMARY KEY (`question_id`) +); + + + +CREATE TABLE IF NOT EXISTS `s2_chat_config` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT DEFAULT NULL , + `default_metrics` varchar(655) DEFAULT NULL, + `visibility` varchar(655) , -- invisible dimension metric information + `entity_info` varchar(655) , + `dictionary_info` varchar(655) , -- dictionary-related dimension setting information + `created_at` TIMESTAMP NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_by` varchar(100) NOT NULL , + `status` INT NOT NULL DEFAULT '0' , -- domain extension information status : 0 is normal, 1 is off the shelf, 2 is deleted + PRIMARY KEY (`id`) +) ; +COMMENT ON TABLE s2_chat_config IS 'chat config information table '; + + + + +CREATE TABLE IF NOT EXISTS `s2_dictionary` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `dim_value_infos` LONGVARCHAR , -- dimension value setting information + `created_at` TIMESTAMP NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_by` varchar(100) DEFAULT NULL , + `status` INT NOT NULL DEFAULT '0' , -- domain extension information status : 0 is normal, 1 is off the shelf, 2 is deleted + PRIMARY KEY (`id`), + UNIQUE (domain_id) + ); +COMMENT ON TABLE s2_dictionary IS 'dictionary configuration information table'; + + +CREATE TABLE IF NOT EXISTS `s2_dictionary_task` ( + `id` INT NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL , -- task name + `description` varchar(255) , + `command`LONGVARCHAR NOT NULL , -- task Request Parameters + `command_md5` varchar(255) NOT NULL , -- task Request Parameters md5 + `status` INT NOT NULL , -- the final status of the task + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP , + `created_by` varchar(100) NOT NULL , + `progress` DOUBLE default 0.00 , -- task real-time progress + `elapsed_ms` bigINT DEFAULT NULL , -- the task takes time in milliseconds + `message` LONGVARCHAR , -- remark related information + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_dictionary_task IS 'dictionary task information table'; + + +create table s2_user +( + id INT AUTO_INCREMENT, + name varchar(100) not null, + display_name varchar(100) null, + password varchar(100) null, + email varchar(100) null, + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_user IS 'user information table'; + +-- semantic tables + +CREATE TABLE IF NOT EXISTS `s2_domain` ( + `id` INT NOT NULL AUTO_INCREMENT , + `name` varchar(255) DEFAULT NULL , -- domain name + `biz_name` varchar(255) DEFAULT NULL , -- internal name + `parent_id` INT DEFAULT '0' , -- parent domain ID + `status` INT NOT NULL , + `created_at` TIMESTAMP DEFAULT NULL , + `created_by` varchar(100) DEFAULT NULL , + `updated_at` TIMESTAMP DEFAULT NULL , + `updated_by` varchar(100) DEFAULT NULL , + `is_unique` INT DEFAULT NULL , -- 0 is non-unique, 1 is unique + `admin` varchar(3000) DEFAULT NULL , -- domain administrator + `admin_org` varchar(3000) DEFAULT NULL , -- domain administrators organization + `is_open` TINYINT DEFAULT NULL , -- whether the domain is public + `viewer` varchar(3000) DEFAULT NULL , -- domain available users + `view_org` varchar(3000) DEFAULT NULL , -- domain available organization + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_domain IS 'domain basic information'; + + +CREATE TABLE `s2_database` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `description` varchar(500) DEFAULT NULL , + `type` varchar(20) NOT NULL , -- type: mysql,clickhouse,tdw + `config` varchar(655) NOT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL, + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_database IS 'database instance table'; + +CREATE TABLE IF NOT EXISTS `s2_datasource` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `biz_name` varchar(255) NOT NULL , + `description` varchar(500) DEFAULT NULL , + `database_id` INT NOT NULL , + `datasource_detail` LONGVARCHAR NOT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL, + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_datasource IS 'datasource table'; + +create table s2_auth_groups +( + group_id INT, + config varchar(2048), + PRIMARY KEY (`group_id`) +); + +CREATE TABLE IF NOT EXISTS `s2_metric` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `biz_name` varchar(255) NOT NULL , + `description` varchar(500) DEFAULT NULL , + `status` INT NOT NULL , -- status, 0 is normal, 1 is off the shelf, 2 is deleted + `sensitive_level` INT NOT NULL , + `type` varchar(50) NOT NULL , -- type proxy,expr + `type_params` LONGVARCHAR DEFAULT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL , + `data_format_type` varchar(50) DEFAULT NULL , + `data_format` varchar(500) DEFAULT NULL, + `alias` varchar(500) DEFAULT NULL, + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_metric IS 'metric information table'; + + +CREATE TABLE IF NOT EXISTS `s2_dimension` ( + `id` INT NOT NULL AUTO_INCREMENT , + `domain_id` INT NOT NULL , + `datasource_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `biz_name` varchar(255) NOT NULL , + `description` varchar(500) NOT NULL , + `status` INT NOT NULL , -- status, 0 is normal, 1 is off the shelf, 2 is deleted + `sensitive_level` INT DEFAULT NULL , + `type` varchar(50) NOT NULL , -- type categorical,time + `type_params` LONGVARCHAR DEFAULT NULL , + `expr` LONGVARCHAR NOT NULL , -- expression + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL , + `semantic_type` varchar(20) NOT NULL, -- semantic type: DATE, ID, CATEGORY + `alias` varchar(500) DEFAULT NULL, + `default_values` varchar(500) DEFAULT NULL, + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_dimension IS 'dimension information table'; + +create table s2_datasource_rela +( + id INT AUTO_INCREMENT, + domain_id INT null, + datasource_from INT null, + datasource_to INT null, + join_key varchar(100) null, + created_at TIMESTAMP null, + created_by varchar(100) null, + updated_at TIMESTAMP null, + updated_by varchar(100) null, + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_datasource_rela IS 'data source association table'; + +create table s2_view_info +( + id INT auto_increment, + domain_id INT null, + type varchar(20) null comment 'datasource、dimension、metric', + config LONGVARCHAR null comment 'config detail', + created_at TIMESTAMP null, + created_by varchar(100) null, + updated_at TIMESTAMP null, + updated_by varchar(100) not null +); +COMMENT ON TABLE s2_view_info IS 'view information table'; + + +CREATE TABLE `s2_query_stat_info` ( + `id` INT NOT NULL AUTO_INCREMENT, + `trace_id` varchar(200) DEFAULT NULL, -- query unique identifier + `domain_id` INT DEFAULT NULL, + `user` varchar(200) DEFAULT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP , + `query_type` varchar(200) DEFAULT NULL, -- the corresponding scene + `query_type_back` INT DEFAULT '0' , -- query type, 0-normal query, 1-pre-refresh type + `query_sql_cmd`LONGVARCHAR , -- sql type request parameter + `sql_cmd_md5` varchar(200) DEFAULT NULL, -- sql type request parameter md5 + `query_struct_cmd`LONGVARCHAR , -- struct type request parameter + `struct_cmd_md5` varchar(200) DEFAULT NULL, -- struct type request parameter md5值 + `sql`LONGVARCHAR , + `sql_md5` varchar(200) DEFAULT NULL, -- sql md5 + `query_engine` varchar(20) DEFAULT NULL, + `elapsed_ms` bigINT DEFAULT NULL, + `query_state` varchar(20) DEFAULT NULL, + `native_query` INT DEFAULT NULL, -- 1-detail query, 0-aggregation query + `start_date` varchar(50) DEFAULT NULL, + `end_date` varchar(50) DEFAULT NULL, + `dimensions`LONGVARCHAR , -- dimensions involved in sql + `metrics`LONGVARCHAR , -- metric involved in sql + `select_cols`LONGVARCHAR , + `agg_cols`LONGVARCHAR , + `filter_cols`LONGVARCHAR , + `group_by_cols`LONGVARCHAR , + `order_by_cols`LONGVARCHAR , + `use_result_cache` TINYINT DEFAULT '-1' , -- whether to hit the result cache + `use_sql_cache` TINYINT DEFAULT '-1' , -- whether to hit the sql cache + `sql_cache_key`LONGVARCHAR , -- sql cache key + `result_cache_key`LONGVARCHAR , -- result cache key + PRIMARY KEY (`id`) +) ; +COMMENT ON TABLE s2_query_stat_info IS 'query statistics table'; + + +CREATE TABLE IF NOT EXISTS `s2_semantic_pasre_info` ( + `id` INT NOT NULL AUTO_INCREMENT, + `trace_id` varchar(200) NOT NULL , + `domain_id` INT NOT NULL , + `dimensions`LONGVARCHAR , + `metrics`LONGVARCHAR , + `orders`LONGVARCHAR , + `filters`LONGVARCHAR , + `date_info`LONGVARCHAR , + `limit` INT NOT NULL , + `native_query` TINYINT NOT NULL DEFAULT '0' , + `sql`LONGVARCHAR , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `status` INT NOT NULL , + `elapsed_ms` bigINT DEFAULT NULL , + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_semantic_pasre_info IS 'semantic layer sql parsing information table'; + + +CREATE TABLE IF NOT EXISTS `s2_available_date_info` ( + `id` INT NOT NULL AUTO_INCREMENT , + `item_id` INT NOT NULL , + `type` varchar(255) NOT NULL , + `date_format` varchar(64) NOT NULL , + `start_date` varchar(64) , + `end_date` varchar(64) , + `unavailable_date` LONGVARCHAR DEFAULT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL , + `status` INT DEFAULT '0', -- 1-in use 0 is normal, 1 is off the shelf, 2 is deleted + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_dimension IS 'dimension information table'; + + +-------demo for semantic and chat +CREATE TABLE IF NOT EXISTS `s2_user_department` ( + `user_name` varchar(200) NOT NULL, + `department` varchar(200) NOT NULL -- department of user + ); +COMMENT ON TABLE s2_user_department IS 'user_department_info'; + +CREATE TABLE IF NOT EXISTS `s2_pv_uv_statis` ( + `imp_date` varchar(200) NOT NULL, + `user_name` varchar(200) NOT NULL, + `page` varchar(200) NOT NULL + ); +COMMENT ON TABLE s2_pv_uv_statis IS 's2_pv_uv_statis'; + +CREATE TABLE IF NOT EXISTS `s2_stay_time_statis` ( + `imp_date` varchar(200) NOT NULL, + `user_name` varchar(200) NOT NULL, + `stay_hours` DOUBLE NOT NULL, + `page` varchar(200) NOT NULL + ); +COMMENT ON TABLE s2_stay_time_statis IS 's2_stay_time_statis_info'; + + + + + + diff --git a/launchers/standalone/src/main/resources/hanlp.properties b/launchers/standalone/src/main/resources/hanlp.properties new file mode 100644 index 000000000..9d91904eb --- /dev/null +++ b/launchers/standalone/src/main/resources/hanlp.properties @@ -0,0 +1,2 @@ +root=. +CustomDictionaryPath=data/dictionary/custom/DimValue_1_1.txt;data/dictionary/custom/DimValue_1_2.txt;data/dictionary/custom/DimValue_1_3.txt; \ No newline at end of file diff --git a/launchers/standalone/src/main/resources/logback-spring.xml b/launchers/standalone/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..810eda9b3 --- /dev/null +++ b/launchers/standalone/src/main/resources/logback-spring.xml @@ -0,0 +1,93 @@ + + + logback + + + + + + + %d{HH:mm:ss} [%thread] %-5level %logger{36} %line - %msg%n + + + + + + ${LOG_PATH}/info.${LOG_APPNAME}.log + + + + ${LOG_PATH}/info.${LOG_APPNAME}.%d{yyyy-MM-dd}.log.gz + + 30 + + + + + + UTF-8 + %d [%thread] %-5level [%X{TRACE_ID}] %logger{36} %line - %msg%n + + + + + + + Error + + + ${LOG_PATH}/error.${LOG_APPNAME}.log + + + + ${LOG_PATH}/error.${LOG_APPNAME}.%d{yyyy-MM-dd}.log.gz + + 90 + + + + + + UTF-8 + %d [%thread] %-5level [%X{TRACE_ID}] %logger{36} %line - %msg%n + + + + + + + + + + + + ${LOG_PATH}/serviceinfo.${LOG_APPNAME}.log + + + + ${LOG_PATH}/serviceinfo.${LOG_APPNAME}.%d{yyyy-MM-dd}.log.gz + + 30 + + + + + + UTF-8 + %d [%thread] %-5level [%X{TRACE_ID}] %logger{36} %line - %msg%n + + + + + + + \ No newline at end of file diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/db/MybatisConfig.java b/launchers/standalone/src/test/java/com/tencent/supersonic/db/MybatisConfig.java new file mode 100644 index 000000000..f3428741a --- /dev/null +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/db/MybatisConfig.java @@ -0,0 +1,31 @@ +package com.tencent.supersonic.db; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import javax.sql.DataSource; + + +@Configuration +@MapperScan(value = "com.tencent.supersonic", annotationClass = Mapper.class) +public class MybatisConfig { + + private static final String MAPPER_LOCATION = "classpath*:mapper/**/*.xml"; + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + bean.setConfiguration(configuration); + bean.setDataSource(dataSource); + + bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION)); + return bean.getObject(); + } +} diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/test/QueryIntegrationTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/test/QueryIntegrationTest.java new file mode 100644 index 000000000..14bf6ff77 --- /dev/null +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/test/QueryIntegrationTest.java @@ -0,0 +1,371 @@ +package com.tencent.supersonic.test; + +import com.tencent.supersonic.StandaloneLauncher; +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.pojo.Filter; +import com.tencent.supersonic.chat.api.request.QueryContextReq; +import com.tencent.supersonic.chat.api.response.QueryResultResp; +import com.tencent.supersonic.chat.application.query.MetricCompare; +import com.tencent.supersonic.chat.application.query.MetricDomain; +import com.tencent.supersonic.chat.application.query.MetricFilter; +import com.tencent.supersonic.chat.application.query.MetricGroupBy; +import com.tencent.supersonic.chat.domain.service.ChatService; +import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.common.pojo.SchemaItem; +import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import com.tencent.supersonic.chat.domain.service.QueryService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = StandaloneLauncher.class) +@ActiveProfiles("local") +public class QueryIntegrationTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(QueryIntegrationTest.class); + + @Autowired + private ChatService chatService; + + @Autowired + @Qualifier("chatQueryService") + private QueryService queryService; + + //case:alice的访问次数,queryMode:METRIC_FILTER, + @Test + public void queryTest1() { + QueryContextReq queryContextReq = getQueryContextReq("alice的访问次数"); + try { + QueryResultResp queryResultResp = queryService.executeQuery(queryContextReq); + LOGGER.info("QueryResultResp queryResultResp:{}", queryResultResp); + //assert queryState + Assert.assertEquals(queryResultResp.getQueryState(), 0); + //assert queryMode + Assert.assertEquals(queryResultResp.getQueryMode(), MetricFilter.QUERY_MODE); + //assert aggType + Assert.assertEquals(queryResultResp.getChatContext().getAggType(), null); + //assert 主题域Id + assertThat(queryResultResp.getChatContext().getDomainId()).isEqualTo(1L); + + //assert 指标 + Set metrics = queryResultResp.getChatContext().getMetrics(); + SchemaItem schemaItemMetric = new SchemaItem(); + schemaItemMetric.setId(2L); + schemaItemMetric.setName("访问次数"); + schemaItemMetric.setBizName("pv"); + Boolean metricExist = false; + for (SchemaItem schemaItem : metrics) { + if (schemaItem.getId().equals(schemaItemMetric.getId()) && + schemaItem.getName().equals(schemaItemMetric.getName()) && + schemaItem.getBizName().equals(schemaItemMetric.getBizName())) { + metricExist = true; + } + } + assertThat(metricExist).isEqualTo(true); + + //assert 维度 + Set dimensions = queryResultResp.getChatContext().getDimensions(); + SchemaItem schemaItemDimension = new SchemaItem(); + schemaItemDimension.setBizName("sys_imp_date"); + Boolean dimensionExist = false; + for (SchemaItem schemaItem : dimensions) { + if (schemaItem.getBizName().equals(schemaItemDimension.getBizName())) { + dimensionExist = true; + } + } + assertThat(dimensionExist).isEqualTo(true); + + //assert 维度filter + Set dimensionFilters = queryResultResp.getChatContext().getDimensionFilters(); + Filter dimensionFilter = new Filter(); + dimensionFilter.setBizName("user_name"); + dimensionFilter.setOperator(FilterOperatorEnum.EQUALS); + dimensionFilter.setValue("alice"); + dimensionFilter.setName("用户名"); + dimensionFilter.setElementID(2L); + Boolean dimensionFilterExist = false; + for (Filter filter : dimensionFilters) { + if (filter.getBizName().equals(dimensionFilter.getBizName()) && + filter.getOperator().equals(dimensionFilter.getOperator()) && + filter.getValue().equals(dimensionFilter.getValue()) && + filter.getElementID().equals(dimensionFilter.getElementID()) && + filter.getName().equals(dimensionFilter.getName())) { + dimensionFilterExist = true; + } + } + assertThat(dimensionFilterExist).isEqualTo(true); + + //assert 时间filter + DateConf dateInfo = new DateConf(); + dateInfo.setUnit(7); + dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setPeriod("DAY"); + Boolean timeFilterExist = + queryResultResp.getChatContext().getDateInfo().getUnit().equals(dateInfo.getUnit()) && + queryResultResp.getChatContext().getDateInfo().getDateMode().equals(dateInfo.getDateMode()) + && + queryResultResp.getChatContext().getDateInfo().getPeriod().equals(dateInfo.getPeriod()); + assertThat(timeFilterExist).isEqualTo(true); + + //assert nativeQuery + assertThat(queryResultResp.getChatContext().getNativeQuery()).isEqualTo(false); + + } catch (Exception e) { + + } + } + + //case:超音数的访问次数,queryMode:METRIC_DOMAIN + @Test + public void queryTest2() { + QueryContextReq queryContextReq = getQueryContextReq("超音数的访问次数"); + try { + QueryResultResp queryResultResp = queryService.executeQuery(queryContextReq); + LOGGER.info("QueryResultResp queryResultResp:{}", queryResultResp); + //assert queryState + Assert.assertEquals(queryResultResp.getQueryState(), 0); + //assert queryMode + Assert.assertEquals(queryResultResp.getQueryMode(), MetricDomain.QUERY_MODE); + //assert aggType + Assert.assertEquals(queryResultResp.getChatContext().getAggType(), null); + //assert 主题域Id + assertThat(queryResultResp.getChatContext().getDomainId()).isEqualTo(1L); + + //assert 指标 + Set metrics = queryResultResp.getChatContext().getMetrics(); + SchemaItem schemaItemMetric = new SchemaItem(); + schemaItemMetric.setId(2L); + schemaItemMetric.setName("访问次数"); + schemaItemMetric.setBizName("pv"); + Boolean metricExist = false; + for (SchemaItem schemaItem : metrics) { + if (schemaItem.getId().equals(schemaItemMetric.getId()) && + schemaItem.getName().equals(schemaItemMetric.getName()) && + schemaItem.getBizName().equals(schemaItemMetric.getBizName())) { + metricExist = true; + } + } + assertThat(metricExist).isEqualTo(true); + + //assert 维度 + Set dimensions = queryResultResp.getChatContext().getDimensions(); + SchemaItem schemaItemDimension = new SchemaItem(); + schemaItemDimension.setBizName("sys_imp_date"); + Boolean dimensionExist = false; + for (SchemaItem schemaItem : dimensions) { + if (schemaItem.getBizName().equals(schemaItemDimension.getBizName())) { + dimensionExist = true; + } + } + assertThat(dimensionExist).isEqualTo(true); + + //assert 时间filter + DateConf dateInfo = new DateConf(); + dateInfo.setUnit(7); + dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setPeriod("DAY"); + Boolean timeFilterExist = + queryResultResp.getChatContext().getDateInfo().getUnit().equals(dateInfo.getUnit()) && + queryResultResp.getChatContext().getDateInfo().getDateMode().equals(dateInfo.getDateMode()) + && + queryResultResp.getChatContext().getDateInfo().getPeriod().equals(dateInfo.getPeriod()); + assertThat(timeFilterExist).isEqualTo(true); + + //assert nativeQuery + assertThat(queryResultResp.getChatContext().getNativeQuery()).isEqualTo(false); + + } catch (Exception e) { + + } + } + + + //case:超音数各部门的访问次数,queryMode:METRIC_GROUPBY + @Test + public void queryTest3() { + QueryContextReq queryContextReq = getQueryContextReq("超音数各部门的访问次数"); + try { + QueryResultResp queryResultResp = queryService.executeQuery(queryContextReq); + LOGGER.info("QueryResultResp queryResultResp:{}", queryResultResp); + //assert queryState + Assert.assertEquals(queryResultResp.getQueryState(), 0); + //assert queryMode + Assert.assertEquals(queryResultResp.getQueryMode(), MetricGroupBy.QUERY_MODE); + //assert aggType + Assert.assertEquals(queryResultResp.getChatContext().getAggType(), null); + //assert 主题域Id + assertThat(queryResultResp.getChatContext().getDomainId()).isEqualTo(1L); + + //assert 指标 + Set metrics = queryResultResp.getChatContext().getMetrics(); + SchemaItem schemaItemMetric = new SchemaItem(); + schemaItemMetric.setId(2L); + schemaItemMetric.setName("访问次数"); + schemaItemMetric.setBizName("pv"); + Boolean metricExist = false; + for (SchemaItem schemaItem : metrics) { + if (schemaItem.getId().equals(schemaItemMetric.getId()) && + schemaItem.getName().equals(schemaItemMetric.getName()) && + schemaItem.getBizName().equals(schemaItemMetric.getBizName())) { + metricExist = true; + } + } + assertThat(metricExist).isEqualTo(true); + + //assert 维度 + Set dimensions = queryResultResp.getChatContext().getDimensions(); + SchemaItem schemaItemDimension = new SchemaItem(); + schemaItemDimension.setBizName("sys_imp_date"); + Boolean dimensionExist = false; + for (SchemaItem schemaItem : dimensions) { + if (schemaItem.getBizName().equals(schemaItemDimension.getBizName())) { + dimensionExist = true; + } + } + assertThat(dimensionExist).isEqualTo(true); + + SchemaItem schemaItemDimension1 = new SchemaItem(); + schemaItemDimension1.setBizName("department"); + schemaItemDimension1.setName("部门"); + schemaItemDimension1.setId(1L); + Boolean dimensionExist1 = false; + for (SchemaItem schemaItem : dimensions) { + if (schemaItem.getBizName().equals(schemaItemDimension1.getBizName()) && + schemaItem.getId().equals(schemaItemDimension1.getId()) && + schemaItem.getName().equals(schemaItemDimension1.getName())) { + dimensionExist1 = true; + } + } + assertThat(dimensionExist1).isEqualTo(true); + + //assert 时间filter + DateConf dateInfo = new DateConf(); + dateInfo.setUnit(7); + dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setPeriod("DAY"); + Boolean timeFilterExist = + queryResultResp.getChatContext().getDateInfo().getUnit().equals(dateInfo.getUnit()) && + queryResultResp.getChatContext().getDateInfo().getDateMode().equals(dateInfo.getDateMode()) + && + queryResultResp.getChatContext().getDateInfo().getPeriod().equals(dateInfo.getPeriod()); + assertThat(timeFilterExist).isEqualTo(true); + + //assert nativeQuery + assertThat(queryResultResp.getChatContext().getNativeQuery()).isEqualTo(false); + + } catch (Exception e) { + + } + } + + + //case:对比alice和lucy的访问次数,queryMode:METRIC_COMPARE + @Test + public void queryTest4() { + QueryContextReq queryContextReq = getQueryContextReq("对比alice和lucy的访问次数"); + try { + QueryResultResp queryResultResp = queryService.executeQuery(queryContextReq); + LOGGER.info("QueryResultResp queryResultResp:{}", queryResultResp); + //assert queryState + Assert.assertEquals(queryResultResp.getQueryState(), 0); + //assert queryMode + Assert.assertEquals(queryResultResp.getQueryMode(), MetricCompare.QUERY_MODE); + //assert aggType + Assert.assertEquals(queryResultResp.getChatContext().getAggType(), null); + //assert 主题域Id + assertThat(queryResultResp.getChatContext().getDomainId()).isEqualTo(1L); + + //assert 指标 + Set metrics = queryResultResp.getChatContext().getMetrics(); + SchemaItem schemaItemMetric = new SchemaItem(); + schemaItemMetric.setId(2L); + schemaItemMetric.setName("访问次数"); + schemaItemMetric.setBizName("pv"); + Boolean metricExist = false; + for (SchemaItem schemaItem : metrics) { + if (schemaItem.getId().equals(schemaItemMetric.getId()) && + schemaItem.getName().equals(schemaItemMetric.getName()) && + schemaItem.getBizName().equals(schemaItemMetric.getBizName())) { + metricExist = true; + } + } + assertThat(metricExist).isEqualTo(true); + + //assert 维度 + Set dimensions = queryResultResp.getChatContext().getDimensions(); + SchemaItem schemaItemDimension = new SchemaItem(); + schemaItemDimension.setBizName("sys_imp_date"); + Boolean dimensionExist = false; + for (SchemaItem schemaItem : dimensions) { + if (schemaItem.getBizName().equals(schemaItemDimension.getBizName())) { + dimensionExist = true; + } + } + assertThat(dimensionExist).isEqualTo(true); + + //assert 维度filter + Set dimensionFilters = queryResultResp.getChatContext().getDimensionFilters(); + Filter dimensionFilter = new Filter(); + dimensionFilter.setBizName("user_name"); + dimensionFilter.setOperator(FilterOperatorEnum.IN); + List list = new ArrayList<>(); + list.add("alice"); + list.add("lucy"); + dimensionFilter.setValue(list); + dimensionFilter.setName("用户名"); + dimensionFilter.setElementID(2L); + Boolean dimensionFilterExist = false; + for (Filter filter : dimensionFilters) { + if (filter.getBizName().equals(dimensionFilter.getBizName()) && + filter.getOperator().equals(dimensionFilter.getOperator()) && + filter.getValue().toString().equals(dimensionFilter.getValue().toString()) && + filter.getElementID().equals(dimensionFilter.getElementID()) && + filter.getName().equals(dimensionFilter.getName())) { + dimensionFilterExist = true; + } + } + assertThat(dimensionFilterExist).isEqualTo(true); + + //assert 时间filter + DateConf dateInfo = new DateConf(); + dateInfo.setUnit(7); + dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setPeriod("DAY"); + Boolean timeFilterExist = + queryResultResp.getChatContext().getDateInfo().getUnit().equals(dateInfo.getUnit()) && + queryResultResp.getChatContext().getDateInfo().getDateMode().equals(dateInfo.getDateMode()) + && + queryResultResp.getChatContext().getDateInfo().getPeriod().equals(dateInfo.getPeriod()); + assertThat(timeFilterExist).isEqualTo(true); + + //assert nativeQuery + assertThat(queryResultResp.getChatContext().getNativeQuery()).isEqualTo(false); + + } catch (Exception e) { + + } + } + + public QueryContextReq getQueryContextReq(String query) { + QueryContextReq queryContextReq = new QueryContextReq(); + queryContextReq.setQueryText(query);//"alice的访问次数" + queryContextReq.setChatId(1); + queryContextReq.setUser(new User(1L, "admin", "admin", "admin@email")); + return queryContextReq; + } + +} diff --git a/launchers/standalone/src/test/resources/META-INF/spring.factories b/launchers/standalone/src/test/resources/META-INF/spring.factories new file mode 100644 index 000000000..bc01631d4 --- /dev/null +++ b/launchers/standalone/src/test/resources/META-INF/spring.factories @@ -0,0 +1,23 @@ +com.tencent.supersonic.chat.api.component.SchemaMapper=\ + com.tencent.supersonic.chat.application.mapper.HanlpSchemaMapper + +com.tencent.supersonic.chat.api.component.SemanticParser=\ + com.tencent.supersonic.chat.application.parser.DomainSemanticParser, \ + com.tencent.supersonic.chat.application.parser.TimeSemanticParser, \ + com.tencent.supersonic.chat.application.parser.AggregateSemanticParser +# com.tencent.supersonic.chat.application.parser.LLMSemanticParser + +com.tencent.supersonic.chat.api.component.QueryProcessor=\ + com.tencent.supersonic.chat.application.processor.SemanticQueryProcessor + +com.tencent.supersonic.chat.api.component.SemanticLayer=\ + com.tencent.supersonic.chat.infrastructure.semantic.LocalSemanticLayerImpl + +com.tencent.supersonic.chat.application.query.QuerySelector=\ + com.tencent.supersonic.chat.application.query.HeuristicQuerySelector + +com.tencent.supersonic.chat.application.query.DomainResolver=\ + com.tencent.supersonic.chat.application.query.HeuristicDomainResolver + +com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ + com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor diff --git a/launchers/standalone/src/test/resources/application-local.yaml b/launchers/standalone/src/test/resources/application-local.yaml new file mode 100644 index 000000000..4c029c184 --- /dev/null +++ b/launchers/standalone/src/test/resources/application-local.yaml @@ -0,0 +1,28 @@ +spring: + h2: + console: + path: /h2-console/chat + # enabled web + enabled: true + datasource: + driver-class-name: org.h2.Driver + schema: classpath:db/schema-h2.sql + data: classpath:db/data-h2.sql + url: jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false + username: root + password: semantic + +server: + port: 9080 + +authentication: + enable: false + exclude: + path: /api/auth/user/register,/api/auth/user/login + +semantic: + url: + prefix: http://127.0.0.1:9081 + +mybatis: + mapper-locations=classpath:mappers/custom/*.xml,classpath*:/mappers/*.xml diff --git a/launchers/standalone/src/test/resources/application.yaml b/launchers/standalone/src/test/resources/application.yaml new file mode 100644 index 000000000..83b731d26 --- /dev/null +++ b/launchers/standalone/src/test/resources/application.yaml @@ -0,0 +1,7 @@ +spring: + profiles: + active: local + application: + name: chat +mybatis: + mapper-locations=classpath:mappers/custom/*.xml,classpath*:/mappers/*.xml diff --git a/launchers/standalone/src/test/resources/data/README.url b/launchers/standalone/src/test/resources/data/README.url new file mode 100644 index 000000000..e37374f27 --- /dev/null +++ b/launchers/standalone/src/test/resources/data/README.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://github.com/hankcs/HanLP/ diff --git a/launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.mini.txt b/launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.mini.txt new file mode 100644 index 000000000..6014daa6e --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.mini.txt @@ -0,0 +1,3 @@ +龚 nr 1 +龛 ng 1 +龛影 n 1 \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt b/launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt new file mode 100644 index 000000000..562f7f1cc --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/CoreNatureDictionary.ngram.mini.txt @@ -0,0 +1,4 @@ +买@水果 1 +然后@来 1 +我@遗忘 10 +遗忘@我 10 \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_1.txt b/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_1.txt new file mode 100644 index 000000000..c21ae2edb --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_1.txt @@ -0,0 +1,5 @@ +hr _1_1 876 +sales _1_1 872 +marketing _1_1 310 +strategy _1_1 360 +sales _1_1 500 \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_2.txt b/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_2.txt new file mode 100644 index 000000000..e0570f4b0 --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_2.txt @@ -0,0 +1,7 @@ +tom _1_2 52 +alice _1_2 47 +lucy _1_2 31 +dean _1_2 36 +john _1_2 50 +jack _1_2 38 +admin _1_2 70 \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_3.txt b/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_3.txt new file mode 100644 index 000000000..f519c23fb --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/custom/DimValue_1_3.txt @@ -0,0 +1,6 @@ +p1 _1_3 52 +p2 _1_3 47 +p3 _1_3 31 +p4 _1_3 36 +p5 _1_3 50 +p6 _1_3 38 \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/data/dictionary/other/CharTable.txt b/launchers/standalone/src/test/resources/data/dictionary/other/CharTable.txt new file mode 100644 index 000000000..a66a33e07 --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/other/CharTable.txt @@ -0,0 +1,4890 @@ += +A=a +B=b +C=c +D=d +E=e +F=f +G=g +H=h +I=i +J=j +K=k +L=l +M=m +N=n +O=o +P=p +Q=q +R=r +S=s +T=t +U=u +V=v +W=w +X=x +Y=y +Z=z +[=《 +]=》 +{=《 +}=》 + = +«=《 +»=》 +“=" +”=" +•=· +‹=《 +›=》 +①=一 +②=二 +③=三 +④=四 +⑤=五 +⑥=六 +⑦=七 +⑧=八 +⑨=九 +⑩=十 +〈=《 +〉=》 +「=“ +」=” +『=‘ +』=’ +【=《 +】=》 +〔=《 +〕=》 +〖=《 +〗=" +〝=" +〞=" +と=之 +ふ=子 +ル=儿 +ㄖ=日 +丟=丢 +両=两 +並=并 +丼=井 +乁=乙 +乗=乘 +乧=斗 +乷=沙 +乹=乾 +乾=干 +亀=龟 +亁=乾 +亂=乱 +亙=亘 +亝=斋 +亞=亚 +亯=享 +亱=夜 +亷=廉 +亾=亡 +仈=八 +仏=佛 +仛=托 +仩=上 +仯=秒 +仴=月 +仸=袄 +仹=丰 +仺=仓 +伕=夫 +伖=友 +伝=传 +伮=奴 +佀=侣 +佇=伫 +佋=召 +佔=占 +佘=畲 +佡=仙 +佪=徊 +佱=企 +佲=铭 +併=并 +佷=很 +佹=危 +佽=次 +侀=型 +來=来 +侇=姨 +侎=眯 +侓=律 +侖=仑 +侢=再 +侶=侣 +侷=局 +侹=廷 +俁=俣 +係=系 +俆=徐 +俔=伣 +俠=侠 +俢=修 +俥=伡 +俬=私 +俻=备 +俽=欣 +倀=伥 +倁=蜘 +倂=并 +倆=俩 +倈=俫 +倉=仓 +個=个 +倐=倏 +們=们 +倖=幸 +倞=京 +倣=仿 +倫=伦 +倳=事 +倶=具 +倸=睬 +倹=俭 +倻=椰 +倽=啥 +偅=重 +偉=伟 +偓=屋 +偘=侃 +偡=湛 +偢=秋 +偪=逼 +偲=思 +側=侧 +偵=侦 +偸=偷 +偺=咱 +偽=伪 +傑=杰 +傓=扇 +傖=伧 +傘=伞 +備=备 +傚=效 +傢=家 +傪=参 +傭=佣 +傯=偬 +傳=传 +傴=伛 +債=债 +傷=伤 +傹=镜 +傾=倾 +僂=偻 +僅=仅 +僉=佥 +僊=仙 +働=動 +僐=善 +僑=侨 +僒=窘 +僕=仆 +僜=澄 +僞=伪 +僟=机 +僥=侥 +僨=偾 +僮=童 +僱=雇 +價=价 +僽=愁 +儀=仪 +儁=俊 +儂=侬 +億=亿 +儈=侩 +儉=俭 +儌=侥 +儐=傧 +儔=俦 +儕=侪 +儘=尽 +償=偿 +優=优 +儭=亲 +儲=储 +儵=倏 +儷=俪 +儸=箩 +儹=攒 +儺=傩 +儻=傥 +儼=俨 +兇=凶 +兌=兑 +兎=兔 +兒=儿 +兗=兖 +兠=兜 +內=内 +兩=两 +兯=节 +兲=天 +兿=艺 +冄=冉 +冇=没 +冊=册 +冋=回 +冐=冒 +冨=富 +冩=写 +冪=幂 +冴=讶 +冺=泯 +凂=免 +凃=涂 +凅=固 +凈=净 +凊=清 +凍=冻 +凗=摧 +凘=斯 +凜=凛 +凞=熙 +凢=几 +凣=凡 +処=处 +凧=巾 +凱=凯 +凲=兼 +凴=凭 +凾=涵 +刄=刃 +刅=办 +刋=刊 +別=别 +刦=劫 +刧=劫 +刪=删 +刴=剁 +刼=劫 +剄=刭 +則=则 +剋=克 +剎=刹 +剏=创 +剗=刬 +剘=期 +剙=創 +剛=刚 +剝=剥 +剨=割 +剮=剐 +剳=劄 +剴=剀 +創=创 +剷=铲 +剹=戮 +剼=删 +劃=划 +劄=札 +劇=剧 +劈=噼 +劉=刘 +劊=刽 +劌=刿 +劍=剑 +劑=剂 +劒=剑 +劦=力 +効=效 +勁=劲 +勄=敏 +勅=敕 +勌=倦 +動=动 +勗=勖 +務=务 +勛=勋 +勝=胜 +勞=劳 +勠=戮 +勢=势 +勦=剿 +勧=劝 +勩=勚 +勱=劢 +勳=勋 +勵=励 +勸=劝 +勻=匀 +勼=九 +匄=亡 +匊=菊 +匋=掏 +匑=躬 +匢=勿 +匨=壮 +匬=愈 +匭=匦 +匯=汇 +匱=匮 +匲=奁 +匳=奁 +匴=算 +匵=椟 +匼=合 +匽=宴 +區=区 +卂=汛 +卆=杂 +協=协 +卙=甚 +卛=率 +卬=仰 +卲=邵 +卹=恤 +卻=却 +卽=即 +厊=芽 +厐=庞 +厔=室 +厗=辛 +厙=厍 +厛=听 +厞=匪 +厠=厕 +厡=原 +厤=历 +厫=廒 +厭=厌 +厯=历 +厰=厂 +厲=厉 +厴=厣 +厷=公 +厾=去 +參=参 +叄=叁 +収=收 +叒=双 +叚=假 +叜=叟 +叡=睿 +叢=丛 +叧=另 +叺=入 +吂=盲 +吒=咤 +吘=午 +吚=咿 +吢=吣 +吳=吴 +吶=呐 +吷=决 +吿=告 +呁=钧 +呂=吕 +呉=吴 +呌=叫 +呎=迟 +呞=司 +呪=咒 +呮=只 +呱=哌 +呴=句 +呺=号 +咊=和 +咑=打 +咓=瓦 +咗=左 +咜=它 +咟=百 +咮=珠 +咰=询 +咷=啕 +咼=呙 +哃=同 +哋=的 +哠=告 +員=员 +哢=咔 +哣=痘 +哬=呵 +哯=现 +哴=琅 +哾=悦 +唂=谷 +唄=呗 +唍=完 +唎=例 +唕=唣 +唘=启 +唚=吣 +唡=俩 +唥=冷 +唦=砂 +唫=金 +唰=刷 +唵=俺 +唶=锡 +唸=念 +唻=来 +唽=析 +啇=商 +啌=控 +啍=享 +啎=忤 +問=问 +啑=捷 +啓=启 +啗=啖 +啘=婉 +啚=鄙 +啝=和 +啞=哑 +啟=启 +啢=唡 +啣=衔 +啨=晴 +啩=挂 +啱=岩 +啴=单 +喎=㖞 +喐=郁 +喒=咱 +喖=枯 +喚=唤 +喥=度 +喦=岩 +喪=丧 +喫=吃 +喬=乔 +單=单 +喰=食 +喲=哟 +営=宫 +喼=急 +嗁=啼 +嗆=呛 +嗇=啬 +嗊=唝 +嗎=吗 +嗏=茶 +嗐=害 +嗗=骨 +嗚=呜 +嗛=谦 +嗩=唢 +嗬=呵 +嗱=拿 +嗶=哔 +嗹=莲 +嗻=遮 +嗼=摸 +嘂=叫 +嘄=鸣 +嘆=叹 +嘋=教 +嘍=喽 +嘓=啯 +嘔=呕 +嘖=啧 +嘗=尝 +嘙=婆 +嘚=得 +嘜=唛 +嘠=嘎 +嘢=野 +嘩=哗 +嘫=然 +嘮=唠 +嘯=啸 +嘰=叽 +嘵=哓 +嘸=呒 +嘽=啴 +噂=遵 +噅=咴 +噉=啖 +噐=器 +噑=嗥 +噓=嘘 +噖=琴 +噝=咝 +噠=哒 +噥=哝 +噦=哕 +噭=激 +噯=嗳 +噲=哙 +噴=喷 +噸=吨 +噹=当 +噺=新 +嚀=咛 +嚂=滥 +嚇=吓 +嚌=哜 +嚐=尝 +嚕=噜 +嚗=爆 +嚙=啮 +嚜=墨 +嚠=刘 +嚡=鞋 +嚤=蘑 +嚦=呖 +嚨=咙 +嚮=向 +嚲=亸 +嚳=喾 +嚴=严 +嚶=嘤 +囀=啭 +囁=嗫 +囂=嚣 +囅=冁 +囈=呓 +囉=啰 +囑=嘱 +囓=啮 +囗=口 +囘=回 +囙=因 +囥=亢 +囩=云 +囪=囱 +囬=回 +囮=化 +囯=国 +囸=正 +圅=函 +圇=囵 +國=国 +圍=围 +圎=园 +園=园 +圓=圆 +圖=图 +團=团 +圝=圞 +圤=扑 +圧=庄 +圱=升 +圵=止 +圷=吓 +坆=玫 +坧=石 +坰=垧 +坵=丘 +坿=附 +垇=坳 +垉=咆 +垐=茨 +垜=垛 +垨=守 +垯=达 +垳=行 +垵=埯 +垹=绑 +垿=序 +埐=侵 +埖=花 +埜=野 +埡=垭 +埥=请 +埰=采 +埱=叔 +埳=坎 +執=执 +堅=坚 +堊=垩 +堒=坤 +堔=深 +堖=垴 +堘=塍 +堝=埚 +堦=階 +堯=尧 +報=报 +場=场 +堷=音 +堿=碱 +塆=弯 +塊=块 +塋=茔 +塏=垲 +塒=埘 +塖=乘 +塗=涂 +塚=冢 +塟=葬 +塡=填 +塢=坞 +塤=埙 +塨=恭 +塮=谢 +塲=场 +塵=尘 +塹=堑 +塼=砖 +墊=埝 +墖=塔 +墘=乾 +墛=蔚 +墜=坠 +墪=墩 +墭=盛 +墮=堕 +墳=坟 +墵=坛 +墶=垯 +墻=墙 +墾=垦 +壃=僵 +壄=野 +壇=坛 +壋=垱 +壎=埙 +壓=压 +壖=堧 +壘=垒 +壙=圹 +壚=垆 +壜=坛 +壞=坏 +壟=垅 +壠=垅 +壢=坜 +壩=坝 +壪=塆 +壯=壮 +壺=壶 +壻=婿 +壼=壸 +壽=寿 +壿=蹲 +夀=寿 +夃=孕 +夅=降 +夌=菱 +夗=苑 +夘=卯 +夝=胜 +夠=够 +夢=梦 +夥=伙 +夰=介 +夲=本 +夾=夹 +奐=奂 +奧=奥 +奨=奖 +奩=奁 +奪=夺 +奬=奖 +奮=奋 +奷=奸 +奼=姹 +妀=改 +妏=文 +妑=芭 +妔=坑 +妕=钟 +妝=妆 +妠=呐 +妢=纷 +妬=妒 +妭=拨 +妱=招 +妳=你 +妶=弦 +妷=失 +妸=可 +妺=妹 +妽=申 +姃=征 +姄=民 +姉=姊 +姌=冉 +姍=姗 +姎=央 +姖=巨 +姙=妊 +姟=该 +姠=响 +姦=奸 +姧=歼 +姩=年 +姪=侄 +姵=佩 +姸=妍 +姺=先 +娕=束 +娖=促 +娛=娱 +娝=否 +娦=兵 +娪=语 +娫=延 +娬=武 +娯=娱 +娸=其 +娿=啊 +婁=娄 +婂=锦 +婄=赔 +婇=菜 +婑=矮 +婔=菲 +婖=添 +婜=娶 +婣=姻 +婤=稠 +婥=卓 +婦=妇 +婫=混 +婬=淫 +婭=娅 +婯=丽 +婸=扬 +婹=要 +婼=若 +媈=挥 +媌=苗 +媍=妇 +媔=面 +媗=喧 +媙=威 +媟=谍 +媠=惰 +媣=染 +媥=偏 +媦=胃 +媧=娲 +媨=酋 +媫=婕 +媯=妫 +媴=袁 +媷=辱 +媹=溜 +媼=媪 +媽=妈 +媿=愧 +嫃=真 +嫆=蓉 +嫊=素 +嫋=袅 +嫎=膀 +嫗=妪 +嫙=旋 +嫚=蔓 +嫝=康 +嫟=匿 +嫧=责 +嫰=嫩 +嫲=麻 +嫵=妩 +嫺=娴 +嫻=娴 +嫼=黑 +嫽=撩 +嫿=婳 +嬀=妫 +嬈=娆 +嬋=婵 +嬌=娇 +嬑=意 +嬘=遂 +嬙=嫱 +嬝=袅 +嬡=嫒 +嬤=嬷 +嬪=嫔 +嬭=奶 +嬰=婴 +嬶=鼻 +嬸=婶 +嬾=懒 +嬿=燕 +孃=娘 +孄=栏 +孌=娈 +孒=了 +孡=抬 +孧=幼 +孫=孙 +孶=孳 +學=学 +孼=孽 +孿=孪 +宂=冗 +宖=宏 +宮=宫 +宼=寇 +寀=采 +寃=冤 +寑=寝 +寕=宁 +寢=寝 +實=实 +寧=宁 +審=审 +寫=写 +寬=宽 +寲=疑 +寳=宝 +寵=宠 +寶=宝 +尅=克 +將=将 +專=专 +尋=寻 +對=对 +導=导 +尒=尔 +尙=尚 +尟=鲜 +尠=鲜 +尩=尪 +尫=尪 +尲=尴 +尷=尴 +屆=届 +屍=尸 +屓=屃 +屗=尾 +屙=疴 +屚=漏 +屛=屏 +屜=屉 +屟=屉 +屢=屡 +層=层 +屨=屦 +屬=属 +屭=屃 +屰=逆 +屾=山 +岅=坂 +岆=妖 +岠=拒 +岡=冈 +岥=坡 +岼=坪 +峝=峒 +峩=峨 +峫=邪 +峬=捕 +峮=裙 +峯=峰 +峴=岘 +島=岛 +峸=城 +峽=峡 +崈=宗 +崍=崃 +崐=昆 +崕=崖 +崗=岗 +崙=仑 +崠=岽 +崢=峥 +崣=萎 +崧=嵩 +崫=窟 +崬=岽 +崳=嵛 +崶=封 +崾=腰 +嵐=岚 +嵒=岩 +嵔=畏 +嵗=岁 +嵻=慷 +嵿=顶 +嶁=嵝 +嶃=崭 +嶄=崭 +嶇=岖 +嶒=曾 +嶔=嵚 +嶗=崂 +嶠=峤 +嶢=峣 +嶧=峄 +嶨=峃 +嶮=崄 +嶴=岙 +嶵=罪 +嶶=微 +嶸=嵘 +嶺=岭 +嶼=屿 +嶽=岳 +巋=岿 +巒=峦 +巔=巅 +巗=岩 +巛=川 +巟=荒 +巰=巯 +巵=卮 +巹=卺 +巿=市 +帀=匝 +帉=粉 +帋=纸 +帞=陌 +帥=帅 +師=师 +帬=裙 +帳=帐 +帶=带 +帹=接 +帾=赌 +幀=帧 +幃=帏 +幇=帮 +幈=屏 +幎=幂 +幑=徽 +幗=帼 +幘=帻 +幙=幕 +幚=帮 +幟=帜 +幣=币 +幫=帮 +幬=帱 +幷=并 +幹=干 +幺=么 +幾=几 +広=广 +庅=么 +庝=疼 +庫=库 +庻=庶 +庽=寓 +庿=庙 +廁=厕 +廂=厢 +廄=厩 +廈=厦 +廎=庼 +廐=厩 +廕=荫 +廚=厨 +廜=屠 +廝=厮 +廟=庙 +廠=厂 +廡=庑 +廢=废 +廣=广 +廩=廪 +廬=庐 +廰=厅 +廳=厅 +廵=巡 +廸=迪 +廹=迫 +廻=回 +廼=迺 +弌=壹 +弍=贰 +弒=弑 +弔=吊 +弖=弓 +弚=弟 +弜=弱 +弳=弪 +張=张 +強=强 +彅=简 +彆=别 +彈=弹 +彊=强 +彌=弥 +彎=弯 +彔=录 +彙=汇 +彜=彝 +彞=彝 +彠=彟 +彡=三 +彥=彦 +彫=雕 +彯=飘 +彵=他 +彶=及 +彽=低 +彿=佛 +徃=往 +後=后 +徑=径 +從=从 +徠=徕 +徣=借 +徦=假 +徧=遍 +復=复 +徬=彷 +徳=德 +徵=征 +徹=彻 +忊=订 +忬=舒 +忲=太 +忹=汪 +忼=杭 +怇=矩 +怉=饱 +怌=呸 +怓=努 +怞=油 +怭=必 +怱=匆 +怳=恍 +怵=憷 +怶=披 +怺=咏 +恆=恒 +恉=旨 +恏=好 +恠=怪 +恡=吝 +恥=耻 +悀=涌 +悅=悦 +悇=余 +悈=戒 +悊=哲 +悋=吝 +悘=医 +悙=亨 +悞=悮 +悡=梨 +悢=恨 +悤=匆 +悩=脑 +悪=恶 +悮=误 +悵=怅 +悶=闷 +悽=凄 +惓=倦 +惔=淡 +惡=恶 +惢=蕊 +惥=恿 +惪=德 +惱=恼 +惲=恽 +惷=蠢 +惻=恻 +愅=革 +愙=客 +愛=爱 +愜=惬 +愨=悫 +愬=诉 +愮=瑶 +愰=晃 +愴=怆 +愷=恺 +愺=草 +愽=博 +愾=忾 +慁=恩 +慂=恿 +慄=栗 +慇=殷 +態=态 +慍=愠 +慓=漂 +慘=惨 +慙=惭 +慚=惭 +慛=崔 +慟=恸 +慠=傲 +慣=惯 +慤=悫 +慥=造 +慦=救 +慪=怄 +慫=怂 +慬=懂 +慮=虑 +慲=瞒 +慳=悭 +慴=慑 +慶=庆 +慹=热 +慼=戚 +慽=戚 +慾=欲 +憂=忧 +憅=动 +憇=憩 +憉=彭 +憊=惫 +憐=怜 +憑=凭 +憒=愦 +憕=登 +憖=慭 +憚=惮 +憛=潭 +憜=堕 +憡=策 +憤=愤 +憫=悯 +憮=怃 +憲=宪 +憶=忆 +懃=勤 +懄=勤 +懆=操 +懇=恳 +應=应 +懌=怿 +懍=懔 +懞=蒙 +懟=怼 +懣=懑 +懨=恹 +懪=暴 +懲=惩 +懶=懒 +懷=怀 +懸=悬 +懺=忏 +懼=惧 +懽=欢 +懾=慑 +戀=恋 +戇=戆 +戉=钺 +戓=或 +戔=戋 +戞=戛 +戧=戗 +戨=歌 +戩=戬 +戰=战 +戱=戯 +戲=戏 +戶=户 +戹=厄 +戼=卯 +扂=店 +扆=衣 +扙=丈 +扜=迂 +扝=亏 +扡=扦 +扱=吸 +抝=拗 +抳=拟 +抴=曳 +拋=抛 +拑=钳 +拕=拖 +拚=拼 +拡=扩 +拤=掐 +拹=协 +拾=十 +挌=格 +挘=劣 +挩=捝 +挱=挲 +挵=弄 +挶=局 +挾=挟 +捄=救 +捊=浮 +捨=舍 +捫=扪 +捲=卷 +捳=岳 +掃=扫 +掄=抡 +掗=挜 +掙=挣 +掛=挂 +採=采 +掫=取 +掱=手 +掵=命 +掹=猛 +掽=碰 +揀=拣 +揅=研 +揌=塞 +揑=捏 +揗=循 +揙=编 +揚=扬 +換=换 +揫=揪 +揮=挥 +揵=健 +揷=插 +揹=背 +搆=构 +搇=揿 +搉=榷 +損=损 +搖=摇 +搗=捣 +搣=灭 +搤=扼 +搥=捶 +搧=扇 +搨=拓 +搯=掏 +搵=揾 +搶=抢 +搾=榨 +摀=捂 +摂=摄 +摃=扛 +摋=杀 +摑=掴 +摜=掼 +摟=搂 +摣=揸 +摤=爽 +摯=挚 +摳=抠 +摶=抟 +摺=折 +摻=掺 +摽=标 +撁=牵 +撃=击 +撈=捞 +撏=挦 +撐=撑 +撓=挠 +撚=捻 +撝=㧑 +撟=挢 +撢=掸 +撣=掸 +撥=拨 +撦=扯 +撧=撅 +撫=抚 +撲=扑 +撳=揿 +撴=蹾 +撻=挞 +撽=邀 +撾=挝 +撿=捡 +擁=拥 +擄=掳 +擇=择 +擊=击 +擋=挡 +擏=敬 +擓=㧟 +擔=担 +擕=携 +據=据 +擝=盟 +擠=挤 +擡=抬 +擣=捣 +擧=举 +擬=拟 +擯=摈 +擰=拧 +擱=搁 +擲=掷 +擴=扩 +擷=撷 +擺=摆 +擻=擞 +擼=撸 +擾=扰 +擿=摘 +攃=擦 +攄=摅 +攆=撵 +攋=赖 +攏=拢 +攔=拦 +攖=撄 +攙=搀 +攛=撺 +攜=携 +攝=摄 +攢=攒 +攣=挛 +攤=摊 +攩=挡 +攪=搅 +攬=揽 +攷=考 +敁=掂 +敂=叩 +敍=叙 +敎=教 +敗=败 +敘=叙 +敟=典 +敩=学 +敭=扬 +敵=敌 +數=数 +敺=驱 +斂=敛 +斃=毙 +斈=学 +斉=齐 +斕=斓 +斚=斝 +斬=斩 +斷=断 +於=于 +旂=旗 +旛=幡 +旣=既 +旤=祸 +旪=叶 +旹=时 +旾=春 +昇=升 +昋=吞 +昐=盼 +昜=杨 +昬=昏 +昻=昂 +時=时 +晉=晋 +晎=哄 +晝=昼 +晵=启 +晿=唱 +暀=往 +暈=晕 +暉=晖 +暎=映 +暒=星 +暘=旸 +暠=皓 +暡=翁 +暢=畅 +暫=暂 +暱=昵 +曂=黄 +曃=逮 +曄=晔 +曆=历 +曇=昙 +曉=晓 +曊=费 +曏=向 +曖=暧 +曟=晨 +曠=旷 +曡=叠 +曨=昽 +曬=晒 +曱=甲 +書=书 +朂=最 +會=会 +朓=跳 +朞=期 +朢=望 +朧=胧 +朩=木 +朮=术 +朳=扒 +朶=朵 +杇=圬 +杘=尿 +杧=忙 +東=东 +杴=锨 +杺=心 +枃=匀 +枈=柴 +枒=桠 +枱=台 +枴=拐 +枾=柿 +柂=拖 +柆=垃 +柈=伴 +柭=跋 +柵=栅 +柷=祝 +柸=杯 +柹=柿 +柺=拐 +査=查 +栁=柳 +栆=枣 +栔=契 +栞=刊 +栢=柏 +栤=冰 +栰=筏 +栱=供 +栺=指 +桒=桑 +桚=拶 +桭=振 +桮=杯 +桺=柳 +桿=杆 +梔=栀 +梘=枧 +梚=挽 +條=条 +梟=枭 +梲=棁 +梹=槟 +梽=志 +棃=梨 +棄=弃 +棈=精 +棊=棋 +棌=睬 +棑=排 +棖=枨 +棗=枣 +棟=栋 +棡= +棧=栈 +棩=渊 +棬=桊 +棲=栖 +棴=服 +棶=梾 +椀=碗 +椉=乘 +椏=桠 +椗=碇 +椘=楚 +椨=俯 +椮=渗 +椶=棕 +椾=笺 +楃=握 +楊=杨 +楓=枫 +楛=苦 +楨=桢 +楩=便 +業=业 +楱=奏 +楳=梅 +極=极 +楿=相 +榘=矩 +榦=干 +榪=杩 +榮=荣 +榲=榅 +榿=桤 +槀=槁 +槁=藁 +槃=盘 +構=构 +槍=枪 +槑=呆 +槓=杠 +槕=桌 +槤=梿 +槧=椠 +槨=椁 +槩=概 +槪=概 +槮=椮 +槳=桨 +槶=椢 +槹=槔 +槼=規 +樁=桩 +樂=乐 +樅=枞 +樐=橹 +樑=梁 +樓=楼 +標=标 +樝=楂 +樞=枢 +樣=样 +樭=基 +樸=朴 +樹=树 +樺=桦 +樿=椫 +橆=舞 +橈=桡 +橋=桥 +橓=瞬 +橖=棠 +橜=橛 +機=机 +橢=椭 +橤=蕊 +橫=横 +橰=槔 +橴=紫 +檁=檩 +檇=槜 +檉=柽 +檔=档 +檜=桧 +檝=楫 +檟=槚 +檢=检 +檣=樯 +檤=道 +檭=银 +檮=梼 +檯=台 +檳=槟 +檸=柠 +檻=槛 +檾=苘 +櫂=棹 +櫃=柜 +櫇=颇 +櫈=凳 +櫓=橹 +櫕=替 +櫚=榈 +櫛=栉 +櫝=椟 +櫞=橼 +櫟=栎 +櫥=橱 +櫧=槠 +櫨=栌 +櫪=枥 +櫫=橥 +櫬=榇 +櫱=蘖 +櫳=栊 +櫸=榉 +櫺=棂 +櫻=樱 +欄=栏 +欅=榉 +權=权 +欎=郁 +欏=椤 +欑=攒 +欒=栾 +欖=榄 +欞=棂 +欥=吹 +欵=款 +欽=钦 +歎=叹 +歐=欧 +歔=墟 +歗=啸 +歘=欻 +歛=敛 +歟=欤 +歡=欢 +歭=持 +歮=址 +歯=齿 +歲=岁 +歳=岁 +歴=历 +歷=历 +歸=归 +歿=殁 +殀=夭 +殗=淹 +殘=残 +殙=婚 +殞=殒 +殣=谨 +殤=殇 +殨=㱮 +殫=殚 +殭=僵 +殮=殓 +殯=殡 +殲=歼 +殸=声 +殺=杀 +殻=壳 +殼=壳 +毀=毁 +毃=敲 +毆=殴 +毇=毁 +毉=医 +毐=毒 +毘=毗 +毝=毛 +毣=笔 +毧=绒 +毿=毵 +氂=牦 +氈=毡 +氊=毡 +氌=氇 +氣=气 +氫=氢 +氬=氩 +氳=氲 +氷=冰 +氹=凼 +氾=犯 +氿=酒 +汃=趴 +汈=叼 +汋=勺 +汍=丸 +汎=帆 +汏=大 +汒=茫 +汘=纤 +汙=污 +汚=污 +汢=土 +汥=枝 +汮=均 +汸=坊 +決=决 +汻=许 +沋=优 +沍=冱 +沑=扭 +沒=没 +沕=吻 +沖=冲 +沬=抹 +沰=拓 +沴=珍 +沵=你 +沶=示 +況=况 +泂=炯 +泇=架 +泙=砰 +泚=此 +泝=溯 +泩=生 +泬=穴 +泹=担 +洀=舟 +洂=亦 +洃=灰 +洊=存 +洏=而 +洘=拷 +洝=按 +洣=迷 +洤=全 +洩=泄 +洭=眶 +洶=汹 +洸=光 +洺=名 +洿=夸 +浀=曲 +浄=净 +浉=狮 +浌=伐 +浐=产 +浕=尽 +浗=球 +浛=含 +浢=逗 +浧=逞 +浨=宋 +浭=更 +浵=彤 +浹=浃 +浽=馁 +涃=捆 +涇=泾 +涊=忍 +涍=哮 +涖=莅 +涜=壳 +涥=哼 +涭=授 +涳=空 +涴=碗 +涺=锯 +涻=社 +涼=凉 +涽=昏 +淉=果 +淍=碉 +淒=凄 +淓=芳 +淔=植 +淚=泪 +淛=浙 +淣=倪 +淥=渌 +淨=净 +淩=凌 +淪=沦 +淰=念 +淵=渊 +淶=涞 +淺=浅 +渀=奔 +渁=水 +渇=渴 +渉=涉 +渏=奇 +渓=溪 +渘=揉 +渙=涣 +減=减 +渞=首 +渟=停 +渢=沨 +渦=涡 +渧=蒂 +測=测 +渱=虹 +渶=英 +渻=省 +渾=浑 +湁=拾 +湈=煤 +湊=凑 +湌=餐 +湏=须 +湜=是 +湝=皆 +湞=浈 +湠=碳 +湢=福 +湣=愍 +湤=施 +湥=突 +湧=涌 +湯=汤 +湰=隆 +湴=碰 +湺=保 +湻=淳 +湼=涅 +溇=楼 +溈=沩 +溓=嫌 +溔=糕 +準=准 +溙=泰 +溚=搭 +溜=熘 +溝=沟 +溡=时 +溤=冯 +溫=温 +溮=浉 +溳=涢 +溼=湿 +溿=畔 +滃=嗡 +滄=沧 +滅=灭 +滈=高 +滌=涤 +滎=荥 +滒=哥 +滘=浩 +滙=汇 +滛=淫 +滬=沪 +滭=毕 +滮=彪 +滯=滞 +滲=渗 +滵=蜜 +滷=卤 +滸=浒 +滺=悠 +滻=浐 +滽=庸 +滾=磙 +滿=满 +漁=渔 +漅=巢 +漈=际 +漊=溇 +漑=概 +漚=沤 +漟=堂 +漢=汉 +漣=涟 +漨=逢 +漬=渍 +漲=涨 +漴=崇 +漵=溆 +漷=郭 +漸=渐 +漹=焉 +漻=廖 +漿=浆 +潁=颍 +潄=漱 +潅=罐 +潎=撇 +潐=焦 +潑=泼 +潒=橡 +潔=洁 +潗=集 +潙=沩 +潛=潜 +潠=噀 +潤=润 +潪=智 +潯=浔 +潰=溃 +潵=撒 +潶=嘿 +潷=滗 +潹=森 +潾=磷 +潿=涠 +澀=涩 +澁=涩 +澆=浇 +澇=涝 +澊=尊 +澕=华 +澗=涧 +澠=渑 +澢=挡 +澣=浣 +澤=泽 +澥=懈 +澦=滪 +澩=泶 +澭=雍 +澮=浍 +澱=淀 +澼=辟 +濁=浊 +濃=浓 +濆=愤 +濇=涩 +濐=暑 +濔=沵 +濕=湿 +濘=泞 +濜=浕 +濟=济 +濢=粹 +濤=涛 +濨=磁 +濫=漤 +濰=潍 +濱=滨 +濳=潜 +濶=阔 +濸=呛 +濺=溅 +濼=泺 +濾=滤 +瀅=滢 +瀆=渎 +瀉=泻 +瀋=渖 +瀍=缠 +瀏=浏 +瀕=濒 +瀘=泸 +瀜=融 +瀝=沥 +瀟=潇 +瀠=潆 +瀦=潴 +瀧=泷 +瀨=濑 +瀭=输 +瀲=潋 +瀻=戴 +瀾=澜 +瀿=繁 +灀=霜 +灃=沣 +灄=滠 +灋=法 +灑=洒 +灕=漓 +灘=滩 +灝=灏 +灠=漤 +灡=烂 +灢=囊 +灣=湾 +灤=滦 +灧=滟 +灨=赣 +灩=滟 +災=灾 +炡=政 +炤=照 +炪=出 +為=为 +烄=胶 +烏=乌 +烑=姚 +烖=灾 +烥=臣 +烮=列 +烱=炯 +烴=烃 +烸=梅 +烺=浪 +烾=炎 +焄=君 +焒=吕 +無=无 +煆=煅 +煇=辉 +煉=炼 +煑=煮 +煒=炜 +煕=熙 +煖=暖 +煗=暖 +煙=烟 +煠=炸 +煢=茕 +煥=焕 +煩=烦 +煬=炀 +煭=裂 +煱=锅 +煷=亮 +熅=煴 +熈=熙 +熋=熊 +熒=荧 +熖=焰 +熗=炝 +熱=热 +熲=颎 +熷=增 +熾=炽 +燁=烨 +燄=焰 +燈=灯 +燉=炖 +燐=磷 +燒=烧 +燙=烫 +燜=焖 +營=营 +燦=灿 +燭=烛 +燳=照 +燴=烩 +燻=熏 +燼=烬 +燾=焘 +燿=耀 +爊=熬 +爍=烁 +爐=炉 +爕=燮 +爗=烨 +爘=餐 +爛=烂 +爭=争 +爲=为 +爺=爷 +爾=尔 +爿=丬 +牀=床 +牆=墙 +牋=笺 +牎=窗 +牐=闸 +牓=榜 +牕=窗 +牘=牍 +牠=它 +牴=抵 +牸=字 +牽=牵 +犂=犁 +犇=牛 +犖=荦 +犛=牦 +犠=牺 +犢=犊 +犧=牺 +犼=吼 +狀=状 +狆=中 +狌=牲 +狔=泥 +狚=胆 +狣=挑 +狥=徇 +狪=洞 +狹=狭 +狽=狈 +猂=悍 +猙=狰 +猦=枫 +猨=猿 +猵=遍 +猶=犹 +猻=狲 +獁=犸 +獃=呆 +獄=狱 +獅=狮 +獊=沧 +獌=馒 +獎=奖 +獏=貘 +獓=敖 +獘=毙 +獙=敝 +獞=撞 +獣=兽 +獧=狷 +獨=独 +獪=狯 +獫=猃 +獮=狝 +獰=狞 +獲=获 +獵=猎 +獷=犷 +獸=兽 +獺=獭 +獻=献 +獼=猕 +玀=猡 +玅=妙 +玆=玄 +玙=与 +玞=夫 +玨=珏 +玪=玲 +珆=台 +珒=津 +珓=较 +珪=圭 +珮=佩 +珵=呈 +珶=梯 +珻=悔 +現=现 +琇=锈 +琎=进 +琓=玩 +琖=盏 +琱=雕 +琹=琴 +琺=珐 +琿=珲 +瑆=惺 +瑇=玳 +瑉=珉 +瑋=玮 +瑏=穿 +瑒=玚 +瑝=皇 +瑠=琉 +瑣=琐 +瑤=瑶 +瑩=莹 +瑪=玛 +瑫=滔 +瑯=琅 +瑱=填 +瑲=玱 +瑺=常 +璄=境 +璉=琏 +璔=憎 +璡=琎 +璢=琉 +璣=玑 +璦=瑷 +璪=澡 +璫=珰 +環=环 +璵=玙 +璸=瑸 +璽=玺 +璿=璇 +瓈=璃 +瓊=琼 +瓌=瑰 +瓏=珑 +瓔=璎 +瓚=瓒 +瓟=爬 +甁=瓶 +甆=瓷 +甌=瓯 +甎=砖 +甕=瓮 +甖=罂 +甛=甜 +甞=尝 +產=产 +産=产 +甦=苏 +甯=宁 +甴=由 +甶=田 +甽=圳 +畆=亩 +畊=耕 +畝=亩 +畡=垓 +畢=毕 +畣=答 +畧=略 +畨=番 +畩=依 +畫=画 +畮=亩 +異=异 +畱=留 +畵=画 +當=当 +畼=场 +疇=畴 +疉=叠 +疊=叠 +疍=蛋 +疎=疏 +疒=病 +疧=底 +疺=乏 +疿=痱 +痌=恫 +痐=蛔 +痙=痉 +痠=酸 +痩=瘦 +痲=痳 +痺=痹 +痽=准 +痾=疴 +瘂=痖 +瘉=愈 +瘋=疯 +瘍=疡 +瘓=痪 +瘖=喑 +瘚=撅 +瘞=瘗 +瘡=疮 +瘧=疟 +瘮=瘆 +瘲=疭 +瘺=瘘 +瘻=瘘 +療=疗 +癅=瘤 +癆=痨 +癇=痫 +癉=瘅 +癒=愈 +癘=疠 +癙=鼠 +癟=瘪 +癡=痴 +癢=痒 +癤=疖 +癥=症 +癧=疬 +癩=癞 +癬=癣 +癭=瘿 +癮=瘾 +癰=痈 +癱=瘫 +癲=癫 +癷=罕 +發=发 +皁=皂 +皃=貌 +皐=皋 +皒=俄 +皗=绸 +皚=皑 +皛=白 +皜=皓 +皰=疱 +皷=鼓 +皸=皲 +皺=皱 +盁=盈 +盃=杯 +盇=盍 +盉=盒 +盋=钵 +盌=碗 +盜=盗 +盞=盏 +盡=尽 +監=监 +盤=盘 +盧=卢 +盪=荡 +盬=监 +眀=明 +眎=视 +眏=映 +眡=视 +眥=眦 +眪=丙 +眫=胖 +眾=众 +睋=蛾 +睏=困 +睓=腆 +睜=睁 +睞=睐 +睠=眷 +睪=睾 +睲=腥 +瞇=眯 +瞐=晶 +瞖=翳 +瞘=眍 +瞜=䁖 +瞞=瞒 +瞮=澈 +瞶=瞆 +瞼=睑 +矁=瞅 +矓=眬 +矙=瞰 +矚=瞩 +矯=矫 +矴=碇 +矼=缸 +矽=硅 +砡=玉 +砲=炮 +砽=拥 +硏=研 +硜=硁 +硤=硖 +硧=桶 +硨=砗 +硯=砚 +碁=棋 +碕=埼 +碙=刚 +碩=硕 +碪=砧 +碭=砀 +碸=砜 +確=确 +碼=码 +磆=猾 +磍=瞎 +磎=溪 +磑=硙 +磚=砖 +磟=碌 +磠=硵 +磣=碜 +磧=碛 +磯=矶 +磽=硗 +礃=掌 +礄=硚 +礆=硷 +礎=础 +礙=碍 +礦=矿 +礪=砺 +礫=砾 +礬=矾 +礮=炮 +礱=砻 +礶=罐 +祐=右 +祕=秘 +祘=算 +祿=禄 +禍=祸 +禎=祯 +禕=祎 +禞=篙 +禡=祃 +禢=塌 +禦=御 +禩=祀 +禪=禅 +禮=礼 +禰=祢 +禱=祷 +禸=内 +禿=秃 +秇=执 +秈=籼 +秊=年 +秌=秋 +秏=耗 +秐=耘 +秔=粳 +秖=祇 +秝=禾 +秡=泼 +秥=拈 +秱=桐 +稅=税 +稈=秆 +稉=粳 +稜=棱 +稟=禀 +稤=掠 +稬=糯 +稭=秸 +種=种 +稱=称 +稲=蹈 +稵=滋 +稺=稚 +稾=稿 +穀=谷 +穅=糠 +穉=稚 +穌=稣 +積=积 +穎=颖 +穏=稳 +穠=秾 +穡=穑 +穢=秽 +穤=糯 +穨=颓 +穩=稳 +穫=获 +穭=稆 +穽=阱 +窂=牢 +窉=柄 +窓=窗 +窩=窝 +窪=洼 +窮=穷 +窯=窑 +窰=窑 +窵=窎 +窶=窭 +窷=聊 +窺=窥 +窻=窗 +窾=款 +竄=窜 +竅=窍 +竇=窦 +竈=灶 +竊=窃 +竒=奇 +竚=伫 +竝=并 +竢=俟 +竪=竖 +竲=蹭 +競=竞 +竾=篪 +笀=芒 +笁=工 +笗=冬 +笣=包 +笩=代 +笵=范 +筃=茵 +筆=笔 +筍=笋 +筎=茹 +筗=忠 +筞=策 +筧=笕 +筩=筒 +筭=算 +筯=箸 +筴=䇲 +筺=筐 +箁=菩 +箇=个 +箋=笺 +箌=倒 +箎=篪 +箏=筝 +箒=帚 +箘=菌 +箚=札 +箛=孤 +箠=垂 +箥=玻 +箶=胡 +箹=约 +節=节 +範=范 +築=筑 +篋=箧 +篔=筼 +篛=箬 +篜=蒸 +篠=筱 +篤=笃 +篨=除 +篩=筛 +篲=彗 +篳=筚 +簀=箦 +簃=移 +簆=筘 +簍=篓 +簑=蓑 +簒=篡 +簘=萧 +簞=箪 +簡=简 +簣=篑 +簫=箫 +簮=簪 +簰=牌 +簷=檐 +簹=筜 +簽=签 +簾=帘 +籃=篮 +籌=筹 +籐=藤 +籙=箓 +籛=篯 +籜=箨 +籟=籁 +籠=笼 +籤=签 +籨=奁 +籩=笾 +籪=簖 +籬=篱 +籮=箩 +籲=吁 +籿=村 +粀=杖 +粃=秕 +粄=饭 +粅=物 +粦=磷 +粧=妆 +粩=姥 +粵=粤 +糉=粽 +糊=煳 +糘=稼 +糝=糁 +糞=粪 +糧=粮 +糱=糵 +糲=粝 +糴=籴 +糵=孽 +糶=粜 +糸=纟 +糹=纟 +糼=攻 +糾=纠 +紀=纪 +紁=叉 +紂=纣 +紃=驯 +約=约 +紅=红 +紆=纡 +紇=纥 +紈=纨 +紉=纫 +紋=纹 +納=纳 +紐=纽 +紓=纾 +純=纯 +紕=纰 +紖=纼 +紗=纱 +紘=纮 +紙=纸 +級=级 +紛=纷 +紜=纭 +紝=纴 +紡=纺 +紥=扎 +紬=䌷 +紮=扎 +細=细 +紱=绂 +紲=绁 +紳=绅 +紵=纻 +紹=绍 +紺=绀 +紼=绋 +紿=绐 +絀=绌 +終=终 +絃=弦 +組=组 +絅=䌹 +絆=绊 +絎=绗 +絏=绁 +結=结 +絒=酬 +絕=绝 +絛=绦 +絝=绔 +絞=绞 +絡=络 +絢=绚 +給=给 +絨=绒 +絪=姻 +絰=绖 +統=统 +絲=丝 +絳=绛 +絶=绝 +絸=茧 +絹=绢 +綁=绑 +綃=绡 +綆=绠 +綈=绨 +綉=绣 +綌=绤 +綏=绥 +綑=捆 +經=经 +綜=综 +綞=缍 +綠=绿 +綢=绸 +綣=绻 +綫=线 +綬=绶 +維=维 +綯=绹 +綰=绾 +綱=纲 +網=网 +綳=绷 +綴=缀 +綵=彩 +綸=纶 +綹=绺 +綺=绮 +綻=绽 +綽=绰 +綾=绫 +綿=绵 +緄=绲 +緇=缁 +緊=紧 +緋=绯 +緐=繁 +緑=绿 +緒=绪 +緓=绬 +緔=绱 +緗=缃 +緘=缄 +緙=缂 +線=线 +緜=绵 +緝=缉 +緞=缎 +締=缔 +緡=缗 +緣=缘 +緥=褓 +緦=缌 +編=编 +緩=缓 +緬=缅 +緯=纬 +緱=缑 +緲=缈 +練=练 +緶=缏 +緹=缇 +緻=致 +縂=总 +縈=萦 +縉=缙 +縊=缢 +縋=缒 +縐=绉 +縑=缣 +縕=缊 +縗=缞 +縚=绦 +縛=缚 +縝=缜 +縞=缟 +縟=缛 +縣=县 +縧=绦 +縨=幌 +縫=缝 +縭=缡 +縮=缩 +縱=纵 +縲=缧 +縳=䌸 +縵=缦 +縶=絷 +縷=缕 +縹=缥 +總=总 +績=绩 +繃=绷 +繅=缫 +繆=缪 +繈=襁 +繒=缯 +織=织 +繕=缮 +繖=伞 +繚=缭 +繞=绕 +繡=绣 +繢=缋 +繦=襁 +繩=绳 +繪=绘 +繫=系 +繭=茧 +繮=缰 +繯=缳 +繰=缲 +繳=缴 +繸=䍁 +繹=绎 +繺=煞 +繼=继 +繽=缤 +繾=缱 +纇=颣 +纈=缬 +纊=纩 +續=续 +纍=累 +纏=缠 +纓=缨 +纖=纤 +纘=缵 +纜=缆 +缞=衰 +缽=钵 +缾=瓶 +罁=缸 +罇=樽 +罈=坛 +罌=罂 +罎=坛 +罏=垆 +罓=冈 +罞=茅 +罣=挂 +罰=罚 +罵=骂 +罷=罢 +罸=罚 +羅=罗 +羆=罴 +羈=羁 +羋=芈 +羕=漾 +羗=羌 +羙=美 +羢=绒 +羣=群 +羥=羟 +羨=羡 +義=义 +羱=源 +羴=膻 +羶=膻 +翄=翅 +習=习 +翫=玩 +翬=翚 +翶=翱 +翹=翘 +翺=翱 +翽=翙 +耈=耇 +耉=耇 +耡=锄 +耬=耧 +耮=耢 +聅=联 +聖=圣 +聙=睛 +聞=闻 +聯=联 +聰=聪 +聲=声 +聳=耸 +聵=聩 +聶=聂 +職=职 +聹=聍 +聼=听 +聽=听 +聾=聋 +肅=肃 +肎=肯 +肐=胳 +肔=池 +肗=汝 +肧=胚 +肬=疣 +肻=肯 +胒=尼 +胕=附 +胷=胸 +脃=脆 +脅=胁 +脇=胁 +脈=脉 +脊=嵴 +脕=晚 +脗=吻 +脛=胫 +脣=唇 +脫=脱 +脹=胀 +腁=胼 +腄=捶 +腎=肾 +腖=胨 +腡=脶 +腦=脑 +腫=肿 +腳=脚 +腷=逼 +腸=肠 +膁=肷 +膃=腽 +膆=嗉 +膓=肠 +膕=腘 +膚=肤 +膠=胶 +膤=雪 +膩=腻 +膽=胆 +膾=脍 +膿=脓 +臈=腊 +臉=脸 +臋=臀 +臍=脐 +臏=膑 +臒=癯 +臕=膘 +臘=腊 +臙=胭 +臚=胪 +臝=裸 +臟=脏 +臠=脔 +臢=臜 +臥=卧 +臨=临 +臯=皋 +臱=旁 +臸=至 +臺=台 +舃=舄 +與=与 +興=兴 +舉=举 +舊=旧 +舎=舍 +舖=铺 +舗=铺 +舘=馆 +舙=舌 +舝=辖 +舩=船 +艙=舱 +艢=樯 +艣=橹 +艤=舣 +艦=舰 +艪=橹 +艫=舻 +艱=艰 +艷=艳 +艸=艹 +芉=竿 +芐=下 +芢=仁 +芣=不 +芲=花 +芶=勾 +芻=刍 +苆=切 +苉=匹 +苎=苧 +苐=第 +苝=北 +苧=苎 +苸=呼 +苺=莓 +茐=葱 +茖=各 +茘=荔 +茲=兹 +茿=筑 +荅=答 +荊=荆 +荍=收 +荕=筋 +荖=老 +荝=则 +荢=宇 +荰=杜 +荳=豆 +荴=扶 +荶=吟 +荹=步 +荿=成 +莁=巫 +莂=别 +莇=助 +莈=没 +莊=庄 +莋=做 +莏=抄 +莑=蓬 +莕=荇 +莖=茎 +莢=荚 +莣=忘 +莧=苋 +莮=男 +菈=拉 +菋=味 +菓=果 +菗=抽 +菢=抱 +菦=近 +菭=治 +菮=庚 +華=华 +菴=庵 +菸=烟 +菿=到 +萇=苌 +萉=肥 +萊=莱 +萚=择 +萠=萌 +萣=定 +萪=科 +萫=香 +萬=万 +萭=万 +萯=负 +萲=萱 +萵=莴 +萷=削 +萹=篇 +萺=冒 +萿=活 +葀=括 +葁=姜 +葃=昨 +葉=叶 +葒=荭 +葓=洪 +葠=参 +葢=盖 +葤=荮 +葦=苇 +葮=锻 +葯=药 +葰=所 +葲=泉 +葷=荤 +葾=怨 +葿=眉 +蒄=冠 +蒓=莼 +蒔=莳 +蒛=缺 +蒝=愿 +蒞=莅 +蒢=滁 +蒤=途 +蒩=租 +蒼=苍 +蓀=荪 +蓅=流 +蓆=席 +蓈=榔 +蓋=盖 +蓒=轩 +蓕=桂 +蓙=座 +蓜=配 +蓡=參 +蓢=廊 +蓧=莜 +蓮=莲 +蓯=苁 +蓱=萍 +蓳=董 +蓴=莼 +蓷=推 +蓸=曹 +蓽=荜 +蔀=部 +蔂=累 +蔆=菱 +蔉=滚 +蔋=淑 +蔍=鹿 +蔎=设 +蔔=卜 +蔕=蒂 +蔘=参 +蔞=蒌 +蔠=终 +蔣=蒋 +蔥=葱 +蔦=茑 +蔭=荫 +蔵=藏 +蕁=荨 +蕅=藕 +蕆=蒇 +蕋=蕊 +蕎=荞 +蕏=猪 +蕐=哗 +蕒=荬 +蕓=芸 +蕔=报 +蕕=莸 +蕗=露 +蕘=荛 +蕚=萼 +蕜=悲 +蕝=绝 +蕢=蒉 +蕥=雅 +蕩=荡 +蕪=芜 +蕭=萧 +蕯=萨 +蕶=零 +蕷=蓣 +蕿=萱 +薀=蕰 +薈=荟 +薊=蓟 +薌=芗 +薑=姜 +薔=蔷 +薘=荙 +薟=莶 +薦=荐 +薩=萨 +薴=苧 +薵=筹 +薺=荠 +藂=聚 +藅=罚 +藍=蓝 +藎=荩 +藙=毅 +藝=艺 +藞=磊 +藥=药 +藪=薮 +藴=蕴 +藶=苈 +藷=薯 +藹=蔼 +藺=蔺 +藼=萱 +蘀=萚 +蘂=蕊 +蘄=蕲 +蘆=芦 +蘇=苏 +蘊=蕴 +蘋=苹 +蘐=萱 +蘓=苏 +蘚=藓 +蘞=蔹 +蘢=茏 +蘤=花 +蘭=兰 +蘶=巍 +蘺=蓠 +蘿=萝 +虆=蔂 +處=处 +虖=呼 +虛=虚 +虜=虏 +號=号 +虧=亏 +虯=虬 +虵=蛇 +蚘=蛔 +蚡=鼢 +蚦=蚺 +蛕=蛔 +蛧=网 +蛫=跪 +蛬=蚕 +蛺=蛱 +蛻=蜕 +蜆=蚬 +蜋=螂 +蜖=蛔 +蜨=蝶 +蜯=蚌 +蜺=霓 +蝂=版 +蝕=蚀 +蝟=猬 +蝦=虾 +蝨=虱 +蝯=猿 +蝱=虻 +蝸=蜗 +螄=蛳 +螎=融 +螘=蚁 +螙=蠹 +螞=蚂 +螡=蚊 +螢=萤 +螻=蝼 +螾=蚓 +螿=螀 +蟁=蚊 +蟄=蛰 +蟇=蟆 +蟈=蝈 +蟎=螨 +蟣=虮 +蟬=蝉 +蟯=蛲 +蟲=虫 +蟶=蛏 +蟻=蚁 +蠅=蝇 +蠆=虿 +蠍=蝎 +蠏=蟹 +蠐=蛴 +蠑=蝾 +蠒=茧 +蠔=蚝 +蠟=蜡 +蠣=蛎 +蠧=蠹 +蠨=蟏 +蠭=蜂 +蠱=蛊 +蠶=蚕 +蠻=蛮 +衂=衄 +衆=众 +衇=脉 +衒=炫 +術=术 +衕=同 +衖=弄 +衚=胡 +衛=卫 +衝=冲 +衞=卫 +衹=只 +衺=邪 +袉=鸵 +袑=绍 +袓=祖 +袞=衮 +袵=衽 +裊=袅 +裌=夹 +裏=里 +裑=身 +補=补 +裝=装 +裠=裙 +裡=里 +裩=裈 +製=制 +褃=裉 +複=复 +褌=裈 +褘=袆 +褭=袅 +褲=裤 +褳=裢 +褸=褛 +褻=亵 +襃=褒 +襆=幞 +襇=裥 +襉=裥 +襍=杂 +襏=袯 +襖=袄 +襝=裣 +襠=裆 +襤=褴 +襪=袜 +襬=摆 +襯=衬 +襲=袭 +襴=襕 +覀=西 +覇=霸 +覈=核 +覊=羁 +見=见 +覎=觃 +規=规 +覓=觅 +覔=觅 +視=视 +覘=觇 +覡=觋 +覥=觍 +覦=觎 +覩=睹 +親=亲 +覬=觊 +覯=觏 +覰=觑 +覲=觐 +覷=觑 +覺=觉 +覽=览 +覿=觌 +觀=观 +觔=斤 +觝=抵 +觧=解 +觴=觞 +觶=觯 +觸=触 +訁=讠 +訂=订 +訃=讣 +計=计 +訊=讯 +訌=讧 +討=讨 +訐=讦 +訒=讱 +訓=训 +訕=讪 +訖=讫 +託=讬 +記=记 +訛=讹 +訝=讶 +訟=讼 +訢=䜣 +訣=诀 +訤=驳 +訥=讷 +訩=讻 +訪=访 +訬=吵 +設=设 +許=许 +訴=诉 +訶=诃 +訷=伸 +診=诊 +註=注 +証=证 +詁=诂 +詆=诋 +詎=讵 +詐=诈 +詒=诒 +詔=诏 +評=评 +詖=诐 +詗=诇 +詘=诎 +詛=诅 +詞=词 +詠=咏 +詡=诩 +詢=询 +詣=诣 +試=试 +詧=察 +詩=诗 +詫=诧 +詬=诟 +詭=诡 +詮=诠 +詰=诘 +話=话 +該=该 +詳=详 +詵=诜 +詶=州 +詻=洛 +詼=诙 +詿=诖 +誄=诔 +誅=诛 +誆=诓 +誇=夸 +誌=志 +認=认 +誑=诳 +誒=诶 +誕=诞 +誖=悖 +誘=诱 +誚=诮 +語=语 +誠=诚 +誡=诫 +誣=诬 +誤=误 +誥=诰 +誦=诵 +誧=哺 +誨=诲 +說=说 +説=说 +誯=昌 +誰=谁 +課=课 +誶=谇 +誹=诽 +誼=谊 +誾=訚 +調=调 +諂=谄 +諄=谆 +諆=棋 +談=谈 +諉=诿 +請=请 +諍=诤 +諏=诹 +諐=愆 +諑=诼 +諒=谅 +論=论 +諗=谂 +諛=谀 +諜=谍 +諝=谞 +諞=谝 +諟=堤 +諠=喧 +諡=谥 +諢=诨 +諤=谔 +諦=谛 +諧=谐 +諨=幅 +諫=谏 +諭=谕 +諮=谘 +諱=讳 +諳=谙 +諴=诚 +諶=谌 +諷=讽 +諸=诸 +諺=谚 +諼=谖 +諾=诺 +謀=谋 +謁=谒 +謂=谓 +謄=誊 +謅=诌 +謊=谎 +謌=歌 +謍=誉 +謎=谜 +謐=谧 +謔=谑 +謖=谡 +謗=谤 +謙=谦 +謚=谥 +講=讲 +謝=谢 +謠=谣 +謡=谣 +謨=谟 +謩=谟 +謫=谪 +謬=谬 +謭=谫 +謳=讴 +謸=傲 +謹=谨 +謾=谩 +謿=嘲 +譁=哗 +譆=嘻 +證=证 +譌=讹 +譎=谲 +譏=讥 +譒=播 +譔=撰 +譖=谮 +識=识 +譙=谯 +譚=谭 +譜=谱 +譟=噪 +譫=谵 +譭=毁 +譯=译 +議=议 +譴=谴 +護=护 +譸=诪 +譽=誉 +譾=谫 +讀=读 +讁=谪 +讅=审 +變=变 +讋=詟 +讌=宴 +讎=雠 +讐=雠 +讒=谗 +讓=让 +讕=谰 +讖=谶 +讚=赞 +讜=谠 +讞=谳 +谉=审 +谘=咨 +豈=岂 +豎=竖 +豐=丰 +豓=艳 +豔=艳 +豞=狗 +豩=逐 +豬=猪 +豶=豮 +貍=狸 +貓=猫 +貛=獾 +貝=贝 +貞=贞 +貟=贠 +負=负 +財=财 +貢=贡 +貧=贫 +貨=货 +販=贩 +貪=贪 +貫=贯 +責=责 +貯=贮 +貰=贳 +貲=赀 +貳=贰 +貴=贵 +貶=贬 +買=买 +貸=贷 +貺=贶 +費=费 +貼=贴 +貽=贻 +貿=贸 +賀=贺 +賁=贲 +賂=赂 +賃=赁 +賄=贿 +賅=赅 +資=资 +賈=贾 +賉=恤 +賊=贼 +賍=赃 +賑=赈 +賒=赊 +賓=宾 +賕=赇 +賗=串 +賙=赒 +賚=赉 +賛=赞 +賜=赐 +賞=赏 +賠=赔 +賡=赓 +賢=贤 +賣=卖 +賤=贱 +賦=赋 +賧=赕 +質=质 +賫=赍 +賬=账 +賭=赌 +賴=赖 +賵=赗 +賷=赍 +賸=剩 +賺=赚 +賻=赙 +購=购 +賽=赛 +賾=赜 +贄=贽 +贅=赘 +贇=赟 +贈=赠 +贊=赞 +贋=赝 +贍=赡 +贏=赢 +贐=赆 +贑=贛 +贓=赃 +贔=赑 +贖=赎 +贗=赝 +贛=赣 +贜=赃 +赑=贝 +赬=赪 +赽=块 +趂=趁 +趉=走 +趐=翅 +趕=赶 +趙=赵 +趚=速 +趦=趑 +趧=题 +趨=趋 +趫=超 +趬=翘 +趭=瞧 +趲=趱 +跕=沾 +跡=迹 +跥=跺 +跩=拽 +跴=踩 +跿=陡 +踁=胫 +踐=践 +踡=蜷 +踭=争 +踰=逾 +踴=踊 +蹆=腿 +蹌=跄 +蹍=展 +蹏=蹄 +蹓=溜 +蹔=暂 +蹕=跸 +蹜=宿 +蹟=迹 +蹠=跖 +蹣=蹒 +蹤=踪 +蹧=糟 +蹵=蹴 +蹺=跷 +躂=跶 +躉=趸 +躊=踌 +躋=跻 +躍=跃 +躑=踯 +躒=跞 +躓=踬 +躕=蹰 +躚=跹 +躡=蹑 +躥=蹿 +躦=躜 +躪=躏 +躭=耽 +躱=躲 +躳=躬 +躶=裸 +軀=躯 +軆=体 +車=车 +軋=轧 +軌=轨 +軍=军 +軑=轪 +軒=轩 +軔=轫 +軛=轭 +軟=软 +軤=轷 +軫=轸 +軲=轱 +軸=轴 +軹=轵 +軺=轺 +軻=轲 +軼=轶 +軾=轼 +較=较 +輅=辂 +輇=辁 +輈=辀 +載=载 +輊=轾 +輒=辄 +輓=挽 +輔=辅 +輕=轻 +輙=辄 +輛=辆 +輜=辎 +輝=辉 +輞=辋 +輟=辍 +輥=辊 +輦=辇 +輩=辈 +輪=轮 +輬=辌 +輭=软 +輯=辑 +輳=辏 +輸=输 +輻=辐 +輾=辗 +輿=舆 +轀=辒 +轂=毂 +轄=辖 +轅=辕 +轆=辘 +轉=转 +轍=辙 +轎=轿 +轔=辚 +轟=轰 +轡=辔 +轢=轹 +轤=轳 +辠=罪 +辢=辣 +辤=辞 +辦=办 +辧=辨 +辭=辞 +辮=辫 +辯=辩 +農=农 +辳=农 +辴=冁 +迀=干 +迆=迤 +迉=尸 +迊=迎 +迋=逛 +迖=达 +迡=呢 +迣=世 +迯=逃 +迴=回 +迻=移 +逈=迥 +逕=迳 +這=这 +連=连 +逥=回 +逩=奔 +逬=迸 +週=周 +進=进 +逷=逖 +逺=远 +遉=侦 +遊=游 +運=运 +過=过 +達=达 +違=违 +遖=南 +遙=遥 +遜=逊 +遝=沓 +遞=递 +遠=远 +遡=溯 +遦=惯 +適=适 +遯=遁 +遲=迟 +遶=绕 +遷=迁 +選=选 +遺=遗 +遼=辽 +邁=迈 +還=还 +邇=迩 +邊=边 +邏=逻 +邐=逦 +郉=邢 +郟=郏 +郤=郄 +郰=邓 +郵=邮 +鄆=郓 +鄉=乡 +鄒=邹 +鄔=邬 +鄖=郧 +鄧=邓 +鄭=郑 +鄰=邻 +鄲=郸 +鄴=邺 +鄶=郐 +鄺=邝 +酇=酂 +酈=郦 +酔=醉 +酧=酬 +酨=栽 +醃=腌 +醕=醇 +醖=酝 +醜=丑 +醞=酝 +醣=糖 +醫=医 +醬=酱 +醯=酰 +醱=酦 +醻=酬 +醼=宴 +醿=醾 +釀=酿 +釁=衅 +釃=酾 +釅=酽 +釋=释 +釐=厘 +釒=钅 +釓=钆 +釔=钇 +釕=钌 +釗=钊 +釘=钉 +釙=钋 +針=针 +釡=斧 +釢=乃 +釣=钓 +釤=钐 +釦=扣 +釧=钏 +釩=钒 +釬=焊 +釭=肛 +釵=钗 +釷=钍 +釹=钕 +釺=钎 +釿=斤 +鈀=钯 +鈁=钫 +鈃=钘 +鈄=钭 +鈅=钥 +鈆=铅 +鈈=钚 +鈉=钠 +鈍=钝 +鈎=钩 +鈐=钤 +鈑=钣 +鈒=钑 +鈔=钞 +鈕=钮 +鈞=钧 +鈣=钙 +鈥=钬 +鈦=钛 +鈧=钪 +鈫=纹 +鈮=铌 +鈰=铈 +鈳=钶 +鈴=铃 +鈷=钴 +鈸=钹 +鈹=铍 +鈺=钰 +鈽=钸 +鈾=铀 +鈿=钿 +鉀=钾 +鉄=铁 +鉅=钜 +鉆=钻 +鉈=铊 +鉉=铉 +鉋=铇 +鉍=铋 +鉏=锄 +鉑=铂 +鉕=钷 +鉗=钳 +鉚=铆 +鉛=铅 +鉞=钺 +鉢=钵 +鉤=钩 +鉦=钲 +鉬=钼 +鉭=钽 +鉮=神 +鉲=卡 +鉶=铏 +鉸=铰 +鉺=铒 +鉻=铬 +鉽=式 +鉿=铪 +銀=银 +銃=铳 +銅=铜 +銍=铚 +銑=铣 +銓=铨 +銕=铁 +銖=铢 +銘=铭 +銚=铫 +銛=铦 +銜=衔 +銠=铑 +銣=铷 +銥=铱 +銦=铟 +銨=铵 +銩=铥 +銪=铕 +銫=铯 +銬=铐 +銰=艾 +銱=铞 +銲=焊 +銳=锐 +銷=销 +銹=锈 +銻=锑 +銼=锉 +鋁=铝 +鋂=镅 +鋃=锒 +鋅=锌 +鋇=钡 +鋌=铤 +鋏=铗 +鋒=锋 +鋖=妥 +鋙=铻 +鋝=锊 +鋟=锓 +鋣=铘 +鋤=锄 +鋥=锃 +鋦=锔 +鋨=锇 +鋩=铓 +鋪=铺 +鋭=锐 +鋮=铖 +鋯=锆 +鋰=锂 +鋱=铽 +鋶=锍 +鋸=锯 +鋼=钢 +鋽=掉 +錁=锞 +錄=录 +錆=锖 +錇=锫 +錈=锩 +錏=铔 +錐=锥 +錒=锕 +錕=锟 +錘=锤 +錙=锱 +錚=铮 +錛=锛 +錞=醇 +錟=锬 +錠=锭 +錡=锜 +錢=钱 +錦=锦 +錨=锚 +錩=锠 +錫=锡 +錮=锢 +錯=错 +録=录 +錳=锰 +錶=表 +錸=铼 +錼=镎 +鍀=锝 +鍁=锨 +鍃=锪 +鍆=钔 +鍇=锴 +鍈=锳 +鍋=锅 +鍍=镀 +鍔=锷 +鍘=铡 +鍚=钖 +鍛=锻 +鍠=锽 +鍤=锸 +鍥=锲 +鍩=锘 +鍫=锹 +鍬=锹 +鍰=锾 +鍳=鉴 +鍴=端 +鍵=键 +鍶=锶 +鍺=锗 +鍼=针 +鍾=锺 +鎂=镁 +鎄=锿 +鎅=界 +鎇=镅 +鎊=镑 +鎌=镰 +鎍=索 +鎔=镕 +鎖=锁 +鎗=枪 +鎘=镉 +鎚=锤 +鎛=镈 +鎟=桑 +鎡=镃 +鎢=钨 +鎣=蓥 +鎦=镏 +鎧=铠 +鎩=铩 +鎪=锼 +鎬=镐 +鎮=镇 +鎰=镒 +鎲=镋 +鎳=镍 +鎵=镓 +鎸=镌 +鎹=送 +鎻=锁 +鎿=镎 +鏃=镞 +鏇=镟 +鏈=链 +鏌=镆 +鏍=镙 +鏐=镠 +鏑=镝 +鏗=铿 +鏘=锵 +鏜=镗 +鏝=镘 +鏞=镛 +鏟=铲 +鏡=镜 +鏢=镖 +鏤=镂 +鏨=錾 +鏰=镚 +鏵=铧 +鏷=镤 +鏹=镪 +鏽=锈 +鐃=铙 +鐋=铴 +鐐=镣 +鐒=铹 +鐓=镦 +鐔=镡 +鐘=钟 +鐙=镫 +鐝=镢 +鐠=镨 +鐦=锎 +鐧=锏 +鐨=镄 +鐫=镌 +鐮=镰 +鐰=糙 +鐲=镯 +鐳=镭 +鐴=避 +鐵=铁 +鐶=镮 +鐸=铎 +鐺=铛 +鐿=镱 +鑀=锿 +鑄=铸 +鑊=镬 +鑌=镔 +鑑=鉴 +鑒=鉴 +鑔=镲 +鑕=锧 +鑚=钻 +鑛=矿 +鑞=镴 +鑠=铄 +鑣=镳 +鑤=刨 +鑥=镥 +鑭=镧 +鑰=钥 +鑱=镵 +鑲=镶 +鑵=罐 +鑷=镊 +鑹=镩 +鑼=锣 +鑽=钻 +鑾=銮 +鑿=凿 +钁=镢 +钂=镋 +钜=鉅 +铇=刨 +镚=崩 +镟=碹 +镵=馋 +長=长 +門=门 +閁=闪 +閂=闩 +閃=闪 +閄=门 +閆=闫 +閈=闬 +閉=闭 +開=开 +閌=闶 +閎=闳 +閏=闰 +閑=闲 +閒=闲 +間=间 +閔=闵 +閘=闸 +閙=闹 +閞=开 +閡=阂 +関=关 +閣=阁 +閥=阀 +閨=闺 +閩=闽 +閫=阃 +閬=阆 +閭=闾 +閱=阅 +閲=阅 +閶=阊 +閷=刹 +閹=阉 +閻=阎 +閼=阏 +閽=阍 +閾=阈 +閿=阌 +闃=阒 +闆=板 +闈=闱 +闊=阔 +闋=阕 +闌=阑 +闍=阇 +闐=阗 +闒=阘 +闓=闿 +闔=阖 +闕=阙 +闖=闯 +闚=窥 +關=关 +闝=嫖 +闞=阚 +闠=阓 +闡=阐 +闢=辟 +闤=阛 +闥=闼 +阣=吃 +阨=厄 +阪=坂 +阬=坑 +阯=址 +陏=隋 +陗=峭 +陘=陉 +陝=陕 +陣=阵 +陥=馅 +陰=阴 +陳=陈 +陸=陆 +険=险 +陻=堙 +陼=堵 +陽=阳 +隂=阴 +隄=堤 +隉=陧 +隊=队 +階=阶 +隕=陨 +隖=坞 +際=际 +隟=隙 +隢=饶 +隣=邻 +隨=随 +險=险 +隱=隐 +隴=陇 +隷=隶 +隸=隶 +隻=只 +雋=隽 +雖=虽 +雙=双 +雛=雏 +雜=杂 +雝=雍 +雞=鸡 +離=离 +難=难 +雲=云 +電=电 +霚=雾 +霛=灵 +霡=脉 +霢=霡 +霤=溜 +霧=雾 +霩=廓 +霽=霁 +靂=雳 +靃=霍 +靄=霭 +靆=叇 +靈=灵 +靉=叆 +靚=靓 +靜=静 +靣=面 +靦=腼 +靨=靥 +靭=韧 +靱=韧 +鞀=鼗 +鞉=鼗 +鞌=鞍 +鞏=巩 +鞝=绱 +鞵=鞋 +鞽=鞒 +韁=缰 +韃=鞑 +韈=袜 +韉=鞯 +韋=韦 +韌=韧 +韍=韨 +韓=韩 +韙=韪 +韜=韬 +韝=鞴 +韞=韫 +韤=袜 +韮=韭 +韻=韵 +響=响 +頁=页 +頂=顶 +頃=顷 +項=项 +順=顺 +頇=顸 +須=须 +頊=顼 +頌=颂 +頎=颀 +頏=颃 +預=预 +頑=顽 +頒=颁 +頓=顿 +頗=颇 +領=领 +頙=项 +頜=颌 +頟=额 +頡=颉 +頤=颐 +頦=颏 +頭=头 +頮=颒 +頰=颊 +頲=颋 +頴=颕 +頷=颔 +頸=颈 +頹=颓 +頻=频 +頼=赖 +頽=颓 +顆=颗 +顋=腮 +題=题 +額=额 +顎=颚 +顏=颜 +顒=颙 +顓=颛 +顔=颜 +願=愿 +顙=颡 +顛=颠 +類=类 +顢=颟 +顥=颢 +顦=憔 +顧=顾 +顫=颤 +顬=颥 +顯=显 +顰=颦 +顱=颅 +顳=颞 +顴=颧 +颕=颖 +風=风 +颩=风 +颭=飐 +颮=飑 +颯=飒 +颱=台 +颳=刮 +颶=飓 +颸=飔 +颺=飏 +颻=飖 +颼=飕 +飀=飗 +飃=飘 +飄=飘 +飆=飚 +飈=飚 +飚=飙 +飛=飞 +飜=翻 +飠=饣 +飢=饥 +飣=饤 +飤=饲 +飥=饦 +飩=饨 +飪=饪 +飫=饫 +飭=饬 +飮=饮 +飯=饭 +飱=飧 +飲=饮 +飴=饴 +飺=糍 +飼=饲 +飽=饱 +飾=饰 +飿=饳 +餀=哎 +餁=饪 +餃=饺 +餄=饸 +餅=饼 +餈=糍 +餉=饷 +養=养 +餌=饵 +餎=饹 +餏=饻 +餑=饽 +餒=馁 +餓=饿 +餕=馂 +餖=饾 +餘=馀 +餚=肴 +餛=馄 +餜=馃 +餞=饯 +餡=馅 +餧=喂 +館=馆 +餬=糊 +餱=糇 +餳=饧 +餵=喂 +餶=馉 +餷=馇 +餹=糖 +餺=馎 +餻=糕 +餼=饩 +餽=馈 +餾=馏 +餿=馊 +饁=馌 +饃=馍 +饅=馒 +饈=馐 +饉=馑 +饊=馓 +饋=馈 +饌=馔 +饍=膳 +饑=饥 +饒=饶 +饗=飨 +饜=餍 +饝=馍 +饞=馋 +饟=饷 +饢=馕 +饤=盯 +馀=余 +馬=马 +馭=驭 +馮=冯 +馱=驮 +馳=驰 +馴=驯 +馶=驶 +馹=驲 +馿=驴 +駁=驳 +駆=驱 +駈=驱 +駐=驻 +駑=驽 +駒=驹 +駔=驵 +駕=驾 +駘=骀 +駙=驸 +駛=驶 +駝=驼 +駞=驼 +駟=驷 +駡=骂 +駢=骈 +駦=藤 +駭=骇 +駮=驳 +駰=骃 +駱=骆 +駸=骎 +駿=骏 +騁=骋 +騂=骍 +騅=骓 +騌=骔 +騍=骒 +騎=骑 +騏=骐 +騐=验 +騒=骚 +験=验 +騖=骛 +騗=骗 +騙=骗 +騣=鬃 +騤=骙 +騧=䯄 +騫=骞 +騭=骘 +騮=骝 +騰=腾 +騶=驺 +騷=骚 +騸=骟 +騾=骡 +驀=蓦 +驁=骜 +驂=骖 +驃=骠 +驄=骢 +驅=驱 +驊=骅 +驌=骕 +驍=骁 +驏=骣 +驕=骄 +驗=验 +驘=骡 +驚=惊 +驛=驿 +驟=骤 +驢=驴 +驤=骧 +驥=骥 +驦=骦 +驪=骊 +驫=骉 +骉=马 +骔=鬃 +骯=肮 +骼=胳 +骾=鲠 +髈=膀 +髊=搓 +髏=髅 +髒=脏 +體=体 +髕=髌 +髖=髋 +髠=髡 +髣=仿 +髥=髯 +髩=鬓 +髮=发 +髴=佛 +鬀=剃 +鬁=疬 +鬂=鬓 +鬆=松 +鬉=鬃 +鬍=胡 +鬚=须 +鬢=鬓 +鬥=斗 +鬦=斗 +鬧=闹 +鬨=闹 +鬩=阋 +鬪=斗 +鬭=斗 +鬮=阄 +鬰=郁 +鬱=郁 +鬴=釜 +魊=蜮 +魎=魉 +魘=魇 +魚=鱼 +魛=鱽 +魢=鱾 +魨=鲀 +魯=鲁 +魴=鲂 +魷=鱿 +魺=鲄 +鮁=鲅 +鮃=鲆 +鮊=鲌 +鮋=鲉 +鮍=鲏 +鮎=鲇 +鮐=鲐 +鮑=鲍 +鮒=鲋 +鮓=鲊 +鮚=鲒 +鮜=鲘 +鮝=鲞 +鮞=鲕 +鮟=安 +鮦=鲖 +鮪=鲔 +鮫=鲛 +鮭=鲑 +鮮=鲜 +鮳=鲓 +鮶=鲪 +鮺=鲝 +鯀=鲧 +鯁=鲠 +鯇=鲩 +鯉=鲤 +鯊=鲨 +鯒=鲬 +鯔=鲻 +鯕=鲯 +鯖=鲭 +鯗=鲞 +鯛=鲷 +鯝=鲴 +鯡=鲱 +鯢=鲵 +鯤=鲲 +鯧=鲳 +鯨=鲸 +鯪=鲮 +鯫=鲰 +鯰=鲶 +鯴=鲺 +鯵=鲹 +鯷=鳀 +鯽=鲫 +鯿=鳊 +鰁=鳈 +鰂=鲗 +鰃=鳂 +鰈=鲽 +鰉=鳇 +鰌=鳅 +鰍=鳅 +鰏=鲾 +鰐=鳄 +鰒=鳆 +鰓=鳃 +鰛=鳁 +鰜=鳒 +鰟=鳑 +鰠=鳋 +鰣=鲥 +鰥=鳏 +鰨=鳎 +鰩=鳐 +鰭=鳍 +鰮=鳁 +鰱=鲢 +鰲=鳌 +鰳=鳓 +鰵=鳘 +鰷=鲦 +鰹=鲣 +鰺=鲹 +鰻=鳗 +鰼=鳛 +鰾=鳔 +鱂=鳉 +鱅=鳙 +鱈=鳕 +鱉=鳖 +鱏=鲟 +鱒=鳟 +鱓=鳝 +鱔=鳝 +鱖=鳜 +鱗=鳞 +鱘=鲟 +鱝=鲼 +鱟=鲎 +鱠=鲙 +鱣=鳣 +鱤=鳡 +鱧=鳢 +鱨=鲿 +鱭=鲚 +鱯=鳠 +鱷=鳄 +鱸=鲈 +鱺=鲡 +鱻=鲜 +鳥=鸟 +鳧=凫 +鳩=鸠 +鳬=凫 +鳯=凤 +鳲=鸤 +鳳=凤 +鳴=鸣 +鳶=鸢 +鳾=䴓 +鴆=鸩 +鴇=鸨 +鴈=雁 +鴉=鸦 +鴒=鸰 +鴕=鸵 +鴛=鸳 +鴝=鸲 +鴞=鸮 +鴟=鸱 +鴣=鸪 +鴦=鸯 +鴨=鸭 +鴬=鸴 +鴯=鸸 +鴰=鸹 +鴴=鸻 +鴷=䴕 +鴻=鸿 +鴿=鸽 +鵁=䴔 +鵂=鸺 +鵃=鸼 +鵐=鹀 +鵑=鹃 +鵒=鹆 +鵓=鹁 +鵜=鹈 +鵝=鹅 +鵞=鹅 +鵠=鹄 +鵡=鹉 +鵪=鹌 +鵬=鹏 +鵮=鹐 +鵯=鹎 +鵰=雕 +鵲=鹊 +鵶=鸦 +鵷=鹓 +鵾=鹍 +鶄=䴖 +鶇=鸫 +鶉=鹑 +鶊=鹒 +鶏=鸡 +鶓=鹋 +鶖=鹙 +鶘=鹕 +鶚=鹗 +鶡=鹖 +鶤=鹍 +鶥=鹛 +鶩=鹜 +鶪=䴗 +鶬=鸧 +鶯=莺 +鶲=鹟 +鶴=鹤 +鶹=鹠 +鶺=鹡 +鶻=鹘 +鶼=鹣 +鶿=鹚 +鷀=鹚 +鷁=鹢 +鷂=鹞 +鷄=鸡 +鷈=䴘 +鷊=鹝 +鷓=鹧 +鷖=鹥 +鷗=鸥 +鷙=鸷 +鷚=鹨 +鷥=鸶 +鷦=鹪 +鷫=鹔 +鷯=鹩 +鷰=燕 +鷲=鹫 +鷳=鹇 +鷴=鹇 +鷸=鹬 +鷹=鹰 +鷺=鹭 +鷽=鸴 +鷿=䴙 +鸇=鹯 +鸌=鹱 +鸎=莺 +鸏=鹲 +鸕=鸬 +鸘=鹴 +鸚=鹦 +鸛=鹳 +鸝=鹂 +鸞=鸾 +鹵=卤 +鹹=咸 +鹺=鹾 +鹻=碱 +鹼=硷 +鹽=盐 +麁=粗 +麅=狍 +麐=麟 +麕=麇 +麗=丽 +麞=獐 +麤=粗 +麥=麦 +麩=麸 +麪=面 +麵=面 +麼=么 +麽=么 +黃=黄 +黌=黉 +點=点 +黨=党 +黲=黪 +黴=霉 +黶=黡 +黷=黩 +黽=黾 +黿=鼋 +鼂=鼌 +鼃=蛙 +鼇=鳌 +鼈=鳖 +鼉=鼍 +鼔=鼓 +鼡=用 +鼦=貂 +鼴=鼹 +齇=齄 +齊=齐 +齋=斋 +齎=赍 +齏=齑 +齒=齿 +齔=龀 +齕=龁 +齗=龂 +齙=龅 +齜=龇 +齟=龃 +齠=龆 +齡=龄 +齦=龈 +齧=啮 +齩=咬 +齪=龊 +齬=龉 +齰=醋 +齲=龋 +齶=腭 +齷=龌 +龍=龙 +龎=厐 +龐=庞 +龔=龚 +龕=龛 +龜=龟 +龞=鳖 +龢=和 +︰=﹕ +︵=《 +︶=》 +︷=《 +︸=》 +︹=《 +︺=》 +︻=《 +︼=》 +︽=《 +︾=》 +︿=《 +﹀=》 +﹁=《 +﹂=》 +﹃=《 +﹄=》 +﹝=《 +﹞=》 +﹢=+ +﹤=《 +﹦== +﹩=$ +﹪=% +﹫=@ +!=! +?=? +/=/ +、=, +%=% +(=( +)=) +,=, +.=. +0=0 +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +A=a +B=b +C=c +D=d +E=e +F=f +G=g +H=h +I=i +J=j +K=k +L=l +M=m +N=n +O=o +P=p +Q=q +R=r +S=s +T=t +U=u +V=v +W=w +X=x +Y=y +Z=z +a=a +b=b +c=c +d=d +e=e +f=f +g=g +h=h +i=i +j=j +k=k +l=l +m=m +n=n +o=o +p=p +q=q +r=r +s=s +t=t +u=u +v=v +w=w +x=x +y=y +z=z +º=0 +¹=1 +²=2 +³=3 +⁴=4 +⁵=5 +⁶=6 +⁷=7 +⁸=8 +⁹=9 +₀=0 +₁=1 +₂=2 +₃=3 +₄=4 +₅=5 +₆=6 +₇=7 +₈=8 +₉=9 +ⁿ=n +:=: +"=" +#=# +$=$ +&=& +'=' +*=* ++=+ +-=- +;=; +<=《 +=== +>=》 +@=@ +[=《 +\=\ +]=》 +^=^ +_=_ +`=` +{=《 +|=| +}=》 +~=~ diff --git a/launchers/standalone/src/test/resources/data/dictionary/other/TagPKU98.csv b/launchers/standalone/src/test/resources/data/dictionary/other/TagPKU98.csv new file mode 100644 index 000000000..c9268b302 --- /dev/null +++ b/launchers/standalone/src/test/resources/data/dictionary/other/TagPKU98.csv @@ -0,0 +1,44 @@ +序号,代码,名称,帮助记忆的诠释,例子及注解 +1,Ag,形语素,形容词性语素。形容词代码为a,语素代码g前面置以A。,绿色/n 似/d 锦/Ag , +2,a,形容词,取英语形容词adjective的第1个字母,[重要/a 步伐/n]NP ,美丽/a ,看似/v 抽象/a , +3,ad,副形词,直接作状语的形容词。形容词代码a和副词代码d并在一起。,[积极/ad 谋求/v]V-ZZ ,幻象/n 易/ad 逝/Vg , +4,an,名形词,具有名词功能的形容词。形容词代码a和名词代码n并在一起。,[外交/n 和/c 安全/an]NP-BL , +5,Bg,区别语素,区别词性语素。区别词代码为b,语素代码g前面置以B。,赤/Ag 橙/Bg 黄/a 绿/a 青/a 蓝/a 紫/a , +6,b,区别词,取汉字“别”的声母。,女/b 司机/n, 金/b 手镯/n, 慢性/b 胃炎/n, 古/b 钱币/n, 副/b 主任/n, 总/b 公司/n单音节区别词和单音节名词或名语素组合,作为一个词,并标以名词词性n。 雄鸡/n, 雌象/n, 女魔/n, 古币/n少数“单音节区别词+双音节词”的结构作为一个词。总书记/n , +7,c,连词,取英语连词conjunction的第1个字母。,合作/vn 与/c 伙伴/n +8,Dg,副语素,副词性语素。副词代码为d,语素代码g前面置以D。,了解/v 甚/Dg 深/a ,煞/Dg 是/v 喜人/a , +9,d,副词,取adverb的第2个字母,因其第1个字母已用于形容词。,进一步/d 发展/v , +10,e,叹词,取英语叹词exclamation的第1个字母。,啊/e ,/w 那/r 金灿灿/z 的/u 麦穗/n , +11,f,方位词,取汉字“方”。,军人/n 的/u 眼睛/n 里/f 不/d 是/v 没有/v 风景/n , +12,h,前接成分,取英语head的第1个字母。,许多/m 非/h 主角/n 人物/n ,办事处/n 的/u “/w 准/h 政府/n ”/w 功能/n 不断/d 加强/v , +13,i,成语,取英语成语idiom的第1个字母。,一言一行/i ,义无反顾/i , +14,j,简称略语,取汉字“简”的声母。,[德/j 外长/n]NP ,文教/j , +15,k,后接成分,后接成分。,少年儿童/l 朋友/n 们/k ,身体/n 健康/a 者/k , +16,l,习用语,习用语尚未成为成语,有点“临时性”,取“临”的声母。,少年儿童/l 朋友/n 们/k ,落到实处/l , +17,Mg,数语素,数词性语素。数词代码为m,语素代码g前面置以M。,甲/Mg 减下/v 的/u 人/n 让/v 乙/Mg 背上/v ,凡/d “/w 寅/Mg 年/n ”/w 中/f 出生/v 的/u 人/n 生肖/n 都/d 属/v 虎/n , +18,m,数词,取英语numeral的第3个字母,n,u已有他用。,1.数量词组应切分为数词和量词。 三/m 个/q, 10/m 公斤/q, 一/m 盒/q 点心/n ,但少数数量词已是词典的登录单位,则不再切分。 一个/m , 一些/m ,2. 基数、序数、小数、分数、百分数一律不予切分,为一个切分单位,标注为 m 。一百二十三/m,20万/m, 123.54/m, 一个/m, 第一/m, 第三十五/m, 20%/m, 三分之二/m, 千分之三十/m, 几十/m 人/n, 十几万/m 元/q, 第一百零一/m 个/q ,3. 约数,前加副词、形容词或后加“来、多、左右”等助数词的应予分开。约/d 一百/m 多/m 万/m,仅/d 一百/m 个/q, 四十/m 来/m 个/q,二十/m 余/m 只/q, 十几/m 个/q,三十/m 左右/m ,两个数词相连的及“成百”、“上千”等则不予切分。五六/m 年/q, 七八/m 天/q,十七八/m 岁/q, 成百/m 学生/n,上千/m 人/n, 4.表序关系的“数+名”结构,应予切分。二/m 连/n , 三/m 部/n , +19,Ng,名语素,名词性语素。名词代码为n,语素代码g前面置以N。,出/v 过/u 两/m 天/q 差/Ng, 理/v 了/u 一/m 次/q 发/Ng, +20,n,名词,取英语名词noun的第1个字母。,(参见 动词--v)岗位/n , 城市/n , 机会/n ,她/r 是/v 责任/n 编辑/n , +21,nr,人名,名词代码n和“人(ren)”的声母并在一起。,1. 汉族人及与汉族起名方式相同的非汉族人的姓和名单独切分,并分别标注为nr。张/nr 仁伟/nr, 欧阳/nr 修/nr, 阮/nr 志雄/nr, 朴/nr 贞爱/nr汉族人除有单姓和复姓外,还有双姓,即有的女子出嫁后,在原来的姓上加上丈夫的姓。如:陈方安生。这种情况切分、标注为:陈/nr 方/nr 安生/nr;唐姜氏,切分、标注为:唐/nr 姜氏/nr。2. 姓名后的职务、职称或称呼要分开。江/nr 主席/n, 小平/nr 同志/n, 江/nr 总书记/n,张/nr 教授/n, 王/nr 部长/n, 陈/nr 老总/n, 李/nr 大娘/n, 刘/nr 阿姨/n, 龙/nr 姑姑/n3. 对人的简称、尊称等若为两个字,则合为一个切分单位,并标以nr。老张/nr, 大李/nr, 小郝/nr, 郭老/nr, 陈总/nr4. 明显带排行的亲属称谓要切分开,分不清楚的则不切开。三/m 哥/n, 大婶/n, 大/a 女儿/n, 大哥/n, 小弟/n, 老爸/n5. 一些著名作者的或不易区分姓和名的笔名通常作为一个切分单位。鲁迅/nr, 茅盾/nr, 巴金/nr, 三毛/nr, 琼瑶/nr, 白桦/nr6. 外国人或少数民族的译名(包括日本人的姓名)不予切分,标注为nr。克林顿/nr, 叶利钦/nr, 才旦卓玛/nr, 小林多喜二/nr, 北研二/nr,华盛顿/nr, 爱因斯坦/nr有些西方人的姓名中有小圆点,也不分开。卡尔·马克思/nr +22,ns,地名,名词代码n和处所词代码s并在一起。,(参见2。短语标记说明--NS)安徽/ns,深圳/ns,杭州/ns,拉萨/ns,哈尔滨/ns, 呼和浩特/ns, 乌鲁木齐/ns,长江/ns,黄海/ns,太平洋/ns, 泰山/ns, 华山/ns,亚洲/ns, 海南岛/ns,太湖/ns,白洋淀/ns, 俄罗斯/ns,哈萨克斯坦/ns,彼得堡/ns, 伏尔加格勒/ns 1. 国名不论长短,作为一个切分单位。中国/ns, 中华人民共和国/ns, 日本国/ns, 美利坚合众国/ns, 美国/ns2. 地名后有“省”、“市”、“县”、“区”、“乡”、“镇”、“村”、“旗”、“州”、“都”、“府”、“道”等单字的行政区划名称时,不切分开,作为一个切分单位。四川省/ns, 天津市/ns,景德镇/ns沙市市/ns, 牡丹江市/ns,正定县/ns,海淀区/ns, 通州区/ns,东升乡/ns, 双桥镇/ns 南化村/ns,华盛顿州/ns,俄亥俄州/ns,东京都/ns, 大阪府/ns,北海道/ns, 长野县/ns,开封府/ns,宣城县/ns3. 地名后的行政区划有两个以上的汉字,则将地名同行政区划名称切开,不过要将地名同行政区划名称用方括号括起来,并标以短语NS。[芜湖/ns 专区/n] NS,[宣城/ns 地区/n]ns,[内蒙古/ns 自治区/n]NS,[深圳/ns 特区/n]NS, [厦门/ns 经济/n 特区/n]NS, [香港/ns 特别/a 行政区/n]NS,[香港/ns 特区/n]NS, [华盛顿/ns 特区/n]NS,4. 地名后有表示地形地貌的一个字的普通名词,如“江、河、山、洋、海、岛、峰、湖”等,不予切分。鸭绿江/ns,亚马逊河/ns, 喜马拉雅山/ns, 珠穆朗玛峰/ns,地中海/ns,大西洋/ns,洞庭湖/ns, 塞普路斯岛/ns 5. 地名后接的表示地形地貌的普通名词若有两个以上汉字,则应切开。然后将地名同该普通名词标成短语NS。[台湾/ns 海峡/n]NS,[华北/ns 平原/n]NS,[帕米尔/ns 高原/n]NS, [南沙/ns 群岛/n]NS,[京东/ns 大/a 峡谷/n]NS [横断/b 山脉/n]NS6.地名后有表示自然区划的一个字的普通名词,如“ 街,路,道,巷,里,町,庄,村,弄,堡”等,不予切分。 中关村/ns,长安街/ns,学院路/ns, 景德镇/ns, 吴家堡/ns, 庞各庄/ns, 三元里/ns,彼得堡/ns, 北菜市巷/ns, 7.地名后接的表示自然区划的普通名词若有两个以上汉字,则应切开。然后将地名同自然区划名词标成短语NS。[米市/ns 大街/n]NS, [蒋家/nz 胡同/n]NS , [陶然亭/ns 公园/n]NS , 8. 大小地名相连时的标注方式为:北京市/ns 海淀区/ns 海淀镇/ns [南/f 大街/n]NS [蒋家/nz 胡同/n]NS 24/m 号/q , +23,nt,机构团体,“团”的声母为t,名词代码n和t并在一起。,(参见2。短语标记说明--NT)联合国/nt,中共中央/nt,国务院/nt, 北京大学/nt1.大多数团体、机构、组织的专有名称一般是短语型的,较长,且含有地名或人名等专名,再组合,标注为短语NT。[中国/ns 计算机/n 学会/n]NT, [香港/ns 钟表业/n 总会/n]NT, [烟台/ns 大学/n]NT, [香港/ns 理工大学/n]NT, [华东/ns 理工大学/n]NT,[合肥/ns 师范/n 学院/n]NT, [北京/ns 图书馆/n]NT, [富士通/nz 株式会社/n]NT, [香山/ns 植物园/n]NT, [安娜/nz 美容院/n]NT,[上海/ns 手表/n 厂/n]NT, [永和/nz 烧饼铺/n]NT,[北京/ns 国安/nz 队/n]NT,2. 对于在国际或中国范围内的知名的唯一的团体、机构、组织的名称即使前面没有专名,也标为nt或NT。联合国/nt,国务院/nt,外交部/nt, 财政部/nt,教育部/nt, 国防部/nt,[世界/n 贸易/n 组织/n]NT, [国家/n 教育/vn 委员会/n]NT,[信息/n 产业/n 部/n]NT,[全国/n 信息/n 技术/n 标准化/vn 委员会/n]NT,[全国/n 总/b 工会/n]NT,[全国/n 人民/n 代表/n 大会/n]NT,美国的“国务院”,其他国家的“外交部、财政部、教育部”,必须在其所属国的国名之后出现时,才联合标注为NT。[美国/ns 国务院/n]NT,[法国/ns 外交部/n]NT,[美/j 国会/n]NT,日本有些政府机构名称很特别,无论是否出现在“日本”国名之后都标为nt。[日本/ns 外务省/nt]NT,[日/j 通产省/nt]NT通产省/nt 3. 前后相连有上下位关系的团体机构组织名称的处理方式如下:[联合国/nt 教科文/j 组织/n]NT, [中国/ns 银行/n 北京/ns 分行/n]NT,[河北省/ns 正定县/ns 西平乐乡/ns 南化村/ns 党支部/n]NT, 当下位名称含有专名(如“北京/ns 分行/n”、“南化村/ns 党支部/n”、“昌平/ns 分校/n”)时,也可脱离前面的上位名称单独标注为NT。[中国/ns 银行/n]NT [北京/ns 分行/n]NT,北京大学/nt [昌平/ns 分校/n]NT,4. 团体、机构、组织名称中用圆括号加注简称时:[宝山/ns 钢铁/n (/w 宝钢/j )/w 总/b 公司/n]NT,[宝山/ns 钢铁/n 总/b 公司/n]NT,(/w 宝钢/j )/w +24,nx,外文字符,外文字符。,A/nx 公司/n ,B/nx 先生/n ,X/nx 君/Ng ,24/m K/nx 镀金/n ,C/nx 是/v 光速/n ,Windows98/nx ,PentiumIV/nx ,I LOVE THIS GAME/nx , +25,nz,其他专名,“专”的声母的第1个字母为z,名词代码n和z并在一起。,(参见2。短语标记说明--NZ)除人名、国名、地名、团体、机构、组织以外的其他专有名词都标以nz。满族/nz,俄罗斯族/nz,汉语/nz,罗马利亚语/nz, 捷克语/nz,中文/nz, 英文/nz, 满人/nz, 哈萨克人/nz, 诺贝尔奖/nz, 茅盾奖/nz, 1.包含专有名称(或简称)的交通线,标以nz;短语型的,标为NZ。津浦路/nz, 石太线/nz, [京/j 九/j 铁路/n]NZ, [京/j 津/j 高速/b 公路/n]NZ, 2. 历史上重要事件、运动等专有名称一般是短语型的,按短语型专有名称处理,标以NZ。[卢沟桥/ns 事件/n]NZ, [西安/ns 事变/n]NZ,[五四/t 运动/n]NZ, [明治/nz 维新/n]NZ,[甲午/t 战争/n]NZ,3.专有名称后接多音节的名词,如“语言”、“文学”、“文化”、“方式”、“精神”等,失去专指性,则应分开。欧洲/ns 语言/n, 法国/ns 文学/n, 西方/ns 文化/n, 贝多芬/nr 交响乐/n, 雷锋/nr 精神/n, 美国/ns 方式/n,日本/ns 料理/n, 宋朝/t 古董/n 4. 商标(包括专名及后接的“牌”、“型”等)是专指的,标以nz,但其后所接的商品仍标以普通名词n。康师傅/nr 方便面/n, 中华牌/nz 香烟/n, 牡丹III型/nz 电视机/n, 联想/nz 电脑/n, 鳄鱼/nz 衬衣/n, 耐克/nz 鞋/n5. 以序号命名的名称一般不认为是专有名称。2/m 号/q 国道/n ,十一/m 届/q 三中全会/j如果前面有专名,合起来作为短语型专名。[中国/ns 101/m 国道/n]NZ, [中共/j 十一/m 届/q 三中全会/j]NZ,6. 书、报、杂志、文档、报告、协议、合同等的名称通常有书名号加以标识,不作为专有名词。由于这些名字往往较长,名字本身按常规处理。《/w 宁波/ns 日报/n 》/w ,《/w 鲁迅/nr 全集/n 》/w,中华/nz 读书/vn 报/n, 杜甫/nr 诗选/n,少数书名、报刊名等专有名称,则不切分。红楼梦/nz, 人民日报/nz,儒林外史/nz 7. 当有些专名无法分辨它们是人名还是地名或机构名时,暂标以nz。[巴黎/ns 贝尔希/nz 体育馆/n]NT,其中“贝尔希”只好暂标为nz。 +26,o,拟声词,取英语拟声词onomatopoeia的第1个字母。,哈哈/o 一/m 笑/v ,装载机/n 隆隆/o 推进/v , +27,p,介词,取英语介词prepositional的第1个字母。,对/p 子孙后代/n 负责/v ,以/p 煤/n 养/v 农/Ng ,为/p 治理/v 荒山/n 服务/v , 把/p 青年/n 推/v 上/v 了/u 领导/vn 岗位/n , +28,q,量词,取英语quantity的第1个字母。,(参见数词m)首/m 批/q ,一/m 年/q , +29,Rg,代语素,代词性语素。代词代码为r,在语素的代码g前面置以R。,读者/n 就/d 是/v 这/r 两/m 棵/q 小树/n 扎根/v 于/p 斯/Rg 、/w 成长/v 于/p 斯/Rg 的/u 肥田/n 沃土/n , +30,r,代词,取英语代词pronoun的第2个字母,因p已用于介词。,单音节代词“本”、“每”、“各”、“诸”后接单音节名词时,和后接的单音节名词合为代词;当后接双音节名词时,应予切分。本报/r, 每人/r, 本社/r, 本/r 地区/n, 各/r 部门/n +31,s,处所词,取英语space的第1个字母。,家里/s 的/u 电脑/n 都/d 联通/v 了/u 国际/n 互联网/n ,西部/s 交通/n 咽喉/n , +32,Tg,时语素,时间词性语素。时间词代码为t,在语素的代码g前面置以T。,3日/t 晚/Tg 在/p 总统府/n 发表/v 声明/n ,尊重/v 现/Tg 执政/vn 当局/n 的/u 权威/n , +33,t,时间词,取英语time的第1个字母。,1. 年月日时分秒,按年、月、日、时、分、秒切分,标注为t 。1997年/t 3月/t 19日/t 下午/t 2时/t 18分/t若数字后无表示时间的“年、月、日、时、分、秒”等的标为数词m。1998/m 中文/n 信息/n 处理/vn 国际/n 会议/n 2. 历史朝代的名称虽然有专有名词的性质,仍标注为t。西周/t, 秦朝/t, 东汉/t, 南北朝/t, 清代/t“牛年、虎年”等一律不予切分,标注为:牛年/t, 虎年/t, 甲午年/t, 甲午/t 战争/n, 庚子/t 赔款/n, 戊戌/t 变法/n +34,u,助词,取英语助词auxiliary。,[[俄罗斯/ns 和/c 北约/j]NP-BL 之间/f [战略/n 伙伴/n 关系/n]NP 的/u 建立/vn]NP 填平/v 了/u [[欧洲/ns 安全/a 政治/n]NP 的/u 鸿沟/n]NP +35,Vg,动语素,动词性语素。动词代码为v。在语素的代码g前面置以V。,洗/v 了/u 一个/m 舒舒服服/z 的/u 澡/Vg +36,v,动词,取英语动词verb的第一个字母。,(参见 名词--n)[[[欧盟/j 扩大/v]S 的/u [历史性/n 决定/n]NP]NP 和/c [北约/j 开放/v]S]NP-BL [为/p [创建/v [一/m 种/q 新/a 的/u 欧洲/ns 安全/a 格局/n]NP]VP-SBI]PP-MD [奠定/v 了/u 基础/n]V-SBI ,, +37,vd,副动词,直接作状语的动词。动词和副词的代码并在一起。,形势/n 会/v 持续/vd 好转/v ,认为/v 是/v 电话局/n 收/v 错/vd 了/u 费/n , +38,vn,名动词,指具有名词功能的动词。动词和名词的代码并在一起。,引起/v 人们/n 的/u 关注/vn 和/c 思考/vn ,收费/vn 电话/n 的/u 号码/n , +39,w,标点符号,,”/w :/w +40,x,非语素字,非语素字只是一个符号,字母x通常用于代表未知数、符号。, +41,Yg,语气语素,语气词性语素。语气词代码为y。在语素的代码g前面置以Y。,唯/d 大力/d 者/k 能/v 致/v 之/u 耳/Yg +42,y,语气词,取汉字“语”的声母。,会/v 泄露/v 用户/n 隐私/n 吗/y ,又/d 何在/v 呢/y ? +43,z,状态词,取汉字“状”的声母的前一个字母。,取得/v 扎扎实实/z 的/u 突破性/n 进展/vn ,四季/n 常青/z 的/u 热带/n 树木/n ,短短/z 几/m 年/q 间, \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/data/version.txt b/launchers/standalone/src/test/resources/data/version.txt new file mode 100644 index 000000000..6a126f402 --- /dev/null +++ b/launchers/standalone/src/test/resources/data/version.txt @@ -0,0 +1 @@ +1.7.5 diff --git a/launchers/standalone/src/test/resources/db/data-h2.sql b/launchers/standalone/src/test/resources/db/data-h2.sql new file mode 100644 index 000000000..61413b07d --- /dev/null +++ b/launchers/standalone/src/test/resources/db/data-h2.sql @@ -0,0 +1,1070 @@ +-- chat data +insert into s2_user (id, `name`, password, display_name, email) values (1, 'admin','admin','admin','admin@xx.com'); +insert into s2_user (id, `name`, password, display_name, email) values (2, 'jack','123456','jack','jack@xx.com'); +insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom','123456','tom','tom@xx.com'); +insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); + +insert into s2_chat_config (`id` ,`domain_id` ,`default_metrics`,`visibility`,`entity_info` ,`dictionary_info`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) values (1,1,'[{"metricId":1,"unit":7,"period":"DAY"}]','{"blackDimIdList":[],"blackMetricIdList":[]}','{"entityIds":[2],"names":["用户","用户姓名"],"detailData":{"dimensionIds":[1,2],"metricIds":[2]}}','[{"itemId":1,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":2,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":3,"type":"DIMENSION","blackList":[],"isDictInfo":true}]','2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); + +insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (1, '超音数访问统计','2023-06-10 10:00:52.495','2023-06-10 10:00:52','admin','您好,欢迎使用内容智能小Q','0','0'); +insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (2, '用户访问统计','2023-06-10 10:01:04.528','2023-06-10 10:01:04','admin','您好,欢迎使用内容智能小Q','0','0'); + +insert into s2_chat_context (chat_id, modified_at , `user`, `query_text`, `semantic_parse` ,ext_data) VALUES(1, '2023-06-10 10:40:49.877', 'admin', '访问', '{"queryMode":"METRIC_ORDERBY","aggType":"NONE","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":3,"name":"访问人数","bizName":"uv","status":1,"sensitiveLevel":0},{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"nativeQuery":false}', 'admin'); +insert into s2_chat_context (chat_id, modified_at , `user`, `query_text`, `semantic_parse` ,ext_data) VALUES(2, '2023-06-10 10:42:02.184', 'null', '访问', '{"queryMode":"METRIC_ORDERBY","aggType":"NONE","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":3,"name":"访问人数","bizName":"uv","status":1,"sensitiveLevel":0},{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"nativeQuery":false}', 'null'); + +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(1, '2023-06-10 10:39:55.178', '超音数 访问次数', 'admin',0,1,'{"queryMode":"METRIC_DOMAIN","querySql":"SELECT `sys_imp_date` , `pv` FROM ( SELECT `sys_imp_date` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` FROM ( SELECT `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_f370` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' ) GROUP BY `sys_imp_date` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"}},"chatContext":{"queryMode":"METRIC_DOMAIN","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","pv":11},{"sys_imp_date":"2023-06-04","pv":14},{"sys_imp_date":"2023-06-05","pv":1},{"sys_imp_date":"2023-06-06","pv":19},{"sys_imp_date":"2023-06-07","pv":18},{"sys_imp_date":"2023-06-08","pv":24},{"sys_imp_date":"2023-06-09","pv":23}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(2, '2023-06-10 10:40:12.259', '按页面', 'admin',0,1,'{"queryMode":"METRIC_ORDERBY","querySql":"SELECT `sys_imp_date` , `page` , `pv` FROM ( SELECT `sys_imp_date` , `page` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` , `page` FROM ( SELECT `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` , `page` AS `page` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_ecf5` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' ) GROUP BY `sys_imp_date` , `page` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"页面","type":"VARCHAR","nameEn":"page","showType":"CATEGORY","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"}},"chatContext":{"queryMode":"METRIC_ORDERBY","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","page":"p1","pv":2},{"sys_imp_date":"2023-06-03","page":"p2","pv":3},{"sys_imp_date":"2023-06-03","page":"p3","pv":2},{"sys_imp_date":"2023-06-03","page":"p4","pv":1},{"sys_imp_date":"2023-06-03","page":"p5","pv":3},{"sys_imp_date":"2023-06-04","page":"p1","pv":3},{"sys_imp_date":"2023-06-04","page":"p2","pv":1},{"sys_imp_date":"2023-06-04","page":"p3","pv":2},{"sys_imp_date":"2023-06-04","page":"p4","pv":4},{"sys_imp_date":"2023-06-04","page":"p5","pv":4}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(3, '2023-06-10 10:40:49.877', '访问', 'admin',0,1,'{"queryMode":"METRIC_ORDERBY","querySql":"SELECT `sys_imp_date` , `page` , `uv` , `pv` FROM ( SELECT `sys_imp_date` , `page` , `s2_pv_uv_statis_uv` AS `uv` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT COUNT ( DISTINCT `s2_pv_uv_statis_uv` ) AS `s2_pv_uv_statis_uv` , SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` , `page` FROM ( SELECT `uv` AS `s2_pv_uv_statis_uv` , `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` , `page` AS `page` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_022c` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' ) GROUP BY `sys_imp_date` , `page` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"页面","type":"VARCHAR","nameEn":"page","showType":"CATEGORY","authorized":true},{"name":"访问人数","type":"BIGINT","nameEn":"uv","showType":"NUMBER","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"}},"chatContext":{"queryMode":"METRIC_ORDERBY","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":3,"name":"访问人数","bizName":"uv","status":1,"sensitiveLevel":0},{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"id":3,"name":"页面","bizName":"page","status":1,"sensitiveLevel":0},{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","page":"p1","uv":2,"pv":2},{"sys_imp_date":"2023-06-03","page":"p2","uv":3,"pv":3},{"sys_imp_date":"2023-06-03","page":"p3","uv":2,"pv":2},{"sys_imp_date":"2023-06-03","page":"p4","uv":1,"pv":1},{"sys_imp_date":"2023-06-03","page":"p5","uv":3,"pv":3},{"sys_imp_date":"2023-06-04","page":"p1","uv":2,"pv":3},{"sys_imp_date":"2023-06-04","page":"p2","uv":1,"pv":1},{"sys_imp_date":"2023-06-04","page":"p3","uv":2,"pv":2},{"sys_imp_date":"2023-06-04","page":"p4","uv":3,"pv":4},{"sys_imp_date":"2023-06-04","page":"p5","uv":3,"pv":4}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(4, '2023-06-10 10:41:18.589','alice 访问次数','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `pv` FROM ( SELECT `sys_imp_date` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` FROM ( SELECT `user_name` , `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_b825` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","pv":2},{"sys_imp_date":"2023-06-04","pv":2},{"sys_imp_date":"2023-06-06","pv":2},{"sys_imp_date":"2023-06-07","pv":2},{"sys_imp_date":"2023-06-08","pv":5},{"sys_imp_date":"2023-06-09","pv":2}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(5, '2023-06-10 10:41:48.211','停留时长','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); +insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(6, '2023-06-10 10:42:02.184','访问','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); + + +-- semantic data +insert into s2_database (id, domain_id , `name`, description, `type` ,config ,created_at ,created_by ,updated_at ,updated_by) VALUES(1, 1, 'H2数据实例', '', 'h2', '{"password":"semantic","url":"jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false","userName":"root"}', '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin'); +insert into s2_datasource (id , domain_id, `name`, biz_name, description, database_id ,datasource_detail, created_at, created_by, updated_at, updated_by ) VALUES(1, 1, '停留时长统计', 's2_stay_time_statis', '停留时长统计', 1, '{"dimensions":[{"bizName":"imp_date","dateFormat":"yyyy-MM-dd","expr":"imp_date","isCreateDimension":0,"type":"time","typeParams":{"isPrimary":"true","timeGranularity":"day"}},{"bizName":"page","dateFormat":"yyyy-MM-dd","expr":"page","isCreateDimension":0,"type":"categorical"}],"identifiers":[{"bizName":"user_name","name":"用户名","type":"primary"}],"measures":[{"agg":"sum","bizName":"s2_stay_time_statis_stay_hours","expr":"stay_hours","isCreateMetric":1,"name":"停留时长"}],"queryType":"sql_query","sqlQuery":"SELECT imp_date, page,user_name,stay_hours FROM s2_stay_time_statis"}', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource (id , domain_id, `name`, biz_name, description, database_id ,datasource_detail, created_at, created_by, updated_at, updated_by ) VALUES(2, 1, 'PVUV统计', 's2_pv_uv_statis', 'PVUV统计', 1, '{"dimensions":[{"bizName":"imp_date","dateFormat":"yyyy-MM-dd","expr":"imp_date","isCreateDimension":0,"type":"time","typeParams":{"isPrimary":"true","timeGranularity":"day"}},{"bizName":"page","dateFormat":"yyyy-MM-dd","expr":"page","isCreateDimension":0,"type":"categorical"}],"identifiers":[{"bizName":"user_name","name":"用户名","type":"primary"}],"measures":[{"agg":"sum","bizName":"s2_pv_uv_statis_pv","expr":"pv","isCreateMetric":1,"name":"访问次数"},{"agg":"count_distinct","bizName":"s2_pv_uv_statis_uv","expr":"uv","isCreateMetric":1,"name":"访问人数"}],"queryType":"sql_query","sqlQuery":"SELECT imp_date, user_name,page,1 as pv, user_name as uv FROM s2_pv_uv_statis"}', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource (id , domain_id, `name`, biz_name, description, database_id ,datasource_detail, created_at, created_by, updated_at, updated_by ) VALUES(3, 1, '用户部门', 'user_department', '用户部门', 1, '{"dimensions":[{"bizName":"department","dateFormat":"yyyy-MM-dd","expr":"department","isCreateDimension":1,"name":"部门","type":"categorical"}],"identifiers":[{"bizName":"user_name","name":"用户名","type":"primary"}],"measures":[],"queryType":"sql_query","sqlQuery":"SELECT user_name,department FROM s2_user_department"}', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource_rela (id , domain_id, `datasource_from`, datasource_to, join_key, created_at, created_by, updated_at, updated_by ) VALUES(1, 1, 1, 2, 'user_name', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource_rela (id , domain_id, `datasource_from`, datasource_to, join_key, created_at, created_by, updated_at, updated_by ) VALUES(2, 1, 1, 3, 'user_name', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_datasource_rela (id , domain_id, `datasource_from`, datasource_to, join_key, created_at, created_by, updated_at, updated_by ) VALUES(3, 1, 2, 3, 'user_name', '2023-05-25 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin'); +insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(1, 1, 3, '部门', 'department', '部门', 1, 0, 'categorical', NULL, 'department', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); +insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(2, 1, 1, '用户名', 'user_name', '用户名', 1, 0, 'primary', NULL, 'user_name', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); +insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(3, 1, 2, '页面', 'page', '页面', 1, 2, 'categorical', NULL, 'page', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); +insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(1, '超音数', 'supersonic', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); +insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(1, 1, '停留时长', 'stay_hours', '停留时长', 1, 2, 'expr', '{"expr":"s2_stay_time_statis_stay_hours","measures":[{"agg":"sum","expr":"stay_hours","isCreateMetric":1,"datasourceId":1,"bizName":"s2_stay_time_statis_stay_hours","name":"s2_stay_time_statis_stay_hours"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); +insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(2, 1, '访问次数', 'pv', '访问次数', 1, 0, 'expr', ' {"expr":"s2_pv_uv_statis_pv","measures":[{"agg":"sum","bizName":"s2_pv_uv_statis_pv","datasourceId":2,"expr":"pv","isCreateMetric":1,"name":"s2_pv_uv_statis_pv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); +insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(3, 1, '访问人数', 'uv', '访问人数', 1, 0, 'expr', ' {"expr":"s2_pv_uv_statis_uv","measures":[{"agg":"count_distinct","bizName":"s2_pv_uv_statis_uv","datasourceId":2,"expr":"uv","isCreateMetric":1,"name":"s2_pv_uv_statis_uv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); + +insert into s2_available_date_info(`item_id` ,`type` ,`date_format` ,`start_date` ,`end_date` ,`unavailable_date` ,`created_at` ,`created_by` ,`updated_at` ,`updated_by` ) +values (1, 'dimension', 'yyyy-MM-dd', DATEADD('DAY', -28, CURRENT_DATE()), DATEADD('DAY', -1, CURRENT_DATE()), '[]', '2023-06-01', 'admin', '2023-06-01', 'admin'); +insert into s2_available_date_info(`item_id` ,`type` ,`date_format` ,`start_date` ,`end_date` ,`unavailable_date` ,`created_at` ,`created_by` ,`updated_at` ,`updated_by` ) +values (2, 'dimension', 'yyyy-MM-dd', DATEADD('DAY', -28, CURRENT_DATE()), DATEADD('DAY', -1, CURRENT_DATE()), '[]', '2023-06-01', 'admin', '2023-06-01', 'admin'); +insert into s2_available_date_info(`item_id` ,`type` ,`date_format` ,`start_date` ,`end_date` ,`unavailable_date` ,`created_at` ,`created_by` ,`updated_at` ,`updated_by` ) +values (3, 'dimension', 'yyyy-MM-dd', DATEADD('DAY', -28, CURRENT_DATE()), DATEADD('DAY', -1, CURRENT_DATE()), '[]', '2023-06-01', 'admin', '2023-06-01', 'admin'); + +insert into s2_auth_groups (group_id, config) +values (1, '{"domainId":"1","name":"admin-permission","groupId":1,"authRules":[{"metrics":["stay_hours"],"dimensions":["page"]}],"dimensionFilters":[""],"dimensionFilterDescription":"授权admin 页面和停留时长权限","authorizedUsers":["admin"],"authorizedDepartmentIds":[]}'); +insert into s2_auth_groups (group_id, config) +values (2, '{"domainId":"1","name":"tom_sales_permission","groupId":2,"authRules":[{"metrics":["stay_hours"],"dimensions":["page"]}],"dimensionFilters":["department in (''sales'')"],"dimensionFilterDescription":"开通 tom sales部门权限", "authorizedUsers":["tom"],"authorizedDepartmentIds":[]}'); + + +-- insert into s2_user (id, `name`, password, display_name, email) values (1, 'admin','admin','admin','admin@xx.com'); +-- insert into s2_user (id, `name`, password, display_name, email) values (2, 'jack','123456','jack','jack@xx.com'); +-- insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom','123456','tom','tom@xx.com'); +-- insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); + +---demo data for semantic and chat +insert into s2_user_department (user_name, department) values ('jack','HR'); +insert into s2_user_department (user_name, department) values ('tom','sales'); +insert into s2_user_department (user_name, department) values ('lucy','marketing'); +insert into s2_user_department (user_name, department) values ('john','strategy'); +insert into s2_user_department (user_name, department) values ('alice','sales'); +insert into s2_user_department (user_name, department) values ('dean','marketing'); + + +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -5, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'alice', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'jack', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', 'p2'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', 'p1'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', 'p3'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', 'p4'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', 'p5'); +INSERT INTO s2_pv_uv_statis (imp_date, user_name, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', 'p4'); + + + + + +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.7636857512911863', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', '0.17663327393462436', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.38943688941552057', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', '0.2715819955225307', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.9358210273119568', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.9364586435510802', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.9707723036513162', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', '0.8497763866782723', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.15504417761372413', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.9507563118298399', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.9746364180572994', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', '0.12869214941133378', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.3024970533288409', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.6639702099980812', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'lucy', '0.4929901454858626', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.06853040276026445', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.8488086078299616', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', '0.8589111177125592', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', '0.5576357066482228', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.8047888670006846', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.766944548494366', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.5280072184505449', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.9693343356046343', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', '0.12805203958456424', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', '0.16963603387027637', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.5901202956521101', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.12710364646712236', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.6346530909156196', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', '0.12461289103639872', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', '0.9863947334662437', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', '0.48899961064192987', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'alice', '0.5382796792688207', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'dean', '0.3506568687014143', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.8633072449771709', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.13999135315363687', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', '0.07258740493845894', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', '0.5244413940436958', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.13258670732966138', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.6015982054464575', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', '0.05513158944480323', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'alice', '0.6707121735296985', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', '0.9330440339006469', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', '0.5630674323371607', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', '0.8720647566229917', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.8331899070546519', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', '0.6712876436249856', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', '0.6694409980332703', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', '0.3703307480606334', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.775368688472696', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.9151205443267096', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.09543108823305857', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'dean', '0.7893992120771057', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', '0.5119923080070498', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', '0.49906724167974936', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.046258282700961884', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'dean', '0.44843595680103954', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', '0.7743935471689718', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.5855299615656824', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', '0.9412963512379853', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.8383247587082538', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'lucy', '0.14517876867236124', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.9327229861441061', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.19042326582894153', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.6029067818254513', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.21715964747214422', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', '0.34259842721045974', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.7064419016593382', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'lucy', '0.5725636566517865', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', '0.22332539583809208', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.8049036189055911', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', '0.6029674758974956', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.11884976360561716', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'alice', '0.7124916829130662', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.5893693718556829', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.602073304496253', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.10491061160039927', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', '0.9006548872378379', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.8545144244288455', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', '0.16915384987875726', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.2271640700690446', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', '0.7807518577160636', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.8919859648888653', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', '0.1564450687270359', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.5840549187653847', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', '0.2213255596777869', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', '0.07868261880306426', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.07710010861455818', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.5131249730162654', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.5035035055368601', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.8996978291173905', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.057442290722216294', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.6443079066865616', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', '0.7398098480748726', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'dean', '0.9835694815034591', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'john', '0.9879213445635557', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.4020136688147111', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', '0.6698797170128024', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.17325132416789113', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'lucy', '0.5784229486763606', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.9185978183932058', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.5474783153973963', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.9730731954700215', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', '0.5390873359288765', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'alice', '0.20522241320887713', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', '0.4088233242325021', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.7608047695853417', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.2749731221085713', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.06154055374702494', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.460668002022406', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.4474746325306228', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.5761666885467472', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'dean', '0.33233441360339655', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', '0.7426534909874778', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', '0.5841437875889118', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.2818296500094526', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', '0.8670888843915217', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'alice', '0.5249294365740248', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.5483356748008438', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', '0.7278566847412673', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.6779976902157362', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.09995341651736978', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', '0.4528538159233879', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.5870756885301056', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.9842091927290255', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.04580936015706816', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.8814678270145769', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', '0.06517379256096412', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.8769832364187129', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', '0.584562279025023', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.8102404090621375', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.11481653429176686', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.43422888918962554', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'lucy', '0.0684414272594508', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.976546463969412', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.617906858141431', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'jack', '0.08663740247579998', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', '0.7124944606691416', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'alice', '0.1321700521239627', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'jack', '0.3078946609431664', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.6149442855237194', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'alice', '0.5963801306980994', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', '0.6999542038973406', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.4599112653446624', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', '0.20300901401048832', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.39989705958717037', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.2486378364940327', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', '0.16880398079144077', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.73927288385526', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.8645283506689198', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', '0.3266940826759587', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.9195490073037541', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.9452523036658287', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.21269683438120535', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.7377502855387184', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.38981597634408716', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.7001799391999863', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.6616720024008785', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'dean', '0.497721735058096', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'jack', '0.22255613760959603', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.05247640233319417', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'dean', '0.27237572107833363', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'alice', '0.9529452406380252', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.28243045060463157', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'lucy', '0.17880444250082506', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.035050038002381156', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', '0.840803223728221', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.5318457377361356', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.9280332892460665', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', '0.752354382202208', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'dean', '0.1866528331789219', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', '0.7016165545791373', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.4191547989960899', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', '0.7025516699007639', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.6160127317884274', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'alice', '0.91223094958137', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.4383056089013998', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'jack', '0.595750781166582', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'lucy', '0.9472349338730268', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'jack', '0.0519104588842193', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.48043983034526205', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.14754707786497478', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', '0.36124288370035695', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'dean', '0.21777919493494613', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.22637666702475057', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.9378215576942598', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.3309229261144562', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.7602880453727515', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.9470462487873785', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.6770215935547629', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.1586074803669385', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'lucy', '0.2754855564794071', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', '0.8355347738454384', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.7251813505573811', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'jack', '0.006606625589642534', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', '0.304832277753024', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.026368662837989554', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'tom', '0.6855977520602776', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'tom', '0.8193746826441749', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.021179295102459972', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.1533849522536005', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.18893553542301778', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.39870999343833624', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.9985665103520182', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.6961441157700171', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.9861933923851885', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', '0.993076500099477', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.4320547269058953', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', '0.18441071030375877', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.1501504986117118', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.252021845734527', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'lucy', '0.24442701577183745', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.07563738855797564', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', '0.34247820646440985', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.9456979276862031', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.19494357263973816', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.9371493867882469', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.6136241316589367', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', '0.8922330760877784', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'dean', '0.9001986074661864', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.4889702884422866', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.2689551234431401', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.5223573993758465', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'tom', '0.05042295556527243', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', '0.2717147121880483', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', '0.7397093309370814', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', '0.157064341631733', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'lucy', '0.7213399784998017', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'tom', '0.764081440588005', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.7514070600074144', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.611647412825278', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.6600796877195596', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', '0.8942204153751679', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.07398121085929721', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', '0.1652506990439564', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.5849759516111703', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.1672502732600889', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.7836135556233219', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'dean', '0.26181269644936356', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', '0.6577275876355586', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.3067293364197956', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.8608288543866495', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.814283434116926', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'jack', '0.33993584425872936', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'john', '0.010812798859160089', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.5156558224263926', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'jack', '0.46320035330198406', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', '0.2651020283994786', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.42467241545664147', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.3695905136678498', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'tom', '0.15269122123348644', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.6755688670583248', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'jack', '0.39064306179528907', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', '0.36479296691952023', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'lucy', '0.5069249157662691', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.4785315495532231', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', '0.7582526218052175', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.42064109605717914', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'dean', '0.5587757581237022', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'lucy', '0.3561686564964428', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.7101688305173135', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.6518061375522985', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.7564485884156583', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.36531347293134464', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'jack', '0.5201689359070235', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'john', '0.7138792929290383', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.9751003716333827', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.5281906318027629', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.6291356541485003', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', '0.1938712974807698', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'john', '0.6267850210775459', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.4469970592043767', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', '0.7690659124175409', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'jack', '0.13335067838090386', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'jack', '0.2966621725922035', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.5740481445089863', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'alice', '0.838028890036331', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', '0.8094354537628714', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'alice', '0.5552924586108698', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.49150373927678315', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.7264346889377966', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.9292830287297702', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.3905616258240767', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.15912349648571666', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'alice', '0.6030082006630102', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'lucy', '0.8712354035243679', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.7685306377211826', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.2869913942171415', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.7142615166855639', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.5625978475154423', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.13611601734791123', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'alice', '0.6977333962685311', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', '0.35140477709778295', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.8805119222967716', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.7014124236538637', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', '0.12759538003439375', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.7515403792213445', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'lucy', '0.03700239289885987', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', '0.31674618364630946', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.4491378834800146', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', '0.6742764131652571', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'lucy', '0.5286362221140248', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.007890326473113496', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', '0.8046560540950831', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.7198364371127147', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.7400546712169153', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.16859870460868698', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.8462852684569557', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.010211452005474353', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'alice', '0.8617802368201087', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.21667479046797633', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.8667689615468714', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', '0.16140709875863557', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.16713368182304666', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'lucy', '0.8957484629768053', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'tom', '0.457835758220534', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.9435170960198477', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'jack', '0.9699253608913104', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.2309897429566834', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.7879705066452681', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.20795869239817255', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', '0.4110352469382019', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'jack', '0.4979592772533561', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', '0.18810865430947044', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'tom', '0.5001240246982048', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'jack', '0.08341934160029707', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.04812784841651041', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.4655982693269717', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'dean', '0.8539357978460663', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'john', '0.9649541785823592', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', '0.8243635648047365', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.929949719929735', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.055983276861168996', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'tom', '0.07845430274829746', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'alice', '0.28257674222099116', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.1578419214960578', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', '0.7853118484860825', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', '0.20790127125904156', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.8650538395535204', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.902116091225815', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'lucy', '0.48542770770171373', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.16725337150113984', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'lucy', '0.3157444453259486', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.565727220131555', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', '0.2531688065358064', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', '0.9191434620980499', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.9224628853942058', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'jack', '0.3256288410730337', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'jack', '0.9709152566761661', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.9794173893522709', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'alice', '0.16582064407977237', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', '0.2652519246960059', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'alice', '0.04092489871261762', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.3020444893927522', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'john', '0.4655412764350543', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'dean', '0.9226436424888846', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.4707663393012884', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.3277970119243966', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'tom', '0.4730675479071551', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'jack', '0.10261940477901954', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'alice', '0.4148892373198616', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.2877219827348403', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.16212409974675845', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.9567425121214822', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'lucy', '0.19795350030679149', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.6954199597749198', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'alice', '0.32884293488801164', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'john', '0.4789917995407148', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'lucy', '0.0698927593996298', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.3352267723792438', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', '0.8085116661598726', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.17515060210353794', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.6006963088370202', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.8794167536704468', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.04091469320757368', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'tom', '0.6709116812690366', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', '0.4850646101328463', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'tom', '0.547488212623346', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'dean', '0.6301717145008927', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'lucy', '0.06123370093612068', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'alice', '0.2545600223228257', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'john', '0.28355287519210803', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.3231348374147818', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.4585172495754063', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.7893945285152268', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'john', '0.6810596014794181', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'john', '0.7136031244915907', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'jack', '0.259734039051829', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.7759518703827996', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'john', '0.06288891046833589', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'dean', '0.8242980461154241', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.36590300307021595', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'lucy', '0.20254092528445444', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.5427356081880325', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.1467846603517391', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.8975527268892767', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'dean', '0.3483541520806722', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.6922544855316723', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.3690185253006011', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'tom', '0.7564541265683148', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', '0.3634152133342695', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.33740378933701987', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.7942640738315301', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.7894896778233523', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'jack', '0.7153281477198108', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'tom', '0.5546359859065261', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'john', '0.7727157385809087', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'dean', '0.8707097754747494', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'john', '0.3873936520764878', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.7590305068820566', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'john', '0.512826935863365', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'john', '0.19120284727846926', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'dean', '0.5382693105670825', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'john', '0.826241649014955', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', '0.6133080470571559', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'jack', '0.6452862617544055', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'lucy', '0.3025772179023586', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '4.709864550322962E-4', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.024816355013726588', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -6, CURRENT_DATE()), 'alice', '0.8407500495605565', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'alice', '0.8420879584266481', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'lucy', '0.2719224735814776', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'tom', '0.8939712577294938', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'dean', '0.8086189323362379', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'tom', '0.6063415085381448', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'tom', '0.39783242658234674', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.6085577206028068', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'tom', '0.5154289424127074', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'john', '0.878436600887031', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.5577906295015223', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'lucy', '0.1143260282925247', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', '0.312756557275364', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.05548807854726956', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'tom', '0.12140791431139175', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.23897628700410234', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.22223137342481392', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.12379891645900953', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'john', '0.33729146112854247', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.8816768640060831', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -21, CURRENT_DATE()), 'jack', '0.6301700633426532', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -3, CURRENT_DATE()), 'alice', '0.4566295223861714', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', '0.1777378523933678', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.8163769471165477', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'tom', '0.4380805149704541', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', '0.2987018822475964', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'dean', '0.6726495645391617', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.8394327461109705', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'dean', '0.820512945501936', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'tom', '0.1580105370757261', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -20, CURRENT_DATE()), 'jack', '0.9961450897279505', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -7, CURRENT_DATE()), 'john', '0.6574891890500061', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'john', '0.5201205570085158', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'alice', '0.2445069633928285', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -17, CURRENT_DATE()), 'john', '0.3155229654901067', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'jack', '0.3665971881269575', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'john', '0.5544977915912215', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.15978771803015113', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'lucy', '0.038128748344929186', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'tom', '0.49026304025118594', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.5166802080526571', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.22568230066042194', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -28, CURRENT_DATE()), 'john', '0.9888634109849955', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'jack', '0.21022365182102054', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'john', '0.47052993358031114', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.25686122383263454', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.18929054223320718', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'jack', '0.7925339862375451', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -12, CURRENT_DATE()), 'john', '0.12613308249498645', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.7381524971311578', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -4, CURRENT_DATE()), 'alice', '0.08639585437319919', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -27, CURRENT_DATE()), 'tom', '0.9519897106846164', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.33446548574801926', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'jack', '0.40667134603483324', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -10, CURRENT_DATE()), 'jack', '0.17100718420628735', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -26, CURRENT_DATE()), 'lucy', '0.4445585525686886', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'tom', '0.47372916928883013', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'john', '0.19826861093848824', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -13, CURRENT_DATE()), 'john', '0.13679268112019338', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -24, CURRENT_DATE()), 'tom', '0.9805515708224516', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'dean', '0.4738376165601095', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'dean', '0.5739441073158964', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'alice', '0.8428505498030564', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'lucy', '0.32655416551155336', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -14, CURRENT_DATE()), 'tom', '0.7055736367780644', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -2, CURRENT_DATE()), 'tom', '0.9621355090189875', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -9, CURRENT_DATE()), 'jack', '0.9665339161730553', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'dean', '0.44309781869697995', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -18, CURRENT_DATE()), 'tom', '0.8651220802537761', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.6451892308277741', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -16, CURRENT_DATE()), 'dean', '0.056797307451316725', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.6847604118085596', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -23, CURRENT_DATE()), 'jack', '0.13428051757364667', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -29, CURRENT_DATE()), 'lucy', '0.9814797176951834', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -11, CURRENT_DATE()), 'tom', '0.7386074051153445', 'p3'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -25, CURRENT_DATE()), 'alice', '0.4825297824657663', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', '0.06608870508231235', 'p5'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -22, CURRENT_DATE()), 'lucy', '0.6278253028988848', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -1, CURRENT_DATE()), 'alice', '0.6705580511822682', 'p1'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -19, CURRENT_DATE()), 'alice', '0.8131712486302015', 'p2'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -15, CURRENT_DATE()), 'lucy', '0.8124302447925607', 'p4'); +INSERT INTO s2_stay_time_statis (imp_date, user_name, stay_hours, page) VALUES (DATEADD('DAY', -8, CURRENT_DATE()), 'lucy', '0.039935860913407284', 'p2'); diff --git a/launchers/standalone/src/test/resources/db/schema-h2.sql b/launchers/standalone/src/test/resources/db/schema-h2.sql new file mode 100644 index 000000000..e75454e90 --- /dev/null +++ b/launchers/standalone/src/test/resources/db/schema-h2.sql @@ -0,0 +1,341 @@ +-- chat tables +CREATE TABLE IF NOT EXISTS `s2_chat_context` +( + `chat_id` BIGINT NOT NULL , -- context chat id + `modified_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , -- row modify time + `user` varchar(64) DEFAULT NULL , -- row modify user + `query_text` LONGVARCHAR DEFAULT NULL , -- query text + `semantic_parse` LONGVARCHAR DEFAULT NULL , -- parse data + `ext_data` LONGVARCHAR DEFAULT NULL , -- extend data + PRIMARY KEY (`chat_id`) +); + +CREATE TABLE IF NOT EXISTS `s2_chat` +( + `chat_id` BIGINT auto_increment ,-- AUTO_INCREMENT, + `chat_name` varchar(100) DEFAULT NULL, + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , + `last_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , + `creator` varchar(30) DEFAULT NULL, + `last_question` varchar(200) DEFAULT NULL, + `is_delete` INT DEFAULT '0' COMMENT 'is deleted', + `is_top` INT DEFAULT '0' COMMENT 'is top', + PRIMARY KEY (`chat_id`) +) ; + + +CREATE TABLE `s2_chat_query` +( + `question_id` BIGINT NOT NULL AUTO_INCREMENT, + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `query_text` mediumtext, + `user_name` varchar(150) DEFAULT NULL COMMENT '', + `query_state` int(1) DEFAULT NULL, + `chat_id` BIGINT NOT NULL , -- context chat id + `query_response` mediumtext NOT NULL , + `score` int DEFAULT '0', + `feedback` varchar(1024) DEFAULT '', + PRIMARY KEY (`question_id`) +); + + + +CREATE TABLE IF NOT EXISTS `s2_chat_config` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT DEFAULT NULL , + `default_metrics` varchar(655) DEFAULT NULL, + `visibility` varchar(655) , -- invisible dimension metric information + `entity_info` varchar(655) , + `dictionary_info` varchar(655) , -- dictionary-related dimension setting information + `created_at` TIMESTAMP NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_by` varchar(100) NOT NULL , + `status` INT NOT NULL DEFAULT '0' , -- domain extension information status : 0 is normal, 1 is off the shelf, 2 is deleted + PRIMARY KEY (`id`) +) ; +COMMENT ON TABLE s2_chat_config IS 'chat config information table '; + + + + +CREATE TABLE IF NOT EXISTS `s2_dictionary` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `dim_value_infos` LONGVARCHAR , -- dimension value setting information + `created_at` TIMESTAMP NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_by` varchar(100) DEFAULT NULL , + `status` INT NOT NULL DEFAULT '0' , -- domain extension information status : 0 is normal, 1 is off the shelf, 2 is deleted + PRIMARY KEY (`id`), + UNIQUE (domain_id) + ); +COMMENT ON TABLE s2_dictionary IS 'dictionary configuration information table'; + + +CREATE TABLE IF NOT EXISTS `s2_dictionary_task` ( + `id` INT NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL , -- task name + `description` varchar(255) , + `command`LONGVARCHAR NOT NULL , -- task Request Parameters + `command_md5` varchar(255) NOT NULL , -- task Request Parameters md5 + `status` INT NOT NULL , -- the final status of the task + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP , + `created_by` varchar(100) NOT NULL , + `progress` DOUBLE default 0.00 , -- task real-time progress + `elapsed_ms` bigINT DEFAULT NULL , -- the task takes time in milliseconds + `message` LONGVARCHAR , -- remark related information + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_dictionary_task IS 'dictionary task information table'; + + +create table s2_user +( + id INT AUTO_INCREMENT, + name varchar(100) not null, + display_name varchar(100) null, + password varchar(100) null, + email varchar(100) null, + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_user IS 'user information table'; + +-- semantic tables + +CREATE TABLE IF NOT EXISTS `s2_domain` ( + `id` INT NOT NULL AUTO_INCREMENT , + `name` varchar(255) DEFAULT NULL , -- domain name + `biz_name` varchar(255) DEFAULT NULL , -- internal name + `parent_id` INT DEFAULT '0' , -- parent domain ID + `status` INT NOT NULL , + `created_at` TIMESTAMP DEFAULT NULL , + `created_by` varchar(100) DEFAULT NULL , + `updated_at` TIMESTAMP DEFAULT NULL , + `updated_by` varchar(100) DEFAULT NULL , + `is_unique` INT DEFAULT NULL , -- 0 is non-unique, 1 is unique + `admin` varchar(3000) DEFAULT NULL , -- domain administrator + `admin_org` varchar(3000) DEFAULT NULL , -- domain administrators organization + `is_open` TINYINT DEFAULT NULL , -- whether the domain is public + `viewer` varchar(3000) DEFAULT NULL , -- domain available users + `view_org` varchar(3000) DEFAULT NULL , -- domain available organization + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_domain IS 'domain basic information'; + + +CREATE TABLE `s2_database` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `description` varchar(500) DEFAULT NULL , + `type` varchar(20) NOT NULL , -- type: mysql,clickhouse,tdw + `config` varchar(655) NOT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL, + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_database IS 'database instance table'; + +CREATE TABLE IF NOT EXISTS `s2_datasource` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `biz_name` varchar(255) NOT NULL , + `description` varchar(500) DEFAULT NULL , + `database_id` INT NOT NULL , + `datasource_detail` LONGVARCHAR NOT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL, + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_datasource IS 'datasource table'; + +create table s2_auth_groups +( + group_id INT, + config varchar(2048), + PRIMARY KEY (`group_id`) +); + +CREATE TABLE IF NOT EXISTS `s2_metric` ( + `id` INT NOT NULL AUTO_INCREMENT, + `domain_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `biz_name` varchar(255) NOT NULL , + `description` varchar(500) DEFAULT NULL , + `status` INT NOT NULL , -- status, 0 is normal, 1 is off the shelf, 2 is deleted + `sensitive_level` INT NOT NULL , + `type` varchar(50) NOT NULL , -- type proxy,expr + `type_params` LONGVARCHAR DEFAULT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL , + `data_format_type` varchar(50) DEFAULT NULL , + `data_format` varchar(500) DEFAULT NULL, + `alias` varchar(500) DEFAULT NULL, + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_metric IS 'metric information table'; + + +CREATE TABLE IF NOT EXISTS `s2_dimension` ( + `id` INT NOT NULL AUTO_INCREMENT , + `domain_id` INT NOT NULL , + `datasource_id` INT NOT NULL , + `name` varchar(255) NOT NULL , + `biz_name` varchar(255) NOT NULL , + `description` varchar(500) NOT NULL , + `status` INT NOT NULL , -- status, 0 is normal, 1 is off the shelf, 2 is deleted + `sensitive_level` INT DEFAULT NULL , + `type` varchar(50) NOT NULL , -- type categorical,time + `type_params` LONGVARCHAR DEFAULT NULL , + `expr` LONGVARCHAR NOT NULL , -- expression + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL , + `semantic_type` varchar(20) NOT NULL, -- semantic type: DATE, ID, CATEGORY + `alias` varchar(500) DEFAULT NULL, + `default_values` varchar(500) DEFAULT NULL, + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_dimension IS 'dimension information table'; + +create table s2_datasource_rela +( + id INT AUTO_INCREMENT, + domain_id INT null, + datasource_from INT null, + datasource_to INT null, + join_key varchar(100) null, + created_at TIMESTAMP null, + created_by varchar(100) null, + updated_at TIMESTAMP null, + updated_by varchar(100) null, + PRIMARY KEY (`id`) +); +COMMENT ON TABLE s2_datasource_rela IS 'data source association table'; + +create table s2_view_info +( + id INT auto_increment, + domain_id INT null, + type varchar(20) null comment 'datasource、dimension、metric', + config LONGVARCHAR null comment 'config detail', + created_at TIMESTAMP null, + created_by varchar(100) null, + updated_at TIMESTAMP null, + updated_by varchar(100) not null +); +COMMENT ON TABLE s2_view_info IS 'view information table'; + + +CREATE TABLE `s2_query_stat_info` ( + `id` INT NOT NULL AUTO_INCREMENT, + `trace_id` varchar(200) DEFAULT NULL, -- query unique identifier + `domain_id` INT DEFAULT NULL, + `user` varchar(200) DEFAULT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP , + `query_type` varchar(200) DEFAULT NULL, -- the corresponding scene + `query_type_back` INT DEFAULT '0' , -- query type, 0-normal query, 1-pre-refresh type + `query_sql_cmd`LONGVARCHAR , -- sql type request parameter + `sql_cmd_md5` varchar(200) DEFAULT NULL, -- sql type request parameter md5 + `query_struct_cmd`LONGVARCHAR , -- struct type request parameter + `struct_cmd_md5` varchar(200) DEFAULT NULL, -- struct type request parameter md5值 + `sql`LONGVARCHAR , + `sql_md5` varchar(200) DEFAULT NULL, -- sql md5 + `query_engine` varchar(20) DEFAULT NULL, + `elapsed_ms` bigINT DEFAULT NULL, + `query_state` varchar(20) DEFAULT NULL, + `native_query` INT DEFAULT NULL, -- 1-detail query, 0-aggregation query + `start_date` varchar(50) DEFAULT NULL, + `end_date` varchar(50) DEFAULT NULL, + `dimensions`LONGVARCHAR , -- dimensions involved in sql + `metrics`LONGVARCHAR , -- metric involved in sql + `select_cols`LONGVARCHAR , + `agg_cols`LONGVARCHAR , + `filter_cols`LONGVARCHAR , + `group_by_cols`LONGVARCHAR , + `order_by_cols`LONGVARCHAR , + `use_result_cache` TINYINT DEFAULT '-1' , -- whether to hit the result cache + `use_sql_cache` TINYINT DEFAULT '-1' , -- whether to hit the sql cache + `sql_cache_key`LONGVARCHAR , -- sql cache key + `result_cache_key`LONGVARCHAR , -- result cache key + PRIMARY KEY (`id`) +) ; +COMMENT ON TABLE s2_query_stat_info IS 'query statistics table'; + + +CREATE TABLE IF NOT EXISTS `s2_semantic_pasre_info` ( + `id` INT NOT NULL AUTO_INCREMENT, + `trace_id` varchar(200) NOT NULL , + `domain_id` INT NOT NULL , + `dimensions`LONGVARCHAR , + `metrics`LONGVARCHAR , + `orders`LONGVARCHAR , + `filters`LONGVARCHAR , + `date_info`LONGVARCHAR , + `limit` INT NOT NULL , + `native_query` TINYINT NOT NULL DEFAULT '0' , + `sql`LONGVARCHAR , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `status` INT NOT NULL , + `elapsed_ms` bigINT DEFAULT NULL , + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_semantic_pasre_info IS 'semantic layer sql parsing information table'; + + +CREATE TABLE IF NOT EXISTS `s2_available_date_info` ( + `id` INT NOT NULL AUTO_INCREMENT , + `item_id` INT NOT NULL , + `type` varchar(255) NOT NULL , + `date_format` varchar(64) NOT NULL , + `start_date` varchar(64) , + `end_date` varchar(64) , + `unavailable_date` LONGVARCHAR DEFAULT NULL , + `created_at` TIMESTAMP NOT NULL , + `created_by` varchar(100) NOT NULL , + `updated_at` TIMESTAMP NOT NULL , + `updated_by` varchar(100) NOT NULL , + `status` INT DEFAULT '0', -- 1-in use 0 is normal, 1 is off the shelf, 2 is deleted + PRIMARY KEY (`id`) + ); +COMMENT ON TABLE s2_dimension IS 'dimension information table'; + + +-------demo for semantic and chat +CREATE TABLE IF NOT EXISTS `s2_user_department` ( + `user_name` varchar(200) NOT NULL, + `department` varchar(200) NOT NULL -- department of user + ); +COMMENT ON TABLE s2_user_department IS 'user_department_info'; + +CREATE TABLE IF NOT EXISTS `s2_pv_uv_statis` ( + `imp_date` varchar(200) NOT NULL, + `user_name` varchar(200) NOT NULL, + `page` varchar(200) NOT NULL + ); +COMMENT ON TABLE s2_pv_uv_statis IS 's2_pv_uv_statis'; + +CREATE TABLE IF NOT EXISTS `s2_stay_time_statis` ( + `imp_date` varchar(200) NOT NULL, + `user_name` varchar(200) NOT NULL, + `stay_hours` DOUBLE NOT NULL, + `page` varchar(200) NOT NULL + ); +COMMENT ON TABLE s2_stay_time_statis IS 's2_stay_time_statis_info'; + + + + + + diff --git a/launchers/standalone/src/test/resources/hanlp.properties b/launchers/standalone/src/test/resources/hanlp.properties new file mode 100644 index 000000000..9d91904eb --- /dev/null +++ b/launchers/standalone/src/test/resources/hanlp.properties @@ -0,0 +1,2 @@ +root=. +CustomDictionaryPath=data/dictionary/custom/DimValue_1_1.txt;data/dictionary/custom/DimValue_1_2.txt;data/dictionary/custom/DimValue_1_3.txt; \ No newline at end of file diff --git a/launchers/standalone/src/test/resources/logback-spring.xml b/launchers/standalone/src/test/resources/logback-spring.xml new file mode 100644 index 000000000..810eda9b3 --- /dev/null +++ b/launchers/standalone/src/test/resources/logback-spring.xml @@ -0,0 +1,93 @@ + + + logback + + + + + + + %d{HH:mm:ss} [%thread] %-5level %logger{36} %line - %msg%n + + + + + + ${LOG_PATH}/info.${LOG_APPNAME}.log + + + + ${LOG_PATH}/info.${LOG_APPNAME}.%d{yyyy-MM-dd}.log.gz + + 30 + + + + + + UTF-8 + %d [%thread] %-5level [%X{TRACE_ID}] %logger{36} %line - %msg%n + + + + + + + Error + + + ${LOG_PATH}/error.${LOG_APPNAME}.log + + + + ${LOG_PATH}/error.${LOG_APPNAME}.%d{yyyy-MM-dd}.log.gz + + 90 + + + + + + UTF-8 + %d [%thread] %-5level [%X{TRACE_ID}] %logger{36} %line - %msg%n + + + + + + + + + + + + ${LOG_PATH}/serviceinfo.${LOG_APPNAME}.log + + + + ${LOG_PATH}/serviceinfo.${LOG_APPNAME}.%d{yyyy-MM-dd}.log.gz + + 30 + + + + + + UTF-8 + %d [%thread] %-5level [%X{TRACE_ID}] %logger{36} %line - %msg%n + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index a7b34fd6a..7e6c4be5a 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 2.5.1 1.34.0 1.23.0 - 0.5-SNAPSHOT + 0.6-SNAPSHOT diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/MetricTypeEnum.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/MetricTypeEnum.java index 87e612136..09674c884 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/MetricTypeEnum.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/MetricTypeEnum.java @@ -3,15 +3,7 @@ package com.tencent.supersonic.semantic.api.core.enums; public enum MetricTypeEnum { - EXPR("expr"); + ATOMIC, + DERIVED - private String name; - - MetricTypeEnum(String name) { - this.name = name; - } - - public String getName() { - return name; - } } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/OperatorEnum.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/OperatorEnum.java index 7b302ccea..3a37f9a31 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/OperatorEnum.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/OperatorEnum.java @@ -19,12 +19,12 @@ public enum OperatorEnum { UNKNOWN("UNKNOWN"); + private String operator; + OperatorEnum(String operator) { this.operator = operator; } - private String operator; - public String getOperator() { return operator; } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeBackEnum.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeBackEnum.java index 7f1a1d5fb..942f02ee3 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeBackEnum.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeBackEnum.java @@ -14,14 +14,6 @@ public enum QueryTypeBackEnum { this.state = state; } - public String getValue() { - return value; - } - - public Integer getState() { - return state; - } - public static QueryTypeBackEnum of(String src) { for (QueryTypeBackEnum operatorEnum : QueryTypeBackEnum.values()) { if (src.toUpperCase().contains(operatorEnum.value)) { @@ -31,5 +23,13 @@ public enum QueryTypeBackEnum { return null; } + public String getValue() { + return value; + } + + public Integer getState() { + return state; + } + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeEnum.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeEnum.java index d53f04ab6..0cfa26322 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeEnum.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/QueryTypeEnum.java @@ -12,10 +12,6 @@ public enum QueryTypeEnum { this.value = value; } - public String getValue() { - return value; - } - public static QueryTypeEnum of(String src) { for (QueryTypeEnum operatorEnum : QueryTypeEnum.values()) { if (src.toUpperCase().contains(operatorEnum.value)) { @@ -25,5 +21,9 @@ public enum QueryTypeEnum { return null; } + public String getValue() { + return value; + } + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/TimeDimensionEnum.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/TimeDimensionEnum.java index 92f827271..0ad46bca3 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/TimeDimensionEnum.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/enums/TimeDimensionEnum.java @@ -18,11 +18,11 @@ public enum TimeDimensionEnum { this.name = name; } - public String getName() { - return name; - } - public static List getNameList() { return Arrays.stream(TimeDimensionEnum.values()).map(TimeDimensionEnum::getName).collect(Collectors.toList()); } + + public String getName() { + return name; + } } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/MetricTypeParams.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/MetricTypeParams.java index e32356833..b97bf56f8 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/MetricTypeParams.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/MetricTypeParams.java @@ -1,12 +1,13 @@ package com.tencent.supersonic.semantic.api.core.pojo; import java.util.List; +import com.google.common.collect.Lists; import lombok.Data; @Data public class MetricTypeParams { - private List measures; + private List measures = Lists.newArrayList(); private String expr; diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/QueryColumn.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/QueryColumn.java index 9adc33066..6fa981635 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/QueryColumn.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/pojo/QueryColumn.java @@ -15,12 +15,12 @@ public class QueryColumn { private String showType; private Boolean authorized = true; - public void setType(String type) { - this.type = type == null ? null : type; - } - public QueryColumn(String nameEn, String type) { this.type = type; this.nameEn = nameEn; } + + public void setType(String type) { + this.type = type == null ? null : type; + } } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatabaseReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatabaseReq.java index cefb5e929..1924d91ed 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatabaseReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatabaseReq.java @@ -1,5 +1,6 @@ package com.tencent.supersonic.semantic.api.core.request; +import com.tencent.supersonic.semantic.api.core.enums.DataTypeEnum; import lombok.Data; import org.apache.commons.lang3.StringUtils; @@ -7,7 +8,6 @@ import org.apache.commons.lang3.StringUtils; @Data public class DatabaseReq { - private Long id; private Long domainId; @@ -34,6 +34,10 @@ public class DatabaseReq { if (StringUtils.isNotBlank(url)) { return url; } + if (type.equalsIgnoreCase(DataTypeEnum.MYSQL.getFeature())) { + return String.format("jdbc:%s://%s:%s?sessionVariables=sql_mode='IGNORE_SPACE'&allowMultiQueries=true", + type, host, port); + } return String.format("jdbc:%s://%s:%s", type, host, port); } } \ No newline at end of file diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatasourceReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatasourceReq.java index 460cfbde7..bcfeb3363 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatasourceReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DatasourceReq.java @@ -19,7 +19,7 @@ public class DatasourceReq extends SchemaItem { private String sqlQuery; - private String sqlTable; + private String tableQuery; private Long domainId; diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DimensionReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DimensionReq.java index 36262f079..31a101129 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DimensionReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/DimensionReq.java @@ -3,6 +3,7 @@ package com.tencent.supersonic.semantic.api.core.request; import com.tencent.supersonic.common.pojo.SchemaItem; import javax.validation.constraints.NotNull; import lombok.Data; +import java.util.List; @Data public class DimensionReq extends SchemaItem { @@ -20,5 +21,9 @@ public class DimensionReq extends SchemaItem { //DATE ID CATEGORY private String semanticType = "CATEGORY"; + private String alias; + + private List defaultValues; + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricBaseReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricBaseReq.java index b77d317cd..fcea10637 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricBaseReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricBaseReq.java @@ -10,4 +10,6 @@ public class MetricBaseReq extends SchemaItem { private Long domainId; + private String alias; + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricReq.java index d38937129..b7f6cf337 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/request/MetricReq.java @@ -1,12 +1,31 @@ package com.tencent.supersonic.semantic.api.core.request; +import com.tencent.supersonic.semantic.api.core.enums.MetricTypeEnum; +import com.tencent.supersonic.semantic.api.core.pojo.Measure; import com.tencent.supersonic.semantic.api.core.pojo.MetricTypeParams; import lombok.Data; +import java.util.List; @Data public class MetricReq extends MetricBaseReq { + private MetricTypeEnum metricType; + private MetricTypeParams typeParams; + public MetricTypeEnum getMetricType() { + if (metricType != null) { + return metricType; + } + List measureList = typeParams.getMeasures(); + if (measureList.size() > 1) { + return MetricTypeEnum.DERIVED; + } + if (measureList.size() == 1 && typeParams.getExpr().trim().equalsIgnoreCase(measureList.get(0).getBizName())) { + return MetricTypeEnum.ATOMIC; + } + throw new RuntimeException("measure can not be none"); + } + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DimensionResp.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DimensionResp.java index e1dca29e6..38a0ce7a1 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DimensionResp.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DimensionResp.java @@ -3,6 +3,7 @@ package com.tencent.supersonic.semantic.api.core.response; import com.tencent.supersonic.common.pojo.SchemaItem; import lombok.Data; +import java.util.List; @Data @@ -24,5 +25,9 @@ public class DimensionResp extends SchemaItem { //DATE ID CATEGORY private String semanticType; + private String alias; + + private List defaultValues; + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DomainResp.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DomainResp.java index 1d21f2b8c..e0b8689e4 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DomainResp.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/DomainResp.java @@ -23,5 +23,9 @@ public class DomainResp extends SchemaItem { private Integer isOpen = 0; + private Integer dimensionCnt; + + private Integer metricCnt; + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/MetricResp.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/MetricResp.java index f9bc22c58..c3dfb0295 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/MetricResp.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/core/response/MetricResp.java @@ -14,16 +14,18 @@ public class MetricResp extends SchemaItem { private String domainName; - //measure_proxy ratio expr cumulative derived + //ATOMIC DERIVED private String type; private MetricTypeParams typeParams; private String fullPath; - private String dataFormatType; private DataFormat dataFormat; + private String alias; + + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/enums/FilterOperatorEnum.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/enums/FilterOperatorEnum.java index a1bb243a7..fd6823f37 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/enums/FilterOperatorEnum.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/enums/FilterOperatorEnum.java @@ -26,11 +26,6 @@ public enum FilterOperatorEnum { this.value = value; } - @JsonValue - public String getValue() { - return value; - } - @JsonCreator public static FilterOperatorEnum getSqlOperator(String type) { for (FilterOperatorEnum operatorEnum : FilterOperatorEnum.values()) { @@ -41,5 +36,10 @@ public enum FilterOperatorEnum { return null; } + @JsonValue + public String getValue() { + return value; + } + } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Criterion.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Criterion.java index 8436b4bcc..162779ba1 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Criterion.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Criterion.java @@ -20,6 +20,26 @@ public class Criterion { private String dataType; + public Criterion(String column, FilterOperatorEnum operator, Object value, String dataType) { + super(); + this.column = column; + this.operator = operator; + this.value = value; + this.dataType = dataType; + + if (FilterOperatorEnum.BETWEEN.name().equals(operator) || FilterOperatorEnum.IN.name().equals(operator) + || FilterOperatorEnum.NOT_IN.name().equals(operator)) { + this.values = (List) value; + } + } + + public boolean isNeedApostrophe() { + return Arrays.stream(StringDataType.values()) + .filter(value -> this.dataType.equalsIgnoreCase(value.getType())).findFirst() + .isPresent(); + } + + public enum NumericDataType { TINYINT("TINYINT"), SMALLINT("SMALLINT"), @@ -43,6 +63,7 @@ public class Criterion { } } + public enum StringDataType { VARCHAR("VARCHAR"), STRING("STRING"), @@ -59,25 +80,4 @@ public class Criterion { } - public Criterion(String column, FilterOperatorEnum operator, Object value, String dataType) { - super(); - this.column = column; - this.operator = operator; - this.value = value; - this.dataType = dataType; - - if (FilterOperatorEnum.BETWEEN.name().equals(operator) || FilterOperatorEnum.IN.name().equals(operator) - || FilterOperatorEnum.NOT_IN.name().equals(operator)) { - this.values = (List) value; - } - } - - - public boolean isNeedApostrophe() { - return Arrays.stream(StringDataType.values()) - .filter(value -> this.dataType.equalsIgnoreCase(value.getType())).findFirst() - .isPresent(); - } - - } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Filter.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Filter.java index 8ea40b380..9c3d2506e 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Filter.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/pojo/Filter.java @@ -13,19 +13,11 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class Filter { - public enum Relation { - FILTER, OR, AND - } - private Relation relation = Relation.FILTER; private String bizName; - private String name; - private FilterOperatorEnum operator; - private Object value; - private List children; public Filter(String bizName, FilterOperatorEnum operator, Object value) { @@ -34,7 +26,6 @@ public class Filter { this.value = value; } - public Filter(Relation relation, String bizName, FilterOperatorEnum operator, Object value) { this.relation = relation; this.bizName = bizName; @@ -60,4 +51,8 @@ public class Filter { sb.append('}'); return sb.toString(); } + + public enum Relation { + FILTER, OR, AND + } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatabaseServiceImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatabaseServiceImpl.java index 3c1060008..c61bdf965 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatabaseServiceImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatabaseServiceImpl.java @@ -5,6 +5,8 @@ import com.tencent.supersonic.semantic.api.core.request.DatabaseReq; import com.tencent.supersonic.semantic.api.core.response.DatabaseResp; import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; import com.tencent.supersonic.semantic.api.core.response.SqlParserResp; +import com.tencent.supersonic.semantic.core.domain.adaptor.engineadapter.EngineAdaptor; +import com.tencent.supersonic.semantic.core.domain.adaptor.engineadapter.EngineAdaptorFactory; import com.tencent.supersonic.semantic.core.domain.dataobject.DatabaseDO; import com.tencent.supersonic.semantic.core.domain.repository.DatabaseRepository; import com.tencent.supersonic.semantic.core.domain.utils.DatabaseConverter; @@ -24,9 +26,8 @@ import org.springframework.stereotype.Service; @Service public class DatabaseServiceImpl implements DatabaseService { - private DatabaseRepository databaseRepository; - private final SqlUtils sqlUtils; + private DatabaseRepository databaseRepository; public DatabaseServiceImpl(DatabaseRepository databaseRepository, SqlUtils sqlUtils) { this.databaseRepository = databaseRepository; @@ -53,6 +54,7 @@ public class DatabaseServiceImpl implements DatabaseService { return DatabaseConverter.convert(databaseDO); } + @Override public DatabaseResp getDatabase(Long id) { DatabaseDO databaseDO = databaseRepository.getDatabase(id); @@ -74,8 +76,6 @@ public class DatabaseServiceImpl implements DatabaseService { @Override public QueryResultWithSchemaResp executeSql(String sql, DatabaseResp databaseResp) { - - SqlUtils sqlUtils = this.sqlUtils.init(databaseResp); return queryWithColumns(sql, databaseResp); } @@ -93,7 +93,7 @@ public class DatabaseServiceImpl implements DatabaseService { private QueryResultWithSchemaResp queryWithColumns(String sql, DatabaseResp databaseResp) { QueryResultWithSchemaResp queryResultWithColumns = new QueryResultWithSchemaResp(); SqlUtils sqlUtils = this.sqlUtils.init(databaseResp); - log.info("query SQL: {}" , sql); + log.info("query SQL: {}", sql); sqlUtils.queryInternal(sql, queryResultWithColumns); return queryResultWithColumns; } @@ -103,4 +103,31 @@ public class DatabaseServiceImpl implements DatabaseService { return databaseDOS.stream().findFirst(); } + @Override + public QueryResultWithSchemaResp getDbNames(Long id) { + DatabaseResp databaseResp = getDatabase(id); + EngineAdaptor engineAdaptor = EngineAdaptorFactory.getEngineAdaptor(databaseResp.getType()); + String metaQueryTpl = engineAdaptor.getDbMetaQueryTpl(); + return queryWithColumns(metaQueryTpl, databaseResp); + } + + @Override + public QueryResultWithSchemaResp getTables(Long id, String db) { + DatabaseResp databaseResp = getDatabase(id); + EngineAdaptor engineAdaptor = EngineAdaptorFactory.getEngineAdaptor(databaseResp.getType()); + String metaQueryTpl = engineAdaptor.getTableMetaQueryTpl(); + String metaQuerySql = String.format(metaQueryTpl, db); + return queryWithColumns(metaQuerySql, databaseResp); + } + + + @Override + public QueryResultWithSchemaResp getColumns(Long id, String db, String table) { + DatabaseResp databaseResp = getDatabase(id); + EngineAdaptor engineAdaptor = EngineAdaptorFactory.getEngineAdaptor(databaseResp.getType()); + String metaQueryTpl = engineAdaptor.getColumnMetaQueryTpl(); + String metaQuerySql = String.format(metaQueryTpl, db, table); + return queryWithColumns(metaQuerySql, databaseResp); + } + } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatasourceServiceImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatasourceServiceImpl.java index a9f59430f..139b92f6c 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatasourceServiceImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DatasourceServiceImpl.java @@ -63,8 +63,6 @@ public class DatasourceServiceImpl implements DatasourceService { private DatasourceRepository datasourceRepository; - private DatasourceYamlManager datasourceYamlManager; - private DatabaseService databaseService; private DimensionService dimensionService; @@ -73,19 +71,13 @@ public class DatasourceServiceImpl implements DatasourceService { private DateInfoRepository dateInfoRepository; - private DomainService domainService; - public DatasourceServiceImpl(DatasourceRepository datasourceRepository, - DatasourceYamlManager datasourceYamlManager, - DomainService domainService, DatabaseService databaseService, @Lazy DimensionService dimensionService, @Lazy MetricService metricService, DateInfoRepository dateInfoRepository) { - this.domainService = domainService; this.datasourceRepository = datasourceRepository; - this.datasourceYamlManager = datasourceYamlManager; this.databaseService = databaseService; this.dimensionService = dimensionService; this.metricService = metricService; @@ -107,10 +99,6 @@ public class DatasourceServiceImpl implements DatasourceService { datasource.setId(datasourceDesc.getId()); batchCreateDimension(datasource, user); batchCreateMetric(datasource, user); - List dimensionDescsExist = dimensionService.getDimensionsByDatasource(datasource.getId()); - DatabaseResp databaseResp = databaseService.getDatabase(datasource.getDatabaseId()); - datasourceYamlManager.generateYamlFile(datasource, databaseResp, - domainService.getDomainFullPath(datasource.getDomainId()), dimensionDescsExist); return datasourceDesc; } @@ -124,10 +112,6 @@ public class DatasourceServiceImpl implements DatasourceService { batchCreateDimension(datasource, user); batchCreateMetric(datasource, user); - List dimensionDescsExist = dimensionService.getDimensionsByDatasource(datasource.getId()); - DatabaseResp databaseResp = databaseService.getDatabase(datasource.getDatabaseId()); - datasourceYamlManager.generateYamlFile(datasource, databaseResp, - domainService.getDomainFullPath(datasource.getDomainId()), dimensionDescsExist); DatasourceDO datasourceDO = updateDatasource(datasource, user); return DatasourceConverter.convert(datasourceDO); } @@ -207,8 +191,6 @@ public class DatasourceServiceImpl implements DatasourceService { } - - private void preCheck(DatasourceReq datasourceReq) { List dims = datasourceReq.getDimensions(); if (CollectionUtils.isEmpty(dims)) { @@ -245,8 +227,6 @@ public class DatasourceServiceImpl implements DatasourceService { } - - @Override public Map getDatasourceMap() { Map map = new HashMap<>(); @@ -264,9 +244,16 @@ public class DatasourceServiceImpl implements DatasourceService { if (datasourceDO == null) { return; } + checkDelete(datasourceDO.getDomainId(), id); datasourceRepository.deleteDatasource(id); - datasourceYamlManager.deleteYamlFile(datasourceDO.getBizName(), - domainService.getDomainFullPath(datasourceDO.getDomainId())); + } + + private void checkDelete(Long domainId, Long datasourceId) { + List metricResps = metricService.getMetrics(domainId, datasourceId); + List dimensionResps = dimensionService.getDimensionsByDatasource(datasourceId); + if (!CollectionUtils.isEmpty(metricResps) || !CollectionUtils.isEmpty(dimensionResps)) { + throw new RuntimeException("exist dimension or metric on this datasource, please check"); + } } @@ -315,6 +302,7 @@ public class DatasourceServiceImpl implements DatasourceService { } + @Override public ItemDateResp getDateDate(ItemDateFilter dimension, ItemDateFilter metric) { List itemDates = new ArrayList<>(); List dimensions = dateInfoRepository.getDateInfos(dimension); @@ -355,10 +343,10 @@ public class DatasourceServiceImpl implements DatasourceService { String startDate1 = item.getStartDate(); String endDate1 = item.getEndDate(); List unavailableDateList1 = item.getUnavailableDateList(); - if (Strings.isNotEmpty(startDate1) && startDate1.compareTo(startDate) < 0) { + if (Strings.isNotEmpty(startDate1) && startDate1.compareTo(startDate) > 0) { startDate = startDate1; } - if (Strings.isNotEmpty(endDate1) && startDate1.compareTo(endDate1) > 0) { + if (Strings.isNotEmpty(endDate1) && endDate1.compareTo(endDate) < 0) { endDate = endDate1; } if (!CollectionUtils.isEmpty(unavailableDateList1)) { diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DimensionServiceImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DimensionServiceImpl.java index 7a4f98f97..3fcfdf0d4 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DimensionServiceImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DimensionServiceImpl.java @@ -11,7 +11,6 @@ import com.tencent.supersonic.semantic.api.core.response.DatasourceResp; import com.tencent.supersonic.semantic.api.core.response.DimensionResp; import com.tencent.supersonic.common.enums.SensitiveLevelEnum; import com.tencent.supersonic.semantic.core.domain.dataobject.DimensionDO; -import com.tencent.supersonic.semantic.core.domain.manager.DimensionYamlManager; import com.tencent.supersonic.semantic.core.domain.repository.DimensionRepository; import com.tencent.supersonic.semantic.core.domain.utils.DimensionConverter; import com.tencent.supersonic.semantic.core.domain.DatasourceService; @@ -19,7 +18,6 @@ import com.tencent.supersonic.semantic.core.domain.DimensionService; import com.tencent.supersonic.semantic.core.domain.DomainService; import com.tencent.supersonic.semantic.core.domain.pojo.Dimension; import com.tencent.supersonic.semantic.core.domain.pojo.DimensionFilter; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,59 +35,54 @@ public class DimensionServiceImpl implements DimensionService { private DimensionRepository dimensionRepository; - private DimensionYamlManager dimensionYamlManager; - private DatasourceService datasourceService; private DomainService domainService; public DimensionServiceImpl(DimensionRepository dimensionRepository, - DimensionYamlManager dimensionYamlManager, DomainService domainService, DatasourceService datasourceService) { this.domainService = domainService; this.dimensionRepository = dimensionRepository; - this.dimensionYamlManager = dimensionYamlManager; this.datasourceService = datasourceService; } @Override - public void createDimension(DimensionReq dimensionReq, User user) throws Exception { + public void createDimension(DimensionReq dimensionReq, User user) { checkExist(Lists.newArrayList(dimensionReq)); Dimension dimension = DimensionConverter.convert(dimensionReq); log.info("[create dimension] object:{}", JSONObject.toJSONString(dimension)); - saveDimensionAndGenerateYaml(dimension, user); + dimension.createdBy(user.getName()); + saveDimension(dimension); } @Override - public void createDimensionBatch(List dimensionReqs, User user) throws Exception { + public void createDimensionBatch(List dimensionReqs, User user) { if (CollectionUtils.isEmpty(dimensionReqs)) { return; } Long domainId = dimensionReqs.get(0).getDomainId(); - List dimensionDescs = getDimensions(domainId); - Map dimensionDescMap = dimensionDescs.stream() + List dimensionResps = getDimensions(domainId); + Map dimensionRespMap = dimensionResps.stream() .collect(Collectors.toMap(DimensionResp::getBizName, a -> a, (k1, k2) -> k1)); List dimensions = dimensionReqs.stream().map(DimensionConverter::convert) .collect(Collectors.toList()); List dimensionToInsert = dimensions.stream() - .filter(dimension -> !dimensionDescMap.containsKey(dimension.getBizName())) + .filter(dimension -> !dimensionRespMap.containsKey(dimension.getBizName())) .collect(Collectors.toList()); log.info("[create dimension] object:{}", JSONObject.toJSONString(dimensions)); saveDimensionBatch(dimensionToInsert, user); - generateYamlFile(dimensions.get(0).getDatasourceId(), dimensions.get(0).getDomainId()); } @Override - public void updateDimension(DimensionReq dimensionReq, User user) throws Exception { + public void updateDimension(DimensionReq dimensionReq, User user) { Dimension dimension = DimensionConverter.convert(dimensionReq); dimension.updatedBy(user.getName()); log.info("[update dimension] object:{}", JSONObject.toJSONString(dimension)); updateDimension(dimension); - generateYamlFile(dimension.getDatasourceId(), dimension.getDomainId()); } protected void updateDimension(Dimension dimension) { @@ -100,13 +93,13 @@ public class DimensionServiceImpl implements DimensionService { @Override public DimensionResp getDimension(String bizName, Long domainId) { - List dimensionDescs = getDimensions(domainId); - if (CollectionUtils.isEmpty(dimensionDescs)) { + List dimensionResps = getDimensions(domainId); + if (CollectionUtils.isEmpty(dimensionResps)) { return null; } - for (DimensionResp dimensionDesc : dimensionDescs) { - if (dimensionDesc.getBizName().equalsIgnoreCase(bizName)) { - return dimensionDesc; + for (DimensionResp dimensionResp : dimensionResps) { + if (dimensionResp.getBizName().equalsIgnoreCase(bizName)) { + return dimensionResp; } } return null; @@ -132,16 +125,16 @@ public class DimensionServiceImpl implements DimensionService { @Override public List getDimensions(List ids) { - List dimensionDescs = Lists.newArrayList(); + List dimensionResps = Lists.newArrayList(); List dimensionDOS = dimensionRepository.getDimensionListByIds(ids); Map fullDomainPathMap = domainService.getDomainFullPath(); if (!CollectionUtils.isEmpty(dimensionDOS)) { - dimensionDescs = dimensionDOS.stream() - .map(dimensionDO -> DimensionConverter.convert2DimensionDesc(dimensionDO, fullDomainPathMap, + dimensionResps = dimensionDOS.stream() + .map(dimensionDO -> DimensionConverter.convert2DimensionResp(dimensionDO, fullDomainPathMap, new HashMap<>())) .collect(Collectors.toList()); } - return dimensionDescs; + return dimensionResps; } @Override @@ -149,52 +142,46 @@ public class DimensionServiceImpl implements DimensionService { return convertList(getDimensionDOS(domainId), datasourceService.getDatasourceMap()); } - - public List getDimensionList(Long datasourceId) { - List dimensions = Lists.newArrayList(); - List dimensionDOS = dimensionRepository.getDimensionListOfDatasource(datasourceId); - if (!CollectionUtils.isEmpty(dimensionDOS)) { - dimensions = dimensionDOS.stream().map(DimensionConverter::convert2Dimension).collect(Collectors.toList()); - } - return dimensions; + @Override + public List getDimensions() { + return convertList(getDimensionDOS(), datasourceService.getDatasourceMap()); } @Override public List getDimensionsByDatasource(Long datasourceId) { - List dimensionDescs = Lists.newArrayList(); + List dimensionResps = Lists.newArrayList(); List dimensionDOS = dimensionRepository.getDimensionListOfDatasource(datasourceId); if (!CollectionUtils.isEmpty(dimensionDOS)) { - dimensionDescs = dimensionDOS.stream() - .map(dimensionDO -> DimensionConverter.convert2DimensionDesc(dimensionDO, new HashMap<>(), + dimensionResps = dimensionDOS.stream() + .map(dimensionDO -> DimensionConverter.convert2DimensionResp(dimensionDO, new HashMap<>(), new HashMap<>())) .collect(Collectors.toList()); } - return dimensionDescs; + return dimensionResps; } private List convertList(List dimensionDOS, - Map datasourceDescMap) { - List dimensionDescs = Lists.newArrayList(); + Map datasourceRespMap) { + List dimensionResps = Lists.newArrayList(); Map fullDomainPathMap = domainService.getDomainFullPath(); if (!CollectionUtils.isEmpty(dimensionDOS)) { - dimensionDescs = dimensionDOS.stream() - .map(dimensionDO -> DimensionConverter.convert2DimensionDesc(dimensionDO, fullDomainPathMap, - datasourceDescMap)) + dimensionResps = dimensionDOS.stream() + .map(dimensionDO -> DimensionConverter.convert2DimensionResp(dimensionDO, fullDomainPathMap, + datasourceRespMap)) .collect(Collectors.toList()); } - return dimensionDescs; + return dimensionResps; } - @Override public List getHighSensitiveDimension(Long domainId) { - List dimensionDescs = getDimensions(domainId); - if (CollectionUtils.isEmpty(dimensionDescs)) { - return dimensionDescs; + List dimensionResps = getDimensions(domainId); + if (CollectionUtils.isEmpty(dimensionResps)) { + return dimensionResps; } - return dimensionDescs.stream() - .filter(dimensionDesc -> SensitiveLevelEnum.HIGH.getCode().equals(dimensionDesc.getSensitiveLevel())) + return dimensionResps.stream() + .filter(dimensionResp -> SensitiveLevelEnum.HIGH.getCode().equals(dimensionResp.getSensitiveLevel())) .collect(Collectors.toList()); } @@ -203,13 +190,17 @@ public class DimensionServiceImpl implements DimensionService { return dimensionRepository.getDimensionListOfDomain(domainId); } + protected List getDimensionDOS() { + return dimensionRepository.getDimensionList(); + } + @Override public List getAllHighSensitiveDimension() { - List dimensionDescs = Lists.newArrayList(); + List dimensionResps = Lists.newArrayList(); List dimensionDOS = dimensionRepository.getAllDimensionList(); if (CollectionUtils.isEmpty(dimensionDOS)) { - return dimensionDescs; + return dimensionResps; } return convertList(dimensionDOS.stream() .filter(dimensionDO -> SensitiveLevelEnum.HIGH.getCode().equals(dimensionDO.getSensitiveLevel())) @@ -217,8 +208,7 @@ public class DimensionServiceImpl implements DimensionService { } - //保存并获取自增ID - private void saveDimension(Dimension dimension) { + public void saveDimension(Dimension dimension) { DimensionDO dimensionDO = DimensionConverter.convert2DimensionDO(dimension); log.info("[save dimension] dimensionDO:{}", JSONObject.toJSONString(dimensionDO)); dimensionRepository.createDimension(dimensionDO); @@ -238,52 +228,30 @@ public class DimensionServiceImpl implements DimensionService { } - - @Override - public void deleteDimension(Long id) throws Exception { + public void deleteDimension(Long id) { DimensionDO dimensionDO = dimensionRepository.getDimensionById(id); if (dimensionDO == null) { throw new RuntimeException(String.format("the dimension %s not exist", id)); } dimensionRepository.deleteDimension(id); - generateYamlFile(dimensionDO.getDatasourceId(), dimensionDO.getDomainId()); - } - - protected void generateYamlFile(Long datasourceId, Long domainId) throws Exception { - String datasourceBizName = datasourceService.getSourceBizNameById(datasourceId); - List dimensionList = getDimensionList(datasourceId); - String fullPath = domainService.getDomainFullPath(domainId); - dimensionYamlManager.generateYamlFile(dimensionList, fullPath, datasourceBizName); } private void checkExist(List dimensionReqs) { Long domainId = dimensionReqs.get(0).getDomainId(); - List dimensionDescs = getDimensions(domainId); + List dimensionResps = getDimensions(domainId); for (DimensionReq dimensionReq : dimensionReqs) { - for (DimensionResp dimensionDesc : dimensionDescs) { - if (dimensionDesc.getName().equalsIgnoreCase(dimensionReq.getBizName())) { + for (DimensionResp dimensionResp : dimensionResps) { + if (dimensionResp.getName().equalsIgnoreCase(dimensionReq.getBizName())) { throw new RuntimeException(String.format("exist same dimension name:%s", dimensionReq.getName())); } - if (dimensionDesc.getBizName().equalsIgnoreCase(dimensionReq.getBizName())) { + if (dimensionResp.getBizName().equalsIgnoreCase(dimensionReq.getBizName())) { throw new RuntimeException( String.format("exist same dimension bizName:%s", dimensionReq.getBizName())); } } } - } - - private void saveDimensionAndGenerateYaml(Dimension dimension, User user) throws Exception { - dimension.createdBy(user.getName()); - saveDimension(dimension); - generateYamlFile(dimension.getDatasourceId(), dimension.getDomainId()); - } - - - - - } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DomainServiceImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DomainServiceImpl.java index 8f0f151bd..9e081b645 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DomainServiceImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/DomainServiceImpl.java @@ -3,24 +3,25 @@ package com.tencent.supersonic.semantic.core.application; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Lists; import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.common.util.mapper.BeanMapper; import com.tencent.supersonic.semantic.api.core.request.DomainReq; import com.tencent.supersonic.semantic.api.core.request.DomainSchemaFilterReq; import com.tencent.supersonic.semantic.api.core.request.DomainUpdateReq; +import com.tencent.supersonic.semantic.api.core.response.DatasourceResp; import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.core.response.DimensionResp; import com.tencent.supersonic.semantic.api.core.response.DomainResp; import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.core.response.MetricResp; import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp; -import com.tencent.supersonic.common.util.mapper.BeanMapper; -import com.tencent.supersonic.semantic.core.domain.dataobject.DomainDO; -import com.tencent.supersonic.semantic.core.domain.repository.DomainRepository; -import com.tencent.supersonic.semantic.core.domain.utils.DomainConvert; +import com.tencent.supersonic.semantic.core.domain.DatasourceService; import com.tencent.supersonic.semantic.core.domain.DimensionService; import com.tencent.supersonic.semantic.core.domain.DomainService; import com.tencent.supersonic.semantic.core.domain.MetricService; +import com.tencent.supersonic.semantic.core.domain.dataobject.DomainDO; import com.tencent.supersonic.semantic.core.domain.pojo.Domain; - +import com.tencent.supersonic.semantic.core.domain.repository.DomainRepository; +import com.tencent.supersonic.semantic.core.domain.utils.DomainConvert; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -42,14 +43,15 @@ public class DomainServiceImpl implements DomainService { private final DomainRepository domainRepository; private final MetricService metricService; private final DimensionService dimensionService; + private final DatasourceService datasourceService; - public DomainServiceImpl(DomainRepository domainRepository, - @Lazy MetricService metricService, - @Lazy DimensionService dimensionService) { + public DomainServiceImpl(DomainRepository domainRepository, @Lazy MetricService metricService, + @Lazy DimensionService dimensionService, @Lazy DatasourceService datasourceService) { this.domainRepository = domainRepository; this.metricService = metricService; this.dimensionService = dimensionService; + this.datasourceService = datasourceService; } @@ -80,9 +82,20 @@ public class DomainServiceImpl implements DomainService { @Override public void deleteDomain(Long id) { + checkDelete(id); domainRepository.deleteDomain(id); } + private void checkDelete(Long id) { + List metricResps = metricService.getMetrics(id); + List dimensionResps = dimensionService.getDimensions(id); + List datasourceResps = datasourceService.getDatasourceList(id); + if (!CollectionUtils.isEmpty(metricResps) || !CollectionUtils.isEmpty(datasourceResps) + || !CollectionUtils.isEmpty(dimensionResps)) { + throw new RuntimeException("exist datasource, dimension or metric in this domain, please check"); + } + } + @Override public String getDomainBizName(Long id) { if (id == null) { @@ -99,7 +112,7 @@ public class DomainServiceImpl implements DomainService { @Override public List getDomainList() { - return convertList(domainRepository.getDomainList()); + return convertList(domainRepository.getDomainList(), new HashMap<>(), new HashMap<>()); } @@ -115,7 +128,11 @@ public class DomainServiceImpl implements DomainService { List domainDOS = domainRepository.getDomainList(); List orgIds = Lists.newArrayList(); log.info("orgIds:{},userName:{}", orgIds, userName); - return convertList(domainDOS).stream() + Map> metricDomainMap = metricService.getMetrics().stream() + .collect(Collectors.groupingBy(MetricResp::getDomainId)); + Map> dimensionDomainMap = dimensionService.getDimensions().stream() + .collect(Collectors.groupingBy(DimensionResp::getDomainId)); + return convertList(domainDOS, metricDomainMap, dimensionDomainMap).stream() .filter(domainDesc -> checkAdminPermission(orgIds, userName, domainDesc)) .collect(Collectors.toList()); } @@ -125,7 +142,7 @@ public class DomainServiceImpl implements DomainService { List domainDOS = domainRepository.getDomainList(); List orgIds = Lists.newArrayList(); log.info("orgIds:{},userName:{}", orgIds, userName); - return convertList(domainDOS).stream() + return convertList(domainDOS, new HashMap<>(), new HashMap<>()).stream() .filter(domainDesc -> checkViewerPermission(orgIds, userName, domainDesc)) .collect(Collectors.toList()); } @@ -160,23 +177,23 @@ public class DomainServiceImpl implements DomainService { } - private List convertList(List domainDOS) { + private List convertList(List domainDOS, Map> metricDomainMap, + Map> dimensionDomainMap) { List domainDescs = Lists.newArrayList(); if (CollectionUtils.isEmpty(domainDOS)) { return domainDescs; } Map fullDomainPathMap = getDomainFullPath(); + return domainDOS.stream() - .map(domainDO -> DomainConvert.convert(domainDO, fullDomainPathMap)) + .map(domainDO -> DomainConvert.convert(domainDO, fullDomainPathMap, dimensionDomainMap, + metricDomainMap)) .collect(Collectors.toList()); } - - @Override public Map getDomainMap() { - return getDomainList().stream().collect(Collectors.toMap(DomainResp::getId, a -> a, (k1, k2) -> k1)); } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/MetricServiceImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/MetricServiceImpl.java index 8a5bdf5e4..2f9d3f8f6 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/MetricServiceImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/application/MetricServiceImpl.java @@ -13,7 +13,6 @@ import com.tencent.supersonic.semantic.api.core.response.DomainResp; import com.tencent.supersonic.semantic.api.core.response.MetricResp; import com.tencent.supersonic.common.enums.SensitiveLevelEnum; import com.tencent.supersonic.semantic.core.domain.dataobject.MetricDO; -import com.tencent.supersonic.semantic.core.domain.manager.MetricYamlManager; import com.tencent.supersonic.semantic.core.domain.pojo.MetricFilter; import com.tencent.supersonic.semantic.core.domain.repository.MetricRepository; import com.tencent.supersonic.semantic.core.domain.utils.MetricConverter; @@ -27,6 +26,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -38,30 +38,26 @@ public class MetricServiceImpl implements MetricService { private MetricRepository metricRepository; - private MetricYamlManager metricYamlManager; - private DomainService domainService; public MetricServiceImpl(MetricRepository metricRepository, - MetricYamlManager metricYamlManager, DomainService domainService) { this.domainService = domainService; this.metricRepository = metricRepository; - this.metricYamlManager = metricYamlManager; } @Override - public void creatExprMetric(MetricReq metricReq, User user) throws Exception { + public void creatExprMetric(MetricReq metricReq, User user) { checkExist(Lists.newArrayList(metricReq)); Metric metric = MetricConverter.convert(metricReq); metric.createdBy(user.getName()); log.info("[create metric] object:{}", JSONObject.toJSONString(metric)); - saveMetricAndGenerateYaml(metric); + saveMetric(metric); } @Override - public void createMetricBatch(List metricReqs, User user) throws Exception { + public void createMetricBatch(List metricReqs, User user) { if (CollectionUtils.isEmpty(metricReqs)) { return; } @@ -74,8 +70,6 @@ public class MetricServiceImpl implements MetricService { .filter(metric -> !metricDescMap.containsKey(metric.getBizName())).collect(Collectors.toList()); log.info("[insert metric] object:{}", JSONObject.toJSONString(metricToInsert)); saveMetricBatch(metricToInsert, user); - - generateYamlFile(metrics.get(0).getDomainId()); } @@ -84,6 +78,11 @@ public class MetricServiceImpl implements MetricService { return convertList(metricRepository.getMetricList(domainId)); } + @Override + public List getMetrics() { + return convertList(metricRepository.getMetricList()); + } + @Override public List getMetrics(Long domainId, Long datasourceId) { List metricResps = convertList(metricRepository.getMetricList(domainId)); @@ -136,27 +135,15 @@ public class MetricServiceImpl implements MetricService { } @Override - public void updateExprMetric(MetricReq metricReq, User user) throws Exception { + public void updateExprMetric(MetricReq metricReq, User user) { preCheckMetric(metricReq); Metric metric = MetricConverter.convert(metricReq); metric.updatedBy(user.getName()); log.info("[update metric] object:{}", JSONObject.toJSONString(metric)); updateMetric(metric); - generateYamlFile(metric.getDomainId()); } - public List getMetricList(Long domainId) { - List metrics = Lists.newArrayList(); - List metricDOS = metricRepository.getMetricList(domainId); - if (!CollectionUtils.isEmpty(metricDOS)) { - metrics = metricDOS.stream().map(MetricConverter::convert2Metric).collect(Collectors.toList()); - } - return metrics; - } - - - //保存并获取自增ID public void saveMetric(Metric metric) { MetricDO metricDO = MetricConverter.convert2MetricDO(metric); log.info("[save metric] metricDO:{}", JSONObject.toJSONString(metricDO)); @@ -205,20 +192,12 @@ public class MetricServiceImpl implements MetricService { } @Override - public void deleteMetric(Long id) throws Exception { + public void deleteMetric(Long id) { MetricDO metricDO = metricRepository.getMetricById(id); if (metricDO == null) { throw new RuntimeException(String.format("the metric %s not exist", id)); } metricRepository.deleteMetric(id); - generateYamlFile(metricDO.getDomainId()); - } - - protected void generateYamlFile(Long domainId) throws Exception { - List metrics = getMetricList(domainId); - String fullPath = domainService.getDomainFullPath(domainId); - String domainBizName = domainService.getDomainBizName(domainId); - metricYamlManager.generateYamlFile(metrics, fullPath, domainBizName); } @@ -232,21 +211,14 @@ public class MetricServiceImpl implements MetricService { metricRepository.createMetricBatch(metricDOS); } - private void saveMetricAndGenerateYaml(Metric metric) throws Exception { - saveMetric(metric); - generateYamlFile(metric.getDomainId()); - } - - - private void preCheckMetric(MetricReq exprMetricReq) { - - MetricTypeParams typeParams = exprMetricReq.getTypeParams(); + private void preCheckMetric(MetricReq metricReq) { + MetricTypeParams typeParams = metricReq.getTypeParams(); List measures = typeParams.getMeasures(); if (CollectionUtils.isEmpty(measures)) { throw new RuntimeException("measure can not be none"); } - for (Measure measure : measures) { - measure.setExpr(null); + if (StringUtils.isBlank(typeParams.getExpr())) { + throw new RuntimeException("expr can not be blank"); } } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DatabaseService.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DatabaseService.java index fb0f56867..85f30a730 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DatabaseService.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DatabaseService.java @@ -24,4 +24,9 @@ public interface DatabaseService { QueryResultWithSchemaResp queryWithColumns(SqlParserResp sqlParser); + QueryResultWithSchemaResp getDbNames(Long id); + + QueryResultWithSchemaResp getTables(Long id, String db); + + QueryResultWithSchemaResp getColumns(Long id, String db, String table); } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DimensionService.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DimensionService.java index 2c131f38e..8dbe47ddb 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DimensionService.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/DimensionService.java @@ -13,6 +13,8 @@ public interface DimensionService { List getDimensions(Long domainId); + List getDimensions(); + DimensionResp getDimension(String bizName, Long domainId); void createDimension(DimensionReq dimensionReq, User user) throws Exception; diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/MetricService.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/MetricService.java index 233362919..a4c764470 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/MetricService.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/MetricService.java @@ -13,6 +13,8 @@ public interface MetricService { List getMetrics(Long domainId); + List getMetrics(); + List getMetrics(Long domainId, Long datasourceId); void creatExprMetric(MetricReq metricReq, User user) throws Exception; diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/ClickHouseAdaptor.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/ClickHouseAdaptor.java index 7d917085c..b03758ceb 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/ClickHouseAdaptor.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/ClickHouseAdaptor.java @@ -27,4 +27,22 @@ public class ClickHouseAdaptor extends EngineAdaptor { return column; } + @Override + public String getDbMetaQueryTpl() { + return " " + + " select " + + " name from system.databases " + + " where name not in('_temporary_and_external_tables','benchmark','default','system');"; + } + + @Override + public String getTableMetaQueryTpl() { + return "select name from system.tables where database = '%s';"; + } + + @Override + public String getColumnMetaQueryTpl() { + return "select name,type as dataType, comment from system.columns where database = '%s' and table='%s'"; + } + } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/EngineAdaptor.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/EngineAdaptor.java index d85efd74b..be5635330 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/EngineAdaptor.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/EngineAdaptor.java @@ -7,4 +7,9 @@ public abstract class EngineAdaptor { public abstract String getDateFormat(String dateType, String dateFormat, String column); + public abstract String getColumnMetaQueryTpl(); + + public abstract String getDbMetaQueryTpl(); + + public abstract String getTableMetaQueryTpl(); } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/MysqlAdaptor.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/MysqlAdaptor.java index 2bdc882e0..1e6fc0073 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/MysqlAdaptor.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/adaptor/engineadapter/MysqlAdaptor.java @@ -18,7 +18,7 @@ public class MysqlAdaptor extends EngineAdaptor { } else if (TimeDimensionEnum.WEEK.name().equalsIgnoreCase(dateType)) { return "to_monday(from_unixtime(unix_timestamp(%s), 'yyyy-MM-dd'))".replace("%s", column); } else { - return "from_unixtime(unix_timestamp(%s), 'yyyy-MM-dd')".replace("%s", column); + return "date_format(str_to_date(%s, '%Y%m%d'),'%Y-%m-%d')".replace("%s", column); } } else if (dateFormat.equalsIgnoreCase(Constants.DAY_FORMAT)) { if (TimeDimensionEnum.MONTH.name().equalsIgnoreCase(dateType)) { @@ -33,4 +33,21 @@ public class MysqlAdaptor extends EngineAdaptor { } + @Override + public String getDbMetaQueryTpl() { + return "select distinct TABLE_SCHEMA from information_schema.tables where TABLE_SCHEMA not in ('information_schema','mysql','performance_schema','sys');"; + } + + @Override + public String getTableMetaQueryTpl() { + return "select TABLE_NAME from information_schema.tables where TABLE_SCHEMA = '%s';"; + } + + @Override + public String getColumnMetaQueryTpl() { + return "SELECT COLUMN_NAME as name, DATA_TYPE as dataType, COLUMN_COMMENT as comment " + + "FROM information_schema.columns WHERE table_schema ='%s' AND table_name = '%s'"; + } + + } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatabaseDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatabaseDOExample.java index 3521334bd..916ba9322 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatabaseDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatabaseDOExample.java @@ -38,13 +38,6 @@ public class DatabaseDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DatabaseDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DatabaseDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DatabaseDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DatabaseDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DatabaseDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_database null */ @@ -812,38 +812,6 @@ public class DatabaseDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -879,5 +847,37 @@ public class DatabaseDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceDOExample.java index 22331f4e8..f8b87fbd4 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceDOExample.java @@ -38,13 +38,6 @@ public class DatasourceDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DatasourceDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DatasourceDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DatasourceDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DatasourceDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DatasourceDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_datasource null */ @@ -872,38 +872,6 @@ public class DatasourceDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -939,5 +907,37 @@ public class DatasourceDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceRelaDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceRelaDOExample.java index 09378686a..f4264d58d 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceRelaDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DatasourceRelaDOExample.java @@ -38,13 +38,6 @@ public class DatasourceRelaDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DatasourceRelaDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DatasourceRelaDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DatasourceRelaDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DatasourceRelaDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DatasourceRelaDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_datasource_rela null */ @@ -787,38 +787,6 @@ public class DatasourceRelaDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -854,5 +822,37 @@ public class DatasourceRelaDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryDOExample.java index 3065b92e0..ff9cbb1c5 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryDOExample.java @@ -38,13 +38,6 @@ public class DictionaryDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DictionaryDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DictionaryDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DictionaryDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DictionaryDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DictionaryDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_dictionary null */ @@ -792,38 +792,6 @@ public class DictionaryDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -859,5 +827,37 @@ public class DictionaryDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryTaskDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryTaskDOExample.java index 94aa5bc87..d8548809c 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryTaskDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DictionaryTaskDOExample.java @@ -37,13 +37,6 @@ public class DictionaryTaskDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -54,8 +47,8 @@ public class DictionaryTaskDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -65,6 +58,13 @@ public class DictionaryTaskDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -116,13 +116,6 @@ public class DictionaryTaskDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -133,8 +126,8 @@ public class DictionaryTaskDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -144,6 +137,13 @@ public class DictionaryTaskDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_dictionary_task null */ @@ -461,38 +461,6 @@ public class DictionaryTaskDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -528,5 +496,37 @@ public class DictionaryTaskDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDO.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDO.java index a406d5362..eec2ef285 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDO.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDO.java @@ -74,6 +74,16 @@ public class DimensionDO { */ private String semanticType; + /** + * + */ + private String alias; + + /** + * default values of dimension when query + */ + private String defaultValues; + /** * 类型参数 */ @@ -336,6 +346,38 @@ public class DimensionDO { this.semanticType = semanticType == null ? null : semanticType.trim(); } + /** + * @return alias + */ + public String getAlias() { + return alias; + } + + /** + * @param alias + */ + public void setAlias(String alias) { + this.alias = alias == null ? null : alias.trim(); + } + + /** + * default values of dimension when query + * + * @return default_values default values of dimension when query + */ + public String getDefaultValues() { + return defaultValues; + } + + /** + * default values of dimension when query + * + * @param defaultValues default values of dimension when query + */ + public void setDefaultValues(String defaultValues) { + this.defaultValues = defaultValues == null ? null : defaultValues.trim(); + } + /** * 类型参数 * diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDOExample.java index 435277083..32ebe9944 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DimensionDOExample.java @@ -38,13 +38,6 @@ public class DimensionDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DimensionDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DimensionDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DimensionDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DimensionDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DimensionDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_dimension null */ @@ -665,6 +665,11 @@ public class DimensionDOExample { return (Criteria) this; } + public Criteria andSensitiveLevelGreaterThanOrEqualTo(Integer value) { + addCriterion("sensitive_level >=", value, "sensitiveLevel"); + return (Criteria) this; + } + public Criteria andSensitiveLevelLessThan(Integer value) { addCriterion("sensitive_level <", value, "sensitiveLevel"); return (Criteria) this; @@ -1094,6 +1099,146 @@ public class DimensionDOExample { addCriterion("semantic_type not between", value1, value2, "semanticType"); return (Criteria) this; } + + public Criteria andAliasIsNull() { + addCriterion("alias is null"); + return (Criteria) this; + } + + public Criteria andAliasIsNotNull() { + addCriterion("alias is not null"); + return (Criteria) this; + } + + public Criteria andAliasEqualTo(String value) { + addCriterion("alias =", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotEqualTo(String value) { + addCriterion("alias <>", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasGreaterThan(String value) { + addCriterion("alias >", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasGreaterThanOrEqualTo(String value) { + addCriterion("alias >=", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasLessThan(String value) { + addCriterion("alias <", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasLessThanOrEqualTo(String value) { + addCriterion("alias <=", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasLike(String value) { + addCriterion("alias like", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotLike(String value) { + addCriterion("alias not like", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasIn(List values) { + addCriterion("alias in", values, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotIn(List values) { + addCriterion("alias not in", values, "alias"); + return (Criteria) this; + } + + public Criteria andAliasBetween(String value1, String value2) { + addCriterion("alias between", value1, value2, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotBetween(String value1, String value2) { + addCriterion("alias not between", value1, value2, "alias"); + return (Criteria) this; + } + + public Criteria andDefaultValuesIsNull() { + addCriterion("default_values is null"); + return (Criteria) this; + } + + public Criteria andDefaultValuesIsNotNull() { + addCriterion("default_values is not null"); + return (Criteria) this; + } + + public Criteria andDefaultValuesEqualTo(String value) { + addCriterion("default_values =", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesNotEqualTo(String value) { + addCriterion("default_values <>", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesGreaterThan(String value) { + addCriterion("default_values >", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesGreaterThanOrEqualTo(String value) { + addCriterion("default_values >=", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesLessThan(String value) { + addCriterion("default_values <", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesLessThanOrEqualTo(String value) { + addCriterion("default_values <=", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesLike(String value) { + addCriterion("default_values like", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesNotLike(String value) { + addCriterion("default_values not like", value, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesIn(List values) { + addCriterion("default_values in", values, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesNotIn(List values) { + addCriterion("default_values not in", values, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesBetween(String value1, String value2) { + addCriterion("default_values between", value1, value2, "defaultValues"); + return (Criteria) this; + } + + public Criteria andDefaultValuesNotBetween(String value1, String value2) { + addCriterion("default_values not between", value1, value2, "defaultValues"); + return (Criteria) this; + } } /** @@ -1127,38 +1272,6 @@ public class DimensionDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -1194,5 +1307,37 @@ public class DimensionDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainDOExample.java index 7443f3ea9..37e67289c 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainDOExample.java @@ -38,13 +38,6 @@ public class DomainDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DomainDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DomainDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DomainDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DomainDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DomainDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_domain null */ @@ -1142,38 +1142,6 @@ public class DomainDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -1209,5 +1177,37 @@ public class DomainDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainExtendDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainExtendDOExample.java index c1be45ec9..df30d4ae2 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainExtendDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/DomainExtendDOExample.java @@ -38,13 +38,6 @@ public class DomainExtendDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class DomainExtendDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class DomainExtendDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class DomainExtendDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class DomainExtendDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class DomainExtendDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_domain_extend null */ @@ -727,38 +727,6 @@ public class DomainExtendDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -794,5 +762,37 @@ public class DomainExtendDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDO.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDO.java index 709e3cff3..9e52c7009 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDO.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDO.java @@ -74,6 +74,11 @@ public class MetricDO { */ private String dataFormat; + /** + * + */ + private String alias; + /** * 类型参数 */ @@ -327,6 +332,20 @@ public class MetricDO { this.dataFormat = dataFormat == null ? null : dataFormat.trim(); } + /** + * @return alias + */ + public String getAlias() { + return alias; + } + + /** + * @param alias + */ + public void setAlias(String alias) { + this.alias = alias == null ? null : alias.trim(); + } + /** * 类型参数 * diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDOExample.java index a3989229a..fa9afb69e 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/MetricDOExample.java @@ -38,13 +38,6 @@ public class MetricDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class MetricDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class MetricDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class MetricDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class MetricDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class MetricDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_metric null */ @@ -605,6 +605,11 @@ public class MetricDOExample { return (Criteria) this; } + public Criteria andSensitiveLevelGreaterThanOrEqualTo(Integer value) { + addCriterion("sensitive_level >=", value, "sensitiveLevel"); + return (Criteria) this; + } + public Criteria andSensitiveLevelLessThan(Integer value) { addCriterion("sensitive_level <", value, "sensitiveLevel"); return (Criteria) this; @@ -990,6 +995,11 @@ public class MetricDOExample { return (Criteria) this; } + public Criteria andDataFormatTypeGreaterThanOrEqualTo(String value) { + addCriterion("data_format_type >=", value, "dataFormatType"); + return (Criteria) this; + } + public Criteria andDataFormatTypeLessThan(String value) { addCriterion("data_format_type <", value, "dataFormatType"); return (Criteria) this; @@ -1099,6 +1109,76 @@ public class MetricDOExample { addCriterion("data_format not between", value1, value2, "dataFormat"); return (Criteria) this; } + + public Criteria andAliasIsNull() { + addCriterion("alias is null"); + return (Criteria) this; + } + + public Criteria andAliasIsNotNull() { + addCriterion("alias is not null"); + return (Criteria) this; + } + + public Criteria andAliasEqualTo(String value) { + addCriterion("alias =", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotEqualTo(String value) { + addCriterion("alias <>", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasGreaterThan(String value) { + addCriterion("alias >", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasGreaterThanOrEqualTo(String value) { + addCriterion("alias >=", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasLessThan(String value) { + addCriterion("alias <", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasLessThanOrEqualTo(String value) { + addCriterion("alias <=", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasLike(String value) { + addCriterion("alias like", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotLike(String value) { + addCriterion("alias not like", value, "alias"); + return (Criteria) this; + } + + public Criteria andAliasIn(List values) { + addCriterion("alias in", values, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotIn(List values) { + addCriterion("alias not in", values, "alias"); + return (Criteria) this; + } + + public Criteria andAliasBetween(String value1, String value2) { + addCriterion("alias between", value1, value2, "alias"); + return (Criteria) this; + } + + public Criteria andAliasNotBetween(String value1, String value2) { + addCriterion("alias not between", value1, value2, "alias"); + return (Criteria) this; + } } /** @@ -1132,38 +1212,6 @@ public class MetricDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -1199,5 +1247,37 @@ public class MetricDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/ViewInfoDOExample.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/ViewInfoDOExample.java index 9ad4570a2..f37588017 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/ViewInfoDOExample.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/dataobject/ViewInfoDOExample.java @@ -38,13 +38,6 @@ public class ViewInfoDOExample { oredCriteria = new ArrayList(); } - /** - * @mbg.generated - */ - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - /** * @mbg.generated */ @@ -55,8 +48,8 @@ public class ViewInfoDOExample { /** * @mbg.generated */ - public void setDistinct(boolean distinct) { - this.distinct = distinct; + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; } /** @@ -66,6 +59,13 @@ public class ViewInfoDOExample { return distinct; } + /** + * @mbg.generated + */ + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + /** * @mbg.generated */ @@ -117,13 +117,6 @@ public class ViewInfoDOExample { distinct = false; } - /** - * @mbg.generated - */ - public void setLimitStart(Integer limitStart) { - this.limitStart = limitStart; - } - /** * @mbg.generated */ @@ -134,8 +127,8 @@ public class ViewInfoDOExample { /** * @mbg.generated */ - public void setLimitEnd(Integer limitEnd) { - this.limitEnd = limitEnd; + public void setLimitStart(Integer limitStart) { + this.limitStart = limitStart; } /** @@ -145,6 +138,13 @@ public class ViewInfoDOExample { return limitEnd; } + /** + * @mbg.generated + */ + public void setLimitEnd(Integer limitEnd) { + this.limitEnd = limitEnd; + } + /** * s2_view_info null */ @@ -672,38 +672,6 @@ public class ViewInfoDOExample { private String typeHandler; - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - protected Criterion(String condition) { super(); this.condition = condition; @@ -739,5 +707,37 @@ public class ViewInfoDOExample { protected Criterion(String condition, Object value, Object secondValue) { this(condition, value, secondValue, null); } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DatasourceYamlManager.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DatasourceYamlManager.java index 002104f16..e0e50816b 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DatasourceYamlManager.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DatasourceYamlManager.java @@ -10,66 +10,22 @@ import com.tencent.supersonic.semantic.api.core.pojo.yaml.DimensionYamlTpl; import com.tencent.supersonic.semantic.api.core.pojo.yaml.IdentifyYamlTpl; import com.tencent.supersonic.semantic.api.core.pojo.yaml.MeasureYamlTpl; import com.tencent.supersonic.semantic.api.core.response.DatabaseResp; -import com.tencent.supersonic.semantic.api.core.response.DimensionResp; -import com.tencent.supersonic.common.enums.TypeEnums; -import com.tencent.supersonic.common.util.yaml.YamlUtils; import com.tencent.supersonic.semantic.core.domain.utils.SysTimeDimensionBuilder; import com.tencent.supersonic.semantic.core.domain.adaptor.engineadapter.EngineAdaptor; import com.tencent.supersonic.semantic.core.domain.adaptor.engineadapter.EngineAdaptorFactory; import com.tencent.supersonic.semantic.core.domain.pojo.Datasource; import com.tencent.supersonic.semantic.core.domain.pojo.DatasourceQueryEnum; - -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; @Service @Slf4j public class DatasourceYamlManager { - - private YamlManager yamlManager; - - public DatasourceYamlManager(YamlManager yamlManager) { - this.yamlManager = yamlManager; - } - - - public void generateYamlFile(Datasource datasource, DatabaseResp databaseResp, String fullPath, - List dimensionDescsExist) throws Exception { - if (!CollectionUtils.isEmpty(dimensionDescsExist)) { - List dimensionBizNames = dimensionDescsExist.stream().map(DimensionResp::getBizName) - .collect(Collectors.toList()); - datasource.getDatasourceDetail().getDimensions() - .removeIf(dim -> dimensionBizNames.contains(dim.getBizName())); - } - String yamlStr = convert2YamlStr(datasource, databaseResp); - log.info("generate yaml str :{} from datasource:{} full path:{}", yamlStr, datasource, fullPath); - yamlManager.generateYamlFile(yamlStr, fullPath, getYamlName(datasource.getBizName())); - } - - public void deleteYamlFile(String datasourceBizName, String fullPath) throws Exception { - log.info("delete datasource yaml :{} ,fullPath:{}", datasourceBizName, fullPath); - yamlManager.deleteYamlFile(fullPath, getYamlName(datasourceBizName)); - } - - public String getYamlName(String name) { - return String.format("%s_%s", name, TypeEnums.DATASOURCE.getName()); - } - - public static String convert2YamlStr(Datasource datasource, DatabaseResp databaseResp) { - DatasourceYamlTpl datasourceYamlTpl = convert2YamlObj(datasource, databaseResp); - Map rootMap = new HashMap<>(); - rootMap.put("data_source", datasourceYamlTpl); - return YamlUtils.toYamlWithoutNull(rootMap); - } - public static DatasourceYamlTpl convert2YamlObj(Datasource datasource, DatabaseResp databaseResp) { DatasourceDetail datasourceDetail = datasource.getDatasourceDetail(); EngineAdaptor engineAdaptor = EngineAdaptorFactory.getEngineAdaptor(databaseResp.getType()); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DimensionYamlManager.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DimensionYamlManager.java index 4f335352e..ffec920ee 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DimensionYamlManager.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/DimensionYamlManager.java @@ -21,44 +21,6 @@ import org.springframework.util.CollectionUtils; public class DimensionYamlManager { - private YamlManager yamlManager; - - public DimensionYamlManager(YamlManager yamlManager) { - this.yamlManager = yamlManager; - } - - - public void generateYamlFile(List dimensions, String fullPath, String datasourceBizName) - throws Exception { - String yamlStr = convert2YamlStr(dimensions, datasourceBizName); - log.info("generate yaml str :{} from metric:{} full path:{}", yamlStr, dimensions, fullPath); - yamlManager.generateYamlFile(yamlStr, fullPath, getYamlName(datasourceBizName)); - } - - public String getYamlName(String name) { - return String.format("%s_%s", name, TypeEnums.DIMENSION.getName()); - } - - public static String convert2YamlStr(List dimensions, String datasourceBizName) { - if (CollectionUtils.isEmpty(dimensions)) { - return ""; - } - List dimensionYamlTpls = dimensions.stream() - .filter(dimension -> !dimension.getType().equalsIgnoreCase("primary")) - .map(DimensionConverter::convert2DimensionYamlTpl).collect(Collectors.toList()); - if (CollectionUtils.isEmpty(dimensionYamlTpls)) { - return ""; - } - Map dataMap = new HashMap<>(); - dataMap.put("source", datasourceBizName); - dataMap.put("dimensions", dimensionYamlTpls); - - Map rootMap = new HashMap<>(); - rootMap.put("dimension", dataMap); - return YamlUtils.toYamlWithoutNull(rootMap); - - } - public static List convert2DimensionYaml(List dimensions) { if (CollectionUtils.isEmpty(dimensions)) { return new ArrayList<>(); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/MetricYamlManager.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/MetricYamlManager.java index 3496c10cc..6a3b570ce 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/MetricYamlManager.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/MetricYamlManager.java @@ -1,17 +1,12 @@ package com.tencent.supersonic.semantic.core.domain.manager; import com.tencent.supersonic.semantic.api.core.pojo.yaml.MetricYamlTpl; -import com.tencent.supersonic.common.enums.TypeEnums; -import com.tencent.supersonic.common.util.yaml.YamlUtils; import com.tencent.supersonic.semantic.core.domain.pojo.Metric; import com.tencent.supersonic.semantic.core.domain.utils.MetricConverter; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; @Slf4j @@ -19,37 +14,6 @@ import org.springframework.util.CollectionUtils; public class MetricYamlManager { - private YamlManager yamlManager; - - public MetricYamlManager(YamlManager yamlManager) { - this.yamlManager = yamlManager; - } - - - public void generateYamlFile(List metrics, String fullPath, String domainBizName) throws Exception { - String yamlStr = convert2YamlStr(metrics); - log.info("generate yaml str :{} from metric:{} full path:{}", yamlStr, metrics, fullPath); - yamlManager.generateYamlFile(yamlStr, fullPath, getYamlName(domainBizName)); - } - - public String getYamlName(String name) { - return String.format("%s_%s", name, TypeEnums.METRIC.getName()); - } - - public static String convert2YamlStr(List metrics) { - if (CollectionUtils.isEmpty(metrics)) { - return ""; - } - StringBuilder yamlBuilder = new StringBuilder(); - for (Metric metric : metrics) { - MetricYamlTpl metricYamlTpl = MetricConverter.convert2MetricYamlTpl(metric); - Map rootMap = new HashMap<>(); - rootMap.put("metric", metricYamlTpl); - yamlBuilder.append(YamlUtils.toYamlWithoutNull(rootMap)).append("\n"); - } - return yamlBuilder.toString(); - } - public static List convert2YamlObj(List metrics) { List metricYamlTpls = new ArrayList<>(); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/YamlManager.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/YamlManager.java deleted file mode 100644 index b85a9d0d1..000000000 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/manager/YamlManager.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.tencent.supersonic.semantic.core.domain.manager; - - -import com.tencent.supersonic.common.constant.Constants; -import com.tencent.supersonic.semantic.core.domain.config.YamlConfig; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; -import org.springframework.stereotype.Service; - - -@Slf4j -@Service -public class YamlManager { - - - protected final YamlConfig yamlConfig; - -// private final ParserService parserService; - - - public YamlManager(YamlConfig yamlConfig -// , @Lazy ParserService parserService - ) { - this.yamlConfig = yamlConfig; -// this.parserService = parserService; - } - - public void generateYamlFile(String yamlStr, String path, String name) throws Exception { - String localPath = generateLocalYamlPath(path, name); - File file = createMetaYamlFile(localPath); - FileUtils.writeStringToFile(file, yamlStr, StandardCharsets.UTF_8); -// parserService.reloadModels(path); - - } - - public void deleteYamlFile(String path, String fileName) { - String localPath = generateLocalYamlPath(path, fileName); - deleteMetaYamlFile(localPath); - } - - private File createMetaYamlFile(String fullPath) throws IOException { - File file = new File(fullPath); - if (file.getParentFile().mkdirs() && file.createNewFile()) { - log.info("File :{} created: " + fullPath); - } else { - log.warn("File:{} create failed.", fullPath); - } - return file; - } - - private void deleteMetaYamlFile(String fullPath) { - File file = new File(fullPath); - if (file.delete()) { - log.info("File :{} deleted: " + fullPath); - } else { - log.info("File :{} delete failed: " + fullPath); - } - } - - private String generateLocalYamlPath(String path, String name) { - return yamlConfig.getmetaYamlFileDir() + path + name + Constants.YAML_FILES_SUFFIX; - } -} diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Dimension.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Dimension.java index 871abfb7f..ba024e60a 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Dimension.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Dimension.java @@ -3,7 +3,7 @@ package com.tencent.supersonic.semantic.core.domain.pojo; import com.tencent.supersonic.common.pojo.SchemaItem; import lombok.Data; - +import java.util.List; @Data public class Dimension extends SchemaItem { @@ -15,9 +15,12 @@ public class Dimension extends SchemaItem { private Long domainId; - private Long datasourceId; private String semanticType; + private String alias; + + private List defaultValues; + } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/JdbcDataSource.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/JdbcDataSource.java index a73d834d3..74aaaa59c 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/JdbcDataSource.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/JdbcDataSource.java @@ -29,42 +29,9 @@ import org.springframework.stereotype.Component; @Component public class JdbcDataSource { - @Bean(name = "wallConfig") - WallConfig wallConfig() { - WallConfig config = new WallConfig(); - config.setDeleteAllow(false); - config.setUpdateAllow(false); - config.setInsertAllow(false); - config.setReplaceAllow(false); - config.setMergeAllow(false); - config.setTruncateAllow(false); - config.setCreateTableAllow(false); - config.setAlterTableAllow(false); - config.setDropTableAllow(false); - config.setCommentAllow(true); - config.setUseAllow(false); - config.setDescribeAllow(false); - config.setShowAllow(false); - config.setSelectWhereAlwayTrueCheck(false); - config.setSelectHavingAlwayTrueCheck(false); - config.setSelectUnionCheck(false); - config.setConditionDoubleConstAllow(true); - config.setConditionAndAlwayTrueAllow(true); - config.setConditionAndAlwayFalseAllow(true); - return config; - } - - @Bean(name = "wallFilter") - @DependsOn("wallConfig") - WallFilter wallFilter(WallConfig wallConfig) { - WallFilter wfilter = new WallFilter(); - wfilter.setConfig(wallConfig); - return wfilter; - } - - @Autowired - WallFilter wallFilter; - + private static final Object lockLock = new Object(); + private static volatile Map dataSourceMap = new ConcurrentHashMap<>(); + private static volatile Map dataSourceLockMap = new ConcurrentHashMap<>(); @Value("${source.lock-time:30}") @Getter protected Long lockTime; @@ -136,10 +103,41 @@ public class JdbcDataSource { @Value("${source.filters:'stat'}") @Getter protected String filters; + @Autowired + WallFilter wallFilter; - private static volatile Map dataSourceMap = new ConcurrentHashMap<>(); - private static volatile Map dataSourceLockMap = new ConcurrentHashMap<>(); - private static final Object lockLock = new Object(); + @Bean(name = "wallConfig") + WallConfig wallConfig() { + WallConfig config = new WallConfig(); + config.setDeleteAllow(false); + config.setUpdateAllow(false); + config.setInsertAllow(false); + config.setReplaceAllow(false); + config.setMergeAllow(false); + config.setTruncateAllow(false); + config.setCreateTableAllow(false); + config.setAlterTableAllow(false); + config.setDropTableAllow(false); + config.setCommentAllow(true); + config.setUseAllow(false); + config.setDescribeAllow(false); + config.setShowAllow(false); + config.setSelectWhereAlwayTrueCheck(false); + config.setSelectHavingAlwayTrueCheck(false); + config.setSelectUnionCheck(false); + config.setConditionDoubleConstAllow(true); + config.setConditionAndAlwayTrueAllow(true); + config.setConditionAndAlwayFalseAllow(true); + return config; + } + + @Bean(name = "wallFilter") + @DependsOn("wallConfig") + WallFilter wallFilter(WallConfig wallConfig) { + WallFilter wfilter = new WallFilter(); + wfilter.setConfig(wallConfig); + return wfilter; + } private Lock getDataSourceLock(String key) { if (dataSourceLockMap.containsKey(key)) { diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Metric.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Metric.java index f0aee38d3..fab864d76 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Metric.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/pojo/Metric.java @@ -21,4 +21,6 @@ public class Metric extends SchemaItem { private DataFormat dataFormat; + private String alias; + } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/DimensionRepository.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/DimensionRepository.java index 4fab4f083..84fe70b2b 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/DimensionRepository.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/DimensionRepository.java @@ -18,6 +18,8 @@ public interface DimensionRepository { List getDimensionListOfDomain(Long domainId); + List getDimensionList(); + List getDimensionListByIds(List ids); DimensionDO getDimensionById(Long id); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/MetricRepository.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/MetricRepository.java index 31e735d87..674257fa6 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/MetricRepository.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/repository/MetricRepository.java @@ -17,6 +17,8 @@ public interface MetricRepository { List getMetricList(Long domainId); + List getMetricList(); + List getMetricListByIds(List ids); MetricDO getMetricById(Long id); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DatasourceConverter.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DatasourceConverter.java index 737972421..520d2c99f 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DatasourceConverter.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DatasourceConverter.java @@ -3,6 +3,7 @@ package com.tencent.supersonic.semantic.core.domain.utils; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Lists; import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.semantic.api.core.enums.MetricTypeEnum; import com.tencent.supersonic.semantic.api.core.pojo.DatasourceDetail; import com.tencent.supersonic.semantic.api.core.pojo.Dim; import com.tencent.supersonic.semantic.api.core.pojo.Identify; @@ -111,16 +112,17 @@ public class DatasourceConverter { public static MetricReq convert(Measure measure, Datasource datasource) { measure.setDatasourceId(datasource.getId()); - MetricReq exprMetricReq = new MetricReq(); - exprMetricReq.setName(measure.getName()); - exprMetricReq.setBizName(measure.getBizName().replace(datasource.getBizName() + "_", "")); - exprMetricReq.setDescription(measure.getName()); - exprMetricReq.setDomainId(datasource.getDomainId()); + MetricReq metricReq = new MetricReq(); + metricReq.setName(measure.getName()); + metricReq.setBizName(measure.getBizName().replace(datasource.getBizName() + "_", "")); + metricReq.setDescription(measure.getName()); + metricReq.setDomainId(datasource.getDomainId()); + metricReq.setMetricType(MetricTypeEnum.ATOMIC); MetricTypeParams exprTypeParams = new MetricTypeParams(); exprTypeParams.setExpr(measure.getBizName()); exprTypeParams.setMeasures(Lists.newArrayList(measure)); - exprMetricReq.setTypeParams(exprTypeParams); - return exprMetricReq; + metricReq.setTypeParams(exprTypeParams); + return metricReq; } public static DimensionReq convert(Identify identify, Datasource datasource) { diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DimensionConverter.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DimensionConverter.java index 0fed66215..06b615e94 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DimensionConverter.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DimensionConverter.java @@ -1,5 +1,6 @@ package com.tencent.supersonic.semantic.core.domain.utils; +import com.alibaba.fastjson.JSONObject; import com.tencent.supersonic.semantic.api.core.pojo.yaml.DimensionYamlTpl; import com.tencent.supersonic.semantic.api.core.request.DimensionReq; import com.tencent.supersonic.semantic.api.core.response.DatasourceResp; @@ -22,36 +23,34 @@ public class DimensionConverter { public static DimensionDO convert(DimensionDO dimensionDO, Dimension dimension) { BeanMapper.mapper(dimension, dimensionDO); + dimensionDO.setDefaultValues(JSONObject.toJSONString(dimension.getDefaultValues())); return dimensionDO; } public static DimensionDO convert2DimensionDO(Dimension dimension) { DimensionDO dimensionDO = new DimensionDO(); BeanUtils.copyProperties(dimension, dimensionDO); + dimensionDO.setDefaultValues(JSONObject.toJSONString(dimension.getDefaultValues())); return dimensionDO; } - public static DimensionResp convert2DimensionDesc(DimensionDO dimensionDO, + public static DimensionResp convert2DimensionResp(DimensionDO dimensionDO, Map fullPathMap, - Map datasourceDescMap - ) { - DimensionResp dimensionDesc = new DimensionResp(); - BeanUtils.copyProperties(dimensionDO, dimensionDesc); - dimensionDesc.setFullPath(fullPathMap.get(dimensionDO.getDomainId()) + dimensionDO.getBizName()); - dimensionDesc.setDatasourceId( - datasourceDescMap.getOrDefault(dimensionDesc.getDatasourceId(), new DatasourceResp()).getId()); - dimensionDesc.setDatasourceName( - datasourceDescMap.getOrDefault(dimensionDesc.getDatasourceId(), new DatasourceResp()).getName()); - dimensionDesc.setDatasourceBizName( - datasourceDescMap.getOrDefault(dimensionDesc.getDatasourceId(), new DatasourceResp()).getBizName()); - return dimensionDesc; - } - - public static Dimension convert2Dimension(DimensionDO dimensionDO) { - Dimension dimension = new Dimension(); - BeanUtils.copyProperties(dimensionDO, dimension); - return dimension; + Map datasourceRespMap) { + DimensionResp dimensionResp = new DimensionResp(); + BeanUtils.copyProperties(dimensionDO, dimensionResp); + dimensionResp.setFullPath(fullPathMap.get(dimensionDO.getDomainId()) + dimensionDO.getBizName()); + dimensionResp.setDatasourceId( + datasourceRespMap.getOrDefault(dimensionResp.getDatasourceId(), new DatasourceResp()).getId()); + dimensionResp.setDatasourceName( + datasourceRespMap.getOrDefault(dimensionResp.getDatasourceId(), new DatasourceResp()).getName()); + dimensionResp.setDatasourceBizName( + datasourceRespMap.getOrDefault(dimensionResp.getDatasourceId(), new DatasourceResp()).getBizName()); + if (dimensionDO.getDefaultValues() != null) { + dimensionResp.setDefaultValues(JSONObject.parseObject(dimensionDO.getDefaultValues(), List.class)); + } + return dimensionResp; } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DomainConvert.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DomainConvert.java index b322e8e89..4951d96d8 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DomainConvert.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/DomainConvert.java @@ -4,12 +4,15 @@ package com.tencent.supersonic.semantic.core.domain.utils; import com.google.common.collect.Lists; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.semantic.api.core.request.DomainReq; +import com.tencent.supersonic.semantic.api.core.response.DimensionResp; import com.tencent.supersonic.semantic.api.core.response.DomainResp; import com.tencent.supersonic.common.enums.StatusEnum; +import com.tencent.supersonic.semantic.api.core.response.MetricResp; import com.tencent.supersonic.semantic.core.domain.dataobject.DomainDO; import com.tencent.supersonic.semantic.core.domain.pojo.Domain; import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; @@ -38,18 +41,26 @@ public class DomainConvert { } public static DomainResp convert(DomainDO domainDO, Map domainFullPathMap) { - DomainResp domainDesc = new DomainResp(); - BeanUtils.copyProperties(domainDO, domainDesc); - domainDesc.setFullPath(domainFullPathMap.get(domainDO.getId())); - domainDesc.setAdmins(StringUtils.isBlank(domainDO.getAdmin()) + DomainResp domainResp = new DomainResp(); + BeanUtils.copyProperties(domainDO, domainResp); + domainResp.setFullPath(domainFullPathMap.get(domainDO.getId())); + domainResp.setAdmins(StringUtils.isBlank(domainDO.getAdmin()) ? Lists.newArrayList() : Arrays.asList(domainDO.getAdmin().split(","))); - domainDesc.setAdminOrgs(StringUtils.isBlank(domainDO.getAdminOrg()) + domainResp.setAdminOrgs(StringUtils.isBlank(domainDO.getAdminOrg()) ? Lists.newArrayList() : Arrays.asList(domainDO.getAdminOrg().split(","))); - domainDesc.setViewers(StringUtils.isBlank(domainDO.getViewer()) + domainResp.setViewers(StringUtils.isBlank(domainDO.getViewer()) ? Lists.newArrayList() : Arrays.asList(domainDO.getViewer().split(","))); - domainDesc.setViewOrgs(StringUtils.isBlank(domainDO.getViewOrg()) + domainResp.setViewOrgs(StringUtils.isBlank(domainDO.getViewOrg()) ? Lists.newArrayList() : Arrays.asList(domainDO.getViewOrg().split(","))); - return domainDesc; + return domainResp; + } + + public static DomainResp convert(DomainDO domainDO, Map domainFullPathMap, + Map> dimensionMap, Map> metricMap) { + DomainResp domainResp = convert(domainDO, domainFullPathMap); + domainResp.setDimensionCnt(dimensionMap.getOrDefault(domainResp.getId(), Lists.newArrayList()).size()); + domainResp.setMetricCnt(metricMap.getOrDefault(domainResp.getId(), Lists.newArrayList()).size()); + return domainResp; } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/JdbcDataSourceUtils.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/JdbcDataSourceUtils.java index d01a21e0a..10099610e 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/JdbcDataSourceUtils.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/JdbcDataSourceUtils.java @@ -29,10 +29,9 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class JdbcDataSourceUtils { - private JdbcDataSource jdbcDataSource; - @Getter private static Set releaseSourceSet = new HashSet(); + private JdbcDataSource jdbcDataSource; public JdbcDataSourceUtils(JdbcDataSource jdbcDataSource) { this.jdbcDataSource = jdbcDataSource; @@ -57,54 +56,6 @@ public class JdbcDataSourceUtils { return false; } - - public DataSource getDataSource(DatabaseResp databaseResp) throws RuntimeException { - return jdbcDataSource.getDataSource(databaseResp); - } - - public Connection getConnection(DatabaseResp databaseResp) throws RuntimeException { - Connection conn = getConnectionWithRetry(databaseResp); - if (conn == null) { - try { - releaseDataSource(databaseResp); - DataSource dataSource = getDataSource(databaseResp); - return dataSource.getConnection(); - } catch (Exception e) { - log.error("Get connection error, jdbcUrl:{}, e:{}", databaseResp.getUrl(), e); - throw new RuntimeException("Get connection error, jdbcUrl:" + databaseResp.getUrl() - + " you can try again later or reset datasource"); - } - } - return conn; - } - - private Connection getConnectionWithRetry(DatabaseResp databaseResp) { - int rc = 1; - for (; ; ) { - - if (rc > 3) { - return null; - } - - try { - Connection connection = getDataSource(databaseResp).getConnection(); - if (connection != null && connection.isValid(5)) { - return connection; - } - } catch (Exception e) { - log.error("e", e); - } - - try { - Thread.sleep((long) Math.pow(2, rc) * 1000); - } catch (InterruptedException e) { - log.error("e", e); - } - - rc++; - } - } - public static void releaseConnection(Connection connection) { if (null != connection) { try { @@ -177,11 +128,6 @@ public class JdbcDataSourceUtils { throw new RuntimeException("Not supported data type: jdbcUrl=" + jdbcUrl); } - - public void releaseDataSource(DatabaseResp databaseResp) { - jdbcDataSource.removeDatasource(databaseResp); - } - public static String getKey(String name, String jdbcUrl, String username, String password, String version, boolean isExt) { @@ -199,4 +145,55 @@ public class JdbcDataSourceUtils { return MD5Util.getMD5(sb.toString(), true, 64); } + + public DataSource getDataSource(DatabaseResp databaseResp) throws RuntimeException { + return jdbcDataSource.getDataSource(databaseResp); + } + + public Connection getConnection(DatabaseResp databaseResp) throws RuntimeException { + Connection conn = getConnectionWithRetry(databaseResp); + if (conn == null) { + try { + releaseDataSource(databaseResp); + DataSource dataSource = getDataSource(databaseResp); + return dataSource.getConnection(); + } catch (Exception e) { + log.error("Get connection error, jdbcUrl:{}, e:{}", databaseResp.getUrl(), e); + throw new RuntimeException("Get connection error, jdbcUrl:" + databaseResp.getUrl() + + " you can try again later or reset datasource"); + } + } + return conn; + } + + private Connection getConnectionWithRetry(DatabaseResp databaseResp) { + int rc = 1; + for (; ; ) { + + if (rc > 3) { + return null; + } + + try { + Connection connection = getDataSource(databaseResp).getConnection(); + if (connection != null && connection.isValid(5)) { + return connection; + } + } catch (Exception e) { + log.error("e", e); + } + + try { + Thread.sleep((long) Math.pow(2, rc) * 1000); + } catch (InterruptedException e) { + log.error("e", e); + } + + rc++; + } + } + + public void releaseDataSource(DatabaseResp databaseResp) { + jdbcDataSource.removeDatasource(databaseResp); + } } \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/MetricConverter.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/MetricConverter.java index 58a6c749b..e251ef844 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/MetricConverter.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/MetricConverter.java @@ -26,7 +26,7 @@ public class MetricConverter { public static Metric convert(MetricReq metricReq) { Metric metric = new Metric(); BeanUtils.copyProperties(metricReq, metric); - metric.setType(MetricTypeEnum.EXPR.getName()); + metric.setType(metricReq.getMetricType().name()); metric.setTypeParams(metricReq.getTypeParams()); return metric; } @@ -75,7 +75,7 @@ public class MetricConverter { return metric; } - + public static MetricYamlTpl convert2MetricYamlTpl(Metric metric) { MetricYamlTpl metricYamlTpl = new MetricYamlTpl(); BeanUtils.copyProperties(metric, metricYamlTpl); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SqlUtils.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SqlUtils.java index e9ecf0d46..98d12bd3c 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SqlUtils.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SqlUtils.java @@ -47,6 +47,15 @@ public class SqlUtils { @Getter private JdbcDataSourceUtils jdbcDataSourceUtils; + public SqlUtils() { + + } + + public SqlUtils(DatabaseResp databaseResp) { + this.databaseResp = databaseResp; + this.dataTypeEnum = DataTypeEnum.urlOf(databaseResp.getUrl()); + } + public SqlUtils init(DatabaseResp databaseResp) { //todo Password decryption return SqlUtilsBuilder @@ -62,16 +71,6 @@ public class SqlUtils { .build(); } - public SqlUtils() { - - } - - public SqlUtils(DatabaseResp databaseResp) { - this.databaseResp = databaseResp; - this.dataTypeEnum = DataTypeEnum.urlOf(databaseResp.getUrl()); - } - - public List> execute(String sql) throws ServerException { try { List> list = jdbcTemplate().queryForList(sql); @@ -97,7 +96,6 @@ public class SqlUtils { JdbcDataSourceUtils.releaseConnection(connection); } DataSource dataSource = jdbcDataSourceUtils.getDataSource(databaseResp); - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setDatabaseProductName(databaseResp.getName()); jdbcTemplate.setFetchSize(500); @@ -232,4 +230,4 @@ public class SqlUtils { return sqlUtils; } } -} \ No newline at end of file +} diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SysTimeDimensionBuilder.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SysTimeDimensionBuilder.java index e4f6d8981..673358695 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SysTimeDimensionBuilder.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/domain/utils/SysTimeDimensionBuilder.java @@ -23,7 +23,7 @@ public class SysTimeDimensionBuilder { dims.add(generateSysDayDimension(timeDim, engineAdaptor)); dims.add(generateSysWeekDimension(timeDim, engineAdaptor)); dims.add(generateSysMonthDimension(timeDim, engineAdaptor)); - log.info("addSysTimeDimension after:{}, engineAdaptor:{}", dims, engineAdaptor); + log.debug("addSysTimeDimension after:{}, engineAdaptor:{}", dims, engineAdaptor); } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DimensionDOMapper.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DimensionDOMapper.java index 594bf7bf0..5f586c637 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DimensionDOMapper.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DimensionDOMapper.java @@ -3,9 +3,9 @@ package com.tencent.supersonic.semantic.core.infrastructure.mapper; import com.tencent.supersonic.semantic.core.domain.dataobject.DimensionDO; import com.tencent.supersonic.semantic.core.domain.dataobject.DimensionDOExample; +import org.apache.ibatis.annotations.Mapper; import java.util.List; -import org.apache.ibatis.annotations.Mapper; @Mapper public interface DimensionDOMapper { @@ -59,4 +59,4 @@ public interface DimensionDOMapper { * @mbg.generated */ int updateByPrimaryKey(DimensionDO record); -} +} \ No newline at end of file diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DomainDOMapper.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DomainDOMapper.java index 965332270..177bc0559 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DomainDOMapper.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/mapper/DomainDOMapper.java @@ -48,4 +48,4 @@ public interface DomainDOMapper { * @mbg.generated */ int updateByPrimaryKey(DomainDO record); -} \ No newline at end of file +} diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DatasourceRepositoryImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DatasourceRepositoryImpl.java index ccf73ad27..e521c9e3c 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DatasourceRepositoryImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DatasourceRepositoryImpl.java @@ -53,7 +53,7 @@ public class DatasourceRepositoryImpl implements DatasourceRepository { public DatasourceDO getDatasourceById(Long id) { return datasourceMapper.selectByPrimaryKey(id); } - + @Override public void deleteDatasource(Long id) { datasourceMapper.deleteByPrimaryKey(id); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DimensionRepositoryImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DimensionRepositoryImpl.java index 4d2a44ed0..eeef1f45a 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DimensionRepositoryImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DimensionRepositoryImpl.java @@ -56,6 +56,12 @@ public class DimensionRepositoryImpl implements DimensionRepository { return dimensionDOMapper.selectByExampleWithBLOBs(dimensionDOExample); } + @Override + public List getDimensionList() { + DimensionDOExample dimensionDOExample = new DimensionDOExample(); + return dimensionDOMapper.selectByExampleWithBLOBs(dimensionDOExample); + } + @Override public List getDimensionListByIds(List ids) { DimensionDOExample dimensionDOExample = new DimensionDOExample(); @@ -86,7 +92,7 @@ public class DimensionRepositoryImpl implements DimensionRepository { dimensionDOExample.getOredCriteria().get(0).andNameLike("%" + dimensionFilter.getName() + "%"); } if (dimensionFilter.getBizName() != null) { - dimensionDOExample.getOredCriteria().get(0).andBizNameEqualTo("%" + dimensionFilter.getBizName() + "%"); + dimensionDOExample.getOredCriteria().get(0).andBizNameLike("%" + dimensionFilter.getBizName() + "%"); } if (dimensionFilter.getCreatedBy() != null) { dimensionDOExample.getOredCriteria().get(0).andCreatedByEqualTo(dimensionFilter.getCreatedBy()); @@ -94,6 +100,9 @@ public class DimensionRepositoryImpl implements DimensionRepository { if (dimensionFilter.getDomainId() != null) { dimensionDOExample.getOredCriteria().get(0).andDomainIdEqualTo(dimensionFilter.getDomainId()); } + if (dimensionFilter.getSensitiveLevel() != null) { + dimensionDOExample.getOredCriteria().get(0).andSensitiveLevelEqualTo(dimensionFilter.getSensitiveLevel()); + } return dimensionDOMapper.selectByExampleWithBLOBs(dimensionDOExample); } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DomainRepositoryImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DomainRepositoryImpl.java index 2ed8285ab..dcea2c7a7 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DomainRepositoryImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/DomainRepositoryImpl.java @@ -5,10 +5,13 @@ import com.tencent.supersonic.semantic.core.domain.dataobject.DomainDOExample; import com.tencent.supersonic.semantic.core.domain.repository.DomainRepository; import com.tencent.supersonic.semantic.core.infrastructure.mapper.DomainDOMapper; import java.util.List; + +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component +@Slf4j public class DomainRepositoryImpl implements DomainRepository { private DomainDOMapper domainDOMapper; diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/MetricRepositoryImpl.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/MetricRepositoryImpl.java index f7c7c67f1..8ff981b45 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/MetricRepositoryImpl.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/infrastructure/repository/MetricRepositoryImpl.java @@ -48,6 +48,12 @@ public class MetricRepositoryImpl implements MetricRepository { return metricDOMapper.selectByExampleWithBLOBs(metricDOExample); } + @Override + public List getMetricList() { + MetricDOExample metricDOExample = new MetricDOExample(); + return metricDOMapper.selectByExampleWithBLOBs(metricDOExample); + } + @Override public List getMetricListByIds(List ids) { MetricDOExample metricDOExample = new MetricDOExample(); diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/DatabaseController.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/DatabaseController.java index 290f09851..527684374 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/DatabaseController.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/DatabaseController.java @@ -59,5 +59,24 @@ public class DatabaseController { return databaseService.executeSql(sqlExecuteReq.getSql(), sqlExecuteReq.getDomainId()); } + @RequestMapping("/getDbNames/{id}") + public QueryResultWithSchemaResp getDbNames(@PathVariable("id") Long id) { + return databaseService.getDbNames(id); + } + + @RequestMapping("/getTables/{id}/{db}") + public QueryResultWithSchemaResp getTables(@PathVariable("id") Long id, + @PathVariable("db") String db) { + return databaseService.getTables(id, db); + } + + + @RequestMapping("/getColumns/{id}/{db}/{table}") + public QueryResultWithSchemaResp getColumns(@PathVariable("id") Long id, + @PathVariable("db") String db, + @PathVariable("table") String table) { + return databaseService.getColumns(id, db, table); + } + } diff --git a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/ViewInfoController.java b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/ViewInfoController.java index 4d01f7e40..d6e1d16d8 100644 --- a/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/ViewInfoController.java +++ b/semantic/core/src/main/java/com/tencent/supersonic/semantic/core/rest/ViewInfoController.java @@ -32,7 +32,7 @@ public class ViewInfoController { @PostMapping("/createOrUpdateViewInfo") public ViewInfoDO createOrUpdateViewInfo(@RequestBody ViewInfoReq viewInfoReq, HttpServletRequest request, - HttpServletResponse response) { + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return viewInfoServiceImpl.createOrUpdateViewInfo(viewInfoReq, user); } diff --git a/semantic/core/src/main/resources/mapper/DimensionDOMapper.xml b/semantic/core/src/main/resources/mapper/DimensionDOMapper.xml index f24ced025..fa5d52624 100644 --- a/semantic/core/src/main/resources/mapper/DimensionDOMapper.xml +++ b/semantic/core/src/main/resources/mapper/DimensionDOMapper.xml @@ -1,338 +1,341 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and - #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + - - - - id - , domain_id, datasource_id, name, biz_name, description, status, sensitive_level, - type, created_at, created_by, updated_at, updated_by, semantic_type - - - type_params - , expr - - - - - - delete - from s2_dimension - where id = #{id,jdbcType=BIGINT} - - - insert into s2_dimension (id, domain_id, datasource_id, - name, biz_name, description, - status, sensitive_level, type, - created_at, created_by, updated_at, - updated_by, semantic_type, type_params, - expr) - values (#{id,jdbcType=BIGINT}, #{domainId,jdbcType=BIGINT}, #{datasourceId,jdbcType=BIGINT}, - #{name,jdbcType=VARCHAR}, #{bizName,jdbcType=VARCHAR}, - #{description,jdbcType=VARCHAR}, - #{status,jdbcType=INTEGER}, #{sensitiveLevel,jdbcType=INTEGER}, - #{type,jdbcType=VARCHAR}, - #{createdAt,jdbcType=TIMESTAMP}, #{createdBy,jdbcType=VARCHAR}, - #{updatedAt,jdbcType=TIMESTAMP}, - #{updatedBy,jdbcType=VARCHAR}, #{semanticType,jdbcType=VARCHAR}, - #{typeParams,jdbcType=LONGVARCHAR}, - #{expr,jdbcType=LONGVARCHAR}) - - - insert into s2_dimension - - - id, - - - domain_id, - - - datasource_id, - - - name, - - - biz_name, - - - description, - - - status, - - - sensitive_level, - - - type, - - - created_at, - - - created_by, - - - updated_at, - - - updated_by, - - - semantic_type, - - - type_params, - - - expr, - - - - - #{id,jdbcType=BIGINT}, - - - #{domainId,jdbcType=BIGINT}, - - - #{datasourceId,jdbcType=BIGINT}, - - - #{name,jdbcType=VARCHAR}, - - - #{bizName,jdbcType=VARCHAR}, - - - #{description,jdbcType=VARCHAR}, - - - #{status,jdbcType=INTEGER}, - - - #{sensitiveLevel,jdbcType=INTEGER}, - - - #{type,jdbcType=VARCHAR}, - - - #{createdAt,jdbcType=TIMESTAMP}, - - - #{createdBy,jdbcType=VARCHAR}, - - - #{updatedAt,jdbcType=TIMESTAMP}, - - - #{updatedBy,jdbcType=VARCHAR}, - - - #{semanticType,jdbcType=VARCHAR}, - - - #{typeParams,jdbcType=LONGVARCHAR}, - - - #{expr,jdbcType=LONGVARCHAR}, - - - - - - update s2_dimension - - - domain_id = #{domainId,jdbcType=BIGINT}, - - - datasource_id = #{datasourceId,jdbcType=BIGINT}, - - - name = #{name,jdbcType=VARCHAR}, - - - biz_name = #{bizName,jdbcType=VARCHAR}, - - - description = #{description,jdbcType=VARCHAR}, - - - status = #{status,jdbcType=INTEGER}, - - - sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, - - - type = #{type,jdbcType=VARCHAR}, - - - created_at = #{createdAt,jdbcType=TIMESTAMP}, - - - created_by = #{createdBy,jdbcType=VARCHAR}, - - - updated_at = #{updatedAt,jdbcType=TIMESTAMP}, - - - updated_by = #{updatedBy,jdbcType=VARCHAR}, - - - semantic_type = #{semanticType,jdbcType=VARCHAR}, - - - type_params = #{typeParams,jdbcType=LONGVARCHAR}, - - - expr = #{expr,jdbcType=LONGVARCHAR}, - - - where id = #{id,jdbcType=BIGINT} - - - update s2_dimension - set domain_id = #{domainId,jdbcType=BIGINT}, - datasource_id = #{datasourceId,jdbcType=BIGINT}, - name = #{name,jdbcType=VARCHAR}, - biz_name = #{bizName,jdbcType=VARCHAR}, - description = #{description,jdbcType=VARCHAR}, - status = #{status,jdbcType=INTEGER}, - sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, - type = #{type,jdbcType=VARCHAR}, - created_at = #{createdAt,jdbcType=TIMESTAMP}, - created_by = #{createdBy,jdbcType=VARCHAR}, - updated_at = #{updatedAt,jdbcType=TIMESTAMP}, - updated_by = #{updatedBy,jdbcType=VARCHAR}, - semantic_type = #{semanticType,jdbcType=VARCHAR}, - type_params = #{typeParams,jdbcType=LONGVARCHAR}, - expr = #{expr,jdbcType=LONGVARCHAR} - where id = #{id,jdbcType=BIGINT} - - - update s2_dimension - set domain_id = #{domainId,jdbcType=BIGINT}, - datasource_id = #{datasourceId,jdbcType=BIGINT}, - name = #{name,jdbcType=VARCHAR}, - biz_name = #{bizName,jdbcType=VARCHAR}, - description = #{description,jdbcType=VARCHAR}, - status = #{status,jdbcType=INTEGER}, - sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, - type = #{type,jdbcType=VARCHAR}, - created_at = #{createdAt,jdbcType=TIMESTAMP}, - created_by = #{createdBy,jdbcType=VARCHAR}, - updated_at = #{updatedAt,jdbcType=TIMESTAMP}, - updated_by = #{updatedBy,jdbcType=VARCHAR}, - semantic_type = #{semanticType,jdbcType=VARCHAR} - where id = #{id,jdbcType=BIGINT} - + + + + + id, domain_id, datasource_id, name, biz_name, description, status, sensitive_level, + type, created_at, created_by, updated_at, updated_by, semantic_type, alias, default_values + + + type_params, expr + + + + + + delete from s2_dimension + where id = #{id,jdbcType=BIGINT} + + + insert into s2_dimension (id, domain_id, datasource_id, + name, biz_name, description, + status, sensitive_level, type, + created_at, created_by, updated_at, + updated_by, semantic_type, alias, + default_values, type_params, expr + ) + values (#{id,jdbcType=BIGINT}, #{domainId,jdbcType=BIGINT}, #{datasourceId,jdbcType=BIGINT}, + #{name,jdbcType=VARCHAR}, #{bizName,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, + #{status,jdbcType=INTEGER}, #{sensitiveLevel,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, + #{createdAt,jdbcType=TIMESTAMP}, #{createdBy,jdbcType=VARCHAR}, #{updatedAt,jdbcType=TIMESTAMP}, + #{updatedBy,jdbcType=VARCHAR}, #{semanticType,jdbcType=VARCHAR}, #{alias,jdbcType=VARCHAR}, + #{defaultValues,jdbcType=VARCHAR}, #{typeParams,jdbcType=LONGVARCHAR}, #{expr,jdbcType=LONGVARCHAR} + ) + + + insert into s2_dimension + + + id, + + + domain_id, + + + datasource_id, + + + name, + + + biz_name, + + + description, + + + status, + + + sensitive_level, + + + type, + + + created_at, + + + created_by, + + + updated_at, + + + updated_by, + + + semantic_type, + + + alias, + + + default_values, + + + type_params, + + + expr, + + + + + #{id,jdbcType=BIGINT}, + + + #{domainId,jdbcType=BIGINT}, + + + #{datasourceId,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{bizName,jdbcType=VARCHAR}, + + + #{description,jdbcType=VARCHAR}, + + + #{status,jdbcType=INTEGER}, + + + #{sensitiveLevel,jdbcType=INTEGER}, + + + #{type,jdbcType=VARCHAR}, + + + #{createdAt,jdbcType=TIMESTAMP}, + + + #{createdBy,jdbcType=VARCHAR}, + + + #{updatedAt,jdbcType=TIMESTAMP}, + + + #{updatedBy,jdbcType=VARCHAR}, + + + #{semanticType,jdbcType=VARCHAR}, + + + #{alias,jdbcType=VARCHAR}, + + + #{defaultValues,jdbcType=VARCHAR}, + + + #{typeParams,jdbcType=LONGVARCHAR}, + + + #{expr,jdbcType=LONGVARCHAR}, + + + + + + update s2_dimension + + + domain_id = #{domainId,jdbcType=BIGINT}, + + + datasource_id = #{datasourceId,jdbcType=BIGINT}, + + + name = #{name,jdbcType=VARCHAR}, + + + biz_name = #{bizName,jdbcType=VARCHAR}, + + + description = #{description,jdbcType=VARCHAR}, + + + status = #{status,jdbcType=INTEGER}, + + + sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, + + + type = #{type,jdbcType=VARCHAR}, + + + created_at = #{createdAt,jdbcType=TIMESTAMP}, + + + created_by = #{createdBy,jdbcType=VARCHAR}, + + + updated_at = #{updatedAt,jdbcType=TIMESTAMP}, + + + updated_by = #{updatedBy,jdbcType=VARCHAR}, + + + semantic_type = #{semanticType,jdbcType=VARCHAR}, + + + alias = #{alias,jdbcType=VARCHAR}, + + + default_values = #{defaultValues,jdbcType=VARCHAR}, + + + type_params = #{typeParams,jdbcType=LONGVARCHAR}, + + + expr = #{expr,jdbcType=LONGVARCHAR}, + + + where id = #{id,jdbcType=BIGINT} + + + update s2_dimension + set domain_id = #{domainId,jdbcType=BIGINT}, + datasource_id = #{datasourceId,jdbcType=BIGINT}, + name = #{name,jdbcType=VARCHAR}, + biz_name = #{bizName,jdbcType=VARCHAR}, + description = #{description,jdbcType=VARCHAR}, + status = #{status,jdbcType=INTEGER}, + sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, + type = #{type,jdbcType=VARCHAR}, + created_at = #{createdAt,jdbcType=TIMESTAMP}, + created_by = #{createdBy,jdbcType=VARCHAR}, + updated_at = #{updatedAt,jdbcType=TIMESTAMP}, + updated_by = #{updatedBy,jdbcType=VARCHAR}, + semantic_type = #{semanticType,jdbcType=VARCHAR}, + alias = #{alias,jdbcType=VARCHAR}, + default_values = #{defaultValues,jdbcType=VARCHAR}, + type_params = #{typeParams,jdbcType=LONGVARCHAR}, + expr = #{expr,jdbcType=LONGVARCHAR} + where id = #{id,jdbcType=BIGINT} + + + update s2_dimension + set domain_id = #{domainId,jdbcType=BIGINT}, + datasource_id = #{datasourceId,jdbcType=BIGINT}, + name = #{name,jdbcType=VARCHAR}, + biz_name = #{bizName,jdbcType=VARCHAR}, + description = #{description,jdbcType=VARCHAR}, + status = #{status,jdbcType=INTEGER}, + sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, + type = #{type,jdbcType=VARCHAR}, + created_at = #{createdAt,jdbcType=TIMESTAMP}, + created_by = #{createdBy,jdbcType=VARCHAR}, + updated_at = #{updatedAt,jdbcType=TIMESTAMP}, + updated_by = #{updatedBy,jdbcType=VARCHAR}, + semantic_type = #{semanticType,jdbcType=VARCHAR}, + alias = #{alias,jdbcType=VARCHAR}, + default_values = #{defaultValues,jdbcType=VARCHAR} + where id = #{id,jdbcType=BIGINT} + \ No newline at end of file diff --git a/semantic/core/src/main/resources/mapper/MetricDOMapper.xml b/semantic/core/src/main/resources/mapper/MetricDOMapper.xml index b4df05687..cb173cc0a 100644 --- a/semantic/core/src/main/resources/mapper/MetricDOMapper.xml +++ b/semantic/core/src/main/resources/mapper/MetricDOMapper.xml @@ -1,324 +1,316 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and - #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + - - - - id - , domain_id, name, biz_name, description, status, sensitive_level, type, created_at, - created_by, updated_at, updated_by, data_format_type, data_format - - - type_params - - - - - - delete - from s2_metric - where id = #{id,jdbcType=BIGINT} - - - insert into s2_metric (id, domain_id, name, - biz_name, description, status, - sensitive_level, type, created_at, - created_by, updated_at, updated_by, - data_format_type, data_format, type_params) - values (#{id,jdbcType=BIGINT}, #{domainId,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, - #{bizName,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, - #{status,jdbcType=INTEGER}, - #{sensitiveLevel,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, - #{createdAt,jdbcType=TIMESTAMP}, - #{createdBy,jdbcType=VARCHAR}, #{updatedAt,jdbcType=TIMESTAMP}, - #{updatedBy,jdbcType=VARCHAR}, - #{dataFormatType,jdbcType=VARCHAR}, #{dataFormat,jdbcType=VARCHAR}, - #{typeParams,jdbcType=LONGVARCHAR}) - - - insert into s2_metric - - - id, - - - domain_id, - - - name, - - - biz_name, - - - description, - - - status, - - - sensitive_level, - - - type, - - - created_at, - - - created_by, - - - updated_at, - - - updated_by, - - - data_format_type, - - - data_format, - - - type_params, - - - - - #{id,jdbcType=BIGINT}, - - - #{domainId,jdbcType=BIGINT}, - - - #{name,jdbcType=VARCHAR}, - - - #{bizName,jdbcType=VARCHAR}, - - - #{description,jdbcType=VARCHAR}, - - - #{status,jdbcType=INTEGER}, - - - #{sensitiveLevel,jdbcType=INTEGER}, - - - #{type,jdbcType=VARCHAR}, - - - #{createdAt,jdbcType=TIMESTAMP}, - - - #{createdBy,jdbcType=VARCHAR}, - - - #{updatedAt,jdbcType=TIMESTAMP}, - - - #{updatedBy,jdbcType=VARCHAR}, - - - #{dataFormatType,jdbcType=VARCHAR}, - - - #{dataFormat,jdbcType=VARCHAR}, - - - #{typeParams,jdbcType=LONGVARCHAR}, - - - - - - update s2_metric - - - domain_id = #{domainId,jdbcType=BIGINT}, - - - name = #{name,jdbcType=VARCHAR}, - - - biz_name = #{bizName,jdbcType=VARCHAR}, - - - description = #{description,jdbcType=VARCHAR}, - - - status = #{status,jdbcType=INTEGER}, - - - sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, - - - type = #{type,jdbcType=VARCHAR}, - - - created_at = #{createdAt,jdbcType=TIMESTAMP}, - - - created_by = #{createdBy,jdbcType=VARCHAR}, - - - updated_at = #{updatedAt,jdbcType=TIMESTAMP}, - - - updated_by = #{updatedBy,jdbcType=VARCHAR}, - - - data_format_type = #{dataFormatType,jdbcType=VARCHAR}, - - - data_format = #{dataFormat,jdbcType=VARCHAR}, - - - type_params = #{typeParams,jdbcType=LONGVARCHAR}, - - - where id = #{id,jdbcType=BIGINT} - - - update s2_metric - set domain_id = #{domainId,jdbcType=BIGINT}, - name = #{name,jdbcType=VARCHAR}, - biz_name = #{bizName,jdbcType=VARCHAR}, - description = #{description,jdbcType=VARCHAR}, - status = #{status,jdbcType=INTEGER}, - sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, - type = #{type,jdbcType=VARCHAR}, - created_at = #{createdAt,jdbcType=TIMESTAMP}, - created_by = #{createdBy,jdbcType=VARCHAR}, - updated_at = #{updatedAt,jdbcType=TIMESTAMP}, - updated_by = #{updatedBy,jdbcType=VARCHAR}, - data_format_type = #{dataFormatType,jdbcType=VARCHAR}, - data_format = #{dataFormat,jdbcType=VARCHAR}, - type_params = #{typeParams,jdbcType=LONGVARCHAR} - where id = #{id,jdbcType=BIGINT} - - - update s2_metric - set domain_id = #{domainId,jdbcType=BIGINT}, - name = #{name,jdbcType=VARCHAR}, - biz_name = #{bizName,jdbcType=VARCHAR}, - description = #{description,jdbcType=VARCHAR}, - status = #{status,jdbcType=INTEGER}, - sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, - type = #{type,jdbcType=VARCHAR}, - created_at = #{createdAt,jdbcType=TIMESTAMP}, - created_by = #{createdBy,jdbcType=VARCHAR}, - updated_at = #{updatedAt,jdbcType=TIMESTAMP}, - updated_by = #{updatedBy,jdbcType=VARCHAR}, - data_format_type = #{dataFormatType,jdbcType=VARCHAR}, - data_format = #{dataFormat,jdbcType=VARCHAR} - where id = #{id,jdbcType=BIGINT} - + + + + + id, domain_id, name, biz_name, description, status, sensitive_level, type, created_at, + created_by, updated_at, updated_by, data_format_type, data_format, alias + + + type_params + + + + + + delete from s2_metric + where id = #{id,jdbcType=BIGINT} + + + insert into s2_metric (id, domain_id, name, + biz_name, description, status, + sensitive_level, type, created_at, + created_by, updated_at, updated_by, + data_format_type, data_format, alias, + type_params) + values (#{id,jdbcType=BIGINT}, #{domainId,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, + #{bizName,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, + #{sensitiveLevel,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, #{createdAt,jdbcType=TIMESTAMP}, + #{createdBy,jdbcType=VARCHAR}, #{updatedAt,jdbcType=TIMESTAMP}, #{updatedBy,jdbcType=VARCHAR}, + #{dataFormatType,jdbcType=VARCHAR}, #{dataFormat,jdbcType=VARCHAR}, #{alias,jdbcType=VARCHAR}, + #{typeParams,jdbcType=LONGVARCHAR}) + + + insert into s2_metric + + + id, + + + domain_id, + + + name, + + + biz_name, + + + description, + + + status, + + + sensitive_level, + + + type, + + + created_at, + + + created_by, + + + updated_at, + + + updated_by, + + + data_format_type, + + + data_format, + + + alias, + + + type_params, + + + + + #{id,jdbcType=BIGINT}, + + + #{domainId,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{bizName,jdbcType=VARCHAR}, + + + #{description,jdbcType=VARCHAR}, + + + #{status,jdbcType=INTEGER}, + + + #{sensitiveLevel,jdbcType=INTEGER}, + + + #{type,jdbcType=VARCHAR}, + + + #{createdAt,jdbcType=TIMESTAMP}, + + + #{createdBy,jdbcType=VARCHAR}, + + + #{updatedAt,jdbcType=TIMESTAMP}, + + + #{updatedBy,jdbcType=VARCHAR}, + + + #{dataFormatType,jdbcType=VARCHAR}, + + + #{dataFormat,jdbcType=VARCHAR}, + + + #{alias,jdbcType=VARCHAR}, + + + #{typeParams,jdbcType=LONGVARCHAR}, + + + + + + update s2_metric + + + domain_id = #{domainId,jdbcType=BIGINT}, + + + name = #{name,jdbcType=VARCHAR}, + + + biz_name = #{bizName,jdbcType=VARCHAR}, + + + description = #{description,jdbcType=VARCHAR}, + + + status = #{status,jdbcType=INTEGER}, + + + sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, + + + type = #{type,jdbcType=VARCHAR}, + + + created_at = #{createdAt,jdbcType=TIMESTAMP}, + + + created_by = #{createdBy,jdbcType=VARCHAR}, + + + updated_at = #{updatedAt,jdbcType=TIMESTAMP}, + + + updated_by = #{updatedBy,jdbcType=VARCHAR}, + + + data_format_type = #{dataFormatType,jdbcType=VARCHAR}, + + + data_format = #{dataFormat,jdbcType=VARCHAR}, + + + alias = #{alias,jdbcType=VARCHAR}, + + + type_params = #{typeParams,jdbcType=LONGVARCHAR}, + + + where id = #{id,jdbcType=BIGINT} + + + update s2_metric + set domain_id = #{domainId,jdbcType=BIGINT}, + name = #{name,jdbcType=VARCHAR}, + biz_name = #{bizName,jdbcType=VARCHAR}, + description = #{description,jdbcType=VARCHAR}, + status = #{status,jdbcType=INTEGER}, + sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, + type = #{type,jdbcType=VARCHAR}, + created_at = #{createdAt,jdbcType=TIMESTAMP}, + created_by = #{createdBy,jdbcType=VARCHAR}, + updated_at = #{updatedAt,jdbcType=TIMESTAMP}, + updated_by = #{updatedBy,jdbcType=VARCHAR}, + data_format_type = #{dataFormatType,jdbcType=VARCHAR}, + data_format = #{dataFormat,jdbcType=VARCHAR}, + alias = #{alias,jdbcType=VARCHAR}, + type_params = #{typeParams,jdbcType=LONGVARCHAR} + where id = #{id,jdbcType=BIGINT} + + + update s2_metric + set domain_id = #{domainId,jdbcType=BIGINT}, + name = #{name,jdbcType=VARCHAR}, + biz_name = #{bizName,jdbcType=VARCHAR}, + description = #{description,jdbcType=VARCHAR}, + status = #{status,jdbcType=INTEGER}, + sensitive_level = #{sensitiveLevel,jdbcType=INTEGER}, + type = #{type,jdbcType=VARCHAR}, + created_at = #{createdAt,jdbcType=TIMESTAMP}, + created_by = #{createdBy,jdbcType=VARCHAR}, + updated_at = #{updatedAt,jdbcType=TIMESTAMP}, + updated_by = #{updatedBy,jdbcType=VARCHAR}, + data_format_type = #{dataFormatType,jdbcType=VARCHAR}, + data_format = #{dataFormat,jdbcType=VARCHAR}, + alias = #{alias,jdbcType=VARCHAR} + where id = #{id,jdbcType=BIGINT} + \ No newline at end of file diff --git a/semantic/core/src/main/resources/mapper/custom/DateInfoMapper.xml b/semantic/core/src/main/resources/mapper/custom/DateInfoMapper.xml index 467522d96..5df03b47d 100644 --- a/semantic/core/src/main/resources/mapper/custom/DateInfoMapper.xml +++ b/semantic/core/src/main/resources/mapper/custom/DateInfoMapper.xml @@ -32,16 +32,23 @@ diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/ParserServiceImpl.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/ParserServiceImpl.java index cdb124001..9d016d63c 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/ParserServiceImpl.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/ParserServiceImpl.java @@ -12,6 +12,7 @@ import com.tencent.supersonic.semantic.query.domain.parser.dsl.SemanticModel; import java.util.ArrayList; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -31,7 +32,6 @@ public class ParserServiceImpl implements ParserService { } - @Override public SqlParserResp physicalSql(ParseSqlReq sqlCommend) throws Exception { return parser(sqlCommend); @@ -107,6 +107,9 @@ public class ParserServiceImpl implements ParserService { private String formatWhere(String where) { + if (StringUtils.isEmpty(where)) { + return where; + } return where.replace("\"", "\\\\\""); } } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/QueryServiceImpl.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/QueryServiceImpl.java index 198e889a6..c30c38a7c 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/QueryServiceImpl.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/QueryServiceImpl.java @@ -1,28 +1,31 @@ package com.tencent.supersonic.semantic.query.application; import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.common.enums.TaskStatusEnum; +import com.tencent.supersonic.common.util.cache.CacheUtils; +import com.tencent.supersonic.common.util.context.ContextUtils; import com.tencent.supersonic.semantic.api.core.pojo.QueryStat; +import com.tencent.supersonic.semantic.api.core.request.DomainSchemaFilterReq; +import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp; import com.tencent.supersonic.semantic.api.core.response.SqlParserResp; import com.tencent.supersonic.semantic.api.query.pojo.Cache; import com.tencent.supersonic.semantic.api.query.request.ItemUseReq; -import com.tencent.supersonic.semantic.api.query.request.MetricReq; import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq; import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; import com.tencent.supersonic.semantic.api.query.response.ItemUseResp; -import com.tencent.supersonic.common.enums.TaskStatusEnum; -import com.tencent.supersonic.common.util.cache.CacheUtils; import com.tencent.supersonic.semantic.core.domain.DatabaseService; import com.tencent.supersonic.semantic.query.domain.ParserService; import com.tencent.supersonic.semantic.query.domain.QueryService; +import com.tencent.supersonic.semantic.query.domain.SchemaService; import com.tencent.supersonic.semantic.query.domain.annotation.DataPermission; +import com.tencent.supersonic.semantic.query.domain.utils.QueryReqConverter; import com.tencent.supersonic.semantic.query.domain.utils.QueryStructUtils; import com.tencent.supersonic.semantic.query.domain.utils.StatUtils; - +import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; - import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -37,28 +40,37 @@ public class QueryServiceImpl implements QueryService { private final QueryStructUtils queryStructUtils; private final StatUtils statUtils; private final CacheUtils cacheUtils; + private final QueryReqConverter queryReqConverter; @Value("${query.cache.enable:true}") private Boolean cacheEnable; public QueryServiceImpl(ParserService parserService, - DatabaseService databaseService, - QueryStructUtils queryStructUtils, - StatUtils statUtils, - CacheUtils cacheUtils) { + DatabaseService databaseService, + QueryStructUtils queryStructUtils, + StatUtils statUtils, + CacheUtils cacheUtils, + QueryReqConverter queryReqConverter) { this.parserService = parserService; this.databaseService = databaseService; this.queryStructUtils = queryStructUtils; this.statUtils = statUtils; this.cacheUtils = cacheUtils; + this.queryReqConverter = queryReqConverter; } @Override - public Object queryBySql(QuerySqlReq querySqlCmd) throws Exception { - //TODO QuerySqlCmd---> SqlCommend - MetricReq sqlCommend = new MetricReq(); + public Object queryBySql(QuerySqlReq querySqlCmd, User user) throws Exception { + DomainSchemaFilterReq filter = new DomainSchemaFilterReq(); + List domainIds = new ArrayList<>(); + domainIds.add(querySqlCmd.getDomainId()); + + filter.setDomainIds(domainIds); + SchemaService schemaService = ContextUtils.getBean(SchemaService.class); + List domainSchemas = schemaService.fetchDomainSchema(filter, user); + + SqlParserResp sqlParser = queryReqConverter.convert(querySqlCmd, domainSchemas); - SqlParserResp sqlParser = parserService.physicalSql(sqlCommend); return databaseService.executeSql(sqlParser.getSql(), querySqlCmd.getDomainId()); } @@ -128,5 +140,4 @@ public class QueryServiceImpl implements QueryService { } - } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/SchemaServiceImpl.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/SchemaServiceImpl.java index 60ab7f9ec..4a82e5b59 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/SchemaServiceImpl.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/SchemaServiceImpl.java @@ -38,9 +38,9 @@ public class SchemaServiceImpl implements SchemaService { private final MetricService metricService; public SchemaServiceImpl(QueryService queryService, - DomainService domainService, - DimensionService dimensionService, - MetricService metricService) { + DomainService domainService, + DimensionService dimensionService, + MetricService metricService) { this.queryService = queryService; this.domainService = domainService; this.dimensionService = dimensionService; @@ -52,7 +52,7 @@ public class SchemaServiceImpl implements SchemaService { public List fetchDomainSchema(DomainSchemaFilterReq filter, User user) { List domainSchemaDescList = domainService.fetchDomainSchema(filter, user); List statInfos = queryService.getStatInfo(new ItemUseReq()); - log.info("statInfos:{}", statInfos); + log.debug("statInfos:{}", statInfos); fillCnt(domainSchemaDescList, statInfos); return domainSchemaDescList; @@ -66,7 +66,7 @@ public class SchemaServiceImpl implements SchemaService { itemUseInfo -> itemUseInfo.getType() + AT_SYMBOL + AT_SYMBOL + itemUseInfo.getBizName(), itemUseInfo -> itemUseInfo, (item1, item2) -> item1)); - log.info("typeIdAndStatPair:{}", typeIdAndStatPair); + log.debug("typeIdAndStatPair:{}", typeIdAndStatPair); for (DomainSchemaResp domainSchemaDesc : domainSchemaDescList) { fillDimCnt(domainSchemaDesc, typeIdAndStatPair); fillMetricCnt(domainSchemaDesc, typeIdAndStatPair); diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/parser/SemanticSchemaManagerImpl.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/parser/SemanticSchemaManagerImpl.java index 5585d2678..327ae11bf 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/parser/SemanticSchemaManagerImpl.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/application/parser/SemanticSchemaManagerImpl.java @@ -38,18 +38,19 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @Slf4j +@Primary @Service("SemanticSchemaManager") public class SemanticSchemaManagerImpl implements SemanticSchemaManager { - @Autowired - private LoadingCache loadingCache; - private final DatasourceService datasourceService; private final DomainService domainService; + @Autowired + private LoadingCache loadingCache; public SemanticSchemaManagerImpl(DatasourceService datasourceService, @@ -58,62 +59,14 @@ public class SemanticSchemaManagerImpl implements SemanticSchemaManager { this.domainService = domainService; } - - @Override - public SemanticModel reload(String rootPath) { - SemanticModel semanticModel = new SemanticModel(); - semanticModel.setRootPath(rootPath); - Map domainFullPathMap = domainService.getDomainFullPath(); - log.info("domainFullPathMap {}", domainFullPathMap); - Set domainIds = domainFullPathMap.entrySet().stream().filter(e -> e.getValue().startsWith(rootPath)) - .map(e -> e.getKey()).collect(Collectors.toSet()); - if (domainIds.isEmpty()) { - log.error("get domainId empty {}", rootPath); - return semanticModel; - } - Map> dimensionYamlTpls = new HashMap<>(); - List datasourceYamlTpls = new ArrayList<>(); - List metricYamlTpls = new ArrayList<>(); - datasourceService.getModelYamlTplByDomainIds(domainIds, dimensionYamlTpls, datasourceYamlTpls, metricYamlTpls); - if (!datasourceYamlTpls.isEmpty()) { - Map dataSourceMap = datasourceYamlTpls.stream().map(d -> getDatasource(d)) - .collect(Collectors.toMap(DataSource::getName, item -> item)); - semanticModel.setDatasourceMap(dataSourceMap); - } - if (!dimensionYamlTpls.isEmpty()) { - Map> dimensionMap = new HashMap<>(); - for (Map.Entry> entry : dimensionYamlTpls.entrySet()) { - dimensionMap.put(entry.getKey(), getDimensions(entry.getValue())); - } - semanticModel.setDimensionMap(dimensionMap); - } - if (!metricYamlTpls.isEmpty()) { - semanticModel.setMetrics(getMetrics(metricYamlTpls)); - } - return semanticModel; - } - - //private Map semanticSchemaMap = new HashMap<>(); - @Override - public SemanticModel get(String rootPath) throws Exception { - rootPath = formatKey(rootPath); - SemanticModel schema = loadingCache.get(rootPath); - if (schema == null) { - return null; - } - return schema; - } - public static List getMetrics(final List t) { return getMetricsByMetricYamlTpl(t); } - public static List getDimensions(final List t) { return getDimension(t); } - public static DataSource getDatasource(final DatasourceYamlTpl d) { DataSource datasource = new DataSource(); datasource.setSqlQuery(d.getSqlQuery()); @@ -197,7 +150,6 @@ public class SemanticSchemaManagerImpl implements SemanticSchemaManager { return identifies; } - public static void update(SemanticSchema schema, List metric) throws Exception { if (schema != null) { updateMetric(metric, schema.getMetrics()); @@ -273,6 +225,51 @@ public class SemanticSchemaManagerImpl implements SemanticSchemaManager { return key; } + @Override + public SemanticModel reload(String rootPath) { + SemanticModel semanticModel = new SemanticModel(); + semanticModel.setRootPath(rootPath); + Map domainFullPathMap = domainService.getDomainFullPath(); + log.info("domainFullPathMap {}", domainFullPathMap); + Set domainIds = domainFullPathMap.entrySet().stream().filter(e -> e.getValue().startsWith(rootPath)) + .map(e -> e.getKey()).collect(Collectors.toSet()); + if (domainIds.isEmpty()) { + log.error("get domainId empty {}", rootPath); + return semanticModel; + } + Map> dimensionYamlTpls = new HashMap<>(); + List datasourceYamlTpls = new ArrayList<>(); + List metricYamlTpls = new ArrayList<>(); + datasourceService.getModelYamlTplByDomainIds(domainIds, dimensionYamlTpls, datasourceYamlTpls, metricYamlTpls); + if (!datasourceYamlTpls.isEmpty()) { + Map dataSourceMap = datasourceYamlTpls.stream().map(d -> getDatasource(d)) + .collect(Collectors.toMap(DataSource::getName, item -> item)); + semanticModel.setDatasourceMap(dataSourceMap); + } + if (!dimensionYamlTpls.isEmpty()) { + Map> dimensionMap = new HashMap<>(); + for (Map.Entry> entry : dimensionYamlTpls.entrySet()) { + dimensionMap.put(entry.getKey(), getDimensions(entry.getValue())); + } + semanticModel.setDimensionMap(dimensionMap); + } + if (!metricYamlTpls.isEmpty()) { + semanticModel.setMetrics(getMetrics(metricYamlTpls)); + } + return semanticModel; + } + + //private Map semanticSchemaMap = new HashMap<>(); + @Override + public SemanticModel get(String rootPath) throws Exception { + rootPath = formatKey(rootPath); + SemanticModel schema = loadingCache.get(rootPath); + if (schema == null) { + return null; + } + return schema; + } + @Configuration @EnableCaching public class GuavaCacheConfig { diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/QueryService.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/QueryService.java index 57639f440..8415d6940 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/QueryService.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/QueryService.java @@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletRequest; public interface QueryService { - Object queryBySql(QuerySqlReq querySqlCmd) throws Exception; + Object queryBySql(QuerySqlReq querySqlCmd, User user) throws Exception; QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructCmd, User user) throws Exception; diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/Configuration.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/Configuration.java index 8b42e5ca1..6582a78e2 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/Configuration.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/Configuration.java @@ -27,6 +27,11 @@ public class Configuration { public static RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT); public static SqlOperatorTable operatorTable = SqlStdOperatorTable.instance(); public static CalciteConnectionConfig config = new CalciteConnectionConfigImpl(configProperties); + public static SqlValidator.Config validatorConfig = SqlValidator.Config.DEFAULT + .withLenientOperatorLookup(config.lenientOperatorLookup()) + .withSqlConformance(SemanticSqlDialect.DEFAULT.getConformance()) + .withDefaultNullCollation(config.defaultNullCollation()) + .withIdentifierExpansion(true); static { configProperties.put(CalciteConnectionProperty.CASE_SENSITIVE.camelName(), Boolean.TRUE.toString()); @@ -34,12 +39,6 @@ public class Configuration { configProperties.put(CalciteConnectionProperty.QUOTED_CASING.camelName(), Casing.TO_LOWER.toString()); } - public static SqlValidator.Config validatorConfig = SqlValidator.Config.DEFAULT - .withLenientOperatorLookup(config.lenientOperatorLookup()) - .withSqlConformance(SemanticSqlDialect.DEFAULT.getConformance()) - .withDefaultNullCollation(config.defaultNullCollation()) - .withIdentifierExpansion(true); - public static SqlParser.Config getParserConfig() { CalciteConnectionConfig config = new CalciteConnectionConfigImpl(configProperties); SqlParser.ConfigBuilder parserConfig = SqlParser.configBuilder(); diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/Renderer.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/Renderer.java index 70efc72a7..52f12b384 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/Renderer.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/Renderer.java @@ -25,21 +25,6 @@ public abstract class Renderer { protected TableView tableView = new TableView(); - public void setTable(SqlNode table) { - tableView.setTable(table); - } - - public SqlNode builder() { - return tableView.build(); - } - - public SqlNode builderAs(String alias) throws Exception { - return SemanticNode.buildAs(alias, tableView.build()); - } - - public abstract void render(MetricReq metricCommand, List dataSources, SqlValidatorScope scope, - SemanticSchema schema, boolean nonAgg) throws Exception; - public static Optional getDimensionByName(String name, DataSource datasource) { return datasource.getDimensions().stream().filter(d -> d.getName().equalsIgnoreCase(name)).findFirst(); } @@ -58,7 +43,6 @@ public abstract class Renderer { return datasource.getIdentifiers().stream().filter(i -> i.getName().equalsIgnoreCase(name)).findFirst(); } - public static MetricNode buildMetricNode(String metric, DataSource datasource, SqlValidatorScope scope, SemanticSchema schema, boolean nonAgg, String alias) throws Exception { Optional metricOpt = getMetricByName(metric, schema); @@ -105,4 +89,19 @@ public abstract class Renderer { Set tmp = new HashSet<>(list); return tmp.stream().collect(Collectors.toList()); } + + public void setTable(SqlNode table) { + tableView.setTable(table); + } + + public SqlNode builder() { + return tableView.build(); + } + + public SqlNode builderAs(String alias) throws Exception { + return SemanticNode.buildAs(alias, tableView.build()); + } + + public abstract void render(MetricReq metricCommand, List dataSources, SqlValidatorScope scope, + SemanticSchema schema, boolean nonAgg) throws Exception; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/AggFunctionNode.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/AggFunctionNode.java index ae1007be3..5b52f1f95 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/AggFunctionNode.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/AggFunctionNode.java @@ -5,6 +5,13 @@ import org.apache.calcite.sql.validate.SqlValidatorScope; public class AggFunctionNode extends SemanticNode { + public static SqlNode build(String agg, String name, SqlValidatorScope scope) throws Exception { + if (AggFunction.COUNT_DISTINCT.name().equalsIgnoreCase(agg)) { + return parse(AggFunction.COUNT.name() + " ( " + AggFunction.DISTINCT.name() + " " + name + " ) ", scope); + } + return parse(agg + " ( " + name + " ) ", scope); + } + public static enum AggFunction { AVG, COUNT_DISTINCT, @@ -15,12 +22,5 @@ public class AggFunctionNode extends SemanticNode { DISTINCT } - public static SqlNode build(String agg, String name, SqlValidatorScope scope) throws Exception { - if (AggFunction.COUNT_DISTINCT.name().equalsIgnoreCase(agg)) { - return parse(AggFunction.COUNT.name() + " ( " + AggFunction.DISTINCT.name() + " " + name + " ) ", scope); - } - return parse(agg + " ( " + name + " ) ", scope); - } - } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/SemanticNode.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/SemanticNode.java index 51fcc05d4..258105a46 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/SemanticNode.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/node/SemanticNode.java @@ -24,10 +24,6 @@ import org.apache.commons.lang3.StringUtils; public abstract class SemanticNode { - public void accept(Optimization optimization) { - optimization.visit(this); - } - public static SqlNode parse(String expression, SqlValidatorScope scope) throws Exception { SqlParser sqlParser = SqlParser.create(expression, Configuration.getParserConfig()); SqlNode sqlNode = sqlParser.parseExpression(); @@ -77,7 +73,6 @@ public abstract class SemanticNode { return sqlNode instanceof SqlIdentifier; } - public static SqlNode getAlias(SqlNode sqlNode, SqlValidatorScope scope) throws Exception { if (sqlNode instanceof SqlBasicCall) { SqlBasicCall sqlBasicCall = (SqlBasicCall) sqlNode; @@ -117,5 +112,9 @@ public abstract class SemanticNode { return sqlNode; } + public void accept(Optimization optimization) { + optimization.visit(this); + } + } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/JoinRender.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/JoinRender.java index 15bec0241..26ac16d66 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/JoinRender.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/JoinRender.java @@ -90,7 +90,7 @@ public class JoinRender extends Renderer { fieldWhere.add(identify.getName()); } } - TableView tableView = SourceRender.renderOne(false, "", fieldWhere, queryMetrics, queryDimension, + TableView tableView = SourceRender.renderOne("", fieldWhere, queryMetrics, queryDimension, metricCommand.getWhere(), dataSources.get(i), scope, schema, true); log.info("tableView {}", tableView.getTable().toString()); String alias = Constants.JOIN_TABLE_PREFIX + dataSource.getName(); diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/SourceRender.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/SourceRender.java index b53579cb1..eaaffbfa1 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/SourceRender.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/convertor/sql/render/SourceRender.java @@ -33,30 +33,7 @@ import org.springframework.util.CollectionUtils; @Slf4j public class SourceRender extends Renderer { - public void render(MetricReq metricCommand, List dataSources, SqlValidatorScope scope, - SemanticSchema schema, boolean nonAgg) throws Exception { - String queryWhere = metricCommand.getWhere(); - Set whereFields = new HashSet<>(); - List fieldWhere = new ArrayList<>(); - if (queryWhere != null && !queryWhere.isEmpty()) { - SqlNode sqlNode = SemanticNode.parse(queryWhere, scope); - FilterNode.getFilterField(sqlNode, whereFields); - fieldWhere = whereFields.stream().collect(Collectors.toList()); - } - if (dataSources.size() == 1) { - DataSource dataSource = dataSources.get(0); - super.tableView = renderOne(false, "", fieldWhere, metricCommand.getMetrics(), - metricCommand.getDimensions(), - metricCommand.getWhere(), dataSource, scope, schema, nonAgg); - return; - } - JoinRender joinRender = new JoinRender(); - joinRender.render(metricCommand, dataSources, scope, schema, nonAgg); - super.tableView = joinRender.getTableView(); - } - - - public static TableView renderOne(boolean addWhere, String alias, List fieldWhere, + public static TableView renderOne(String alias, List fieldWhere, List reqMetrics, List reqDimensions, String queryWhere, DataSource datasource, SqlValidatorScope scope, SemanticSchema schema, boolean nonAgg) throws Exception { @@ -65,10 +42,10 @@ public class SourceRender extends Renderer { List queryMetrics = new ArrayList<>(reqMetrics); List queryDimensions = new ArrayList<>(reqDimensions); if (!fieldWhere.isEmpty()) { - SqlNode sqlNode = SemanticNode.parse(queryWhere, scope); - if (addWhere) { - output.getFilter().add(sqlNode); - } +// SqlNode sqlNode = SemanticNode.parse(queryWhere, scope); +// if (addWhere) { +// output.getFilter().add(sqlNode); +// } Set dimensions = new HashSet<>(); Set metrics = new HashSet<>(); whereDimMetric(fieldWhere, queryMetrics, queryDimensions, datasource, schema, dimensions, metrics); @@ -104,7 +81,6 @@ public class SourceRender extends Renderer { return output; } - private static void buildDimension(String alias, String dimension, DataSource datasource, SemanticSchema schema, boolean nonAgg, TableView dataSet, TableView output, SqlValidatorScope scope) throws Exception { List dimensionList = schema.getDimension().get(datasource.getName()); @@ -160,7 +136,6 @@ public class SourceRender extends Renderer { } } - private static boolean isWhereHasMetric(List fields, DataSource datasource) { Long metricNum = datasource.getMeasures().stream().filter(m -> fields.contains(m.getName().toLowerCase())) .count(); @@ -227,7 +202,6 @@ public class SourceRender extends Renderer { //getWhere(outputSet,fields,queryMetrics,queryDimensions,datasource,scope,schema); } - public static void whereDimMetric(List fields, List queryMetrics, List queryDimensions, DataSource datasource, SemanticSchema schema, Set dimensions, Set metrics) { @@ -291,7 +265,6 @@ public class SourceRender extends Renderer { return false; } - private static void expandWhere(MetricReq metricCommand, TableView tableView, SqlValidatorScope scope) throws Exception { if (metricCommand.getWhere() != null && !metricCommand.getWhere().isEmpty()) { @@ -303,5 +276,27 @@ public class SourceRender extends Renderer { } } + public void render(MetricReq metricCommand, List dataSources, SqlValidatorScope scope, + SemanticSchema schema, boolean nonAgg) throws Exception { + String queryWhere = metricCommand.getWhere(); + Set whereFields = new HashSet<>(); + List fieldWhere = new ArrayList<>(); + if (queryWhere != null && !queryWhere.isEmpty()) { + SqlNode sqlNode = SemanticNode.parse(queryWhere, scope); + FilterNode.getFilterField(sqlNode, whereFields); + fieldWhere = whereFields.stream().collect(Collectors.toList()); + } + if (dataSources.size() == 1) { + DataSource dataSource = dataSources.get(0); + super.tableView = renderOne("", fieldWhere, metricCommand.getMetrics(), + metricCommand.getDimensions(), + metricCommand.getWhere(), dataSource, scope, schema, nonAgg); + return; + } + JoinRender joinRender = new JoinRender(); + joinRender.render(metricCommand, dataSources, scope, schema, nonAgg); + super.tableView = joinRender.getTableView(); + } + } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Dimension.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Dimension.java index 49d553733..a725237a0 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Dimension.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Dimension.java @@ -9,18 +9,13 @@ import lombok.Data; public class Dimension implements SemanticItem { String name; + private String owners; + private String type; + private String expr; + private DimensionTimeTypeParams dimensionTimeTypeParams; @Override public String getName() { return name; } - - - private String owners; - - private String type; - - private String expr; - - private DimensionTimeTypeParams dimensionTimeTypeParams; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Metric.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Metric.java index 9ed6c2f2c..a45d5e2e3 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Metric.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/Metric.java @@ -10,16 +10,12 @@ import lombok.Data; public class Metric implements SemanticItem { private String name; + private List owners; + private String type; + private MetricTypeParams metricTypeParams; @Override public String getName() { return name; } - - - private List owners; - - private String type; - - private MetricTypeParams metricTypeParams; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/SemanticModel.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/SemanticModel.java index e1f9f71cc..cfa886bf7 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/SemanticModel.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/dsl/SemanticModel.java @@ -8,6 +8,7 @@ import lombok.Data; @Data public class SemanticModel { + private String rootPath; private List metrics = new ArrayList<>(); private Map datasourceMap = new HashMap<>(); diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/DataSourceTable.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/DataSourceTable.java index b22d87948..f7c070b6e 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/DataSourceTable.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/DataSourceTable.java @@ -39,11 +39,14 @@ public class DataSourceTable extends AbstractTable implements ScannableTable, Tr this.statistic = statistic; } + public static Builder newBuilder(String tableName) { + return new Builder(tableName); + } + public String getTableName() { return tableName; } - @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { if (rowType == null) { @@ -71,11 +74,6 @@ public class DataSourceTable extends AbstractTable implements ScannableTable, Tr throw new UnsupportedOperationException("Not implemented"); } - public static Builder newBuilder(String tableName) { - return new Builder(tableName); - } - - public RelNode toRel(RelOptTable.ToRelContext toRelContext, RelOptTable relOptTable) { List hint = new ArrayList<>(); return new LogicalTableScan(toRelContext.getCluster(), toRelContext.getCluster().traitSet(), hint, relOptTable); diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSchema.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSchema.java index 441f3c880..df6c79a1a 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSchema.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSchema.java @@ -26,6 +26,10 @@ public class SemanticSchema extends AbstractSchema { this.tableMap = tableMap; } + public static Builder newBuilder(String rootPath) { + return new Builder(rootPath); + } + public String getRootPath() { return rootPath; } @@ -40,27 +44,22 @@ public class SemanticSchema extends AbstractSchema { return this; } - public static Builder newBuilder(String rootPath) { - return new Builder(rootPath); + public Map getDatasource() { + return semanticModel.getDatasourceMap(); } - public void setDatasource(Map datasource) { semanticModel.setDatasourceMap(datasource); } - public void setDimension(Map> dimensions) { - semanticModel.setDimensionMap(dimensions); - } - - public Map getDatasource() { - return semanticModel.getDatasourceMap(); - } - public Map> getDimension() { return semanticModel.getDimensionMap(); } + public void setDimension(Map> dimensions) { + semanticModel.setDimensionMap(dimensions); + } + public List getMetrics() { return semanticModel.getMetrics(); } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSqlDialect.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSqlDialect.java index 7a9cd6d40..669fb187e 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSqlDialect.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/parser/schema/SemanticSqlDialect.java @@ -12,7 +12,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class SemanticSqlDialect extends SqlDialect { - private static final SqlConformance tagTdwSqlConformance = new SemanticSqlConformance(); public static final Context DEFAULT_CONTEXT = SqlDialect.EMPTY_CONTEXT .withDatabaseProduct(DatabaseProduct.BIG_QUERY) .withLiteralQuoteString("'") @@ -22,13 +21,37 @@ public class SemanticSqlDialect extends SqlDialect { .withUnquotedCasing(Casing.UNCHANGED) .withQuotedCasing(Casing.UNCHANGED) .withCaseSensitive(false); - public static final SqlDialect DEFAULT = new SemanticSqlDialect(DEFAULT_CONTEXT); + private static final SqlConformance tagTdwSqlConformance = new SemanticSqlConformance(); public SemanticSqlDialect(Context context) { super(context); } + public static void unparseFetchUsingAnsi(SqlWriter writer, @Nullable SqlNode offset, @Nullable SqlNode fetch) { + Preconditions.checkArgument(fetch != null || offset != null); + SqlWriter.Frame fetchFrame; + writer.newlineAndIndent(); + fetchFrame = writer.startList(SqlWriter.FrameTypeEnum.OFFSET); + writer.keyword("LIMIT"); + if (offset != null) { + //writer.keyword("OFFSET"); + offset.unparse(writer, -1, -1); + //writer.keyword("ROWS"); + } + + if (fetch != null) { + //writer.newlineAndIndent(); + //fetchFrame = writer.startList(SqlWriter.FrameTypeEnum.FETCH); + writer.keyword(","); + //writer.keyword("NEXT"); + fetch.unparse(writer, -1, -1); + } + + writer.endList(fetchFrame); + + } + @Override public void quoteStringLiteralUnicode(StringBuilder buf, String val) { buf.append("'"); @@ -36,7 +59,6 @@ public class SemanticSqlDialect extends SqlDialect { buf.append("'"); } - @Override public void quoteStringLiteral(StringBuilder buf, String charsetName, String val) { buf.append(literalQuoteString); @@ -70,28 +92,4 @@ public class SemanticSqlDialect extends SqlDialect { public void unparseOffsetFetch(SqlWriter writer, @Nullable SqlNode offset, @Nullable SqlNode fetch) { unparseFetchUsingAnsi(writer, offset, fetch); } - - public static void unparseFetchUsingAnsi(SqlWriter writer, @Nullable SqlNode offset, @Nullable SqlNode fetch) { - Preconditions.checkArgument(fetch != null || offset != null); - SqlWriter.Frame fetchFrame; - writer.newlineAndIndent(); - fetchFrame = writer.startList(SqlWriter.FrameTypeEnum.OFFSET); - writer.keyword("LIMIT"); - if (offset != null) { - //writer.keyword("OFFSET"); - offset.unparse(writer, -1, -1); - //writer.keyword("ROWS"); - } - - if (fetch != null) { - //writer.newlineAndIndent(); - //fetchFrame = writer.startList(SqlWriter.FrameTypeEnum.FETCH); - writer.keyword(","); - //writer.keyword("NEXT"); - fetch.unparse(writer, -1, -1); - } - - writer.endList(fetchFrame); - - } } \ No newline at end of file diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/pojo/ParserSvrResponse.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/pojo/ParserSvrResponse.java index a187e8f2b..70ba1ca3a 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/pojo/ParserSvrResponse.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/pojo/ParserSvrResponse.java @@ -10,22 +10,22 @@ public class ParserSvrResponse { return code; } - public String getMsg() { - return msg; - } - - public T getData() { - return data; - } - public void setCode(String code) { this.code = code; } + public String getMsg() { + return msg; + } + public void setMsg(String msg) { this.msg = msg; } + public T getData() { + return data; + } + public void setData(T data) { this.data = data; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DataPermissionAOP.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DataPermissionAOP.java index a46079790..1dc02c8b8 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DataPermissionAOP.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DataPermissionAOP.java @@ -57,26 +57,20 @@ import org.springframework.util.CollectionUtils; @Slf4j public class DataPermissionAOP { - @Autowired - private QueryStructUtils queryStructUtils; - - @Autowired - private AuthService authService; - - @Autowired - private DimensionService dimensionService; - - @Autowired - private MetricService metricService; - - @Autowired - private DomainService domainService; - - @Value("${permission.data.enable:true}") - private Boolean permissionDataEnable; - private static final ObjectMapper MAPPER = new ObjectMapper().setDateFormat( new SimpleDateFormat(Constants.DAY_FORMAT)); + @Autowired + private QueryStructUtils queryStructUtils; + @Autowired + private AuthService authService; + @Autowired + private DimensionService dimensionService; + @Autowired + private MetricService metricService; + @Autowired + private DomainService domainService; + @Value("${permission.data.enable:true}") + private Boolean permissionDataEnable; @Pointcut("@annotation(com.tencent.supersonic.semantic.query.domain.annotation.DataPermission)") public void dataPermissionAOP() { @@ -179,7 +173,7 @@ public class DataPermissionAOP { } private void addPromptInfoInfo(Long domainId, QueryResultWithSchemaResp queryResultWithColumns, - AuthorizedResourceResp authorizedResource) { + AuthorizedResourceResp authorizedResource) { List filters = authorizedResource.getFilters(); if (!CollectionUtils.isEmpty(filters)) { log.debug("dimensionFilters:{}", filters); @@ -258,7 +252,7 @@ public class DataPermissionAOP { } private AuthorizedResourceResp getAuthorizedResource(User user, HttpServletRequest request, Long domainId, - Set sensitiveResReq) { + Set sensitiveResReq) { List resourceReqList = new ArrayList<>(); sensitiveResReq.stream().forEach(res -> resourceReqList.add(new AuthRes(domainId.toString(), res))); QueryAuthResReq queryAuthResReq = new QueryAuthResReq(); @@ -375,7 +369,7 @@ public class DataPermissionAOP { } private void doFilterCheckLogic(QueryStructReq queryStructCmd, Set resAuthName, - Set sensitiveResReq) { + Set sensitiveResReq) { Set resFilterSet = queryStructUtils.getFilterResNameEnExceptInternalCol(queryStructCmd); Set need2Apply = resFilterSet.stream() .filter(res -> !resAuthName.contains(res) && sensitiveResReq.contains(res)).collect(Collectors.toSet()); @@ -405,7 +399,7 @@ public class DataPermissionAOP { private AuthorizedResourceResp fetchAuthRes(HttpServletRequest request, QueryAuthResReq queryAuthResReq) { log.info("Authorization:{}", request.getHeader("Authorization")); log.info("queryAuthResReq:{}", queryAuthResReq); - return authService.queryAuthorizedResources(request, queryAuthResReq); + return authService.queryAuthorizedResources(queryAuthResReq, request); } } \ No newline at end of file diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DateUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DateUtils.java index f9bfc84e1..5030bba88 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DateUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/DateUtils.java @@ -1,14 +1,16 @@ -package com.tencent.supersonic.domain.semantic.query.domain.utils; +package com.tencent.supersonic.semantic.query.domain.utils; import static com.tencent.supersonic.common.constant.Constants.APOSTROPHE; import static com.tencent.supersonic.common.constant.Constants.COMMA; import static com.tencent.supersonic.common.constant.Constants.DAY; import static com.tencent.supersonic.common.constant.Constants.DAY_FORMAT; import static com.tencent.supersonic.common.constant.Constants.MONTH; +import static com.tencent.supersonic.common.constant.Constants.MONTH_FORMAT; +import static com.tencent.supersonic.common.constant.Constants.WEEK; import com.google.common.base.Strings; -import com.tencent.supersonic.semantic.api.core.response.ItemDateResp; import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.semantic.api.core.response.ItemDateResp; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; @@ -29,6 +31,10 @@ public class DateUtils { @Value("${query.parameter.sys.date:sys_imp_date}") private String sysDateCol; + @Value("${query.parameter.sys.month:sys_imp_month}") + private String sysDateMonthCol; + @Value("${query.parameter.sys.month:sys_imp_week}") + private String sysDateWeekCol; public Boolean recentMode(DateConf dateInfo) { if (Objects.nonNull(dateInfo) && DateConf.DateMode.RECENT_UNITS == dateInfo.getDateMode() @@ -125,6 +131,29 @@ public class DateUtils { return String.format("(%s >= '%s' and %s <= '%s')", sysDateCol, start, sysDateCol, dateDate.getEndDate()); } + public String recentMonthStr(ItemDateResp dateDate, DateConf dateInfo) { + String dateFormatStr = MONTH_FORMAT; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormatStr); + LocalDate end = LocalDate.parse(dateDate.getEndDate(), formatter); + String endStr = end.format(formatter); + Integer unit = dateInfo.getUnit() - 1; + String start = end.minusMonths(unit).format(formatter); + return String.format("(%s >= '%s' and %s <= '%s')", sysDateMonthCol, start, sysDateMonthCol, endStr); + } + + public String recentWeekStr(ItemDateResp dateDate, DateConf dateInfo) { + String dateFormatStr = dateDate.getDateFormat(); + if (Strings.isNullOrEmpty(dateFormatStr)) { + dateFormatStr = DAY_FORMAT; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormatStr); + LocalDate end = LocalDate.parse(dateDate.getEndDate(), formatter); + Integer unit = dateInfo.getUnit() - 1; + String start = end.minusDays(unit * 7).format(formatter); + return String.format("(%s >= '%s' and %s <= '%s')", sysDateWeekCol, start, sysDateWeekCol, + dateDate.getEndDate()); + } + private Long getInterval(String startDate, String endDate, String dateFormat, ChronoUnit chronoUnit) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat); try { @@ -142,6 +171,12 @@ public class DateUtils { if (DAY.equalsIgnoreCase(dateInfo.getPeriod())) { return recentDayStr(dateDate, dateInfo); } + if (MONTH.equalsIgnoreCase(dateInfo.getPeriod())) { + return recentMonthStr(dateDate, dateInfo); + } + if (WEEK.equalsIgnoreCase(dateInfo.getPeriod())) { + return recentWeekStr(dateDate, dateInfo); + } return ""; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/ParserCommandConverter.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/ParserCommandConverter.java index a117ee86e..75e166d9c 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/ParserCommandConverter.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/ParserCommandConverter.java @@ -30,12 +30,11 @@ public class ParserCommandConverter { private final ParserService parserService; private final DomainService domainService; private final CalculateConverterAgg calculateCoverterAgg; + private final DimensionService dimensionService; + private final QueryStructUtils queryStructUtils; @Value("${internal.metric.cnt.suffix:internal_cnt}") private String internalMetricNameSuffix; - private final DimensionService dimensionService; - private List calculateCoverters = new LinkedList<>(); - private final QueryStructUtils queryStructUtils; public ParserCommandConverter(ParserService parserService, DomainService domainService, @@ -78,11 +77,14 @@ public class ParserCommandConverter { sqlCommend.setLimit(queryStructCmd.getLimit()); String rootPath = domainService.getDomainFullPath(queryStructCmd.getDomainId()); sqlCommend.setRootPath(rootPath); + + // todo tmp delete // support detail query if (queryStructCmd.getNativeQuery() && CollectionUtils.isEmpty(sqlCommend.getMetrics())) { String internalMetricName = generateInternalMetricName(queryStructCmd); sqlCommend.getMetrics().add(internalMetricName); } + return sqlCommend; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryReqConverter.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryReqConverter.java new file mode 100644 index 000000000..101c278d4 --- /dev/null +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryReqConverter.java @@ -0,0 +1,72 @@ +package com.tencent.supersonic.semantic.query.domain.utils; + +import com.tencent.supersonic.common.util.calcite.SqlParseUtils; +import com.tencent.supersonic.common.util.calcite.SqlParserInfo; +import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp; +import com.tencent.supersonic.semantic.api.core.response.SqlParserResp; +import com.tencent.supersonic.semantic.api.query.pojo.MetricTable; +import com.tencent.supersonic.semantic.api.query.request.ParseSqlReq; +import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; +import com.tencent.supersonic.semantic.core.domain.DomainService; +import com.tencent.supersonic.semantic.query.domain.ParserService; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +@Component +public class QueryReqConverter { + + @Autowired + private DomainService domainService; + + @Autowired + private ParserService parserService; + + public SqlParserResp convert(QuerySqlReq databaseReq, List domainSchemas) throws Exception { + + List tables = new ArrayList<>(); + MetricTable metricTable = new MetricTable(); + String sql = databaseReq.getSql(); + SqlParserInfo sqlParseInfo = SqlParseUtils.getSqlParseInfo(sql); + + List allFields = sqlParseInfo.getAllFields(); + + if (CollectionUtils.isEmpty(domainSchemas)) { + return new SqlParserResp(); + } + + Set dimensions = domainSchemas.get(0).getDimensions().stream() + .map(entry -> entry.getBizName().toLowerCase()) + .collect(Collectors.toSet()); + dimensions.addAll(QueryStructUtils.internalCols); + + Set metrics = domainSchemas.get(0).getMetrics().stream().map(entry -> entry.getBizName().toLowerCase()) + .collect(Collectors.toSet()); + + metricTable.setMetrics(allFields.stream().filter(entry -> metrics.contains(entry.toLowerCase())) + .map(entry -> entry.toLowerCase()).collect(Collectors.toList())); + Set collect = allFields.stream().filter(entry -> dimensions.contains(entry.toLowerCase())) + .map(entry -> entry.toLowerCase()).collect(Collectors.toSet()); + for (String internalCol : QueryStructUtils.internalCols) { + if (sql.contains(internalCol)) { + collect.add(internalCol); + } + } + metricTable.setDimensions(new ArrayList<>(collect)); + metricTable.setAlias(sqlParseInfo.getTableName().toLowerCase()); + tables.add(metricTable); + + ParseSqlReq result = new ParseSqlReq(); + BeanUtils.copyProperties(databaseReq, result); + result.setRootPath(domainService.getDomainFullPath(databaseReq.getDomainId())); + result.setTables(tables); + + return parserService.physicalSql(result); + } + +} diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryStructUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryStructUtils.java index 789cb07ca..6b8c76f64 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryStructUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryStructUtils.java @@ -42,6 +42,8 @@ import org.springframework.util.CollectionUtils; @Component public class QueryStructUtils { + public static Set internalCols = new HashSet<>( + Arrays.asList("dayno", "plat_sys_var", "sys_imp_date", "sys_imp_week", "sys_imp_month")); private final DatabaseService databaseService; private final QueryUtils queryUtils; private final ParserService parserService; @@ -50,16 +52,12 @@ public class QueryStructUtils { private final DimensionService dimensionService; private final MetricService metricService; private final DatasourceService datasourceService; - private final com.tencent.supersonic.domain.semantic.query.domain.utils.DateUtils dateUtils; + private final DateUtils dateUtils; private final SqlFilterUtils sqlFilterUtils; private final CacheUtils cacheUtils; - @Value("${query.cache.enable:true}") private Boolean cacheEnable; - Set internalCols = new HashSet<>( - Arrays.asList("dayno", "plat_sys_var", "sys_imp_date", "sys_imp_week", "sys_imp_month")); - public QueryStructUtils(DatabaseService databaseService, QueryUtils queryUtils, ParserService parserService, @@ -68,7 +66,7 @@ public class QueryStructUtils { DimensionService dimensionService, MetricService metricService, DatasourceService datasourceService, - com.tencent.supersonic.domain.semantic.query.domain.utils.DateUtils dateUtils, + DateUtils dateUtils, SqlFilterUtils sqlFilterUtils, CacheUtils cacheUtils) { this.databaseService = databaseService; @@ -118,7 +116,10 @@ public class QueryStructUtils { queryUtils.checkSqlParse(sqlParser); log.info("sqlParser:{}", sqlParser); - queryUtils.handleDetail(queryStructCmd, sqlParser); + // todo tmp delete + //queryUtils.handleDetail(queryStructCmd, sqlParser); + queryUtils.handleNoMetric(queryStructCmd, sqlParser); + QueryResultWithSchemaResp queryResultWithColumns = databaseService.queryWithColumns(sqlParser); queryUtils.fillItemNameInfo(queryResultWithColumns, queryStructCmd.getDomainId()); diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryUtils.java index 3f46d14ae..a9a851ec0 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/QueryUtils.java @@ -42,6 +42,23 @@ import org.springframework.util.CollectionUtils; public class QueryUtils { private final Set patterns = new HashSet<>(); + private final MetricService metricService; + private final DimensionService dimensionService; + private final ParserCommandConverter parserCommandConverter; + public QueryUtils(MetricService metricService, + DimensionService dimensionService, + @Lazy ParserCommandConverter parserCommandConverter) { + this.metricService = metricService; + this.dimensionService = dimensionService; + this.parserCommandConverter = parserCommandConverter; + } + + private static void addSysTimeDimension(Map namePair, Map nameTypePair) { + for (TimeDimensionEnum timeDimensionEnum : TimeDimensionEnum.values()) { + namePair.put(timeDimensionEnum.getName(), "date"); + nameTypePair.put(timeDimensionEnum.getName(), "DATE"); + } + } @PostConstruct public void fillPattern() { @@ -52,19 +69,6 @@ public class QueryUtils { } } - private final MetricService metricService; - private final DimensionService dimensionService; - private final ParserCommandConverter parserCommandConverter; - - public QueryUtils(MetricService metricService, - DimensionService dimensionService, - @Lazy ParserCommandConverter parserCommandConverter) { - this.metricService = metricService; - this.dimensionService = dimensionService; - this.parserCommandConverter = parserCommandConverter; - } - - public void checkSqlParse(SqlParserResp sqlParser) { if (Strings.isNullOrEmpty(sqlParser.getSql()) || Strings.isNullOrEmpty(sqlParser.getSourceId())) { throw new RuntimeException("parse Exception: " + sqlParser.getErrMsg()); @@ -76,6 +80,23 @@ public class QueryUtils { queryStructCmd.getMetrics()); } + public SqlParserResp handleNoMetric(QueryStructReq queryStructCmd, SqlParserResp sqlParser) { + String sqlRaw = sqlParser.getSql().trim(); + if (Strings.isNullOrEmpty(sqlRaw)) { + throw new RuntimeException("sql is empty or null"); + } + log.info("before handleNoMetric, sql:{}", sqlRaw); + if (isDetailQuery(queryStructCmd)) { + if (queryStructCmd.getMetrics().size() == 0) { + String sql = String.format("select %s from ( %s ) src_no_metric", + queryStructCmd.getGroups().stream().collect(Collectors.joining(",")), sqlRaw); + sqlParser.setSql(sql); + } + } + log.info("after handleNoMetric, sql:{}", sqlParser.getSql()); + return sqlParser; + } + public SqlParserResp handleDetail(QueryStructReq queryStructCmd, SqlParserResp sqlParser) { String sqlRaw = sqlParser.getSql().trim(); if (Strings.isNullOrEmpty(sqlRaw)) { @@ -223,11 +244,4 @@ public class QueryUtils { } return map; } - - private static void addSysTimeDimension(Map namePair, Map nameTypePair) { - for (TimeDimensionEnum timeDimensionEnum : TimeDimensionEnum.values()) { - namePair.put(timeDimensionEnum.getName(), "date"); - nameTypePair.put(timeDimensionEnum.getName(), "DATE"); - } - } } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlGenerateUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlGenerateUtils.java index 3e72267d6..7dff2bc09 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlGenerateUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlGenerateUtils.java @@ -13,13 +13,6 @@ import org.springframework.util.CollectionUtils; @Slf4j public class SqlGenerateUtils { - public String getLimit(QueryStructReq queryStructCmd) { - if (queryStructCmd.getLimit() > 0) { - return " limit " + String.valueOf(queryStructCmd.getLimit()); - } - return ""; - } - public static String getUnionSelect(QueryStructReq queryStructCmd) { StringBuilder sb = new StringBuilder(); int locate = 0; @@ -41,6 +34,12 @@ public class SqlGenerateUtils { return selectSql; } + public String getLimit(QueryStructReq queryStructCmd) { + if (queryStructCmd.getLimit() > 0) { + return " limit " + String.valueOf(queryStructCmd.getLimit()); + } + return ""; + } public String getSelect(QueryStructReq queryStructCmd) { String aggStr = queryStructCmd.getAggregators().stream().map(this::getSelectField) diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlParserUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlParserUtils.java index f70af9dac..19444d578 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlParserUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/SqlParserUtils.java @@ -22,7 +22,7 @@ public class SqlParserUtils { public SqlParserResp getSqlParserWithoutCache(QueryStructReq queryStructCmd) throws Exception { log.info("stat getSqlParser without cache"); - multiSourceJoinUtils.buildJoinPrefix(queryStructCmd); + //multiSourceJoinUtils.buildJoinPrefix(queryStructCmd); SqlParserResp sqlParser = parserCommandConverter.getSqlParser(queryStructCmd); return sqlParser; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/StatUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/StatUtils.java index e1aaac99f..88fd39543 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/StatUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/domain/utils/StatUtils.java @@ -25,12 +25,11 @@ import org.springframework.stereotype.Component; @Slf4j public class StatUtils { + private static final TransmittableThreadLocal STATS = new TransmittableThreadLocal<>(); private final StatRepository statRepository; private final SqlFilterUtils sqlFilterUtils; private final ObjectMapper objectMapper = new ObjectMapper(); - private static final TransmittableThreadLocal STATS = new TransmittableThreadLocal<>(); - public StatUtils(StatRepository statRepository, SqlFilterUtils sqlFilterUtils) { diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/QueryController.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/QueryController.java index d172e0456..080bdf4de 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/QueryController.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/QueryController.java @@ -2,15 +2,15 @@ package com.tencent.supersonic.semantic.query.rest; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; -import com.tencent.supersonic.semantic.api.query.request.ItemUseReq; -import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq; -import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq; -import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; +import com.tencent.supersonic.semantic.api.core.response.SqlParserResp; +import com.tencent.supersonic.semantic.api.query.request.*; import com.tencent.supersonic.semantic.api.query.response.ItemUseResp; +import com.tencent.supersonic.semantic.query.domain.ParserService; import com.tencent.supersonic.semantic.query.domain.QueryService; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -20,17 +20,25 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/semantic/query") +@Slf4j public class QueryController { @Autowired private QueryService queryService; + @Autowired + private ParserService parserService; + @PostMapping("/sql") - public Object queryBySql(@RequestBody QuerySqlReq querySqlReq) throws Exception { - return queryService.queryBySql(querySqlReq); + public Object queryBySql(@RequestBody QuerySqlReq querySqlReq, + HttpServletRequest request, + HttpServletResponse response) throws Exception { + User user = UserHolder.findUser(request, response); + Object queryBySql = queryService.queryBySql(querySqlReq, user); + log.info("queryBySql:{},queryBySql"); + return queryBySql; } - @PostMapping("/struct") public Object queryByStruct(@RequestBody QueryStructReq queryStructReq, HttpServletRequest request, @@ -39,6 +47,14 @@ public class QueryController { return queryService.queryByStruct(queryStructReq, user, request); } + @PostMapping("/struct/parse") + public SqlParserResp parseByStruct(@RequestBody ParseSqlReq parseSqlReq, + HttpServletRequest request, + HttpServletResponse response) throws Exception { + User user = UserHolder.findUser(request, response); + return parserService.physicalSql(parseSqlReq); + } + /** * queryByMultiStruct */ @@ -61,5 +77,4 @@ public class QueryController { return queryService.getStatInfo(itemUseReq); } - } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/SchemaController.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/SchemaController.java index 535778426..58f059947 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/SchemaController.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/rest/SchemaController.java @@ -32,8 +32,8 @@ public class SchemaController { @PostMapping public List fetchDomainSchema(@RequestBody DomainSchemaFilterReq filter, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return schemaService.fetchDomainSchema(filter, user); } @@ -45,23 +45,23 @@ public class SchemaController { */ @GetMapping("/domain/list") public List getDomainList(HttpServletRequest request, - HttpServletResponse response) { + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return schemaService.getDomainListForAdmin(user); } @PostMapping("/dimension/page") public PageInfo queryDimension(@RequestBody PageDimensionReq pageDimensionCmd, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return schemaService.queryDimension(pageDimensionCmd, user); } @PostMapping("/metric/page") public PageInfo queryMetric(@RequestBody PageMetricReq pageMetricCmd, - HttpServletRequest request, - HttpServletResponse response) { + HttpServletRequest request, + HttpServletResponse response) { User user = UserHolder.findUser(request, response); return schemaService.queryMetric(pageMetricCmd, user); } diff --git a/semantic/query/src/test/java/com/tencent/supersonic/semantic/query/domain/parser/SemanticParserServiceTest.java b/semantic/query/src/test/java/com/tencent/supersonic/semantic/query/domain/parser/SemanticParserServiceTest.java index 2cb63855a..eb5fa8bd2 100644 --- a/semantic/query/src/test/java/com/tencent/supersonic/semantic/query/domain/parser/SemanticParserServiceTest.java +++ b/semantic/query/src/test/java/com/tencent/supersonic/semantic/query/domain/parser/SemanticParserServiceTest.java @@ -47,6 +47,66 @@ class SemanticParserServiceTest { return sqlParser; } + private static void addDepartment(SemanticSchema semanticSchema) { + DatasourceYamlTpl datasource = new DatasourceYamlTpl(); + datasource.setName("user_department"); + datasource.setSourceId(1L); + datasource.setSqlQuery("SELECT imp_date,user_name,department FROM s2_user_department"); + + MeasureYamlTpl measure = new MeasureYamlTpl(); + measure.setAgg("count"); + measure.setName("user_department_internal_cnt"); + measure.setCreateMetric("true"); + measure.setExpr("1"); + List measures = new ArrayList<>(); + measures.add(measure); + + datasource.setMeasures(measures); + + DimensionYamlTpl dimension = new DimensionYamlTpl(); + dimension.setName("sys_imp_date"); + dimension.setExpr("imp_date"); + dimension.setType("time"); + DimensionTimeTypeParamsTpl dimensionTimeTypeParams = new DimensionTimeTypeParamsTpl(); + dimensionTimeTypeParams.setIsPrimary("true"); + dimensionTimeTypeParams.setTimeGranularity("day"); + dimension.setTypeParams(dimensionTimeTypeParams); + List dimensions = new ArrayList<>(); + dimensions.add(dimension); + + DimensionYamlTpl dimension3 = new DimensionYamlTpl(); + dimension3.setName("sys_imp_week"); + dimension3.setExpr("to_monday(from_unixtime(unix_timestamp(imp_date), 'yyyy-MM-dd'))"); + dimension3.setType("time"); + DimensionTimeTypeParamsTpl dimensionTimeTypeParams3 = new DimensionTimeTypeParamsTpl(); + dimensionTimeTypeParams3.setIsPrimary("true"); + dimensionTimeTypeParams3.setTimeGranularity("week"); + dimension3.setTypeParams(dimensionTimeTypeParams3); + dimensions.add(dimension3); + + datasource.setDimensions(dimensions); + + List identifies = new ArrayList<>(); + IdentifyYamlTpl identify = new IdentifyYamlTpl(); + identify.setName("user_name"); + identify.setType("primary"); + identifies.add(identify); + datasource.setIdentifiers(identifies); + + semanticSchema.getDatasource().put("user_department", SemanticSchemaManagerImpl.getDatasource(datasource)); + + DimensionYamlTpl dimension1 = new DimensionYamlTpl(); + dimension1.setExpr("department"); + dimension1.setName("department"); + dimension1.setType("categorical"); + List dimensionYamlTpls = new ArrayList<>(); + dimensionYamlTpls.add(dimension1); + + semanticSchema.getDimension() + .put("user_department", SemanticSchemaManagerImpl.getDimensions(dimensionYamlTpls)); + + } + //@Test public void test() throws Exception { @@ -189,65 +249,4 @@ class SemanticParserServiceTest { } - - - private static void addDepartment(SemanticSchema semanticSchema) { - DatasourceYamlTpl datasource = new DatasourceYamlTpl(); - datasource.setName("user_department"); - datasource.setSourceId(1L); - datasource.setSqlQuery("SELECT imp_date,user_name,department FROM s2_user_department"); - - MeasureYamlTpl measure = new MeasureYamlTpl(); - measure.setAgg("count"); - measure.setName("user_department_internal_cnt"); - measure.setCreateMetric("true"); - measure.setExpr("1"); - List measures = new ArrayList<>(); - measures.add(measure); - - datasource.setMeasures(measures); - - DimensionYamlTpl dimension = new DimensionYamlTpl(); - dimension.setName("sys_imp_date"); - dimension.setExpr("imp_date"); - dimension.setType("time"); - DimensionTimeTypeParamsTpl dimensionTimeTypeParams = new DimensionTimeTypeParamsTpl(); - dimensionTimeTypeParams.setIsPrimary("true"); - dimensionTimeTypeParams.setTimeGranularity("day"); - dimension.setTypeParams(dimensionTimeTypeParams); - List dimensions = new ArrayList<>(); - dimensions.add(dimension); - - DimensionYamlTpl dimension3 = new DimensionYamlTpl(); - dimension3.setName("sys_imp_week"); - dimension3.setExpr("to_monday(from_unixtime(unix_timestamp(imp_date), 'yyyy-MM-dd'))"); - dimension3.setType("time"); - DimensionTimeTypeParamsTpl dimensionTimeTypeParams3 = new DimensionTimeTypeParamsTpl(); - dimensionTimeTypeParams3.setIsPrimary("true"); - dimensionTimeTypeParams3.setTimeGranularity("week"); - dimension3.setTypeParams(dimensionTimeTypeParams3); - dimensions.add(dimension3); - - datasource.setDimensions(dimensions); - - List identifies = new ArrayList<>(); - IdentifyYamlTpl identify = new IdentifyYamlTpl(); - identify.setName("user_name"); - identify.setType("primary"); - identifies.add(identify); - datasource.setIdentifiers(identifies); - - semanticSchema.getDatasource().put("user_department", SemanticSchemaManagerImpl.getDatasource(datasource)); - - DimensionYamlTpl dimension1 = new DimensionYamlTpl(); - dimension1.setExpr("department"); - dimension1.setName("department"); - dimension1.setType("categorical"); - List dimensionYamlTpls = new ArrayList<>(); - dimensionYamlTpls.add(dimension1); - - semanticSchema.getDimension() - .put("user_department", SemanticSchemaManagerImpl.getDimensions(dimensionYamlTpls)); - - } } \ No newline at end of file diff --git a/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs b/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs index 062443458..5f408c308 100644 --- a/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs +++ b/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs @@ -1,5 +1,5 @@ import basicConfig from './rollup.config.mjs' -import { terser } from '@rollup/plugin-terser' +import { terser } from "@rollup/plugin-terser" import replace from '@rollup/plugin-replace' const config = { diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/style.less b/webapp/packages/chat-sdk/src/components/ChatItem/style.less index 0c9be0830..842a035bd 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatItem/style.less @@ -42,7 +42,7 @@ &-typing-bubble { width: fit-content; - padding: 16px !important; + padding: 8px 16px !important; } &-text-bubble { diff --git a/webapp/packages/supersonic-fe/coding_build/build_prd.sh b/webapp/packages/supersonic-fe/coding_build/build_prd.sh index cac545a79..9912dcbab 100644 --- a/webapp/packages/supersonic-fe/coding_build/build_prd.sh +++ b/webapp/packages/supersonic-fe/coding_build/build_prd.sh @@ -6,7 +6,7 @@ if [ $? -ne 0 ]; then exit 1 fi -npm run build +npm run build:inner if [ $? -ne 0 ]; then echo "build failed" exit 1 diff --git a/webapp/packages/supersonic-fe/config/envConfig.ts b/webapp/packages/supersonic-fe/config/envConfig.ts index d28529eca..3ef1ecb6b 100644 --- a/webapp/packages/supersonic-fe/config/envConfig.ts +++ b/webapp/packages/supersonic-fe/config/envConfig.ts @@ -1,2 +1,4 @@ -const ENV_CONFIG = {}; +const ENV_CONFIG = { + tmeAvatarUrl: 'http://tpp.tmeoa.com/photo/48/', +}; export default ENV_CONFIG; diff --git a/webapp/packages/supersonic-fe/src/app.tsx b/webapp/packages/supersonic-fe/src/app.tsx index 7954d1969..2ccfb6ed9 100644 --- a/webapp/packages/supersonic-fe/src/app.tsx +++ b/webapp/packages/supersonic-fe/src/app.tsx @@ -5,12 +5,18 @@ import { history } from 'umi'; import type { RunTimeLayoutConfig } from 'umi'; import RightContent from '@/components/RightContent'; import S2Icon, { ICON } from '@/components/S2Icon'; +import qs from 'qs'; import { queryCurrentUser } from './services/user'; +import { queryToken } from './services/login'; import defaultSettings from '../config/defaultSettings'; import settings from '../config/themeSettings'; +import { deleteUrlQuery } from './utils/utils'; +import { AUTH_TOKEN_KEY, FROM_URL_KEY } from '@/common/constants'; export { request } from './services/request'; import { ROUTE_AUTH_CODES } from '../config/routes'; +const TOKEN_KEY = AUTH_TOKEN_KEY; + const replaceRoute = '/'; const getRuningEnv = async () => { @@ -34,6 +40,25 @@ export const initialStateConfig = { ), }; +const getToken = async () => { + let { search } = window.location; + if (search.length > 0) { + search = search.slice(1); + } + const data = qs.parse(search); + if (data.code) { + try { + const fromUrl = localStorage.getItem(FROM_URL_KEY); + const res = await queryToken(data.code as string); + localStorage.setItem(TOKEN_KEY, res.payload); + const newUrl = deleteUrlQuery(window.location.href, 'code'); + window.location.href = fromUrl || newUrl; + } catch (err) { + console.log(err); + } + } +}; + const getAuthCodes = () => { const { RUN_TYPE, APP_TARGET } = process.env; if (RUN_TYPE === 'local') { @@ -64,6 +89,12 @@ export async function getInitialState(): Promise<{ } catch (error) {} return undefined; }; + const { query } = history.location as any; + const currentToken = query[TOKEN_KEY] || localStorage.getItem(TOKEN_KEY); + + if (window.location.host.includes('tmeoa') && !currentToken) { + await getToken(); + } const currentUser = await fetchUserInfo(); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx index e05536833..66c62fe16 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx @@ -1,13 +1,11 @@ -import { Tabs } from 'antd'; +import { Tabs, Popover } from 'antd'; import React, { useEffect, useState } from 'react'; import { connect, Helmet } from 'umi'; import ProjectListTree from './components/ProjectList'; -import EntitySection from './components/Entity/EntitySection'; import styles from './components/style.less'; import type { StateType } from './model'; -import { RightOutlined, LeftOutlined } from '@ant-design/icons'; -import SplitPane from 'react-split-pane'; -import Pane from 'react-split-pane/lib/Pane'; +import { DownOutlined } from '@ant-design/icons'; +import EntitySection from './components/Entity/EntitySection'; import type { Dispatch } from 'umi'; const { TabPane } = Tabs; @@ -17,21 +15,14 @@ type Props = { dispatch: Dispatch; }; -const DEFAULT_LEFT_SIZE = '300px'; - const ChatSetting: React.FC = ({ domainManger, dispatch }) => { window.RUNNING_ENV = 'chat'; - const [collapsed, setCollapsed] = useState(false); - const [leftSize, setLeftSize] = useState(''); const { selectDomainId, selectDomainName } = domainManger; - useEffect(() => { - const semanticLeftCollapsed = localStorage.getItem('semanticLeftCollapsed'); - const semanticLeftSize = - semanticLeftCollapsed === 'true' ? '0px' : localStorage.getItem('semanticLeftSize'); - setCollapsed(semanticLeftCollapsed === 'true'); - setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE); - }, []); + const [open, setOpen] = useState(false); + const handleOpenChange = (newOpen: boolean) => { + setOpen(newOpen); + }; useEffect(() => { if (selectDomainId) { dispatch({ @@ -49,64 +40,54 @@ const ChatSetting: React.FC = ({ domainManger, dispatch }) => { } }, [selectDomainId]); - const onCollapse = () => { - const collapsedValue = !collapsed; - setCollapsed(collapsedValue); - localStorage.setItem('semanticLeftCollapsed', String(collapsedValue)); - const semanticLeftSize = collapsedValue ? '0px' : localStorage.getItem('semanticLeftSize'); - const sizeValue = parseInt(semanticLeftSize || '0'); - if (!collapsedValue && sizeValue <= 10) { - setLeftSize(DEFAULT_LEFT_SIZE); - localStorage.setItem('semanticLeftSize', DEFAULT_LEFT_SIZE); - } else { - setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE); - } - }; - - useEffect(() => { - const width = document.getElementById('tab'); - const switchWarpper: any = document.getElementById('switch'); - if (width && switchWarpper) { - switchWarpper.style.width = width.offsetWidth * 0.77 + 'px'; - } - }); - return (

- { - localStorage.setItem('semanticLeftSize', size[0]); - setLeftSize(size[0]); - }} - > - -
- -
-
- -
-
- {collapsed ? : } -
-

- {selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'} -

- {selectDomainId ? ( - <> - - - - - - - ) : ( -

请选择项目

- )} -
-
+ {/* 页面改版取消侧边栏转换为popover形式后,因为popover不触发则组件不加载,需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑,在此引入但并不显示 */} +
+ +
+
+

+ { + setOpen(false); + }} + /> + } + trigger="click" + open={open} + onOpenChange={handleOpenChange} + > +
+ + {selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'} + + + + +
+
+

+ {selectDomainId ? ( + <> + + + + + + + ) : ( +

请选择项目

+ )} +
); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceBasicForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceBasicForm.tsx index b2ea88092..a887b473e 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceBasicForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceBasicForm.tsx @@ -1,19 +1,102 @@ -import React from 'react'; -import { Form, Input, Spin } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { Form, Input, Spin, Select, message } from 'antd'; import type { FormInstance } from 'antd/lib/form'; +import { getDbNames, getTables } from '../../service'; const FormItem = Form.Item; const { TextArea } = Input; type Props = { isEdit?: boolean; + dataBaseConfig: any; form: FormInstance; tableLoading?: boolean; + mode?: 'normal' | 'fast'; }; -const DataSourceBasicForm: React.FC = ({ isEdit, tableLoading = false }) => { +const DataSourceBasicForm: React.FC = ({ + isEdit, + dataBaseConfig, + tableLoading = false, + mode = 'normal', +}) => { + const [dbNameList, setDbNameList] = useState([]); + const [tableNameList, setTableNameList] = useState([]); + const [currentDbName, setCurrentDbName] = useState(''); + const [currentTableName, setCurrentTableName] = useState(''); + const queryDbNameList = async (databaseId: number) => { + const { code, data, msg } = await getDbNames(databaseId); + if (code === 200) { + const list = data?.resultList || []; + setDbNameList(list); + } else { + message.error(msg); + } + }; + const queryTableNameList = async (databaseName: string) => { + const { code, data, msg } = await getTables(dataBaseConfig.id, databaseName); + if (code === 200) { + const list = data?.resultList || []; + setTableNameList(list); + } else { + message.error(msg); + } + }; + useEffect(() => { + if (dataBaseConfig?.id) { + queryDbNameList(dataBaseConfig.id); + } + }, [dataBaseConfig]); + return ( + {mode === 'fast' && ( + <> + + + + + + + + )} + void; onSubmit?: (dataSourceInfo: any) => void; - scriptColumns: any[]; + scriptColumns?: any[] | undefined; + basicInfoFormMode?: 'normal' | 'fast'; + onDataBaseTableChange?: (tableName: string) => void; }; const { Step } = Steps; @@ -26,30 +33,40 @@ const initFormVal = { }; const DataSourceCreateForm: React.FC = ({ + domainManger, onCancel, createModalVisible, domainId, scriptColumns, - sql, + sql = '', onSubmit, dataSourceItem, + basicInfoFormMode, }) => { const isEdit = !!dataSourceItem?.id; - const [fields, setFields] = useState([]); + const [fields, setFields] = useState([]); const [currentStep, setCurrentStep] = useState(0); const [saveLoading, setSaveLoading] = useState(false); const formValRef = useRef(initFormVal as any); const [form] = Form.useForm(); - const updateFormVal = (val: SaveDataSetForm) => { + const { dataBaseConfig } = domainManger; + const updateFormVal = (val: any) => { formValRef.current = val; }; + const [fieldColumns, setFieldColumns] = useState(scriptColumns || []); + useEffect(() => { + if (scriptColumns) { + setFieldColumns(scriptColumns); + } + }, [scriptColumns]); + const forward = () => setCurrentStep(currentStep + 1); const backward = () => setCurrentStep(currentStep - 1); - const getFieldsClassify = (fieldsList: FieldItem[]) => { + const getFieldsClassify = (fieldsList: any[]) => { const classify = fieldsList.reduce( - (fieldsClassify, item: FieldItem) => { + (fieldsClassify, item: any) => { const { type, bizName, @@ -126,11 +143,13 @@ const DataSourceCreateForm: React.FC = ({ forward(); } else { setSaveLoading(true); + const { dbName, tableName } = submitForm; const queryParams = { ...submitForm, sqlQuery: sql, - databaseId: dataSourceItem.databaseId, - queryType: 'sql_query', + databaseId: dataSourceItem?.databaseId || dataBaseConfig.id, + queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query', + tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '', domainId, }; const queryDatasource = isEdit ? updateDatasource : createDatasource; @@ -149,8 +168,8 @@ const DataSourceCreateForm: React.FC = ({ } }; - const initFields = (fieldsClassifyList: any[]) => { - const columnFields: any[] = scriptColumns.map((item: any) => { + const initFields = (fieldsClassifyList: any[], columns: any[]) => { + const columnFields: any[] = columns.map((item: any) => { const { type, nameEn } = item; const oldItem = fieldsClassifyList.find((oItem) => oItem.bizName === item.nameEn) || {}; return { @@ -181,13 +200,32 @@ const DataSourceCreateForm: React.FC = ({ }); }; - const initData = () => { + const initData = async () => { + const { queryType, tableQuery } = dataSourceItem.datasourceDetail; + let tableQueryInitValue = {}; + let columns = fieldColumns; + if (queryType === 'table_query') { + const tableQueryString = tableQuery || ''; + const [dbName, tableName] = tableQueryString.split('.'); + columns = await queryTableColumnList(dbName, tableName); + tableQueryInitValue = { + dbName, + tableName, + }; + } + formatterInitData(columns, tableQueryInitValue); + }; + + const formatterInitData = (columns: any[], extendParams: Record = {}) => { const { id, name, bizName, description, datasourceDetail } = dataSourceItem as any; + const { dimensions, identifiers, measures } = datasourceDetail; const initValue = { id, name, bizName, description, + ...extendParams, + // ...tableQueryInitValue, }; const editInitFormVal = { ...formValRef.current, @@ -195,20 +233,19 @@ const DataSourceCreateForm: React.FC = ({ }; updateFormVal(editInitFormVal); form.setFieldsValue(initValue); - const { dimensions, identifiers, measures } = datasourceDetail; const formatFields = [ ...formatterDimensions(dimensions || []), ...(identifiers || []), ...formatterMeasures(measures || []), ]; - initFields(formatFields); + initFields(formatFields, columns); }; useEffect(() => { if (isEdit) { initData(); } else { - initFields([]); + initFields([], fieldColumns); } }, [dataSourceItem]); @@ -227,11 +264,42 @@ const DataSourceCreateForm: React.FC = ({ setFields(result); }; + const queryTableColumnList = async (dbName: string, tableName: string) => { + if (!dataBaseConfig?.id) { + return; + } + const { code, data, msg } = await getColumns(dataBaseConfig.id, dbName, tableName); + if (code === 200) { + const list = data?.resultList || []; + // setTableNameList(list); + const columns = list.map((item: any) => { + const { dataType, name } = item; + return { + nameEn: name, + type: dataType, + }; + }); + // setFields(columns); + initFields([], columns); + setFieldColumns(columns); + return columns; + } else { + message.error(msg); + } + }; + const renderContent = () => { if (currentStep === 1) { return ; } - return ; + return ( + + ); }; const renderFooter = () => { @@ -280,6 +348,13 @@ const DataSourceCreateForm: React.FC = ({ initialValues={{ ...formValRef.current, }} + onValuesChange={(value, values) => { + const { tableName } = value; + const { dbName } = values; + if (tableName) { + queryTableColumnList(dbName, tableName); + } + }} className={styles.form} > {renderContent()} @@ -288,4 +363,6 @@ const DataSourceCreateForm: React.FC = ({ ); }; -export default DataSourceCreateForm; +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(DataSourceCreateForm); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx index 3cac2ced9..42ec92ef4 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Button, Table, message, Tooltip, Space, Dropdown } from 'antd'; import SplitPane from 'react-split-pane'; import Pane from 'react-split-pane/lib/Pane'; +import { connect } from 'umi'; import sqlFormatter from 'sql-formatter'; import { FullscreenOutlined, @@ -15,10 +16,12 @@ import { import { isFunction } from 'lodash'; import FullScreen from '@/components/FullScreen'; import SqlEditor from '@/components/SqlEditor'; -import type { TaskResultParams, TaskResultItem, DataInstanceItem, TaskResultColumn } from '../data'; -import { excuteSql } from '../service'; -import { getDatabaseByDomainId } from '../../service'; +import type { TaskResultItem, DataInstanceItem, TaskResultColumn } from '../data'; +import { excuteSql } from '@/pages/SemanticModel/service'; +// import { getDatabaseByDomainId } from '../../service'; import DataSourceCreateForm from './DataSourceCreateForm'; +import type { Dispatch } from 'umi'; +import type { StateType } from '../../model'; import styles from '../style.less'; import 'ace-builds/src-min-noconflict/ext-searchbox'; @@ -27,7 +30,8 @@ import 'ace-builds/src-min-noconflict/theme-monokai'; import 'ace-builds/src-min-noconflict/mode-sql'; type IProps = { - oprType: 'add' | 'edit'; + domainManger: StateType; + dispatch: Dispatch; dataSourceItem: DataInstanceItem; domainId: number; onUpdateSql?: (sql: string) => void; @@ -52,6 +56,7 @@ type JdbcSourceItems = { }; const SqlDetail: React.FC = ({ + domainManger, dataSourceItem, onSubmitSuccess, domainId, @@ -59,6 +64,7 @@ const SqlDetail: React.FC = ({ onUpdateSql, onJdbcSourceChange, }) => { + const { dataBaseConfig } = domainManger; const [resultTable, setResultTable] = useState([]); const [resultTableLoading, setResultTableLoading] = useState(false); const [resultCols, setResultCols] = useState([]); @@ -111,20 +117,30 @@ const SqlDetail: React.FC = ({ // return 'ClickHouse'; // }); - const queryDatabaseConfig = async () => { - const { code, data } = await getDatabaseByDomainId(domainId); - if (code === 200) { - setJdbcSourceItems([ - { - label: data?.name, - key: data?.id, - }, - ]); - onJdbcSourceChange?.(data?.id && Number(data?.id)); - return; - } - message.error('数据库配置获取错误'); - }; + useEffect(() => { + setJdbcSourceItems([ + { + label: dataBaseConfig?.name, + key: dataBaseConfig?.id, + }, + ]); + onJdbcSourceChange?.(dataBaseConfig?.id && Number(dataBaseConfig?.id)); + }, [dataBaseConfig]); + + // const queryDatabaseConfig = async () => { + // const { code, data } = await getDatabaseByDomainId(domainId); + // if (code === 200) { + // setJdbcSourceItems([ + // { + // label: data?.name, + // key: data?.id, + // }, + // ]); + // onJdbcSourceChange?.(data?.id && Number(data?.id)); + // return; + // } + // message.error('数据库配置获取错误'); + // }; function creatCalcItem(key: string, data: string) { const line = document.createElement('div'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确 @@ -185,7 +201,7 @@ const SqlDetail: React.FC = ({ } }; - const fetchTaskResult = (params: TaskResultParams) => { + const fetchTaskResult = (params) => { setResultTable( params.resultList.map((item, index) => { return { @@ -367,7 +383,7 @@ const SqlDetail: React.FC = ({ }, [resultTable, isSqlResFullScreen]); useEffect(() => { - queryDatabaseConfig(); + // queryDatabaseConfig(); const windowHeight = window.innerHeight; let size: ScreenSize = 'small'; if (windowHeight > 1100) { @@ -527,4 +543,6 @@ const SqlDetail: React.FC = ({ ); }; -export default SqlDetail; +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(SqlDetail); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx index 2b414cfe4..57575e397 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx @@ -1,7 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { Tabs } from 'antd'; import SqlDetail from './SqlDetail'; -import type { SqlItem } from '../data'; import styles from '../style.less'; @@ -11,7 +10,6 @@ type Panes = { type: 'add' | 'edit'; scriptId?: number; sql?: string; - sqlInfo?: SqlItem; isSave?: boolean; // 暂存提示保存 }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/data.d.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/data.d.ts new file mode 100644 index 000000000..307965464 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/data.d.ts @@ -0,0 +1,16 @@ +// 数据类型 +export type DataInstanceItem = { + sourceInstanceId: number; // 数据实例id + sourceInstanceName: string; // 数据实例名 + defaultSourceId: number; // 查询表需要的默认datasource id + bindSourceId: number; +}; + +// 任务查询结果列 +export type TaskResultColumn = { + name: string; + type: string; +}; + +// 任务查询结果 +export type TaskResultItem = Record; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/service.ts index 69ec9f83a..e69de29bb 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/service.ts @@ -1,12 +0,0 @@ -import request from 'umi-request'; - -type ExcuteSqlParams = { - sql: string; - domainId: number; -}; - -// 执行脚本 -export async function excuteSql(params: ExcuteSqlParams) { - const data = { ...params }; - return request.post(`${process.env.API_BASE_URL}database/executeSql`, { data }); -} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx index 3f7467ecb..b15de9dd5 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx @@ -1,4 +1,4 @@ -import { Tabs } from 'antd'; +import { Tabs, Popover } from 'antd'; import React, { useEffect, useState } from 'react'; import { connect, Helmet } from 'umi'; import ProjectListTree from './components/ProjectList'; @@ -9,11 +9,8 @@ import PermissionSection from './components/Permission/PermissionSection'; import DatabaseSection from './components/Database/DatabaseSection'; import styles from './components/style.less'; import type { StateType } from './model'; -import { RightOutlined, LeftOutlined } from '@ant-design/icons'; +import { DownOutlined } from '@ant-design/icons'; import SemanticFlow from './SemanticFlows'; -// import SemanticGraph from './SemanticGraph'; -import SplitPane from 'react-split-pane'; -import Pane from 'react-split-pane/lib/Pane'; import type { Dispatch } from 'umi'; const { TabPane } = Tabs; @@ -23,20 +20,15 @@ type Props = { dispatch: Dispatch; }; -const DEFAULT_LEFT_SIZE = '300px'; - const DomainManger: React.FC = ({ domainManger, dispatch }) => { window.RUNNING_ENV = 'semantic'; - const [collapsed, setCollapsed] = useState(false); - const [leftSize, setLeftSize] = useState(''); const { selectDomainId, selectDomainName } = domainManger; - useEffect(() => { - const semanticLeftCollapsed = localStorage.getItem('semanticLeftCollapsed'); - const semanticLeftSize = - semanticLeftCollapsed === 'true' ? '0px' : localStorage.getItem('semanticLeftSize'); - setCollapsed(semanticLeftCollapsed === 'true'); - setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE); - }, []); + + const [open, setOpen] = useState(false); + + const handleOpenChange = (newOpen: boolean) => { + setOpen(newOpen); + }; useEffect(() => { if (selectDomainId) { @@ -52,23 +44,15 @@ const DomainManger: React.FC = ({ domainManger, dispatch }) => { domainId: selectDomainId, }, }); + dispatch({ + type: 'domainManger/queryDatabaseByDomainId', + payload: { + domainId: selectDomainId, + }, + }); } }, [selectDomainId]); - const onCollapse = () => { - const collapsedValue = !collapsed; - setCollapsed(collapsedValue); - localStorage.setItem('semanticLeftCollapsed', String(collapsedValue)); - const semanticLeftSize = collapsedValue ? '0px' : localStorage.getItem('semanticLeftSize'); - const sizeValue = parseInt(semanticLeftSize || '0'); - if (!collapsedValue && sizeValue <= 10) { - setLeftSize(DEFAULT_LEFT_SIZE); - localStorage.setItem('semanticLeftSize', DEFAULT_LEFT_SIZE); - } else { - setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE); - } - }; - useEffect(() => { const width = document.getElementById('tab'); const switchWarpper: any = document.getElementById('switch'); @@ -80,61 +64,73 @@ const DomainManger: React.FC = ({ domainManger, dispatch }) => { return (
- { - localStorage.setItem('semanticLeftSize', size[0]); - setLeftSize(size[0]); - }} - > - -
- -
-
- -
-
- {collapsed ? : } -
-

- {selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'} -

- {selectDomainId ? ( - <> - - {/* + {/* 页面改版取消侧边栏转换为popover形式后,因为popover不触发则组件不加载,需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑,在此引入但并不显示 */} +
+ +
+
+

+ { + setOpen(false); + }} + /> + } + trigger="click" + open={open} + onOpenChange={handleOpenChange} + > +
+ + {selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'} + + + + +
+
+

+ {selectDomainId ? ( + <> + + {/*
*/} - -
- -
-
- - - - - - - - - - - - - - - -
- - ) : ( -

请选择项目

- )} -
- + +
+ +
+
+ + + + + + + + + + + + + + + +
+ + ) : ( +

请选择项目

+ )} +
); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticFlows/components/XflowJsonSchemaFormDrawerForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticFlows/components/XflowJsonSchemaFormDrawerForm.tsx index 0a0487b6d..ab0c6f164 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticFlows/components/XflowJsonSchemaFormDrawerForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticFlows/components/XflowJsonSchemaFormDrawerForm.tsx @@ -6,6 +6,8 @@ import { NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE } from '../ConfigModelService' import { connect } from 'umi'; import { DATASOURCE_NODE_RENDER_ID } from '../constant'; import DataSourceRelationFormDrawer from './DataSourceRelationFormDrawer'; +import DataSourceCreateForm from '../../Datasource/components/DataSourceCreateForm'; +import ClassDataSourceTypeModal from '../../components/ClassDataSourceTypeModal'; import { GraphApi } from '../service'; import type { StateType } from '../../model'; import DataSource from '../../Datasource'; @@ -19,6 +21,7 @@ export type CreateFormProps = { const XflowJsonSchemaFormDrawerForm: React.FC = (props) => { const { domainManger } = props; + const { selectDomainId } = domainManger; const [visible, setVisible] = useState(false); const [createModalVisible, setCreateModalVisible] = useState(false); const [dataSourceItem, setDataSourceItem] = useState(); @@ -26,7 +29,8 @@ const XflowJsonSchemaFormDrawerForm: React.FC = (props) => { sourceData: {}, targetData: {}, }); - + const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); + const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); const app = useXFlowApp(); // 借用JsonSchemaForm钩子函数对元素状态进行监听 const { state, commandService, modelService } = useJsonSchemaFormModel({ @@ -53,7 +57,15 @@ const XflowJsonSchemaFormDrawerForm: React.FC = (props) => { const { renderKey, payload } = targetData as any; if (renderKey === DATASOURCE_NODE_RENDER_ID) { setDataSourceItem(payload); - setCreateModalVisible(true); + if (!payload) { + setCreateDataSourceModalOpen(true); + } else { + if (payload?.datasourceDetail?.queryType === 'table_query') { + setDataSourceModalVisible(true); + } else { + setCreateModalVisible(true); + } + } } else { const { sourceNodeData, targetNodeData } = targetData as any; setNodeDataSource({ @@ -85,6 +97,31 @@ const XflowJsonSchemaFormDrawerForm: React.FC = (props) => { }} open={visible} /> + {dataSourceModalVisible && ( + { + setDataSourceModalVisible(false); + }} + onSubmit={(dataSourceInfo: any) => { + setDataSourceModalVisible(false); + const { targetCell, targetData } = state; + targetCell?.setData({ + ...targetData, + label: dataSourceInfo.name, + payload: dataSourceInfo, + id: `dataSource-${dataSourceInfo.id}`, + }); + setDataSourceItem(undefined); + commandService.executeCommand(XFlowGraphCommands.SAVE_GRAPH_DATA.id, { + saveGraphDataService: (meta, graphData) => GraphApi.saveGraphData!(meta, graphData), + }); + }} + createModalVisible={dataSourceModalVisible} + /> + )} = (props) => { }} /> + { + { + if (type === 'fast') { + setDataSourceModalVisible(true); + } else { + setCreateModalVisible(true); + } + setCreateDataSourceModalOpen(false); + }} + /> + } ); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolTips.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolTips.tsx index 64e195327..a14cf746d 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolTips.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolTips.tsx @@ -17,7 +17,7 @@ const initTooltips = () => { outDiv.style.width = 'fit-content'; outDiv.style.height = 'fit-content'; const model = e.item.getModel(); - console.log(model, e.item, 'model'); + const { name, bizName, createdBy, updatedAt, description } = model; const list = [ { diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx index ba6d01565..b782fd25c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx @@ -106,7 +106,7 @@ const DomainManger: React.FC = ({ domainManger, domainId }) => { const { id, name } = datasource; const dataSourceId = `dataSource-${id}`; let childrenList = []; - if (type === 'metirc') { + if (type === 'metric') { childrenList = getMetricChildren(metrics, dataSourceId); } if (type === 'dimension') { @@ -247,7 +247,6 @@ const DomainManger: React.FC = ({ domainManger, domainId }) => { }, }, }); - // 我使用TreeGraph进行layout布局,采用{type: 'compactBox',direction: 'LR'}模式,如何使子节点与根节点的连线只连接到上下连接桩上 graphRef.current = new G6.TreeGraph({ container: 'semanticGraph', diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/service.ts index 69ec9f83a..e69de29bb 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/service.ts @@ -1,12 +0,0 @@ -import request from 'umi-request'; - -type ExcuteSqlParams = { - sql: string; - domainId: number; -}; - -// 执行脚本 -export async function excuteSql(params: ExcuteSqlParams) { - const data = { ...params }; - return request.post(`${process.env.API_BASE_URL}database/executeSql`, { data }); -} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx index 439db372e..23b8974a6 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx @@ -1,23 +1,31 @@ import type { ActionType, ProColumns } from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; -import { message, Button, Drawer, Space, Popconfirm } from 'antd'; -import React, { useRef, useState } from 'react'; +import { message, Button, Drawer, Space, Popconfirm, Modal, Card, Row, Col } from 'antd'; +import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons'; +import React, { useRef, useState, useEffect } from 'react'; import type { Dispatch } from 'umi'; import { connect } from 'umi'; +import DataSourceCreateForm from '../Datasource/components/DataSourceCreateForm'; +import ClassDataSourceTypeModal from './ClassDataSourceTypeModal'; import type { StateType } from '../model'; import { getDatasourceList, deleteDatasource } from '../service'; import DataSource from '../Datasource'; import moment from 'moment'; +const { Meta } = Card; type Props = { dispatch: Dispatch; domainManger: StateType; }; const ClassDataSourceTable: React.FC = ({ dispatch, domainManger }) => { - const { selectDomainId } = domainManger; + const { selectDomainId, dataBaseResultColsMap, dataBaseConfig } = domainManger; const [createModalVisible, setCreateModalVisible] = useState(false); const [dataSourceItem, setDataSourceItem] = useState(); + const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); + const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); + const [fastModeSql, setFastModeSql] = useState(''); + const [fastModeTableName, setFastModeTableName] = useState(''); const actionRef = useRef(); @@ -62,6 +70,10 @@ const ClassDataSourceTable: React.FC = ({ dispatch, domainManger }) => { key="classEditBtn" onClick={() => { setDataSourceItem(record); + if (record.datasourceDetail.queryType === 'table_query') { + setDataSourceModalVisible(true); + return; + } setCreateModalVisible(true); }} > @@ -72,12 +84,12 @@ const ClassDataSourceTable: React.FC = ({ dispatch, domainManger }) => { okText="是" cancelText="否" onConfirm={async () => { - const { code } = await deleteDatasource(record.id); + const { code, msg } = await deleteDatasource(record.id); if (code === 200) { setDataSourceItem(undefined); actionRef.current?.reload(); } else { - message.error('删除失败'); + message.error(msg); } }} > @@ -121,6 +133,20 @@ const ClassDataSourceTable: React.FC = ({ dispatch, domainManger }) => { return resData; }; + const queryDataBaseExcuteSql = (tableName: string) => { + const sql = `select * from ${tableName}`; + setFastModeSql(sql); + setFastModeTableName(tableName); + dispatch({ + type: 'domainManger/queryDataBaseExcuteSql', + payload: { + sql, + domainId: selectDomainId, + tableName, + }, + }); + }; + return ( <> = ({ dispatch, domainManger }) => { type="primary" onClick={() => { setDataSourceItem(undefined); - setCreateModalVisible(true); + setCreateDataSourceModalOpen(true); }} > 创建数据源 , ]} /> + { + { + if (type === 'fast') { + setDataSourceModalVisible(true); + } else { + setCreateModalVisible(true); + } + setCreateDataSourceModalOpen(false); + }} + /> + } + {dataSourceModalVisible && ( + { + setDataSourceModalVisible(false); + }} + onDataBaseTableChange={(tableName: string) => { + queryDataBaseExcuteSql(tableName); + }} + onSubmit={() => { + setDataSourceModalVisible(false); + setDataSourceItem(undefined); + actionRef.current?.reload(); + }} + createModalVisible={dataSourceModalVisible} + /> + )} {createModalVisible && ( void; +}; + +const ClassDataSourceTypeModal: React.FC = ({ open, onTypeChange }) => { + const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); + useEffect(() => { + setCreateDataSourceModalOpen(open); + }, [open]); + + return ( + <> + { + setCreateDataSourceModalOpen(false); + }} + footer={null} + centered + closable={false} + > + + + { + onTypeChange('fast'); + setCreateDataSourceModalOpen(false); + }} + cover={ + + } + > + + + + + { + onTypeChange('normal'); + setCreateDataSourceModalOpen(false); + }} + hoverable + style={{ height: 220 }} + cover={ + + } + > + + + + + + + ); +}; +export default ClassDataSourceTypeModal; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx index e87fbba4d..a950560a2 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx @@ -88,6 +88,10 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { dataIndex: 'name', title: '维度名称', }, + { + dataIndex: 'alias', + title: '别名', + }, { dataIndex: 'bizName', title: '字段名称', @@ -146,12 +150,12 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { okText="是" cancelText="否" onConfirm={async () => { - const { code } = await deleteDimension(record.id); + const { code, msg } = await deleteDimension(record.id); if (code === 200) { setDimensionItem(undefined); actionRef.current?.reload(); } else { - message.error('删除失败'); + message.error(msg); } }} > diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx index 89bdaa78e..6e3203d1d 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx @@ -68,6 +68,10 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { dataIndex: 'name', title: '指标名称', }, + { + dataIndex: 'alias', + title: '别名', + }, { dataIndex: 'bizName', title: '字段名称', @@ -118,12 +122,12 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { okText="是" cancelText="否" onConfirm={async () => { - const { code } = await deleteMetric(record.id); + const { code, msg } = await deleteMetric(record.id); if (code === 200) { setMetricItem(undefined); actionRef.current?.reload(); } else { - message.error('删除失败'); + message.error(msg); } }} > diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx index 4d033cfca..402e53404 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx @@ -7,29 +7,39 @@ import { formLayout } from '@/components/FormHelper/utils'; import styles from '../style.less'; type Props = { domainId: number; + dataBaseConfig: any; onSubmit: (params?: any) => void; }; const FormItem = Form.Item; const TextArea = Input.TextArea; -const DatabaseCreateForm: ForwardRefRenderFunction = ({ domainId }, ref) => { +const DatabaseCreateForm: ForwardRefRenderFunction = ( + { domainId, dataBaseConfig, onSubmit }, + ref, +) => { const [form] = Form.useForm(); const [selectedDbType, setSelectedDbType] = useState('h2'); - const queryDatabaseConfig = async () => { - const { code, data } = await getDatabaseByDomainId(domainId); - if (code === 200) { - form.setFieldsValue({ ...data }); - setSelectedDbType(data?.type); - return; - } - message.error('数据库配置获取错误'); - }; + // const queryDatabaseConfig = async () => { + // const { code, data } = await getDatabaseByDomainId(domainId); + // if (code === 200) { + // form.setFieldsValue({ ...data }); + // setSelectedDbType(data?.type); + // return; + // } + // message.error('数据库配置获取错误'); + // }; useEffect(() => { form.resetFields(); - queryDatabaseConfig(); - }, [domainId]); + form.setFieldsValue({ ...dataBaseConfig }); + setSelectedDbType(dataBaseConfig?.type); + }, [dataBaseConfig]); + + // useEffect(() => { + // form.resetFields(); + // // queryDatabaseConfig(); + // }, [domainId]); const getFormValidateFields = async () => { return await form.validateFields(); @@ -48,6 +58,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction = ({ domainId }, if (code === 200) { message.success('保存成功'); + onSubmit?.(); return; } message.error(msg); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseSection.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseSection.tsx index 1b3ca276e..adf64efe5 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseSection.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseSection.tsx @@ -11,8 +11,8 @@ type Props = { domainManger: StateType; }; -const DatabaseSection: React.FC = ({ domainManger }) => { - const { selectDomainId } = domainManger; +const DatabaseSection: React.FC = ({ domainManger, dispatch }) => { + const { selectDomainId, dataBaseConfig } = domainManger; const entityCreateRef = useRef({}); @@ -22,8 +22,16 @@ const DatabaseSection: React.FC = ({ domainManger }) => { {}} + onSubmit={() => { + dispatch({ + type: 'domainManger/queryDatabaseByDomainId', + payload: { + domainId: selectDomainId, + }, + }); + }} /> diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionInfoModal.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionInfoModal.tsx index 37148d11a..9a7a4430c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionInfoModal.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionInfoModal.tsx @@ -85,6 +85,7 @@ const DimensionInfoModal: React.FC = ({ >
+ = ({ ))} + + + = ({ onSubmit, }) => { const [sourceList, setSourceList] = useState([]); - const [visibilityData, setVisibilityData] = useState({}); const [selectedKeyList, setSelectedKeyList] = useState([]); const settingTypeConfig = settingType === 'dimension' ? dimensionConfig : metricConfig; useEffect(() => { @@ -47,27 +48,12 @@ const DimensionMetricVisibleModal: React.FC = ({ setSourceList(list); }, [settingSourceList]); - const queryThemeListData: any = async () => { - const { code, data } = await getDomainExtendDetailConfig({ - domainId, - }); - if (code === 200) { - setVisibilityData(data.visibility); - return; - } - message.error('获取可见信息失败'); - }; - useEffect(() => { - queryThemeListData(); - }, []); - - useEffect(() => { - setSelectedKeyList(visibilityData?.[settingTypeConfig.visibleIdListKey] || []); - }, [visibilityData]); + setSelectedKeyList(themeData.visibility?.[settingTypeConfig.visibleIdListKey] || []); + }, [themeData]); const saveEntity = async () => { - const { id } = themeData; + const { id, entity } = themeData; let saveDomainExtendQuery = addDomainExtend; if (id) { saveDomainExtendQuery = editDomainExtend; @@ -79,6 +65,8 @@ const DimensionMetricVisibleModal: React.FC = ({ } return list; }, []); + const entityParams = exChangeRichEntityListToIds(entity); + themeData.entity = entityParams; const params = { ...themeData, visibility: themeData.visibility || {}, diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx index 07f85230b..bfd7d2d29 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx @@ -2,15 +2,17 @@ import { useEffect, useState, forwardRef, useImperativeHandle } from 'react'; import type { ForwardRefRenderFunction } from 'react'; import { message, Form, Input, Select, Button } from 'antd'; import { addDomainExtend, editDomainExtend } from '../../service'; +import type { ISemantic, IChatConfig } from '../../data'; import { formLayout } from '@/components/FormHelper/utils'; - +import { exChangeRichEntityListToIds } from './utils'; import styles from '../style.less'; + type Props = { - entityData: any; - metricList: any[]; - dimensionList: any[]; + entityData: IChatConfig.IEntity; + metricList: ISemantic.IMetricList; + dimensionList: ISemantic.IDimensionList; domainId: number; - onSubmit: (params?: any) => void; + onSubmit: () => void; }; const FormItem = Form.Item; @@ -34,33 +36,19 @@ const EntityCreateForm: ForwardRefRenderFunction = ( if (Object.keys(entityData).length === 0) { return; } - const { detailData = {}, names = [] } = entityData; - if (!detailData.dimensionIds) { - entityData = { - ...entityData, - detailData: { - ...detailData, - dimensionIds: [], - }, - }; - } - if (!detailData.metricIds) { - entityData = { - ...entityData, - detailData: { - ...detailData, - metricIds: [], - }, - }; - } - form.setFieldsValue({ ...entityData, name: names.join(',') }); + const names = entityData.names || []; + const formatEntityData = exChangeRichEntityListToIds(entityData); + form.setFieldsValue({ + ...formatEntityData, + name: names.join(','), + }); }, [entityData]); useImperativeHandle(ref, () => ({ getFormValidateFields, })); useEffect(() => { - const metricOption = metricList.map((item: any) => { + const metricOption = metricList.map((item: ISemantic.IMetricItem) => { return { label: item.name, value: item.id, @@ -70,7 +58,7 @@ const EntityCreateForm: ForwardRefRenderFunction = ( }, [metricList]); useEffect(() => { - const dimensionEnum = dimensionList.map((item: any) => { + const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => { return { label: item.name, value: item.id, @@ -128,6 +116,13 @@ const EntityCreateForm: ForwardRefRenderFunction = ( mode="multiple" allowClear style={{ width: '100%' }} + filterOption={(inputValue: string, item: any) => { + const { label } = item; + if (label.includes(inputValue)) { + return true; + } + return false; + }} placeholder="请选择主体标识" options={dimensionListOptions} /> @@ -137,6 +132,13 @@ const EntityCreateForm: ForwardRefRenderFunction = ( mode="multiple" allowClear style={{ width: '100%' }} + filterOption={(inputValue: string, item: any) => { + const { label } = item; + if (label.includes(inputValue)) { + return true; + } + return false; + }} placeholder="请选择展示维度信息" options={dimensionListOptions} /> @@ -146,6 +148,13 @@ const EntityCreateForm: ForwardRefRenderFunction = ( mode="multiple" allowClear style={{ width: '100%' }} + filterOption={(inputValue: string, item: any) => { + const { label } = item; + if (label.includes(inputValue)) { + return true; + } + return false; + }} placeholder="请选择展示指标信息" options={metricListOptions} /> diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx index 981d94fb1..1c0054b39 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx @@ -3,10 +3,11 @@ import React, { useState, useEffect, useRef } from 'react'; import type { Dispatch } from 'umi'; import { connect } from 'umi'; import type { StateType } from '../../model'; -import { getDomainExtendConfig } from '../../service'; +import { getDomainExtendConfig, getDomainExtendDetailConfig } from '../../service'; import ProCard from '@ant-design/pro-card'; import EntityCreateForm from './EntityCreateForm'; import MetricSettingForm from './MetricSettingForm'; +import type { IChatConfig } from '../../data'; import DimensionMetricVisibleForm from './DimensionMetricVisibleForm'; type Props = { @@ -17,18 +18,21 @@ type Props = { const EntitySection: React.FC = ({ domainManger, dispatch }) => { const { selectDomainId, dimensionList, metricList } = domainManger; - const [entityData, setEntityData] = useState({}); + const [entityData, setEntityData] = useState({} as IChatConfig.IEntity); const [themeData, setThemeData] = useState({}); const entityCreateRef = useRef({}); const queryThemeListData: any = async () => { - const { code, data } = await getDomainExtendConfig({ + const { code, data } = await getDomainExtendDetailConfig({ domainId: selectDomainId, }); + // getDomainExtendConfig({ + // domainId: selectDomainId, + // }); if (code === 200) { - const target = data?.[0] || {}; + const target = data; if (target) { setThemeData(target); setEntityData({ @@ -75,7 +79,14 @@ const EntitySection: React.FC = ({ domainManger, dispatch }) => { { + const blackMetricIdList = themeData.visibility?.blackMetricIdList; + if (Array.isArray(blackMetricIdList)) { + return !blackMetricIdList.includes(item.id); + } + return false; + })} onSubmit={() => { queryThemeListData(); }} @@ -86,8 +97,22 @@ const EntitySection: React.FC = ({ domainManger, dispatch }) => { ref={entityCreateRef} domainId={Number(selectDomainId)} entityData={entityData} - metricList={metricList} - dimensionList={dimensionList} + // metricList={metricList} + metricList={metricList.filter((item) => { + const blackMetricIdList = themeData.visibility?.blackMetricIdList; + if (Array.isArray(blackMetricIdList)) { + return !blackMetricIdList.includes(item.id); + } + return false; + })} + // dimensionList={dimensionList} + dimensionList={dimensionList.filter((item) => { + const blackDimensionList = themeData.visibility?.blackDimIdList; + if (Array.isArray(blackDimensionList)) { + return !blackDimensionList.includes(item.id); + } + return false; + })} onSubmit={() => { queryThemeListData(); }} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/MetricSettingForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/MetricSettingForm.tsx index 5e74f2ede..643b0dde9 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/MetricSettingForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/MetricSettingForm.tsx @@ -17,7 +17,7 @@ const FormItem = Form.Item; const Option = Select.Option; const MetricSettingForm: ForwardRefRenderFunction = ( - { metricList, domainId, themeData: uniqueMetricData }, + { metricList, domainId, themeData: uniqueMetricData, onSubmit }, ref, ) => { const [form] = Form.useForm(); @@ -82,6 +82,7 @@ const MetricSettingForm: ForwardRefRenderFunction = ( if (code === 200) { form.setFieldValue('id', data); + onSubmit?.(); message.success('保存成功'); return; } @@ -116,6 +117,13 @@ const MetricSettingForm: ForwardRefRenderFunction = ( allowClear showSearch style={{ width: '100%' }} + filterOption={(inputValue: string, item: any) => { + const { label } = item; + if (label.includes(inputValue)) { + return true; + } + return false; + }} placeholder="请选择展示指标信息" options={metricListOptions} /> diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/utils.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/utils.ts new file mode 100644 index 000000000..386831882 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/utils.ts @@ -0,0 +1,28 @@ +import { IChatConfig, ISemantic } from '../../data'; + +export const exChangeRichEntityListToIds = (entityData: IChatConfig.IEntity) => { + const entityList = entityData.entityIds || []; + const detailData: { + dimensionIds: number[]; + metricIds: number[]; + } = { dimensionIds: [], metricIds: [] }; + const { dimensionList, metricList } = entityData.entityInternalDetailDesc || {}; + if (Array.isArray(dimensionList)) { + detailData.dimensionIds = dimensionList.map((item: ISemantic.IDimensionItem) => { + return item.id; + }); + } + if (Array.isArray(metricList)) { + detailData.metricIds = metricList.map((item: ISemantic.IMetricItem) => { + return item.id; + }); + } + const entityIds = entityList.map((item) => { + return item.id; + }); + return { + ...entityData, + entityIds, + detailData, + }; +}; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx index c223db9b6..0ef2f0b8f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx @@ -159,6 +159,9 @@ const MetricInfoCreateForm: React.FC = ({ > + + + = ({ domainManger }) => { okText="是" cancelText="否" onConfirm={async () => { - const { code } = await removeGroupAuth({ + const { code, msg } = await removeGroupAuth({ domainId: record.domainId, groupId: record.groupId, }); @@ -223,7 +223,7 @@ const PermissionTable: React.FC = ({ domainManger }) => { setPermissonData({}); queryListData(); } else { - message.error('删除失败'); + message.error(msg); } }} > diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx index 125e6dbbe..d47168e23 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx @@ -1,7 +1,7 @@ import { DownOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; -import { Input, message, Tree, Popconfirm, Space, Tooltip } from 'antd'; +import { Input, message, Tree, Popconfirm, Space, Tooltip, Row, Col } from 'antd'; import type { DataNode } from 'antd/lib/tree'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import type { FC, Key } from 'react'; import { connect } from 'umi'; import type { Dispatch } from 'umi'; @@ -21,6 +21,8 @@ type ProjectListProps = { selectDomainName: string; createDomainBtnVisible?: boolean; dispatch: Dispatch; + onCreateDomainBtnClick?: () => void; + onTreeSelected?: () => void; }; const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode[] => { @@ -41,6 +43,8 @@ const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode const ProjectListTree: FC = ({ selectDomainId, createDomainBtnVisible = true, + onCreateDomainBtnClick, + onTreeSelected, dispatch, }) => { const [projectTree, setProjectTree] = useState([]); @@ -89,6 +93,7 @@ const ProjectListTree: FC = ({ const targetNodeData = classList.filter((item: any) => { return item.id === selectedKeys; })[0]; + onTreeSelected?.(); dispatch({ type: 'domainManger/setSelectDomain', selectDomainId: selectedKeys, @@ -196,28 +201,29 @@ const ProjectListTree: FC = ({ return (
-

- 主题域 - - {createDomainBtnVisible && ( - - { - setProjectInfoParams({ type: 'top', modelType: 'add' }); - setProjectInfoModalVisible(true); - }} - className={styles.addBtn} - /> - - )} - -

- + + + + + + + { + setProjectInfoParams({ type: 'top', modelType: 'add' }); + setProjectInfoModalVisible(true); + onCreateDomainBtnClick?.(); + }} + className={styles.addBtn} + /> + + + + ; domainData: any; + dataBaseResultColsMap: any; + dataBaseConfig: any; }; export type ModelType = { @@ -19,11 +21,15 @@ export type ModelType = { effects: { queryDimensionList: Effect; queryMetricList: Effect; + queryDataBaseExcuteSql: Effect; + queryDatabaseByDomainId: Effect; }; reducers: { setSelectDomain: Reducer; setPagination: Reducer; setDimensionList: Reducer; + setDataBaseScriptColumn: Reducer; + setDataBaseConfig: Reducer; setMetricList: Reducer; reset: Reducer; }; @@ -38,6 +44,8 @@ export const defaultState: StateType = { dimensionList: [], metricList: [], domainData: {}, + dataBaseResultColsMap: {}, + dataBaseConfig: {}, }; const Model: ModelType = { @@ -61,6 +69,46 @@ const Model: ModelType = { message.error(msg); } }, + *queryDataBaseExcuteSql({ payload }, { call, put, select }) { + const { tableName } = payload; + if (!tableName) { + return; + } + const isExists = yield select((state: any) => { + return state.domainManger.dataBaseResultColsMap[tableName]; + }); + if (isExists) { + return; + } + const { code, data, msg } = yield call(excuteSql, payload); + if (code === 200) { + const resultList = data.resultList.map((item, index) => { + return { + ...item, + index, + }; + }); + const scriptColumns = data.columns; + yield put({ + type: 'setDataBaseScriptColumn', + payload: { resultList, scriptColumns, tableName }, + }); + } else { + message.error(msg); + } + }, + *queryDatabaseByDomainId({ payload }, { call, put }) { + const domainId = payload.domainId; + const { code, data, msg } = yield call(getDatabaseByDomainId, domainId); + if (code === 200) { + yield put({ + type: 'setDataBaseConfig', + payload: { dataBaseConfig: data }, + }); + } else { + message.error(msg); + } + }, }, reducers: { setSelectDomain(state = defaultState, action) { @@ -89,6 +137,21 @@ const Model: ModelType = { ...action.payload, }; }, + setDataBaseScriptColumn(state = defaultState, action) { + return { + ...state, + dataBaseResultColsMap: { + ...state.dataBaseResultColsMap, + [action.payload.tableName]: { ...action.payload }, + }, + }; + }, + setDataBaseConfig(state = defaultState, action) { + return { + ...state, + ...action.payload, + }; + }, reset() { return defaultState; }, diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts index 439588415..b8cbd599e 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts @@ -237,3 +237,32 @@ export function testDatabaseConnect(data: SaveDatabaseParams): Promise { data, }); } + +type ExcuteSqlParams = { + sql: string; + domainId: number; +}; + +// 执行脚本 +export async function excuteSql(params: ExcuteSqlParams) { + const data = { ...params }; + return request.post(`${process.env.API_BASE_URL}database/executeSql`, { data }); +} + +export function getDbNames(dbId: number): Promise { + return request(`${process.env.API_BASE_URL}database/getDbNames/${dbId}`, { + method: 'GET', + }); +} + +export function getTables(dbId: number, dbName: string): Promise { + return request(`${process.env.API_BASE_URL}database/getTables/${dbId}/${dbName}`, { + method: 'GET', + }); +} + +export function getColumns(dbId: number, dbName: string, tableName: string): Promise { + return request(`${process.env.API_BASE_URL}database/getColumns/${dbId}/${dbName}/${tableName}`, { + method: 'GET', + }); +}