onec-interactive-runtime-core 0.1.19__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.
- onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
- onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
- onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
- onec_runtime/AGENTS.md +7 -0
- onec_runtime/__init__.py +3 -0
- onec_runtime/artifacts.py +59 -0
- onec_runtime/bootstrap.py +310 -0
- onec_runtime/breakpoint_workspace.py +249 -0
- onec_runtime/bsl/__init__.py +139 -0
- onec_runtime/bsl/diagnostics.py +1046 -0
- onec_runtime/bsl/full_ast_worker_projection.py +522 -0
- onec_runtime/bsl/generated_semantic_parser.py +6322 -0
- onec_runtime/bsl/lexer.py +164 -0
- onec_runtime/bsl/module_catalog.py +420 -0
- onec_runtime/bsl/module_delta.py +1079 -0
- onec_runtime/bsl/module_universe.py +1877 -0
- onec_runtime/bsl/notebook_cells.py +232 -0
- onec_runtime/bsl/notebook_method_globals.py +74 -0
- onec_runtime/bsl/notebook_methods.py +175 -0
- onec_runtime/bsl/parser_artifact_identity.py +119 -0
- onec_runtime/bsl/parser_target.py +388 -0
- onec_runtime/bsl/preprocessor.py +16 -0
- onec_runtime/bsl/semantic_lowering.py +1539 -0
- onec_runtime/bsl/source_maps.py +2411 -0
- onec_runtime/bsl/worker_dependency_resolver.py +289 -0
- onec_runtime/bsl/worker_preprocessor.py +247 -0
- onec_runtime/bsl/worker_projection_model.py +137 -0
- onec_runtime/bsl/worker_reload_source_map.py +1697 -0
- onec_runtime/capture.py +113 -0
- onec_runtime/capture_source.py +453 -0
- onec_runtime/compact_table.py +376 -0
- onec_runtime/compact_table_backend.py +500 -0
- onec_runtime/config.py +198 -0
- onec_runtime/configurator_agent.py +520 -0
- onec_runtime/controller_worker.py +400 -0
- onec_runtime/credentials.py +18 -0
- onec_runtime/epf_container.py +196 -0
- onec_runtime/errors.py +205 -0
- onec_runtime/experiment.py +130 -0
- onec_runtime/extension_bundle.py +944 -0
- onec_runtime/extension_lifecycle.py +402 -0
- onec_runtime/extension_state.py +222 -0
- onec_runtime/fault_injection.py +30 -0
- onec_runtime/kernel.py +179 -0
- onec_runtime/lease.py +123 -0
- onec_runtime/observation.py +287 -0
- onec_runtime/performance_profile.py +245 -0
- onec_runtime/privacy.py +263 -0
- onec_runtime/processes.py +265 -0
- onec_runtime/prototype_runtime.py +2644 -0
- onec_runtime/rdbg/__init__.py +1 -0
- onec_runtime/rdbg/models.py +130 -0
- onec_runtime/rdbg/reconnect.py +59 -0
- onec_runtime/rdbg/session.py +913 -0
- onec_runtime/rdbg/transport.py +138 -0
- onec_runtime/rdbg/xml_codec.py +980 -0
- onec_runtime/recovery.py +106 -0
- onec_runtime/recovery_journal.py +79 -0
- onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
- onec_runtime/resources/extension/extension-manifest.json +108 -0
- onec_runtime/runtime_api.py +5272 -0
- onec_runtime/runtime_contracts.py +196 -0
- onec_runtime/server_worker.py +2034 -0
- onec_runtime/session.py +2123 -0
- onec_runtime/startup_diagnostics.py +45 -0
- onec_runtime/stop_routing.py +65 -0
- onec_runtime/supervised_processes.py +232 -0
- onec_runtime/supervised_runtime.py +512 -0
- onec_runtime/supervisor.py +363 -0
- onec_runtime/supervisor_model.py +148 -0
- onec_runtime/supervisor_protocol.py +77 -0
- onec_runtime/table_materialization.py +471 -0
- onec_runtime/table_transfer_backend.py +203 -0
- onec_runtime/table_value.py +311 -0
- onec_runtime/toolchain.py +730 -0
- onec_runtime/value_materialization.py +432 -0
- onec_runtime/value_transfer_backend.py +199 -0
- onec_runtime/worker_breakpoints.py +1431 -0
- onec_runtime/worker_epf.py +313 -0
- onec_runtime/worker_epf_template.py +53 -0
- onec_runtime/worker_stage_protocol.py +516 -0
- onec_runtime/worker_universe.py +3772 -0
- onec_runtime_build/__init__.py +1 -0
- onec_runtime_build/parser_target_development.py +116 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from base64 import b64decode
|
|
4
|
+
import binascii
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from hashlib import sha256
|
|
8
|
+
import re
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from onec_runtime.compact_table import decode_compact_table_payload
|
|
14
|
+
from onec_runtime.errors import ProtocolError
|
|
15
|
+
from onec_runtime.experiment import bsl_string_literal
|
|
16
|
+
from onec_runtime.performance_profile import PhaseRecorder
|
|
17
|
+
from onec_runtime.rdbg.models import CollectionRow
|
|
18
|
+
from onec_runtime.table_materialization import ReferenceMode, ReferencePolicy
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_HANDLE = re.compile(
|
|
22
|
+
r"Контекст\.[^\W\d]\w*(?:\.[^\W\d]\w*)*\Z",
|
|
23
|
+
re.UNICODE,
|
|
24
|
+
)
|
|
25
|
+
_CONTEXT_KEY = re.compile(r"__onec_compact_table_[0-9a-f]{32}\Z")
|
|
26
|
+
_HASH = re.compile(r"[0-9a-f]{64}\Z")
|
|
27
|
+
_IDENTIFIER = re.compile(r"[^\W\d]\w*\Z", re.UNICODE)
|
|
28
|
+
_SCALAR_KINDS = frozenset(
|
|
29
|
+
{"string", "nullable_string", "boolean", "integer", "number", "datetime", "uuid"}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class CompactColumn:
|
|
35
|
+
name: str
|
|
36
|
+
kind: str
|
|
37
|
+
is_reference: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _compact_cell_kind(type_name: str) -> tuple[str, bool] | None:
|
|
41
|
+
if type_name in {"Неопределено", "Null"}:
|
|
42
|
+
return None
|
|
43
|
+
if type_name.startswith("ПеречислениеСсылка."):
|
|
44
|
+
return "nullable_string", False
|
|
45
|
+
if "Ссылка." in type_name:
|
|
46
|
+
return "reference", True
|
|
47
|
+
kinds = {
|
|
48
|
+
"Строка": "nullable_string",
|
|
49
|
+
"Булево": "boolean",
|
|
50
|
+
"Число": "number",
|
|
51
|
+
"Дата": "datetime",
|
|
52
|
+
"УникальныйИдентификатор": "uuid",
|
|
53
|
+
}
|
|
54
|
+
kind = kinds.get(type_name)
|
|
55
|
+
if kind is None:
|
|
56
|
+
raise ProtocolError("unsupported compact table column type")
|
|
57
|
+
return kind, False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def infer_compact_columns(
|
|
61
|
+
rows: tuple[CollectionRow, ...],
|
|
62
|
+
) -> tuple[CompactColumn, ...] | None:
|
|
63
|
+
if not rows:
|
|
64
|
+
return None
|
|
65
|
+
names = tuple(cell.name for cell in rows[0].cells)
|
|
66
|
+
if not names or len(set(names)) != len(names):
|
|
67
|
+
raise ProtocolError("compact table frame schema is invalid")
|
|
68
|
+
observed: list[tuple[str, bool] | None] = [None] * len(names)
|
|
69
|
+
for row in rows:
|
|
70
|
+
if tuple(cell.name for cell in row.cells) != names:
|
|
71
|
+
raise ProtocolError("compact table frame schema changed")
|
|
72
|
+
for ordinal, cell in enumerate(row.cells):
|
|
73
|
+
candidate = _compact_cell_kind(cell.type_name)
|
|
74
|
+
if candidate is None:
|
|
75
|
+
continue
|
|
76
|
+
if observed[ordinal] is None:
|
|
77
|
+
observed[ordinal] = candidate
|
|
78
|
+
elif observed[ordinal] != candidate:
|
|
79
|
+
raise ProtocolError("compact table frame column type changed")
|
|
80
|
+
if any(item is None for item in observed):
|
|
81
|
+
return None
|
|
82
|
+
return tuple(
|
|
83
|
+
CompactColumn(name, item[0], item[1])
|
|
84
|
+
for name, item in zip(names, observed, strict=True)
|
|
85
|
+
if item is not None
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def infer_declared_compact_columns(
|
|
90
|
+
rows: tuple[CollectionRow, ...],
|
|
91
|
+
) -> tuple[CompactColumn, ...] | None:
|
|
92
|
+
if not rows:
|
|
93
|
+
return None
|
|
94
|
+
columns: list[CompactColumn] = []
|
|
95
|
+
for row in rows:
|
|
96
|
+
cells = {cell.name: cell for cell in row.cells}
|
|
97
|
+
if set(cells) != {"Имя", "Вид", "Ссылка"}:
|
|
98
|
+
raise ProtocolError("declared compact table schema is invalid")
|
|
99
|
+
name = cells["Имя"].value_string
|
|
100
|
+
kind = cells["Вид"].value_string
|
|
101
|
+
is_reference = cells["Ссылка"].value_boolean
|
|
102
|
+
if not name or is_reference is None:
|
|
103
|
+
raise ProtocolError("declared compact table column is invalid")
|
|
104
|
+
if not kind:
|
|
105
|
+
return None
|
|
106
|
+
if kind not in _SCALAR_KINDS | {"reference"}:
|
|
107
|
+
raise ProtocolError("declared compact table kind is invalid")
|
|
108
|
+
if (kind == "reference") != is_reference:
|
|
109
|
+
raise ProtocolError("declared compact table reference marker is invalid")
|
|
110
|
+
columns.append(CompactColumn(name, kind, is_reference))
|
|
111
|
+
if len({column.name for column in columns}) != len(columns):
|
|
112
|
+
raise ProtocolError("declared compact table column names are invalid")
|
|
113
|
+
return tuple(columns)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _reference_mode(value: str | ReferenceMode) -> ReferenceMode:
|
|
117
|
+
try:
|
|
118
|
+
return ReferenceMode(value)
|
|
119
|
+
except ValueError as error:
|
|
120
|
+
raise ProtocolError("unknown table reference mode") from error
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def build_compact_transfer_instruction(
|
|
124
|
+
handle: str,
|
|
125
|
+
policy: ReferencePolicy,
|
|
126
|
+
context_key: str,
|
|
127
|
+
*,
|
|
128
|
+
runtime_generation: int,
|
|
129
|
+
context_generation: int,
|
|
130
|
+
columns: tuple[CompactColumn, ...] | None = None,
|
|
131
|
+
max_rows: int | None = None,
|
|
132
|
+
max_payload_bytes: int | None = None,
|
|
133
|
+
) -> str:
|
|
134
|
+
if not _HANDLE.fullmatch(handle):
|
|
135
|
+
raise ProtocolError(
|
|
136
|
+
"table handle must be one direct or dotted persistent Context path"
|
|
137
|
+
)
|
|
138
|
+
if not _CONTEXT_KEY.fullmatch(context_key):
|
|
139
|
+
raise ProtocolError("compact table context key is invalid")
|
|
140
|
+
if runtime_generation <= 0 or context_generation <= 0:
|
|
141
|
+
raise ProtocolError("table transfer generations must be positive")
|
|
142
|
+
bounded_rows = _optional_budget(max_rows, "table row budget")
|
|
143
|
+
bounded_bytes = _optional_budget(max_payload_bytes, "table byte budget")
|
|
144
|
+
default = _reference_mode(policy.refs)
|
|
145
|
+
overrides = dict(policy.ref_columns or {})
|
|
146
|
+
if columns is not None:
|
|
147
|
+
return _build_specialized_compact_transfer_instruction(
|
|
148
|
+
handle,
|
|
149
|
+
policy,
|
|
150
|
+
context_key,
|
|
151
|
+
runtime_generation=runtime_generation,
|
|
152
|
+
context_generation=context_generation,
|
|
153
|
+
columns=columns,
|
|
154
|
+
max_rows=bounded_rows,
|
|
155
|
+
max_payload_bytes=bounded_bytes,
|
|
156
|
+
)
|
|
157
|
+
lines = ["РежимыСсылокМатериализации = Новый Соответствие;"]
|
|
158
|
+
for column in sorted(overrides):
|
|
159
|
+
if not isinstance(column, str) or not column:
|
|
160
|
+
raise ProtocolError("table reference column name is invalid")
|
|
161
|
+
mode = _reference_mode(overrides[column])
|
|
162
|
+
lines.append(
|
|
163
|
+
"РежимыСсылокМатериализации.Вставить("
|
|
164
|
+
f"{bsl_string_literal(column)}, {bsl_string_literal(mode.value)});"
|
|
165
|
+
)
|
|
166
|
+
lines.extend(
|
|
167
|
+
[
|
|
168
|
+
"КомпактнаяМатериализация = "
|
|
169
|
+
"RuntimeTableTransferServer.СериализоватьКомпактнуюТаблицу("
|
|
170
|
+
f"{handle}, {bsl_string_literal(default.value)}, "
|
|
171
|
+
"РежимыСсылокМатериализации, "
|
|
172
|
+
f"{bounded_rows}, {bounded_bytes});",
|
|
173
|
+
f"Контекст.Вставить({bsl_string_literal(context_key)}, "
|
|
174
|
+
"КомпактнаяМатериализация.Base64);",
|
|
175
|
+
"Результат = "
|
|
176
|
+
f"Формат({runtime_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
|
|
177
|
+
f"Формат({context_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
|
|
178
|
+
"Формат(КомпактнаяМатериализация.Размер, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
|
|
179
|
+
"КомпактнаяМатериализация.Хеш + \"|\" + "
|
|
180
|
+
"Формат(СтрДлина(КомпактнаяМатериализация.Base64), "
|
|
181
|
+
"\"ЧГ=0; ЧДЦ=0\");",
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
return "\n".join(lines)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _optional_budget(value: int | None, label: str) -> int:
|
|
188
|
+
if value is None:
|
|
189
|
+
return 0
|
|
190
|
+
if type(value) is not int or value <= 0:
|
|
191
|
+
raise ProtocolError(f"{label} must be positive")
|
|
192
|
+
return value
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _table_field(row_name: str, column_name: str) -> str:
|
|
196
|
+
if _IDENTIFIER.fullmatch(column_name):
|
|
197
|
+
return f"{row_name}.{column_name}"
|
|
198
|
+
return f"{row_name}[{bsl_string_literal(column_name)}]"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _metadata_lines(
|
|
202
|
+
context_key: str,
|
|
203
|
+
*,
|
|
204
|
+
runtime_generation: int,
|
|
205
|
+
context_generation: int,
|
|
206
|
+
) -> list[str]:
|
|
207
|
+
return [
|
|
208
|
+
f"Контекст.Вставить({bsl_string_literal(context_key)}, "
|
|
209
|
+
"КомпактнаяМатериализация.Base64);",
|
|
210
|
+
"Результат = "
|
|
211
|
+
f"Формат({runtime_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
|
|
212
|
+
f"Формат({context_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
|
|
213
|
+
"Формат(КомпактнаяМатериализация.Размер, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
|
|
214
|
+
"КомпактнаяМатериализация.Хеш + \"|\" + "
|
|
215
|
+
"Формат(СтрДлина(КомпактнаяМатериализация.Base64), "
|
|
216
|
+
"\"ЧГ=0; ЧДЦ=0\");",
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _build_specialized_compact_transfer_instruction(
|
|
221
|
+
handle: str,
|
|
222
|
+
policy: ReferencePolicy,
|
|
223
|
+
context_key: str,
|
|
224
|
+
*,
|
|
225
|
+
runtime_generation: int,
|
|
226
|
+
context_generation: int,
|
|
227
|
+
columns: tuple[CompactColumn, ...],
|
|
228
|
+
max_rows: int,
|
|
229
|
+
max_payload_bytes: int,
|
|
230
|
+
) -> str:
|
|
231
|
+
if not columns:
|
|
232
|
+
raise ProtocolError("compact table schema has no columns")
|
|
233
|
+
names = [column.name for column in columns]
|
|
234
|
+
if any(not name for name in names) or len(set(names)) != len(names):
|
|
235
|
+
raise ProtocolError("compact table column names are invalid")
|
|
236
|
+
default = _reference_mode(policy.refs)
|
|
237
|
+
overrides = dict(policy.ref_columns or {})
|
|
238
|
+
references = {column.name for column in columns if column.is_reference}
|
|
239
|
+
unknown = set(overrides) - references
|
|
240
|
+
if unknown:
|
|
241
|
+
raise ProtocolError("unknown reference column: " + ", ".join(sorted(unknown)))
|
|
242
|
+
|
|
243
|
+
effective_kinds: list[str] = []
|
|
244
|
+
reference_modes: dict[str, ReferenceMode] = {}
|
|
245
|
+
for column in columns:
|
|
246
|
+
if column.is_reference:
|
|
247
|
+
mode = _reference_mode(overrides.get(column.name, default))
|
|
248
|
+
effective_kinds.append(mode.value)
|
|
249
|
+
reference_modes[column.name] = mode
|
|
250
|
+
else:
|
|
251
|
+
if column.kind not in _SCALAR_KINDS:
|
|
252
|
+
raise ProtocolError(
|
|
253
|
+
f"unsupported compact scalar kind for column {column.name}"
|
|
254
|
+
)
|
|
255
|
+
effective_kinds.append(column.kind)
|
|
256
|
+
|
|
257
|
+
lines = [
|
|
258
|
+
"СтрокиJSONL = Новый Массив;",
|
|
259
|
+
"КолонкиJSONL = Новый Массив;",
|
|
260
|
+
f"МаксимумСтрокМатериализации = {max_rows};",
|
|
261
|
+
f"МаксимумБайтМатериализации = {max_payload_bytes};",
|
|
262
|
+
]
|
|
263
|
+
lines.extend(
|
|
264
|
+
f"КолонкиJSONL.Добавить({bsl_string_literal(name)});" for name in names
|
|
265
|
+
)
|
|
266
|
+
lines.append("ВидыJSONL = Новый Массив;")
|
|
267
|
+
lines.extend(
|
|
268
|
+
f"ВидыJSONL.Добавить({bsl_string_literal(kind)});"
|
|
269
|
+
for kind in effective_kinds
|
|
270
|
+
)
|
|
271
|
+
lines.append("РежимыСсылокJSONL = Новый Структура;")
|
|
272
|
+
lines.extend(
|
|
273
|
+
"РежимыСсылокJSONL.Вставить("
|
|
274
|
+
f"{bsl_string_literal(name)}, {bsl_string_literal(mode.value)});"
|
|
275
|
+
for name, mode in reference_modes.items()
|
|
276
|
+
)
|
|
277
|
+
lines.extend(
|
|
278
|
+
[
|
|
279
|
+
"СхемаJSONL = Новый Структура;",
|
|
280
|
+
'СхемаJSONL.Вставить("version", 1);',
|
|
281
|
+
'СхемаJSONL.Вставить("columns", КолонкиJSONL);',
|
|
282
|
+
'СхемаJSONL.Вставить("kinds", ВидыJSONL);',
|
|
283
|
+
'СхемаJSONL.Вставить("reference_modes", РежимыСсылокJSONL);',
|
|
284
|
+
"ЗаписьСхемыJSONL = Новый ЗаписьJSON;",
|
|
285
|
+
"ЗаписьСхемыJSONL.УстановитьСтроку("
|
|
286
|
+
"Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));",
|
|
287
|
+
"ЗаписатьJSON(ЗаписьСхемыJSONL, СхемаJSONL);",
|
|
288
|
+
"СтрокиJSONL.Добавить(ЗаписьСхемыJSONL.Закрыть());",
|
|
289
|
+
"РазмерJSONL = ПолучитьДвоичныеДанныеИзСтроки(СтрокиJSONL[0] + "
|
|
290
|
+
"Символы.ПС, КодировкаТекста.UTF8, Ложь).Размер();",
|
|
291
|
+
"КоличествоСтрокJSONL = 0;",
|
|
292
|
+
"Если МаксимумБайтМатериализации > 0 И "
|
|
293
|
+
"РазмерJSONL > МаксимумБайтМатериализации Тогда",
|
|
294
|
+
' ВызватьИсключение "Превышен лимит байтов компактной таблицы";',
|
|
295
|
+
"КонецЕсли;",
|
|
296
|
+
"ТаблицаМатериализации = RuntimeTableTransferServer."
|
|
297
|
+
f"ПодготовитьТабличноеЗначение({handle}, "
|
|
298
|
+
"МаксимумСтрокМатериализации);",
|
|
299
|
+
"Для Каждого СтрокаМатериализации Из ТаблицаМатериализации Цикл",
|
|
300
|
+
" Если МаксимумСтрокМатериализации > 0 И "
|
|
301
|
+
"КоличествоСтрокJSONL >= МаксимумСтрокМатериализации Тогда",
|
|
302
|
+
' ВызватьИсключение "Превышен лимит строк компактной таблицы";',
|
|
303
|
+
" КонецЕсли;",
|
|
304
|
+
" ЗначенияСтроки = Новый Массив;",
|
|
305
|
+
]
|
|
306
|
+
)
|
|
307
|
+
for ordinal, column in enumerate(columns):
|
|
308
|
+
value = _table_field("СтрокаМатериализации", column.name)
|
|
309
|
+
if not column.is_reference:
|
|
310
|
+
encoded = (
|
|
311
|
+
f"XMLСтрока({value})"
|
|
312
|
+
if column.kind == "datetime"
|
|
313
|
+
else (
|
|
314
|
+
f"?({value} = Неопределено Или {value} = NULL, "
|
|
315
|
+
f"Неопределено, Строка({value}))"
|
|
316
|
+
)
|
|
317
|
+
if column.kind == "nullable_string"
|
|
318
|
+
else f"Строка({value})"
|
|
319
|
+
if column.kind == "uuid"
|
|
320
|
+
else value
|
|
321
|
+
)
|
|
322
|
+
lines.append(f" ЗначенияСтроки.Добавить({encoded});")
|
|
323
|
+
continue
|
|
324
|
+
mode = reference_modes[column.name]
|
|
325
|
+
if mode is ReferenceMode.PRESENTATION:
|
|
326
|
+
encoded = f"?(ЗначениеЗаполнено({value}), Строка({value}), Неопределено)"
|
|
327
|
+
lines.append(f" ЗначенияСтроки.Добавить({encoded});")
|
|
328
|
+
elif mode is ReferenceMode.UUID:
|
|
329
|
+
encoded = (
|
|
330
|
+
f"?(ЗначениеЗаполнено({value}), "
|
|
331
|
+
f"Строка({value}.УникальныйИдентификатор()), Неопределено)"
|
|
332
|
+
)
|
|
333
|
+
lines.append(f" ЗначенияСтроки.Добавить({encoded});")
|
|
334
|
+
else:
|
|
335
|
+
temporary = f"ОбеФормыСсылки{ordinal}"
|
|
336
|
+
lines.extend(
|
|
337
|
+
[
|
|
338
|
+
f" Если ЗначениеЗаполнено({value}) Тогда",
|
|
339
|
+
f" {temporary} = Новый Массив;",
|
|
340
|
+
f" {temporary}.Добавить(Строка({value}));",
|
|
341
|
+
f" {temporary}.Добавить(Строка("
|
|
342
|
+
f"{value}.УникальныйИдентификатор()));",
|
|
343
|
+
" Иначе",
|
|
344
|
+
f" {temporary} = Неопределено;",
|
|
345
|
+
" КонецЕсли;",
|
|
346
|
+
f" ЗначенияСтроки.Добавить({temporary});",
|
|
347
|
+
]
|
|
348
|
+
)
|
|
349
|
+
lines.extend(
|
|
350
|
+
[
|
|
351
|
+
" ЗаписьСтрокиJSONL = Новый ЗаписьJSON;",
|
|
352
|
+
" ЗаписьСтрокиJSONL.УстановитьСтроку("
|
|
353
|
+
"Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));",
|
|
354
|
+
" ЗаписатьJSON(ЗаписьСтрокиJSONL, ЗначенияСтроки);",
|
|
355
|
+
" СтрокаJSONL = ЗаписьСтрокиJSONL.Закрыть();",
|
|
356
|
+
" РазмерСтрокиJSONL = ПолучитьДвоичныеДанныеИзСтроки("
|
|
357
|
+
"СтрокаJSONL + Символы.ПС, КодировкаТекста.UTF8, Ложь).Размер();",
|
|
358
|
+
" Если МаксимумБайтМатериализации > 0 И "
|
|
359
|
+
"РазмерJSONL + РазмерСтрокиJSONL > МаксимумБайтМатериализации Тогда",
|
|
360
|
+
' ВызватьИсключение "Превышен лимит байтов компактной таблицы";',
|
|
361
|
+
" КонецЕсли;",
|
|
362
|
+
" СтрокиJSONL.Добавить(СтрокаJSONL);",
|
|
363
|
+
" РазмерJSONL = РазмерJSONL + РазмерСтрокиJSONL;",
|
|
364
|
+
" КоличествоСтрокJSONL = КоличествоСтрокJSONL + 1;",
|
|
365
|
+
"КонецЦикла;",
|
|
366
|
+
"КомпактнаяМатериализация = RuntimeTableTransferServer."
|
|
367
|
+
"ЗавершитьКомпактнуюМатериализацию(СтрокиJSONL);",
|
|
368
|
+
]
|
|
369
|
+
)
|
|
370
|
+
lines.extend(
|
|
371
|
+
_metadata_lines(
|
|
372
|
+
context_key,
|
|
373
|
+
runtime_generation=runtime_generation,
|
|
374
|
+
context_generation=context_generation,
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
return "\n".join(lines)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class CompactRuntimeTableTransfer:
|
|
381
|
+
def __init__(
|
|
382
|
+
self,
|
|
383
|
+
instruction_executor: Callable[[str], object],
|
|
384
|
+
context_reader: Callable[[str, int], str],
|
|
385
|
+
*,
|
|
386
|
+
runtime_generation: Callable[[], int],
|
|
387
|
+
context_generation: int,
|
|
388
|
+
context_cleaner: Callable[[str], None] | None = None,
|
|
389
|
+
schema_reader: Callable[[str], tuple[CompactColumn, ...] | None] | None = None,
|
|
390
|
+
max_text_size: int = 100_000_000,
|
|
391
|
+
max_payload_bytes: int = 75_000_000,
|
|
392
|
+
max_rows: int | None = None,
|
|
393
|
+
key_factory: Callable[[], str] | None = None,
|
|
394
|
+
profiler: PhaseRecorder | None = None,
|
|
395
|
+
) -> None:
|
|
396
|
+
self._execute = instruction_executor
|
|
397
|
+
self._read = context_reader
|
|
398
|
+
self._runtime_generation = runtime_generation
|
|
399
|
+
self._context_generation = context_generation
|
|
400
|
+
self._clean = context_cleaner
|
|
401
|
+
self._schema_reader = schema_reader
|
|
402
|
+
self._expected_runtime_generation = runtime_generation()
|
|
403
|
+
self._max_text_size = max_text_size
|
|
404
|
+
if max_payload_bytes <= 0:
|
|
405
|
+
raise ValueError("max payload bytes must be positive")
|
|
406
|
+
self._max_payload_bytes = max_payload_bytes
|
|
407
|
+
self._max_rows = _optional_budget(max_rows, "table row budget")
|
|
408
|
+
self._key_factory = key_factory or (
|
|
409
|
+
lambda: "__onec_compact_table_" + uuid4().hex
|
|
410
|
+
)
|
|
411
|
+
self._profiler = profiler
|
|
412
|
+
|
|
413
|
+
def _profile(self, phase: str, operation, **metadata): # type: ignore[no-untyped-def]
|
|
414
|
+
if self._profiler is None:
|
|
415
|
+
return operation()
|
|
416
|
+
return self._profiler.measure(phase, operation, **metadata)
|
|
417
|
+
|
|
418
|
+
def to_df(self, handle: str, policy: ReferencePolicy) -> pd.DataFrame:
|
|
419
|
+
payload = self.payload(handle, policy)
|
|
420
|
+
return self._profile(
|
|
421
|
+
"table.build_dataframe",
|
|
422
|
+
lambda: decode_compact_table_payload(payload, policy),
|
|
423
|
+
input_bytes=len(payload),
|
|
424
|
+
item_count=lambda frame: len(frame.index),
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def payload(self, handle: str, policy: ReferencePolicy) -> bytes:
|
|
428
|
+
generation = self._runtime_generation()
|
|
429
|
+
if generation != self._expected_runtime_generation:
|
|
430
|
+
raise ProtocolError("table materializer runtime generation is stale")
|
|
431
|
+
key = self._key_factory()
|
|
432
|
+
columns = self._profile(
|
|
433
|
+
"table.schema_read",
|
|
434
|
+
lambda: self._schema_reader(handle) if self._schema_reader else None,
|
|
435
|
+
)
|
|
436
|
+
source = build_compact_transfer_instruction(
|
|
437
|
+
handle,
|
|
438
|
+
policy,
|
|
439
|
+
key,
|
|
440
|
+
runtime_generation=generation,
|
|
441
|
+
context_generation=self._context_generation,
|
|
442
|
+
columns=columns,
|
|
443
|
+
max_rows=self._max_rows or None,
|
|
444
|
+
max_payload_bytes=self._max_payload_bytes,
|
|
445
|
+
)
|
|
446
|
+
metadata = self._profile(
|
|
447
|
+
"table.prepare_jsonl",
|
|
448
|
+
lambda: self._execute(source),
|
|
449
|
+
input_bytes=len(source.encode("utf-8")),
|
|
450
|
+
)
|
|
451
|
+
if not isinstance(metadata, str):
|
|
452
|
+
if self._clean is not None:
|
|
453
|
+
self._clean(key)
|
|
454
|
+
raise ProtocolError("compact table metadata is not a string")
|
|
455
|
+
fields = metadata.split("|")
|
|
456
|
+
if len(fields) != 5:
|
|
457
|
+
if self._clean is not None:
|
|
458
|
+
self._clean(key)
|
|
459
|
+
raise ProtocolError("compact table metadata field count is invalid")
|
|
460
|
+
try:
|
|
461
|
+
observed_runtime = int(fields[0])
|
|
462
|
+
observed_context = int(fields[1])
|
|
463
|
+
byte_count = int(fields[2])
|
|
464
|
+
base64_count = int(fields[4])
|
|
465
|
+
except ValueError as error:
|
|
466
|
+
if self._clean is not None:
|
|
467
|
+
self._clean(key)
|
|
468
|
+
raise ProtocolError("compact table metadata number is invalid") from error
|
|
469
|
+
payload_hash = fields[3]
|
|
470
|
+
if (
|
|
471
|
+
observed_runtime != generation
|
|
472
|
+
or observed_context != self._context_generation
|
|
473
|
+
or byte_count <= 0
|
|
474
|
+
or byte_count > self._max_payload_bytes
|
|
475
|
+
or base64_count <= 0
|
|
476
|
+
or base64_count > self._max_text_size
|
|
477
|
+
or not _HASH.fullmatch(payload_hash)
|
|
478
|
+
):
|
|
479
|
+
if self._clean is not None:
|
|
480
|
+
self._clean(key)
|
|
481
|
+
raise ProtocolError("compact table metadata is invalid")
|
|
482
|
+
content = self._profile(
|
|
483
|
+
"table.transfer_base64",
|
|
484
|
+
lambda: self._read(key, self._max_text_size),
|
|
485
|
+
output_bytes=lambda value: len(value.encode("ascii")),
|
|
486
|
+
)
|
|
487
|
+
if len(content) != base64_count:
|
|
488
|
+
raise ProtocolError("compact table metadata is invalid")
|
|
489
|
+
try:
|
|
490
|
+
payload = self._profile(
|
|
491
|
+
"table.decode_base64",
|
|
492
|
+
lambda: b64decode("".join(content.split()), validate=True),
|
|
493
|
+
input_bytes=len(content.encode("ascii")),
|
|
494
|
+
output_bytes=len,
|
|
495
|
+
)
|
|
496
|
+
except (ValueError, binascii.Error) as error:
|
|
497
|
+
raise ProtocolError("compact table Base64 payload is invalid") from error
|
|
498
|
+
if len(payload) != byte_count or sha256(payload).hexdigest() != payload_hash:
|
|
499
|
+
raise ProtocolError("compact table payload integrity check failed")
|
|
500
|
+
return payload
|