pydpm_xl 0.1.39rc31__py3-none-any.whl → 0.2.0__py3-none-any.whl
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.
- py_dpm/__init__.py +1 -1
- py_dpm/api/__init__.py +58 -189
- py_dpm/api/dpm/__init__.py +20 -0
- py_dpm/api/{data_dictionary.py → dpm/data_dictionary.py} +903 -984
- py_dpm/api/dpm/explorer.py +236 -0
- py_dpm/api/dpm/hierarchical_queries.py +142 -0
- py_dpm/api/{migration.py → dpm/migration.py} +16 -19
- py_dpm/api/{operation_scopes.py → dpm/operation_scopes.py} +319 -267
- py_dpm/api/dpm_xl/__init__.py +25 -0
- py_dpm/api/{ast_generator.py → dpm_xl/ast_generator.py} +3 -3
- py_dpm/api/{complete_ast.py → dpm_xl/complete_ast.py} +192 -168
- py_dpm/api/dpm_xl/semantic.py +354 -0
- py_dpm/api/{syntax.py → dpm_xl/syntax.py} +6 -5
- py_dpm/api/explorer.py +4 -0
- py_dpm/api/semantic.py +30 -306
- py_dpm/cli/__init__.py +9 -0
- py_dpm/{client.py → cli/main.py} +8 -8
- py_dpm/dpm/__init__.py +11 -0
- py_dpm/{models.py → dpm/models.py} +112 -88
- py_dpm/dpm/queries/base.py +100 -0
- py_dpm/dpm/queries/basic_objects.py +33 -0
- py_dpm/dpm/queries/explorer_queries.py +352 -0
- py_dpm/dpm/queries/filters.py +139 -0
- py_dpm/dpm/queries/glossary.py +45 -0
- py_dpm/dpm/queries/hierarchical_queries.py +838 -0
- py_dpm/dpm/queries/tables.py +133 -0
- py_dpm/dpm/utils.py +356 -0
- py_dpm/dpm_xl/__init__.py +8 -0
- py_dpm/dpm_xl/ast/__init__.py +14 -0
- py_dpm/{AST/ASTConstructor.py → dpm_xl/ast/constructor.py} +6 -6
- py_dpm/{AST/MLGeneration.py → dpm_xl/ast/ml_generation.py} +137 -87
- py_dpm/{AST/ModuleAnalyzer.py → dpm_xl/ast/module_analyzer.py} +7 -7
- py_dpm/{AST/ModuleDependencies.py → dpm_xl/ast/module_dependencies.py} +56 -41
- py_dpm/{AST/ASTObjects.py → dpm_xl/ast/nodes.py} +1 -1
- py_dpm/{AST/check_operands.py → dpm_xl/ast/operands.py} +16 -13
- py_dpm/{AST/ASTTemplate.py → dpm_xl/ast/template.py} +2 -2
- py_dpm/{AST/WhereClauseChecker.py → dpm_xl/ast/where_clause.py} +2 -2
- py_dpm/dpm_xl/grammar/__init__.py +18 -0
- py_dpm/dpm_xl/operators/__init__.py +19 -0
- py_dpm/{Operators/AggregateOperators.py → dpm_xl/operators/aggregate.py} +7 -7
- py_dpm/{Operators/NumericOperators.py → dpm_xl/operators/arithmetic.py} +6 -6
- py_dpm/{Operators/Operator.py → dpm_xl/operators/base.py} +5 -5
- py_dpm/{Operators/BooleanOperators.py → dpm_xl/operators/boolean.py} +5 -5
- py_dpm/{Operators/ClauseOperators.py → dpm_xl/operators/clause.py} +8 -8
- py_dpm/{Operators/ComparisonOperators.py → dpm_xl/operators/comparison.py} +5 -5
- py_dpm/{Operators/ConditionalOperators.py → dpm_xl/operators/conditional.py} +7 -7
- py_dpm/{Operators/StringOperators.py → dpm_xl/operators/string.py} +5 -5
- py_dpm/{Operators/TimeOperators.py → dpm_xl/operators/time.py} +6 -6
- py_dpm/{semantics/SemanticAnalyzer.py → dpm_xl/semantic_analyzer.py} +168 -68
- py_dpm/{semantics/Symbols.py → dpm_xl/symbols.py} +3 -3
- py_dpm/dpm_xl/types/__init__.py +13 -0
- py_dpm/{DataTypes/TypePromotion.py → dpm_xl/types/promotion.py} +2 -2
- py_dpm/{DataTypes/ScalarTypes.py → dpm_xl/types/scalar.py} +2 -2
- py_dpm/dpm_xl/utils/__init__.py +14 -0
- py_dpm/{data_handlers.py → dpm_xl/utils/data_handlers.py} +2 -2
- py_dpm/{Utils → dpm_xl/utils}/operands_mapping.py +1 -1
- py_dpm/{Utils → dpm_xl/utils}/operator_mapping.py +8 -8
- py_dpm/{OperationScopes/OperationScopeService.py → dpm_xl/utils/scopes_calculator.py} +148 -58
- py_dpm/{Utils/ast_serialization.py → dpm_xl/utils/serialization.py} +2 -2
- py_dpm/dpm_xl/validation/__init__.py +12 -0
- py_dpm/{Utils/ValidationsGenerationUtils.py → dpm_xl/validation/generation_utils.py} +2 -3
- py_dpm/{ValidationsGeneration/PropertiesConstraintsProcessor.py → dpm_xl/validation/property_constraints.py} +56 -21
- py_dpm/{ValidationsGeneration/auxiliary_functions.py → dpm_xl/validation/utils.py} +2 -2
- py_dpm/{ValidationsGeneration/VariantsProcessor.py → dpm_xl/validation/variants.py} +149 -55
- py_dpm/exceptions/__init__.py +23 -0
- py_dpm/{Exceptions → exceptions}/exceptions.py +7 -2
- pydpm_xl-0.2.0.dist-info/METADATA +278 -0
- pydpm_xl-0.2.0.dist-info/RECORD +88 -0
- pydpm_xl-0.2.0.dist-info/entry_points.txt +2 -0
- py_dpm/Exceptions/__init__.py +0 -0
- py_dpm/OperationScopes/__init__.py +0 -0
- py_dpm/Operators/__init__.py +0 -0
- py_dpm/Utils/__init__.py +0 -0
- py_dpm/Utils/utils.py +0 -2
- py_dpm/ValidationsGeneration/Utils.py +0 -364
- py_dpm/ValidationsGeneration/__init__.py +0 -0
- py_dpm/api/data_dictionary_validation.py +0 -614
- py_dpm/db_utils.py +0 -221
- py_dpm/grammar/__init__.py +0 -0
- py_dpm/grammar/dist/__init__.py +0 -0
- py_dpm/grammar/dpm_xlLexer.g4 +0 -437
- py_dpm/grammar/dpm_xlParser.g4 +0 -263
- py_dpm/semantics/DAG/DAGAnalyzer.py +0 -158
- py_dpm/semantics/DAG/__init__.py +0 -0
- py_dpm/semantics/__init__.py +0 -0
- py_dpm/views/data_types.sql +0 -12
- py_dpm/views/datapoints.sql +0 -65
- py_dpm/views/hierarchy_operand_reference.sql +0 -11
- py_dpm/views/hierarchy_preconditions.sql +0 -13
- py_dpm/views/hierarchy_variables.sql +0 -26
- py_dpm/views/hierarchy_variables_context.sql +0 -14
- py_dpm/views/key_components.sql +0 -18
- py_dpm/views/module_from_table.sql +0 -11
- py_dpm/views/open_keys.sql +0 -13
- py_dpm/views/operation_info.sql +0 -27
- py_dpm/views/operation_list.sql +0 -18
- py_dpm/views/operations_versions_from_module_version.sql +0 -30
- py_dpm/views/precondition_info.sql +0 -17
- py_dpm/views/report_type_operand_reference_info.sql +0 -18
- py_dpm/views/subcategory_info.sql +0 -17
- py_dpm/views/table_info.sql +0 -19
- pydpm_xl-0.1.39rc31.dist-info/METADATA +0 -53
- pydpm_xl-0.1.39rc31.dist-info/RECORD +0 -96
- pydpm_xl-0.1.39rc31.dist-info/entry_points.txt +0 -2
- /py_dpm/{AST → cli/commands}/__init__.py +0 -0
- /py_dpm/{migration.py → dpm/migration.py} +0 -0
- /py_dpm/{AST/ASTVisitor.py → dpm_xl/ast/visitor.py} +0 -0
- /py_dpm/{DataTypes → dpm_xl/grammar/generated}/__init__.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserListener.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserVisitor.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/listeners.py +0 -0
- /py_dpm/{DataTypes/TimeClasses.py → dpm_xl/types/time.py} +0 -0
- /py_dpm/{Utils → dpm_xl/utils}/tokens.py +0 -0
- /py_dpm/{Exceptions → exceptions}/messages.py +0 -0
- {pydpm_xl-0.1.39rc31.dist-info → pydpm_xl-0.2.0.dist-info}/WHEEL +0 -0
- {pydpm_xl-0.1.39rc31.dist-info → pydpm_xl-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {pydpm_xl-0.1.39rc31.dist-info → pydpm_xl-0.2.0.dist-info}/top_level.txt +0 -0
py_dpm/__init__.py
CHANGED
|
@@ -41,7 +41,7 @@ Available packages:
|
|
|
41
41
|
- pydpm.api: Main APIs for migration, syntax, and semantic analysis
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
__version__ = "0.
|
|
44
|
+
__version__ = "0.2.0"
|
|
45
45
|
__author__ = "MeaningfulData S.L."
|
|
46
46
|
__email__ = "info@meaningfuldata.eu"
|
|
47
47
|
__license__ = "GPL-3.0-or-later"
|
py_dpm/api/__init__.py
CHANGED
|
@@ -1,209 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"""
|
|
2
|
+
PyDPM Public API
|
|
3
|
+
|
|
4
|
+
Main entry point for the PyDPM library.
|
|
5
|
+
Provides both DPM-XL specific and general DPM functionality.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import from DPM-XL API
|
|
9
|
+
from py_dpm.api.dpm_xl import (
|
|
10
|
+
SyntaxAPI,
|
|
11
|
+
SemanticAPI,
|
|
12
|
+
ASTGenerator,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Import from general DPM API
|
|
16
|
+
from py_dpm.api.dpm import (
|
|
17
|
+
DataDictionaryAPI,
|
|
18
|
+
ExplorerQueryAPI,
|
|
6
19
|
OperationScopesAPI,
|
|
20
|
+
MigrationAPI,
|
|
21
|
+
HierarchicalQueryAPI,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Import convenience functions and types from DPM API
|
|
25
|
+
from py_dpm.api.dpm.operation_scopes import (
|
|
7
26
|
calculate_scopes_from_expression,
|
|
8
27
|
get_existing_scopes,
|
|
9
|
-
ModuleVersionInfo,
|
|
10
|
-
TableVersionInfo,
|
|
11
|
-
HeaderVersionInfo,
|
|
12
28
|
OperationScopeDetailedInfo,
|
|
13
|
-
OperationScopeResult
|
|
14
|
-
)
|
|
15
|
-
from py_dpm.api.ast_generator import ASTGenerator, parse_expression, validate_expression, parse_batch
|
|
16
|
-
from py_dpm.api.complete_ast import (
|
|
17
|
-
generate_complete_ast,
|
|
18
|
-
generate_complete_batch,
|
|
19
|
-
generate_enriched_ast,
|
|
20
|
-
enrich_ast_with_metadata
|
|
29
|
+
OperationScopeResult,
|
|
21
30
|
)
|
|
22
31
|
|
|
23
|
-
from antlr4 import CommonTokenStream, InputStream
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
from py_dpm.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
from py_dpm.AST.MLGeneration import MLGeneration
|
|
31
|
-
from py_dpm.AST.ModuleAnalyzer import ModuleAnalyzer
|
|
32
|
-
from py_dpm.AST.ModuleDependencies import ModuleDependencies
|
|
33
|
-
from py_dpm.AST.check_operands import OperandsChecking
|
|
34
|
-
from py_dpm.semantics import SemanticAnalyzer
|
|
35
|
-
|
|
36
|
-
from py_dpm.ValidationsGeneration.VariantsProcessor import (
|
|
37
|
-
VariantsProcessor,
|
|
38
|
-
VariantsProcessorChecker,
|
|
33
|
+
# Import AST generator convenience functions
|
|
34
|
+
from py_dpm.api.dpm_xl.ast_generator import (
|
|
35
|
+
parse_expression,
|
|
36
|
+
validate_expression,
|
|
37
|
+
parse_batch,
|
|
39
38
|
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
|
|
40
|
+
# Import complete AST functions
|
|
41
|
+
from py_dpm.api.dpm_xl.complete_ast import (
|
|
42
|
+
generate_complete_ast,
|
|
43
|
+
generate_complete_batch,
|
|
44
|
+
generate_enriched_ast,
|
|
45
|
+
enrich_ast_with_metadata,
|
|
43
46
|
)
|
|
44
47
|
|
|
45
|
-
from py_dpm.db_utils import get_session, get_engine
|
|
46
48
|
|
|
47
49
|
# Export the main API classes
|
|
48
50
|
__all__ = [
|
|
49
51
|
# Complete AST API (recommended - includes data fields)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
"generate_complete_ast",
|
|
53
|
+
"generate_complete_batch",
|
|
53
54
|
# Enriched AST API (engine-ready with framework structure)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
"generate_enriched_ast",
|
|
56
|
+
"enrich_ast_with_metadata",
|
|
57
57
|
# Simple AST API
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
"ASTGenerator",
|
|
59
|
+
"parse_expression",
|
|
60
|
+
"validate_expression",
|
|
61
|
+
"parse_batch",
|
|
63
62
|
# Advanced APIs
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
"MigrationAPI",
|
|
64
|
+
"SyntaxAPI",
|
|
65
|
+
"SemanticAPI",
|
|
66
|
+
"DataDictionaryAPI",
|
|
67
|
+
"OperationScopesAPI",
|
|
68
|
+
"ExplorerQueryAPI",
|
|
70
69
|
# Operation Scopes Convenience Functions
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
"calculate_scopes_from_expression",
|
|
71
|
+
"get_existing_scopes",
|
|
74
72
|
# Operation Scopes Data Classes
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
'API' # Keep for backward compatibility
|
|
73
|
+
"ModuleVersionInfo",
|
|
74
|
+
"TableVersionInfo",
|
|
75
|
+
"HeaderVersionInfo",
|
|
76
|
+
"OperationScopeDetailedInfo",
|
|
77
|
+
"OperationScopeResult",
|
|
82
78
|
]
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class API:
|
|
86
|
-
error_listener = DPMErrorListener()
|
|
87
|
-
visitor = ASTVisitor()
|
|
88
|
-
|
|
89
|
-
def __init__(self, database_path=None, connection_url=None):
|
|
90
|
-
"""
|
|
91
|
-
Initialize the API.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
database_path: Path to SQLite database (optional)
|
|
95
|
-
connection_url: SQLAlchemy connection URL for PostgreSQL (optional)
|
|
96
|
-
"""
|
|
97
|
-
if connection_url:
|
|
98
|
-
# Create isolated engine and session for the provided connection URL
|
|
99
|
-
from sqlalchemy.orm import sessionmaker
|
|
100
|
-
from py_dpm.db_utils import create_engine_from_url
|
|
101
|
-
|
|
102
|
-
# Create engine for the connection URL (supports SQLite, PostgreSQL, MySQL, etc.)
|
|
103
|
-
self.engine = create_engine_from_url(connection_url)
|
|
104
|
-
session_maker = sessionmaker(bind=self.engine)
|
|
105
|
-
self.session = session_maker()
|
|
106
|
-
elif database_path:
|
|
107
|
-
# Create isolated engine and session for this specific database
|
|
108
|
-
from sqlalchemy import create_engine
|
|
109
|
-
from sqlalchemy.orm import sessionmaker
|
|
110
|
-
import os
|
|
111
|
-
|
|
112
|
-
# Create the database directory if it doesn't exist
|
|
113
|
-
db_dir = os.path.dirname(database_path)
|
|
114
|
-
if db_dir and not os.path.exists(db_dir):
|
|
115
|
-
os.makedirs(db_dir)
|
|
116
|
-
|
|
117
|
-
# Create engine for specific database path
|
|
118
|
-
db_connection_url = f"sqlite:///{database_path}"
|
|
119
|
-
self.engine = create_engine(db_connection_url, pool_pre_ping=True)
|
|
120
|
-
session_maker = sessionmaker(bind=self.engine)
|
|
121
|
-
self.session = session_maker()
|
|
122
|
-
else:
|
|
123
|
-
# Use default global connection
|
|
124
|
-
get_engine()
|
|
125
|
-
self.session = get_session()
|
|
126
|
-
self.engine = None
|
|
127
|
-
|
|
128
|
-
@classmethod
|
|
129
|
-
def lexer(cls, text: str):
|
|
130
|
-
"""
|
|
131
|
-
Extracts the tokens from the input expression
|
|
132
|
-
:param text: Expression to be analyzed
|
|
133
|
-
"""
|
|
134
|
-
lexer = dpm_xlLexer(InputStream(text))
|
|
135
|
-
lexer._listeners = [cls.error_listener]
|
|
136
|
-
cls.stream = CommonTokenStream(lexer)
|
|
137
|
-
|
|
138
|
-
@classmethod
|
|
139
|
-
def parser(cls):
|
|
140
|
-
"""
|
|
141
|
-
Parses the token from the lexer stream
|
|
142
|
-
"""
|
|
143
|
-
parser = dpm_xlParser(cls.stream)
|
|
144
|
-
parser._listeners = [cls.error_listener]
|
|
145
|
-
cls.CST = parser.start()
|
|
146
|
-
|
|
147
|
-
if parser._syntaxErrors == 0:
|
|
148
|
-
return True
|
|
149
|
-
|
|
150
|
-
@classmethod
|
|
151
|
-
def syntax_validation(cls, expression):
|
|
152
|
-
"""
|
|
153
|
-
Validates that the input expression is syntactically correct by applying the ANTLR lexer and parser
|
|
154
|
-
:param expression: Expression to be analyzed
|
|
155
|
-
"""
|
|
156
|
-
cls.lexer(expression)
|
|
157
|
-
cls.parser()
|
|
158
|
-
|
|
159
|
-
@classmethod
|
|
160
|
-
def create_ast(cls, expression):
|
|
161
|
-
"""
|
|
162
|
-
Generates the AST from the expression
|
|
163
|
-
:param expression: Expression to be analyzed
|
|
164
|
-
"""
|
|
165
|
-
cls.lexer(expression)
|
|
166
|
-
if cls.parser():
|
|
167
|
-
cls.visitor = ASTVisitor()
|
|
168
|
-
cls.AST = cls.visitor.visit(cls.CST)
|
|
169
|
-
|
|
170
|
-
def semantic_validation(self, expression):
|
|
171
|
-
self.create_ast(expression=expression)
|
|
172
|
-
|
|
173
|
-
oc = OperandsChecking(session=self.session, expression=expression, ast=self.AST, release_id=None)
|
|
174
|
-
semanticAnalysis = SemanticAnalyzer.InputAnalyzer(expression)
|
|
175
|
-
|
|
176
|
-
semanticAnalysis.data = oc.data
|
|
177
|
-
semanticAnalysis.key_components = oc.key_components
|
|
178
|
-
semanticAnalysis.open_keys = oc.open_keys
|
|
179
|
-
|
|
180
|
-
semanticAnalysis.preconditions = oc.preconditions
|
|
181
|
-
|
|
182
|
-
results = semanticAnalysis.visit(self.AST)
|
|
183
|
-
return results
|
|
184
|
-
|
|
185
|
-
def _check_property_constraints(self, ast):
|
|
186
|
-
"""
|
|
187
|
-
Method to check property constraints
|
|
188
|
-
:return: Boolean value indicating if the ast has property constraints
|
|
189
|
-
"""
|
|
190
|
-
pcc = PropertiesConstraintsChecker(ast=ast, session=self.session)
|
|
191
|
-
return pcc.is_property_constraint
|
|
192
|
-
|
|
193
|
-
def _check_property_constraints_from_expression(self, expression):
|
|
194
|
-
"""
|
|
195
|
-
Method to check property constraints
|
|
196
|
-
:return: Boolean value indicating if the ast has property constraints
|
|
197
|
-
"""
|
|
198
|
-
self.create_ast(expression=expression)
|
|
199
|
-
pcc = PropertiesConstraintsChecker(ast=self.AST, session=self.session)
|
|
200
|
-
return pcc.is_property_constraint
|
|
201
|
-
|
|
202
|
-
def _check_variants(self, expression):
|
|
203
|
-
"""
|
|
204
|
-
Method to check table groups
|
|
205
|
-
:return: Boolean value indicating if the ast has table groups
|
|
206
|
-
"""
|
|
207
|
-
self.create_ast(expression=expression)
|
|
208
|
-
tgc = VariantsProcessorChecker(ast=self.AST)
|
|
209
|
-
return tgc.is_variant
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DPM API
|
|
3
|
+
|
|
4
|
+
Public APIs for general DPM functionality (database, exploration, scopes).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from py_dpm.api.dpm.data_dictionary import DataDictionaryAPI
|
|
8
|
+
from py_dpm.api.dpm.explorer import ExplorerQueryAPI
|
|
9
|
+
from py_dpm.api.dpm.operation_scopes import OperationScopesAPI
|
|
10
|
+
from py_dpm.api.dpm.migration import MigrationAPI
|
|
11
|
+
from py_dpm.api.dpm.hierarchical_queries import HierarchicalQueryAPI
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"DataDictionaryAPI",
|
|
16
|
+
"ExplorerQueryAPI",
|
|
17
|
+
"OperationScopesAPI",
|
|
18
|
+
"MigrationAPI",
|
|
19
|
+
"HierarchicalQueryAPI",
|
|
20
|
+
]
|