jcl-parser 2.0.2__tar.gz → 2.0.3__tar.gz
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.
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/PKG-INFO +2 -2
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/PKG-INFO +2 -2
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/__init__.py +1 -1
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_job_builder.py +15 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/explosion.py +69 -19
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/model.py +5 -4
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/sql_detector.py +54 -3
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/pyproject.toml +2 -2
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/LICENSE +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/README.md +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/SOURCES.txt +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/dependency_links.txt +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/entry_points.txt +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/requires.txt +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/top_level.txt +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/__main__.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_model_factories.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_post_parse.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_source_scanner.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_statement_parser.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/builtin_procs.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/cli.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/condition_parser.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/continuation_handler.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/dataset_lifecycle.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/errors.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/include_loader.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/jcl_file_type.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/jes_statements.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/__init__.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/base.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/config.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/rules.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/runner.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/metrics.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/parameter_ast.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/parameter_validators.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/parser.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/plugins/__init__.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/plugins/base.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/proc_loader.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/profiling.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/__init__.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/_control.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/_invoked.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/adrdssu.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/base.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/binder.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/bpxbatch.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/compilers.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/db2_batch.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/dfsrrc00.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/dsnutilb.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/ftp.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/icetool.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/idcams.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebcompr.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebdg.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebgener.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebptpch.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebupdte.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iefbr14.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iehinitt.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iehlist.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iehprogm.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/ikjeft01.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/irxjcl.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/registry.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/sort.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/py.typed +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/rc_analysis.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/system_symbols.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/utils.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/validators.py +0 -0
- {jcl_parser-2.0.2 → jcl_parser-2.0.3}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcl-parser
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: Samuel Dion <sam94dion@gmail.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/landparty/jcl-parser
|
|
8
8
|
Project-URL: Repository, https://github.com/landparty/jcl-parser
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcl-parser
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: Samuel Dion <sam94dion@gmail.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/landparty/jcl-parser
|
|
8
8
|
Project-URL: Repository, https://github.com/landparty/jcl-parser
|
|
@@ -20,7 +20,7 @@ application, for example::
|
|
|
20
20
|
import logging as _logging
|
|
21
21
|
|
|
22
22
|
# Package metadata
|
|
23
|
-
__version__ = "2.0.
|
|
23
|
+
__version__ = "2.0.3" # x-release-please-version
|
|
24
24
|
__author__ = "JCL Parser Team"
|
|
25
25
|
__description__ = "Parse IBM JCL scripts into Python objects"
|
|
26
26
|
|
|
@@ -174,6 +174,10 @@ class JobBuilder:
|
|
|
174
174
|
#: only if some SET symbol is redefined after it was first set.
|
|
175
175
|
self.job_level_steps: list[Step] = []
|
|
176
176
|
self.set_redefined = False
|
|
177
|
+
#: Steps of in-stream / cataloged procedures with the procedure they
|
|
178
|
+
#: belong to; finish() prunes their ``set_symbols`` snapshot the same
|
|
179
|
+
#: way (against the procedure's own final SET table).
|
|
180
|
+
self.procedure_steps: list[tuple[Procedure, Step]] = []
|
|
177
181
|
self.pending_exported: set[str] = set()
|
|
178
182
|
self.pending_jcllib: list[str] = []
|
|
179
183
|
self.pending_includes: list[str] = []
|
|
@@ -315,6 +319,11 @@ class JobBuilder:
|
|
|
315
319
|
**self.job.set_statements,
|
|
316
320
|
}
|
|
317
321
|
self.job_level_steps.append(self.current_step)
|
|
322
|
+
elif self.current_procedure is not None:
|
|
323
|
+
# A SET inside the procedure is in effect from where it is coded,
|
|
324
|
+
# so this step sees the procedure's SET values as they stand now.
|
|
325
|
+
self.current_step.set_symbols = dict(self.current_procedure.set_statements)
|
|
326
|
+
self.procedure_steps.append((self.current_procedure, self.current_step))
|
|
318
327
|
self.in_joblib_concat = False # JOBLIB concatenation ends at first EXEC
|
|
319
328
|
self.concat_override_key = None
|
|
320
329
|
|
|
@@ -820,6 +829,12 @@ class JobBuilder:
|
|
|
820
829
|
for k, v in (step.set_symbols or {}).items()
|
|
821
830
|
):
|
|
822
831
|
step.set_symbols = None
|
|
832
|
+
for procedure, step in self.procedure_steps:
|
|
833
|
+
if all(
|
|
834
|
+
procedure.set_statements.get(k) == v
|
|
835
|
+
for k, v in (step.set_symbols or {}).items()
|
|
836
|
+
):
|
|
837
|
+
step.set_symbols = None
|
|
823
838
|
|
|
824
839
|
def _warn_orphaned_dds(self, job: Job) -> None:
|
|
825
840
|
# orphaned_dds is only ever non-empty here when the whole job had no
|
|
@@ -569,6 +569,8 @@ def expand_proc_invocation(
|
|
|
569
569
|
else:
|
|
570
570
|
# Clone the step
|
|
571
571
|
new_step = _clone_step_for_explosion(proc_step)
|
|
572
|
+
step_scope = _proc_step_scope(expansion_scope, proc_step)
|
|
573
|
+
new_step.set_symbols = None
|
|
572
574
|
|
|
573
575
|
# Generate qualified step name
|
|
574
576
|
if new_step.name:
|
|
@@ -584,18 +586,28 @@ def expand_proc_invocation(
|
|
|
584
586
|
if new_step.program:
|
|
585
587
|
new_step.program = substitute_symbols(
|
|
586
588
|
new_step.program,
|
|
587
|
-
|
|
589
|
+
step_scope,
|
|
588
590
|
strict=strict,
|
|
589
591
|
warnings=warnings,
|
|
590
592
|
)
|
|
591
593
|
|
|
592
|
-
# Substitute symbols in step parameters
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
594
|
+
# Substitute symbols in step parameters. A nested
|
|
595
|
+
# ``EXEC procname`` keeps "&&" in its symbolic values, as a
|
|
596
|
+
# top-level one does (TMP='&&WORK' is a temporary dataset).
|
|
597
|
+
if new_step.is_procedure:
|
|
598
|
+
new_step.parameters = _substitute_proc_call_parameters(
|
|
599
|
+
new_step.parameters,
|
|
600
|
+
step_scope,
|
|
601
|
+
strict=strict,
|
|
602
|
+
warnings=warnings,
|
|
603
|
+
)
|
|
604
|
+
else:
|
|
605
|
+
new_step.parameters = substitute_parameters_dict(
|
|
606
|
+
new_step.parameters,
|
|
607
|
+
step_scope,
|
|
608
|
+
strict=strict,
|
|
609
|
+
warnings=warnings,
|
|
610
|
+
)
|
|
599
611
|
if "COND" in new_step.parameters:
|
|
600
612
|
new_step.parameters["COND"] = _qualify_cond_parameter(
|
|
601
613
|
new_step.parameters["COND"], proc_steps, prefix
|
|
@@ -605,7 +617,7 @@ def expand_proc_invocation(
|
|
|
605
617
|
# for a nested ``EXEC procname``, its PROCSTEP.DDNAME overrides
|
|
606
618
|
for dd in _step_dds_and_overrides(new_step):
|
|
607
619
|
substitute_dd_statement(
|
|
608
|
-
dd,
|
|
620
|
+
dd, step_scope, strict=strict, warnings=warnings
|
|
609
621
|
)
|
|
610
622
|
|
|
611
623
|
result.append(new_step)
|
|
@@ -616,6 +628,24 @@ def expand_proc_invocation(
|
|
|
616
628
|
return expanded_steps
|
|
617
629
|
|
|
618
630
|
|
|
631
|
+
def _proc_step_scope(expansion_scope: SymbolScope, proc_step: Step) -> SymbolScope:
|
|
632
|
+
"""Return *expansion_scope* with the PROC SETs in effect at *proc_step*.
|
|
633
|
+
|
|
634
|
+
A SET inside a procedure is in effect from where it is coded: a step read
|
|
635
|
+
before a later SET redefined a symbol keeps the earlier value
|
|
636
|
+
(``Step.set_symbols``). Symbols it has no snapshot for still resolve
|
|
637
|
+
against the procedure's final SET table.
|
|
638
|
+
"""
|
|
639
|
+
if proc_step.set_symbols is None:
|
|
640
|
+
return expansion_scope
|
|
641
|
+
return SymbolScope(
|
|
642
|
+
set_symbols={**expansion_scope.set_symbols, **proc_step.set_symbols},
|
|
643
|
+
proc_params=expansion_scope.proc_params,
|
|
644
|
+
exec_overrides=expansion_scope.exec_overrides,
|
|
645
|
+
system_provider=expansion_scope.system_provider,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
|
|
619
649
|
_WHOLE_SYMBOL_RE = re.compile(r"&([A-Z0-9@#$]+)\.?")
|
|
620
650
|
|
|
621
651
|
|
|
@@ -1014,6 +1044,33 @@ _EXEC_OVERRIDE_KEYWORDS = frozenset(
|
|
|
1014
1044
|
_PARM_KEYWORDS = frozenset({"PARM", "PARMDD"})
|
|
1015
1045
|
|
|
1016
1046
|
|
|
1047
|
+
def _substitute_proc_call_parameters(
|
|
1048
|
+
parameters: dict[str, Any],
|
|
1049
|
+
scope: SymbolScope,
|
|
1050
|
+
strict: bool = False,
|
|
1051
|
+
allow_undefined: bool = False,
|
|
1052
|
+
warnings: list[str] | None = None,
|
|
1053
|
+
) -> dict[str, Any]:
|
|
1054
|
+
"""Substitute symbols in the parameters of an ``EXEC procname`` statement.
|
|
1055
|
+
|
|
1056
|
+
EXEC keyword overrides (``PARM.PS1='A&&B'``) collapse "&&" to "&" like any
|
|
1057
|
+
other step parameter; a symbolic value keeps it, because it may carry a
|
|
1058
|
+
temporary dataset name (``TMP='&&WORK'``) into the procedure's DDs.
|
|
1059
|
+
"""
|
|
1060
|
+
return {
|
|
1061
|
+
key: substitute_parameters_dict(
|
|
1062
|
+
{key: value},
|
|
1063
|
+
scope,
|
|
1064
|
+
strict=strict,
|
|
1065
|
+
allow_undefined=allow_undefined,
|
|
1066
|
+
warnings=warnings,
|
|
1067
|
+
collapse_double_amp=key.upper().partition(".")[0]
|
|
1068
|
+
in _EXEC_OVERRIDE_KEYWORDS,
|
|
1069
|
+
)[key]
|
|
1070
|
+
for key, value in parameters.items()
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
|
|
1017
1074
|
def _set_exec_keyword(step: Step, keyword: str, value: Any) -> None:
|
|
1018
1075
|
"""Override (or, for an empty value, nullify) *keyword* on *step*."""
|
|
1019
1076
|
if keyword in _PARM_KEYWORDS:
|
|
@@ -1387,16 +1444,9 @@ def explode_job( # noqa: C901 -- complexity baseline, split when touched
|
|
|
1387
1444
|
# So are its EXEC keyword overrides (PARM.PS1='&SYSUID') and
|
|
1388
1445
|
# symbolic values (HLQ=&SYSUID). A symbolic value keeps "&&":
|
|
1389
1446
|
# it may carry a temporary dataset name (TMP='&&WORK').
|
|
1390
|
-
step.parameters =
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
step_scope,
|
|
1394
|
-
allow_undefined=True,
|
|
1395
|
-
collapse_double_amp=key.upper().partition(".")[0]
|
|
1396
|
-
in _EXEC_OVERRIDE_KEYWORDS,
|
|
1397
|
-
)[key]
|
|
1398
|
-
for key, value in step.parameters.items()
|
|
1399
|
-
}
|
|
1447
|
+
step.parameters = _substitute_proc_call_parameters(
|
|
1448
|
+
step.parameters, step_scope, allow_undefined=True
|
|
1449
|
+
)
|
|
1400
1450
|
# Expand procedure
|
|
1401
1451
|
expanded = _explode_step(
|
|
1402
1452
|
step=step,
|
|
@@ -542,10 +542,11 @@ class Step:
|
|
|
542
542
|
dd_override_concatenations: dict[str, list[DDStatement]] = field(
|
|
543
543
|
default_factory=dict
|
|
544
544
|
)
|
|
545
|
-
#: The job's SET symbols as they stood when this step's EXEC was read
|
|
546
|
-
#:
|
|
547
|
-
#:
|
|
548
|
-
#:
|
|
545
|
+
#: The job's SET symbols as they stood when this step's EXEC was read
|
|
546
|
+
#: (for a step inside a PROC body, the procedure's own SETs), kept only
|
|
547
|
+
#: when a later ``SET`` redefines one of them (``None`` otherwise): a SET
|
|
548
|
+
#: value is in effect from the SET statement onward, so this step's
|
|
549
|
+
#: ``&SYM`` references must not see the redefinition.
|
|
549
550
|
set_symbols: dict[str, str] | None = field(default=None, repr=False, compare=False)
|
|
550
551
|
|
|
551
552
|
@property
|
|
@@ -78,6 +78,15 @@ NON_SQL_CONTROL_PROGRAMS: frozenset[str] = frozenset(
|
|
|
78
78
|
}
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
+
# DB2 online-utility programs. Their in-stream data is utility control
|
|
82
|
+
# statements (``RUNSTATS ... UPDATE ALL``, ``MODIFY RECOVERY ... DELETE AGE(n)``)
|
|
83
|
+
# whose options reuse SQL verbs; only the SQL inside an ``EXEC SQL ...
|
|
84
|
+
# ENDEXEC`` utility statement is SQL.
|
|
85
|
+
DB2_UTILITY_CONTROL_PROGRAMS: frozenset[str] = frozenset({"DSNUTILB"})
|
|
86
|
+
|
|
87
|
+
_EXEC_SQL_RE = re.compile(r"\bEXEC\s+SQL\b", re.IGNORECASE)
|
|
88
|
+
_ENDEXEC_RE = re.compile(r"\bENDEXEC\b", re.IGNORECASE)
|
|
89
|
+
|
|
81
90
|
# SYSTSIN holds TSO commands (``DELETE``, ``RENAME`` and ``ALTER`` are TSO
|
|
82
91
|
# commands too); only the DB2 DSN subcommands in it are SQL-related.
|
|
83
92
|
_TSO_COMMAND_DD = "SYSTSIN"
|
|
@@ -178,6 +187,39 @@ def detect_sql_in_text(text: str) -> list[tuple[str, str]]:
|
|
|
178
187
|
return results
|
|
179
188
|
|
|
180
189
|
|
|
190
|
+
def detect_sql_in_utility_statements(text: str) -> list[tuple[str, str]]:
|
|
191
|
+
"""Scan DB2 utility control statements for SQL.
|
|
192
|
+
|
|
193
|
+
Only ``EXEC SQL ... ENDEXEC`` utility statements contribute: the
|
|
194
|
+
``EXEC SQL`` marker itself (``EMBEDDED``) and the SQL statement keywords
|
|
195
|
+
between it and ``ENDEXEC``, which may share a line with either. Every
|
|
196
|
+
other utility statement (``RUNSTATS``, ``REORG``, ``MODIFY RECOVERY``, ...)
|
|
197
|
+
is skipped, so options such as ``UPDATE ALL`` or ``DELETE AGE(90)`` are
|
|
198
|
+
not reported as DML.
|
|
199
|
+
"""
|
|
200
|
+
results: list[tuple[str, str]] = []
|
|
201
|
+
in_exec_sql = False
|
|
202
|
+
for raw_line in split_source_lines(text):
|
|
203
|
+
rest = raw_line.strip()
|
|
204
|
+
while rest:
|
|
205
|
+
if not in_exec_sql:
|
|
206
|
+
start = _EXEC_SQL_RE.search(rest)
|
|
207
|
+
if start is None:
|
|
208
|
+
break
|
|
209
|
+
results.append(("EXEC SQL", _KW_CATEGORY["EXEC SQL"]))
|
|
210
|
+
in_exec_sql = True
|
|
211
|
+
rest = rest[start.end() :]
|
|
212
|
+
continue
|
|
213
|
+
end = _ENDEXEC_RE.search(rest)
|
|
214
|
+
body = rest if end is None else rest[: end.start()]
|
|
215
|
+
results.extend(detect_sql_in_text(body))
|
|
216
|
+
if end is None:
|
|
217
|
+
break
|
|
218
|
+
in_exec_sql = False
|
|
219
|
+
rest = rest[end.end() :]
|
|
220
|
+
return results
|
|
221
|
+
|
|
222
|
+
|
|
181
223
|
def detect_sql_in_dd(dd: DDStatement) -> list[tuple[str, str]]:
|
|
182
224
|
"""Scan a single DD statement's inline data for SQL.
|
|
183
225
|
|
|
@@ -196,7 +238,10 @@ def detect_sql_in_step(step: Step) -> SQLDetectionResult:
|
|
|
196
238
|
the inline data of every DD statement.
|
|
197
239
|
In-stream data of a program in :data:`NON_SQL_CONTROL_PROGRAMS` is not
|
|
198
240
|
scanned, and in a ``SYSTSIN`` DD (TSO commands) only DB2 subcommands such
|
|
199
|
-
as ``BIND`` count.
|
|
241
|
+
as ``BIND`` count. For a program in
|
|
242
|
+
:data:`DB2_UTILITY_CONTROL_PROGRAMS` only ``EXEC SQL ... ENDEXEC``
|
|
243
|
+
utility statements are scanned
|
|
244
|
+
(:func:`detect_sql_in_utility_statements`).
|
|
200
245
|
"""
|
|
201
246
|
result = SQLDetectionResult(
|
|
202
247
|
step_name=step.name,
|
|
@@ -204,15 +249,21 @@ def detect_sql_in_step(step: Step) -> SQLDetectionResult:
|
|
|
204
249
|
is_db2_program=is_db2_program(step.program)
|
|
205
250
|
or bool(step.procedure and step.procedure.upper() in DB2_PROCEDURES),
|
|
206
251
|
)
|
|
207
|
-
|
|
252
|
+
program = step.program.upper() if step.program else ""
|
|
253
|
+
if program in NON_SQL_CONTROL_PROGRAMS:
|
|
208
254
|
return result
|
|
255
|
+
utility_control = program in DB2_UTILITY_CONTROL_PROGRAMS
|
|
209
256
|
is_tso_input = False
|
|
210
257
|
for dd in step.dd_statements:
|
|
211
258
|
# An unnamed DD is a concatenation member of the last named one, so
|
|
212
259
|
# ``// DD *`` after ``//SYSTSIN DD ...`` is TSO input too.
|
|
213
260
|
if dd.name:
|
|
214
261
|
is_tso_input = dd.name.upper() == _TSO_COMMAND_DD
|
|
215
|
-
|
|
262
|
+
if utility_control:
|
|
263
|
+
found = detect_sql_in_utility_statements(dd.inline_data or "")
|
|
264
|
+
else:
|
|
265
|
+
found = detect_sql_in_dd(dd)
|
|
266
|
+
for kw, cat in found:
|
|
216
267
|
if is_tso_input and cat not in _TSO_SQL_CATEGORIES:
|
|
217
268
|
continue
|
|
218
269
|
result.sql_statements.append((kw, cat, dd.name))
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jcl-parser"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.3"
|
|
8
8
|
description = "Parse IBM JCL (Job Control Language) scripts into Python objects"
|
|
9
9
|
authors = [
|
|
10
|
-
{name = "
|
|
10
|
+
{name = "Samuel Dion", email = "sam94dion@gmail.com"}
|
|
11
11
|
]
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = {text = "AGPL-3.0-or-later"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|