feat: DB管线补全 + 新增orchestrator_db/program_schema/to_sql + 清理临时脚本
This commit is contained in:
+161
-32
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user