feat: DB管线补全 + 新增orchestrator_db/program_schema/to_sql + 清理临时脚本

This commit is contained in:
hangshuo652
2026-07-11 14:55:52 +08:00
parent 40e8a50ab4
commit af37e33b98
32 changed files with 3232 additions and 255 deletions
+161 -32
View File
@@ -705,6 +705,12 @@ class _BrParser:
if m_when:
cond_upper = m_when.group(1).strip()
self.advance()
# Continuation: next line may be = VALUE (COBOL multi-line WHEN)
if self.pos < len(self.lines):
peek = self.clean()
if peek and not re.match(r'^(WHEN|AT\s+END|END-SEARCH)', peek, re.IGNORECASE):
cond_upper += ' ' + peek
self.advance()
cond_tree = parse_compound_condition(cond_upper, self.fields)
body_seq = self.parse_seq(
end_check=lambda l: re.match(r'^(WHEN|AT\s+END)\b', l) or l in ('END-SEARCH',)
@@ -761,7 +767,7 @@ class _BrParser:
return node
m = re.match(r'^WHEN\s+(.+?)\s*$', line)
if m:
raw_val = m.group(1).strip().strip("'").strip('"')
raw_val = m.group(1).strip()
self.advance()
# Capture multi-line WHEN conditions (AND/OR continuation)
while self.pos < len(self.lines):
@@ -777,7 +783,7 @@ class _BrParser:
else:
case_seq = self.parse_seq(end_check=lambda l: l.startswith('WHEN') or l == 'END-EVALUATE')
if node.subjects:
vals = [v.strip().strip("'").strip('"')
vals = [v.strip()
for v in re.split(r'\s+ALSO\s+', raw_val)]
node.when_list.append((vals, case_seq))
else:
@@ -1188,6 +1194,21 @@ class _BrParser:
_RE_WHERE = re.compile(r'\bWHERE\b\s+(.*)', re.IGNORECASE)
_RE_SQL_INSERT = re.compile(
r'INSERT\s+INTO\s+(\w[\w-]*)\s*\(([^)]+)\)\s+VALUES\s*\(([^)]+)\)',
re.IGNORECASE
)
_RE_SQL_DELETE = re.compile(
r'DELETE\s+FROM\s+(\w[\w-]*)(?:\s+WHERE\s+(.+))?',
re.IGNORECASE
)
_RE_SQL_UPDATE = re.compile(
r'UPDATE\s+(\w[\w-]*)\s+SET\s+(.+?)(?:\s+WHERE\s+(.+))?$',
re.IGNORECASE
)
def _parse_sql_block(self) -> str:
"""Consume lines from EXEC SQL until END-EXEC. Returns SQL text."""
texts = []
@@ -1209,43 +1230,119 @@ class _BrParser:
def _parse_sql(self, sql_text: str):
"""Parse SQL text from EXEC SQL block. Returns Assign node or None."""
# 1) SELECT ... INTO ... FROM
m = self._RE_SELECT_INTO.search(sql_text)
if not m:
return None
if m:
select_list = m.group(1).strip()
into_raw = m.group(2).strip()
from_table = m.group(3).strip().upper()
remaining = sql_text[m.end():].strip()
select_list = m.group(1).strip()
into_raw = m.group(2).strip()
from_table = m.group(3).strip().upper()
remaining = sql_text[m.end():].strip()
into_vars = []
for v in re.split(r'\s*,\s*', into_raw):
v = v.strip().lstrip(':')
parts = v.split(':')
into_vars.append(parts[0].upper())
if len(parts) > 1:
into_vars.append(parts[1].upper())
# Parse INTO variables (handle indicator vars: :host:indicator)
into_vars = []
for v in re.split(r'\s*,\s*', into_raw):
v = v.strip().lstrip(':')
parts = v.split(':')
into_vars.append(parts[0].upper())
if len(parts) > 1:
into_vars.append(parts[1].upper())
where_clause = ''
wm = self._RE_WHERE.search(remaining)
if wm:
where_clause = wm.group(1).strip()
# Extract WHERE clause
where_clause = ''
wm = self._RE_WHERE.search(remaining)
if wm:
where_clause = wm.group(1).strip()
info = {
'type': 'exec_sql_select',
'table': from_table,
'select_list': select_list,
'into_vars': into_vars,
'where': where_clause,
'sql_text': sql_text,
}
info = {
'type': 'exec_sql_select',
'table': from_table,
'select_list': select_list,
'into_vars': into_vars,
'where': where_clause,
'sql_text': sql_text,
}
for var in into_vars:
self.assignments.setdefault(var, []).append(info)
for var in into_vars:
self.assignments.setdefault(var, []).append(info)
return Assign(into_vars[0], info)
return Assign(into_vars[0], info)
# 2) INSERT INTO table (...) VALUES (...)
m = self._RE_SQL_INSERT.search(sql_text)
if m:
table = m.group(1).strip().upper()
columns_str = m.group(2).strip()
values_str = m.group(3).strip()
host_vars = []
for v in re.split(r'\s*,\s*', values_str):
v = v.strip()
if v.startswith(':'):
v = v.lstrip(':')
parts = v.split(':')
host_vars.append(parts[0].upper())
info = {
'type': 'exec_sql_insert',
'table': table,
'columns': [c.strip() for c in columns_str.split(',')],
'raw_values': values_str,
'host_vars': host_vars,
'sql_text': sql_text,
}
synthetic = f'__SQL_INSERT_{table}'
self.assignments.setdefault(synthetic, []).append(info)
return Assign(synthetic, info)
# 3) DELETE FROM table WHERE ...
m = self._RE_SQL_DELETE.search(sql_text)
if m:
table = m.group(1).strip().upper()
where_clause = m.group(2).strip() if m.group(2) else ''
host_vars = re.findall(r':(\w[\w-]*)', where_clause)
info = {
'type': 'exec_sql_delete',
'table': table,
'where': where_clause,
'host_vars': [h.upper() for h in host_vars],
'sql_text': sql_text,
}
synthetic = f'__SQL_DELETE_{table}'
self.assignments.setdefault(synthetic, []).append(info)
return Assign(synthetic, info)
# 4) UPDATE table SET ... WHERE ...
m = self._RE_SQL_UPDATE.search(sql_text)
if m:
table = m.group(1).strip().upper()
set_clause = m.group(2).strip()
where_clause = m.group(3).strip() if m.group(3) else ''
host_vars = []
for part in re.split(r'\s*,\s*', set_clause):
sm = re.match(r'\w[\w-]*\s*=\s*(:\w[\w-]*(?::\w[\w-]*)?)', part, re.IGNORECASE)
if sm:
hv = sm.group(1).lstrip(':')
parts = hv.split(':')
host_vars.append(parts[0].upper())
for wv in re.findall(r':(\w[\w-]*)', where_clause):
wvu = wv.upper()
if wvu not in host_vars:
host_vars.append(wvu)
info = {
'type': 'exec_sql_update',
'table': table,
'set_clause': set_clause,
'where': where_clause,
'host_vars': host_vars,
'sql_text': sql_text,
}
synthetic = f'__SQL_UPDATE_{table}'
self.assignments.setdefault(synthetic, []).append(info)
return Assign(synthetic, info)
return None
# ── 工具函数 ──
@@ -1527,6 +1624,12 @@ def propagate_assignments(rec, assignments, fields, file_sec=None):
start = asgn['refmod_start'] - 1
end = start + asgn['refmod_length']
src_val = src_val[start:end]
# Type-safe MOVE: alphanumeric→numeric → strip non-digit chars
_tgt_pi = next((f.get('pic_info', {}) for f in fields if f['name'] == resolved_tgt), {})
if _tgt_pi.get('type') == 'numeric' and not src_val.lstrip('-').replace('.', '').isdigit():
digits = _tgt_pi.get('digits', 0) + _tgt_pi.get('decimal', 0)
src_val = ''.join(c for c in src_val if c.isdigit())[:max(digits, 1)] or '0'
src_val = src_val.zfill(max(digits, 1))
rec[resolved_tgt] = src_val
# Pass 2: literal MOVE
@@ -1979,3 +2082,29 @@ def _find_multi_write_fds(tree, field_to_fd):
loop_write = set()
_collect_write_fds(tree.children[main_loop_idx], loop_write, field_to_fd)
return pre_write & loop_write
# ── EXEC SQL ブロック抽出(preprocess で除去される前の生ソースから)──
_RE_EXEC_SQL = re.compile(
r'EXEC\s+SQL\s+(.*?)\s+END-EXEC\.?',
re.DOTALL | re.IGNORECASE
)
def extract_sql_assignments(source: str) -> dict:
"""原ソースから EXEC SQL ブロックを抽出し Assign 情報を返す。
preprocess() が全 EXEC SQL ブロックを除去するため、その前に
生ソースから直接抽出する。戻り値は assignments dict と互換。
"""
from collections import defaultdict
parser = _BrParser([])
parser.assignments = defaultdict(list)
for m in _RE_EXEC_SQL.finditer(source):
sql_text = re.sub(r'\s+', ' ', m.group(1).strip())
parser._parse_sql(sql_text)
return dict(parser.assignments)