semanticdb-core 1.1.20 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -13,6 +13,8 @@ export declare type PropertyTypePrimalType = 'string' | 'number' | 'boolean' | '
|
|
|
13
13
|
export interface PropertyTypeDraft {
|
|
14
14
|
_sid?: string | number;
|
|
15
15
|
_id?: string;
|
|
16
|
+
sid?: string;
|
|
17
|
+
id?: string;
|
|
16
18
|
name: string;
|
|
17
19
|
syno?: string[];
|
|
18
20
|
description?: string;
|
|
@@ -20,6 +22,7 @@ export interface PropertyTypeDraft {
|
|
|
20
22
|
required?: boolean;
|
|
21
23
|
unique?: boolean;
|
|
22
24
|
enum?: any[] | null;
|
|
25
|
+
enumMap?: any[];
|
|
23
26
|
};
|
|
24
27
|
type: PropertyTypeType;
|
|
25
28
|
primal_type?: PropertyTypePrimalType;
|
|
@@ -29,8 +32,10 @@ export interface PropertyTypeDraft {
|
|
|
29
32
|
dependencies: string[];
|
|
30
33
|
function?: (self: any) => any;
|
|
31
34
|
};
|
|
35
|
+
pattern?: RegExp | string;
|
|
32
36
|
is_dynamic?: boolean;
|
|
33
37
|
is_projection_for?: string;
|
|
38
|
+
is_version?: boolean;
|
|
34
39
|
is_name?: boolean;
|
|
35
40
|
is_categorical?: boolean;
|
|
36
41
|
ref?: string;
|
|
@@ -14,7 +14,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.getDrilldownDimensions = void 0;
|
|
16
16
|
const logicform_utils_1 = require("../../logicform/logicform.utils");
|
|
17
|
+
const property_utils_1 = require("../property.utils");
|
|
17
18
|
const moment_1 = __importDefault(require("moment"));
|
|
19
|
+
// 日期粒度,从粗到细
|
|
20
|
+
const DATE_GRANULARITIES = [
|
|
21
|
+
'year',
|
|
22
|
+
'quarter',
|
|
23
|
+
'month',
|
|
24
|
+
'week',
|
|
25
|
+
'day',
|
|
26
|
+
'hour',
|
|
27
|
+
'minute',
|
|
28
|
+
'second',
|
|
29
|
+
];
|
|
18
30
|
const getDrilldownDimensions = (schema_1, schemasDict_1, logicform_1, ...args_1) => __awaiter(void 0, [schema_1, schemasDict_1, logicform_1, ...args_1], void 0, function* (schema, schemasDict, logicform, usage = 'drilldown') {
|
|
19
31
|
if (!schema)
|
|
20
32
|
return []; // 如果没有schema,代表logicform result报错了
|
|
@@ -38,6 +50,7 @@ const getDrilldownDimensions = (schema_1, schemasDict_1, logicform_1, ...args_1)
|
|
|
38
50
|
let drilldownProps = [];
|
|
39
51
|
// 一个递归函数
|
|
40
52
|
const addToArrayWithProperty = (property, prefix, schema) => {
|
|
53
|
+
var _a;
|
|
41
54
|
if (usage === 'analyze' && property.is_analyzable === false)
|
|
42
55
|
return;
|
|
43
56
|
const pushToPropNames = (property, level, prefix, schema) => {
|
|
@@ -62,7 +75,26 @@ const getDrilldownDimensions = (schema_1, schemasDict_1, logicform_1, ...args_1)
|
|
|
62
75
|
return;
|
|
63
76
|
if (property.can_drilldown === false)
|
|
64
77
|
return;
|
|
65
|
-
if (property.
|
|
78
|
+
if (property.primal_type === 'date') {
|
|
79
|
+
// 日期维度的下钻:提供更细粒度的选项
|
|
80
|
+
const propertyId = prefix ? `${prefix}_${property.name}` : property.name;
|
|
81
|
+
// 找到当前 logicform 中该属性的粒度
|
|
82
|
+
const currentGroupBy = (_a = logicform === null || logicform === void 0 ? void 0 : logicform.groupby) === null || _a === void 0 ? void 0 : _a.find((g) => g._id === propertyId);
|
|
83
|
+
const currentGranularity = currentGroupBy === null || currentGroupBy === void 0 ? void 0 : currentGroupBy.level;
|
|
84
|
+
// 获取属性的默认粒度
|
|
85
|
+
const defaultGranularity = (0, property_utils_1.getGranularity)(property);
|
|
86
|
+
// 确定起始粒度索引
|
|
87
|
+
const startIndex = currentGranularity
|
|
88
|
+
? DATE_GRANULARITIES.indexOf(currentGranularity)
|
|
89
|
+
: DATE_GRANULARITIES.indexOf(defaultGranularity);
|
|
90
|
+
if (startIndex >= 0 && startIndex < DATE_GRANULARITIES.length - 1) {
|
|
91
|
+
// 提供比当前粒度更细的所有选项
|
|
92
|
+
for (let i = startIndex + 1; i < DATE_GRANULARITIES.length; i++) {
|
|
93
|
+
pushToPropNames(property, DATE_GRANULARITIES[i], prefix, schema);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (property.is_categorical) {
|
|
66
98
|
pushToPropNames(property, undefined, prefix, schema);
|
|
67
99
|
}
|
|
68
100
|
else if (property.ref && schemasDict[property.ref]) {
|
|
@@ -8,9 +8,18 @@ export interface SchemaValidationResult {
|
|
|
8
8
|
/**
|
|
9
9
|
* 检查 schema 是否有效
|
|
10
10
|
* @param schema - 要检查的 schema
|
|
11
|
+
* @param schemasDict - schema字典,用于验证ref引用
|
|
11
12
|
* @returns 验证结果,包含 valid 和 message
|
|
12
13
|
*/
|
|
13
|
-
export declare function isSchemaValid(schema: Partial<SchemaType>, schemasDict
|
|
14
|
+
export declare function isSchemaValid(schema: Partial<SchemaType>, schemasDict?: Record<string, SchemaType>): SchemaValidationResult;
|
|
15
|
+
/**
|
|
16
|
+
* 验证一个property配置是否有效
|
|
17
|
+
* @param property - 属性配置
|
|
18
|
+
* @param schemaNames - schema名称列表(用于冲突检查)
|
|
19
|
+
* @param hierarchyNames - hierarchy名称列表(用于冲突检查)
|
|
20
|
+
* @returns 验证结果
|
|
21
|
+
*/
|
|
22
|
+
export declare function isPropertyValid(property: Partial<PropertyType>, schemaNames?: string[], hierarchyNames?: string[]): SchemaValidationResult;
|
|
14
23
|
export declare const findPropertyByType: (type: string, schema: SchemaType) => PropertyType | undefined;
|
|
15
24
|
/**
|
|
16
25
|
* Find a property by name within a specific schema
|
|
@@ -2,46 +2,235 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getRefChain = exports.getHierarchyCodeLength = exports.findPropertyForOtherSchema = exports.findTimestampLikeProperty = exports.findDisplayNameProperty = exports.findPropertyByName = exports.findPropertyByType = void 0;
|
|
4
4
|
exports.isSchemaValid = isSchemaValid;
|
|
5
|
+
exports.isPropertyValid = isPropertyValid;
|
|
5
6
|
/**
|
|
6
7
|
* 检查 schema 是否有效
|
|
7
8
|
* @param schema - 要检查的 schema
|
|
9
|
+
* @param schemasDict - schema字典,用于验证ref引用
|
|
8
10
|
* @returns 验证结果,包含 valid 和 message
|
|
9
11
|
*/
|
|
10
12
|
function isSchemaValid(schema, schemasDict) {
|
|
11
|
-
|
|
13
|
+
var _a, _b;
|
|
14
|
+
// ==================== 基本字段检查 ====================
|
|
15
|
+
// 检查name - 兼容空字符串的情况
|
|
16
|
+
if (!schema.name || schema.name.trim() === '') {
|
|
12
17
|
return { valid: false, message: '缺少名称' };
|
|
13
18
|
}
|
|
19
|
+
// 检查type
|
|
14
20
|
if (!schema.type) {
|
|
15
21
|
return { valid: false, message: '缺少类型' };
|
|
16
22
|
}
|
|
17
|
-
if (
|
|
18
|
-
return { valid: false, message: '
|
|
23
|
+
if (['entity', 'event'].indexOf(schema.type) < 0) {
|
|
24
|
+
return { valid: false, message: 'type必须为entity或event之一' };
|
|
25
|
+
}
|
|
26
|
+
// 检查name是否包含非法字符
|
|
27
|
+
if ((_b = (_a = schema === null || schema === void 0 ? void 0 : schema.name) === null || _a === void 0 ? void 0 : _a.includes) === null || _b === void 0 ? void 0 : _b.call(_a, '/')) {
|
|
28
|
+
return { valid: false, message: `schema名字(${schema.name})不能包含 "/"。` };
|
|
19
29
|
}
|
|
30
|
+
// 检查properties
|
|
20
31
|
if (!schema.properties || schema.properties.length === 0) {
|
|
21
32
|
return { valid: false, message: '缺少属性定义' };
|
|
22
33
|
}
|
|
23
|
-
|
|
34
|
+
// 检查每个属性是否有name和type
|
|
35
|
+
if (!schema.properties.every((property) => property.name && property.name.trim() !== '' && property.type)) {
|
|
24
36
|
return { valid: false, message: '每一个属性都必须有名称和类型' };
|
|
25
37
|
}
|
|
26
|
-
//
|
|
38
|
+
// 检查db
|
|
39
|
+
if (!schema.db) {
|
|
40
|
+
return { valid: false, message: '缺少数据库配置' };
|
|
41
|
+
}
|
|
42
|
+
// ==================== tag字段验证 ====================
|
|
43
|
+
if (schema.tag && typeof schema.tag === 'object' && !Array.isArray(schema.tag)) {
|
|
44
|
+
return { valid: false, message: 'schema的标签不能是对象类型' };
|
|
45
|
+
}
|
|
46
|
+
// ==================== syno验证 ====================
|
|
47
|
+
if (schema.syno && !Array.isArray(schema.syno)) {
|
|
48
|
+
return { valid: false, message: 'schema的同义词必须为数组格式' };
|
|
49
|
+
}
|
|
50
|
+
// ==================== 构建名称列表(用于冲突检查) ====================
|
|
51
|
+
const schemaNames = [schema.name, ...(schema.syno || [])];
|
|
52
|
+
let hierarchyNames = [];
|
|
53
|
+
if (schema.hierarchy) {
|
|
54
|
+
for (const h of schema.hierarchy) {
|
|
55
|
+
hierarchyNames = [...hierarchyNames, h.name, ...(h.syno || [])];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ==================== RULE: schema和hierarchy不能有名词重复 ====================
|
|
59
|
+
const conflicts = _intersection(schemaNames, hierarchyNames);
|
|
60
|
+
if (conflicts.length > 0) {
|
|
61
|
+
return {
|
|
62
|
+
valid: false,
|
|
63
|
+
message: `Schema: ${schema.name}和Hierarchy包含相同的关键词: ${conflicts.join(',')}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ==================== 检查时间戳属性数量 ====================
|
|
67
|
+
const timestampProps = schema.properties.filter((p) => p.type === 'timestamp');
|
|
68
|
+
if (schema.type === 'event' && timestampProps.length > 1) {
|
|
69
|
+
return {
|
|
70
|
+
valid: false,
|
|
71
|
+
message: `事件表不能有两个或以上时间戳类型的属性:${timestampProps.map((p) => p.name).join(',')}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// ==================== 验证每个属性 ====================
|
|
75
|
+
for (const property of schema.properties) {
|
|
76
|
+
// 调用属性验证
|
|
77
|
+
const propertyValid = isPropertyValid(property, schemaNames, hierarchyNames);
|
|
78
|
+
if (!propertyValid.valid) {
|
|
79
|
+
return propertyValid;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ==================== 事件表必须有timestamp ====================
|
|
83
|
+
if (schema.type === 'event') {
|
|
84
|
+
const timeProperty = schema.properties.find((p) => p.type === 'timestamp');
|
|
85
|
+
if (timeProperty === undefined) {
|
|
86
|
+
return { valid: false, message: '事件表必须要有一个时间戳类型的属性' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ==================== 检查属性name重复 ====================
|
|
90
|
+
const propertyNames = schema.properties.map((p) => p.name);
|
|
91
|
+
const seenNames = new Set();
|
|
92
|
+
for (const name of propertyNames) {
|
|
93
|
+
if (seenNames.has(name)) {
|
|
94
|
+
return { valid: false, message: `有两个相同名字的属性:${name}` };
|
|
95
|
+
}
|
|
96
|
+
seenNames.add(name);
|
|
97
|
+
}
|
|
98
|
+
// ==================== 检查属性_id重复 ====================
|
|
99
|
+
const propertyIds = schema.properties.map((p) => p.id).filter((id) => !!id);
|
|
100
|
+
const seenIds = new Set();
|
|
101
|
+
for (const id of propertyIds) {
|
|
102
|
+
if (seenIds.has(id)) {
|
|
103
|
+
return { valid: false, message: `${schema.name} 中有两个相同id的属性:${id}` };
|
|
104
|
+
}
|
|
105
|
+
seenIds.add(id);
|
|
106
|
+
}
|
|
107
|
+
// ==================== 生命周期日期检查 ====================
|
|
108
|
+
const birthdayProps = schema.properties.find((p) => p.type === 'start_date');
|
|
109
|
+
const endDateProps = schema.properties.find((p) => p.type === 'end_date');
|
|
110
|
+
if (birthdayProps && !endDateProps) {
|
|
111
|
+
return { valid: false, message: '有生命周期起始时间,则必须有生命周期结束时间' };
|
|
112
|
+
}
|
|
113
|
+
if (endDateProps && !birthdayProps) {
|
|
114
|
+
return { valid: false, message: '有生命周期结束时间,则必须有生命周期起始时间' };
|
|
115
|
+
}
|
|
116
|
+
if (schema.type !== 'entity' && birthdayProps) {
|
|
117
|
+
return { valid: false, message: '只有实体类有生命周期概念' };
|
|
118
|
+
}
|
|
119
|
+
// ==================== 实体表timestamp检查 ====================
|
|
120
|
+
const tmsProps = schema.properties.find((p) => p.type === 'timestamp');
|
|
121
|
+
if (schema.type === 'entity' && tmsProps && !tmsProps.i_am_an_expert) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
message: `实体表 ${schema.name} 不允许使用时间戳类型字段: ${tmsProps.name}。不然会是一张快照表。如确实需要,请在专家模式中给该字段加入i_am_an_expert: true。`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// ==================== entity必须有ID属性 ====================
|
|
27
128
|
if (schema.type === 'entity') {
|
|
28
|
-
const
|
|
29
|
-
if (!
|
|
30
|
-
return { valid: false, message: '
|
|
129
|
+
const found = schema.properties.find((p) => p.type === 'ID');
|
|
130
|
+
if (!found) {
|
|
131
|
+
return { valid: false, message: 'entity schema必须要有一个ID型属性' };
|
|
31
132
|
}
|
|
32
133
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
134
|
+
// ==================== 有nameProp必须要有ID ====================
|
|
135
|
+
const nameProp = schema.properties.find((p) => p.is_name);
|
|
136
|
+
if (nameProp) {
|
|
137
|
+
if (!schema.properties.find((p) => p.type === 'ID')) {
|
|
138
|
+
let eventMsg = '';
|
|
139
|
+
if (schema.type === 'event') {
|
|
140
|
+
eventMsg = '事件表只有在非常特殊的情况下才会用到名称字段。请充分了解名称字段的含义后配置';
|
|
141
|
+
}
|
|
142
|
+
return { valid: false, message: '如果有名称类型,那么必须要有ID类型的字段。' + eventMsg };
|
|
37
143
|
}
|
|
38
144
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
145
|
+
// ==================== 只允许有一个ID类属性 ====================
|
|
146
|
+
const idProps = schema.properties.filter((item) => item.type === 'ID');
|
|
147
|
+
if (idProps.length > 1) {
|
|
148
|
+
return { valid: false, message: `只允许有一个ID类属性` };
|
|
149
|
+
}
|
|
150
|
+
// ==================== 检查ref引用是否有效 ====================
|
|
151
|
+
if (schemasDict) {
|
|
152
|
+
const noRefProperty = schema.properties.find((property) => property.ref && !(property.ref in schemasDict));
|
|
153
|
+
if (noRefProperty) {
|
|
154
|
+
return { valid: false, message: `属性 ${noRefProperty.name} 引用的实体表 ${noRefProperty.ref} 不存在` };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { valid: true, message: '' };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 验证一个property配置是否有效
|
|
161
|
+
* @param property - 属性配置
|
|
162
|
+
* @param schemaNames - schema名称列表(用于冲突检查)
|
|
163
|
+
* @param hierarchyNames - hierarchy名称列表(用于冲突检查)
|
|
164
|
+
* @returns 验证结果
|
|
165
|
+
*/
|
|
166
|
+
function isPropertyValid(property, schemaNames = [], hierarchyNames = []) {
|
|
167
|
+
var _a, _b, _c, _d, _e, _f;
|
|
168
|
+
if (typeof property !== 'object') {
|
|
169
|
+
return { valid: false, message: '属性必须为Object类型' };
|
|
170
|
+
}
|
|
171
|
+
if (!('name' in property)) {
|
|
172
|
+
return { valid: false, message: '属性必须有name属性' };
|
|
173
|
+
}
|
|
174
|
+
if (!('type' in property)) {
|
|
175
|
+
return { valid: false, message: '属性必须有type属性' };
|
|
176
|
+
}
|
|
177
|
+
if ((_b = (_a = property === null || property === void 0 ? void 0 : property.name) === null || _a === void 0 ? void 0 : _a.includes) === null || _b === void 0 ? void 0 : _b.call(_a, '/')) {
|
|
178
|
+
return { valid: false, message: `property 属性名称(${property.name})不能有"/"。` };
|
|
179
|
+
}
|
|
180
|
+
if (property.name && property.name.indexOf('_') >= 0) {
|
|
181
|
+
return { valid: false, message: `属性${property.name}不得包含下划线` };
|
|
182
|
+
}
|
|
183
|
+
// 处理syno
|
|
184
|
+
const syno = property.syno;
|
|
185
|
+
let propertySyno = [];
|
|
186
|
+
if (Array.isArray(syno)) {
|
|
187
|
+
propertySyno = syno;
|
|
188
|
+
}
|
|
189
|
+
else if (typeof syno === 'string') {
|
|
190
|
+
propertySyno = syno.split(',');
|
|
191
|
+
}
|
|
192
|
+
// RULE: 属性名或者属性syno不能和schema名和schema syno一样
|
|
193
|
+
const propertyNames = [property.name || '', ...propertySyno];
|
|
194
|
+
const conflicts = _intersection(schemaNames, propertyNames);
|
|
195
|
+
if (conflicts.length > 0) {
|
|
196
|
+
return { valid: false, message: `属性${property.name}和Schema包含相同的关键词: ${conflicts.join(',')}` };
|
|
197
|
+
}
|
|
198
|
+
// RULE: 属性名或者属性syno不能和hierarchy名和hierarchy syno一样
|
|
199
|
+
const hierarchyConflicts = _intersection(hierarchyNames, propertyNames);
|
|
200
|
+
if (hierarchyConflicts.length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
valid: false,
|
|
203
|
+
message: `属性${property.name}和Hierarchy包含相同的关键词: ${hierarchyConflicts.join(',')}`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// enumMap和enum检查
|
|
207
|
+
if (((_c = property.constraints) === null || _c === void 0 ? void 0 : _c.enumMap) && !((_d = property.constraints) === null || _d === void 0 ? void 0 : _d.enum)) {
|
|
208
|
+
return { valid: false, message: `属性${property.name}设置了enumMap,但是没有设置enum。需要设置enum` };
|
|
209
|
+
}
|
|
210
|
+
if (((_e = property.constraints) === null || _e === void 0 ? void 0 : _e.enumMap) &&
|
|
211
|
+
((_f = property.constraints) === null || _f === void 0 ? void 0 : _f.enum) &&
|
|
212
|
+
Array.isArray(property.constraints.enumMap) &&
|
|
213
|
+
Array.isArray(property.constraints.enum) &&
|
|
214
|
+
property.constraints.enumMap.length !== property.constraints.enum.length) {
|
|
215
|
+
return { valid: false, message: `属性${property.name}的enum和enumMap长度不相等` };
|
|
216
|
+
}
|
|
217
|
+
// 属性名字中的非法字符检查(除了下划线在前面已经检查过)
|
|
218
|
+
const invalidNameCharacters = ['(', ')', '!', ',', ',', '。', '.'];
|
|
219
|
+
if (property.name) {
|
|
220
|
+
for (const char of invalidNameCharacters) {
|
|
221
|
+
if (property.name.indexOf(char) > 0) {
|
|
222
|
+
return { valid: false, message: `${property.name}属性的名字中不得有${char}字符` };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
42
225
|
}
|
|
43
226
|
return { valid: true, message: '' };
|
|
44
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* 计算两个数组的交集
|
|
230
|
+
*/
|
|
231
|
+
function _intersection(arr1, arr2) {
|
|
232
|
+
return arr1.filter((x) => arr2.includes(x));
|
|
233
|
+
}
|
|
45
234
|
const findPropertyByType = (type, schema) => {
|
|
46
235
|
return schema.properties.find((property) => property.type === type);
|
|
47
236
|
};
|