@portll/cobolwork 0.5.0 → 0.7.0
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.
- package/README.md +89 -26
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +56 -14
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +133 -53
- package/lib/capabilities.mjs +13 -11
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +17 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +77 -4
- package/lib/dataflow.mjs +402 -152
- package/lib/db2/cursor.mjs +15 -0
- package/lib/db2/read.mjs +170 -0
- package/lib/db2/rules.mjs +77 -0
- package/lib/db2/stmt/alter.mjs +473 -0
- package/lib/db2/stmt/grant.mjs +125 -0
- package/lib/db2/stmt/index.mjs +141 -0
- package/lib/db2/stmt/misc.mjs +325 -0
- package/lib/db2/stmt/routine.mjs +564 -0
- package/lib/db2/stmt/storage.mjs +146 -0
- package/lib/db2/stmt/table.mjs +540 -0
- package/lib/db2/stmt/view.mjs +146 -0
- package/lib/diff.mjs +17 -5
- package/lib/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +26 -7
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +44 -15
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- package/lib/exec-reading.mjs +51 -0
- package/lib/execution.mjs +3 -2
- package/lib/explain.mjs +2 -0
- package/lib/exploitability.mjs +11 -2
- package/lib/hlasm/asm/data.mjs +128 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +161 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +73 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +146 -0
- package/lib/hlasm/macro/datasets.mjs +107 -0
- package/lib/hlasm/macro/io.mjs +162 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +77 -0
- package/lib/hlasm/macro/program.mjs +184 -0
- package/lib/hlasm/macro/recovery.mjs +85 -0
- package/lib/hlasm/macro/storage.mjs +167 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +97 -0
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +59 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +130 -0
- package/lib/hlasm.mjs +44 -7
- package/lib/ims/dli.mjs +37 -0
- package/lib/ims/macro/dbd.mjs +299 -0
- package/lib/ims/macro/psb.mjs +286 -0
- package/lib/ims/model.mjs +149 -0
- package/lib/ims/operands.mjs +23 -0
- package/lib/ims/read.mjs +37 -0
- package/lib/ims/rules.mjs +135 -0
- package/lib/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +20 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +32 -23
- package/lib/kernel/shared-pass.mjs +163 -0
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/options.mjs +26 -4
- package/lib/parser.mjs +136 -17
- package/lib/pli/cursor.mjs +15 -0
- package/lib/pli/expr.mjs +101 -0
- package/lib/pli/include.mjs +82 -0
- package/lib/pli/layout.mjs +125 -0
- package/lib/pli/lex.mjs +198 -0
- package/lib/pli/program.mjs +280 -0
- package/lib/pli/rules/based.mjs +95 -0
- package/lib/pli/rules/conditions.mjs +68 -0
- package/lib/pli/rules/entry.mjs +130 -0
- package/lib/pli/rules/index.mjs +24 -0
- package/lib/pli/rules/preprocessor.mjs +55 -0
- package/lib/pli/statements.mjs +130 -0
- package/lib/pli/stmt/alloc.mjs +45 -0
- package/lib/pli/stmt/assignment.mjs +56 -0
- package/lib/pli/stmt/call.mjs +104 -0
- package/lib/pli/stmt/conditions.mjs +94 -0
- package/lib/pli/stmt/control.mjs +219 -0
- package/lib/pli/stmt/declare.mjs +149 -0
- package/lib/pli/stmt/exec.mjs +55 -0
- package/lib/pli/stmt/io.mjs +239 -0
- package/lib/pli/stmt/misc.mjs +4 -0
- package/lib/pli/stmt/preprocessor.mjs +242 -0
- package/lib/pli/stmt/procedure.mjs +258 -0
- package/lib/pli/stmt/stream.mjs +283 -0
- package/lib/pli/storage.mjs +129 -0
- package/lib/policy.mjs +6 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +8 -4
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +44 -3
- package/lib/scan.mjs +28 -12
- package/lib/sets/abend.mjs +82 -8
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +20 -38
- package/lib/sets/compile.mjs +46 -18
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +32 -8
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +124 -8
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +11 -9
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +112 -0
- package/lib/sets/semantics.mjs +8 -3
- package/lib/sets/web.mjs +39 -23
- package/lib/site.mjs +10 -0
- package/lib/sources.mjs +80 -20
- package/lib/statement-cursor.mjs +67 -0
- package/lib/verify.mjs +3 -2
- package/lib/version.mjs +6 -0
- package/package.json +3 -2
- package/rules/compliance-cobit2019.json +520 -5
- package/rules/compliance-dora.json +509 -5
- package/rules/compliance-ffiec.json +505 -1
- package/rules/compliance-nist80053.json +557 -1
- package/rules/gitleaks-mainframe.toml +46 -15
- package/rules/hlasm-instructions.json +2396 -0
- package/rules/hlasm-optables.json +8024 -0
- package/schema/cobolwork-baseline.schema.json +36 -0
- package/schema/cobolwork-build-provenance.schema.json +187 -0
- package/schema/cobolwork-build.schema.json +382 -0
- package/schema/cobolwork-capabilities.schema.json +239 -0
- package/schema/cobolwork-diff.schema.json +217 -0
- package/schema/cobolwork-evidence.schema.json +161 -0
- package/schema/cobolwork-execution.schema.json +53 -0
- package/schema/cobolwork-explain.schema.json +360 -0
- package/schema/cobolwork-finding.schema.json +465 -0
- package/schema/cobolwork-flow.schema.json +465 -0
- package/schema/cobolwork-gate.schema.json +211 -0
- package/schema/cobolwork-inventory.schema.json +206 -0
- package/schema/cobolwork-parse.schema.json +105 -0
- package/schema/cobolwork-reach.schema.json +74 -0
- package/schema/cobolwork-report.schema.json +559 -0
- package/schema/cobolwork-witness.schema.json +107 -0
- package/schema/cobolwork.baseline.schema.json +101 -0
- package/schema/cobolwork.policy.schema.json +7 -0
- package/schema/cobolwork.site.schema.json +116 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Db2 for z/OS CREATE PROCEDURE (native SQL, external, external SQL) and CREATE FUNCTION (external
|
|
3
|
+
// scalar, SQL scalar), after the Db2 13 SQL Reference; an SQL routine body is kept as its tokens.
|
|
4
|
+
|
|
5
|
+
import { cursor, Db2Syntax } from '../cursor.mjs';
|
|
6
|
+
import { readName, readInteger, readNumber, readDataType } from './table.mjs';
|
|
7
|
+
|
|
8
|
+
// Form codes: N native SQL procedure, E external procedure, X external SQL procedure, F external
|
|
9
|
+
// scalar function, S SQL scalar function; c marks a clause a native SQL procedure accepts and ignores.
|
|
10
|
+
const ROUTINES = { N: 'NATIVE SQL', E: 'EXTERNAL', X: 'EXTERNAL SQL', F: 'EXTERNAL SCALAR', S: 'SQL SCALAR' };
|
|
11
|
+
const LABELS = { N: 'native SQL procedure', E: 'external procedure', X: 'external SQL procedure', F: 'external scalar function', S: 'SQL scalar function' };
|
|
12
|
+
const A = { N: 'a native SQL procedure', E: 'an external procedure', X: 'an external SQL procedure', F: 'an external scalar function', S: 'an SQL scalar function' };
|
|
13
|
+
const ALL = 'NEXFS';
|
|
14
|
+
const ENCODINGS = ['ASCII', 'EBCDIC', 'UNICODE'];
|
|
15
|
+
const FORMATS = ['ISO', 'EUR', 'USA', 'JIS', 'LOCAL'];
|
|
16
|
+
|
|
17
|
+
const isName = (t) => !!t && (t.t === 'word' || t.t === 'ident');
|
|
18
|
+
const name = (c) => readName(c).join('.');
|
|
19
|
+
|
|
20
|
+
function identifier(c) {
|
|
21
|
+
const t = c.next();
|
|
22
|
+
if (!isName(t)) { c.pos -= t ? 1 : 0; c.fail('a name'); }
|
|
23
|
+
return t.v;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function string(c) {
|
|
27
|
+
const t = c.next();
|
|
28
|
+
if (!t || t.t !== 'lit') { c.pos -= t ? 1 : 0; c.fail('a string constant'); }
|
|
29
|
+
return t.v;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// What another dialect's routine looks like at the token where a Db2 for z/OS one stops.
|
|
33
|
+
function hint(t, after) {
|
|
34
|
+
if (!t) return '';
|
|
35
|
+
const next = after && after.t === 'word' ? after.u : null;
|
|
36
|
+
if (t.t === 'op' && t.v === '/' && after && after.v === '/') return ' (a MySQL DELIMITER terminator, not Db2 for z/OS)';
|
|
37
|
+
if (t.t !== 'word') return '';
|
|
38
|
+
if (t.u === 'AS' || t.u === 'IS') return ` (${t.u} before a routine body is PostgreSQL, Oracle or Transact-SQL, not Db2 for z/OS)`;
|
|
39
|
+
if (t.v.startsWith('$')) return ' (a $-quoted body is PostgreSQL or MySQL, not Db2 for z/OS)';
|
|
40
|
+
if (t.v.startsWith('@')) return ' (an @ parameter is Transact-SQL, not Db2 for z/OS)';
|
|
41
|
+
if (t.u === 'PLPGSQL') return ' (PL/pgSQL is PostgreSQL, not Db2 for z/OS)';
|
|
42
|
+
if (t.u === 'DELIMITER') return ' (a MySQL DELIMITER terminator, not Db2 for z/OS)';
|
|
43
|
+
if (t.u === 'COMMENT' && after && after.t === 'lit') return " (COMMENT 'text' is MySQL, not Db2 for z/OS)";
|
|
44
|
+
if (t.u === 'SQL' && next === 'SECURITY') return ' (SQL SECURITY is MySQL, not Db2 for z/OS)';
|
|
45
|
+
if (t.u === 'THREADSAFE' || (t.u === 'NOT' && (next === 'FENCED' || next === 'THREADSAFE')) || ((t.u === 'NEW' || t.u === 'OLD') && next === 'SAVEPOINT')) {
|
|
46
|
+
return ' (a Db2 for LUW clause, not Db2 for z/OS)';
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function refuse(c, what) {
|
|
52
|
+
const t = c.peek();
|
|
53
|
+
throw new Db2Syntax(`expected ${what}${t ? ` at '${t.v ?? t.t}'` : ' at end of statement'}${hint(t, c.peek(1))}`, t || c.peek(-1));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A routine's CCSID names an encoding scheme where a column's gives a number, so a graphic type is
|
|
57
|
+
// read on its own and its CCSID here.
|
|
58
|
+
const GRAPHIC = ['GRAPHIC', 'VARGRAPHIC', 'DBCLOB'];
|
|
59
|
+
const CHARACTER = ['CHAR', 'VARCHAR', 'CLOB'];
|
|
60
|
+
// TIMESTAMP types written with a precision, and EXTERNAL NAMEs written as strings.
|
|
61
|
+
const SIZED = new WeakSet();
|
|
62
|
+
const QUOTED = new WeakSet();
|
|
63
|
+
|
|
64
|
+
function readType(c) {
|
|
65
|
+
const at = c.pos;
|
|
66
|
+
let type;
|
|
67
|
+
if (c.isWord(GRAPHIC)) {
|
|
68
|
+
c.next();
|
|
69
|
+
if (c.isOp('(')) c.group();
|
|
70
|
+
type = readDataType(cursor(c.slice(at)));
|
|
71
|
+
} else type = readDataType(c);
|
|
72
|
+
const read = c.slice(at);
|
|
73
|
+
if (type.name === 'TIMESTAMP' && read.some((t) => t.t === 'op' && t.v === '(')) SIZED.add(type);
|
|
74
|
+
if (type.long) throw new Db2Syntax(`LONG ${type.name} is not a routine data type in Db2 for z/OS`, read[0]);
|
|
75
|
+
if (typeof type.ccsid === 'number') throw new Db2Syntax('a routine names its CCSID as ASCII, EBCDIC or UNICODE, not a number', read.find((t) => t.t === 'word' && t.u === 'CCSID'));
|
|
76
|
+
if (type.modifier) throw new Db2Syntax('a routine takes XML without a type modifier', read[0]);
|
|
77
|
+
if (type.name === 'DISTINCT') return { name: 'USER-DEFINED', userType: type.distinct };
|
|
78
|
+
if (!CHARACTER.includes(type.name) && !GRAPHIC.includes(type.name)) return type;
|
|
79
|
+
for (;;) {
|
|
80
|
+
if (c.isWord('CCSID') && c.isWord(ENCODINGS, 1)) {
|
|
81
|
+
if ('ccsid' in type) c.fail('no second CCSID');
|
|
82
|
+
c.next();
|
|
83
|
+
type.ccsid = c.next().u;
|
|
84
|
+
} else if (CHARACTER.includes(type.name) && c.isWord('FOR') && c.isWord(['SBCS', 'MIXED', 'BIT'], 1) && c.isWord('DATA', 2)) {
|
|
85
|
+
if ('forData' in type) c.fail('no second FOR … DATA');
|
|
86
|
+
c.next();
|
|
87
|
+
type.forData = c.next().u;
|
|
88
|
+
c.next();
|
|
89
|
+
} else return type;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readParameterType(c, p) {
|
|
94
|
+
if (c.isWord('TABLE') && c.isWord('LIKE', 1)) {
|
|
95
|
+
c.next();
|
|
96
|
+
c.next();
|
|
97
|
+
p.type = { name: 'TABLE LIKE', table: name(c) };
|
|
98
|
+
c.expectWord('AS');
|
|
99
|
+
c.expectWord('LOCATOR');
|
|
100
|
+
p.locator = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
p.type = readType(c);
|
|
104
|
+
if (c.isWord('AS') && c.isWord('LOCATOR', 1)) { c.next(); c.next(); p.locator = true; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A parameter's name may be left out where the routine allows it: the type is tried first, and only
|
|
108
|
+
// when it does not take the whole declaration is the first word a name.
|
|
109
|
+
function readParameter(item, procedure, checks) {
|
|
110
|
+
const s = cursor(item);
|
|
111
|
+
if (!item.length) s.fail('a parameter declaration');
|
|
112
|
+
const p = { mode: 'IN', name: null, type: null, locator: false };
|
|
113
|
+
if (item.length > 1 && s.isWord(['IN', 'OUT', 'INOUT'])) {
|
|
114
|
+
if (!procedure) s.fail("a parameter (a Db2 for z/OS function's parameters take no IN, OUT or INOUT)");
|
|
115
|
+
p.mode = s.next().u;
|
|
116
|
+
}
|
|
117
|
+
const at = s.pos;
|
|
118
|
+
let unnamed = false;
|
|
119
|
+
try {
|
|
120
|
+
readParameterType(s, p);
|
|
121
|
+
unnamed = s.done();
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (!(e instanceof Db2Syntax)) throw e;
|
|
124
|
+
}
|
|
125
|
+
if (!unnamed) {
|
|
126
|
+
s.pos = at;
|
|
127
|
+
p.locator = false;
|
|
128
|
+
p.name = identifier(s);
|
|
129
|
+
if (s.isWord(['IN', 'OUT', 'INOUT'])) s.fail('a data type (a mode after the parameter name is Oracle, not Db2 for z/OS)');
|
|
130
|
+
readParameterType(s, p);
|
|
131
|
+
if (!s.done()) s.fail("',' or ')'");
|
|
132
|
+
}
|
|
133
|
+
checks.push({ type: p.type, locator: p.locator, named: !unnamed, tok: item[at] });
|
|
134
|
+
return p;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Several strings are one name concatenated together (SQLCODE -449).
|
|
138
|
+
function readExternalName(c) {
|
|
139
|
+
if (c.peek() && c.peek().t === 'lit') {
|
|
140
|
+
let v = '';
|
|
141
|
+
while (c.peek() && c.peek().t === 'lit') v += c.next().v;
|
|
142
|
+
const ext = { name: v, implicit: false };
|
|
143
|
+
QUOTED.add(ext);
|
|
144
|
+
return ext;
|
|
145
|
+
}
|
|
146
|
+
return { name: identifier(c), implicit: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// (name,*) runs the routine in its caller's WLM environment when another routine calls it.
|
|
150
|
+
function readWlmEnvironment(c) {
|
|
151
|
+
if (!c.isOp('(')) return { name: identifier(c), callerEnvironment: false };
|
|
152
|
+
const s = cursor(c.group());
|
|
153
|
+
const env = { name: identifier(s), callerEnvironment: true };
|
|
154
|
+
s.expectOp(',');
|
|
155
|
+
s.expectOp('*');
|
|
156
|
+
if (!s.done()) s.fail("')'");
|
|
157
|
+
return env;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function readPackageOwner(c) {
|
|
161
|
+
const owner = { name: identifier(c), as: null };
|
|
162
|
+
if (c.isWord('AS') && c.isWord(['ROLE', 'USER'], 1)) { c.next(); owner.as = c.next().u; }
|
|
163
|
+
return owner;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readSqlPath(c) {
|
|
167
|
+
const path = [];
|
|
168
|
+
do {
|
|
169
|
+
if (c.isWord('SYSTEM') && c.isWord('PATH', 1)) { c.pos += 2; path.push('SYSTEM PATH'); }
|
|
170
|
+
else if (c.isWord(['SESSION_USER', 'USER'])) path.push(c.next().u);
|
|
171
|
+
else path.push(identifier(c));
|
|
172
|
+
} while (c.op(','));
|
|
173
|
+
return path;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Entries as SET CURRENT PACKAGE PATH takes them: collection ids, string constants and registers.
|
|
177
|
+
function readPackagePath(c) {
|
|
178
|
+
const path = [];
|
|
179
|
+
do {
|
|
180
|
+
if (c.isWord('CURRENT') && c.isWord('PACKAGE', 1) && c.isWord('PATH', 2)) { c.pos += 3; path.push('CURRENT PACKAGE PATH'); }
|
|
181
|
+
else if (c.isWord('CURRENT') && c.isWord('PATH', 1)) { c.pos += 2; path.push('CURRENT PATH'); }
|
|
182
|
+
else if (c.isWord(['SESSION_USER', 'USER'])) path.push(c.next().u);
|
|
183
|
+
else if (c.peek() && c.peek().t === 'lit') path.push(c.next().v);
|
|
184
|
+
else path.push(identifier(c));
|
|
185
|
+
} while (c.op(','));
|
|
186
|
+
return path;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function readDecimalArithmetic(c) {
|
|
190
|
+
const s = cursor(c.group());
|
|
191
|
+
const d = { precision: readInteger(s) };
|
|
192
|
+
if (d.precision !== 15 && d.precision !== 31) { s.pos--; s.fail('15 or 31'); }
|
|
193
|
+
if (s.op(',')) d.scale = readInteger(s);
|
|
194
|
+
if (!s.done()) s.fail("')'");
|
|
195
|
+
return d;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readDegree(c) {
|
|
199
|
+
if (c.word('ANY')) return 'ANY';
|
|
200
|
+
const t = c.next();
|
|
201
|
+
if (!t || t.t !== 'num' || t.v !== '1') { c.pos -= t ? 1 : 0; c.fail('1 or ANY'); }
|
|
202
|
+
return '1';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function readStopAfter(c) {
|
|
206
|
+
const n = readInteger(c);
|
|
207
|
+
c.expectWord('FAILURES');
|
|
208
|
+
return n;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function readReturns(c, checks) {
|
|
212
|
+
if (c.isWord('TABLE')) c.fail('a data type (table functions are not read)');
|
|
213
|
+
const returns = { type: null, castFrom: null, locator: false };
|
|
214
|
+
let tok = c.peek();
|
|
215
|
+
returns.type = readType(c);
|
|
216
|
+
checks.push({ type: returns.type, tok, returns: true });
|
|
217
|
+
if (c.isWord('CAST') && c.isWord('FROM', 1)) {
|
|
218
|
+
c.next();
|
|
219
|
+
c.next();
|
|
220
|
+
tok = c.peek();
|
|
221
|
+
returns.castFrom = readType(c);
|
|
222
|
+
checks.push({ type: returns.castFrom, tok, returns: true });
|
|
223
|
+
}
|
|
224
|
+
if (c.isWord('AS') && c.isWord('LOCATOR', 1)) { c.next(); c.next(); returns.locator = true; }
|
|
225
|
+
return returns;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const clause = (phrase, key, value, forms) => ({ words: phrase.split(' '), key, value, forms });
|
|
229
|
+
const failing = (what) => (c) => refuse(c, what);
|
|
230
|
+
const yesNo = (head, key, forms) => [clause(`${head} YES`, key, true, forms), clause(`${head} NO`, key, false, forms), clause(head, key, failing('YES or NO'), forms)];
|
|
231
|
+
const withWithout = (tail, key, forms) => [clause(`WITH ${tail}`, key, true, forms), clause(`WITHOUT ${tail}`, key, false, forms)];
|
|
232
|
+
const choice = (head, key, values, forms) => [
|
|
233
|
+
...values.map((v) => clause(`${head} ${v}`, key, v, forms)),
|
|
234
|
+
clause(head, key, failing(`${values.slice(0, -1).join(', ')} or ${values[values.length - 1]}`), forms),
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
// Each clause as the reference spells it, with the forms that take it; IBM's synonyms (VARIANT, NULL
|
|
238
|
+
// CALL, RESULT SETS, DB2SQL and the rest) read as the clause they stand for.
|
|
239
|
+
const CLAUSES = [
|
|
240
|
+
clause('VERSION', 'version', identifier, 'NS'),
|
|
241
|
+
clause('LANGUAGE SQL', 'language', 'SQL', 'NXS'),
|
|
242
|
+
...['ASSEMBLE', 'C', 'COBOL', 'JAVA', 'PLI'].map((l) => clause(`LANGUAGE ${l}`, 'language', l, 'EF')),
|
|
243
|
+
clause('LANGUAGE REXX', 'language', 'REXX', 'E'),
|
|
244
|
+
clause('LANGUAGE', 'language', failing('ASSEMBLE, C, COBOL, JAVA, PLI, REXX or SQL'), ALL),
|
|
245
|
+
clause('SPECIFIC', 'specific', name, 'NEFS'),
|
|
246
|
+
clause('DETERMINISTIC', 'deterministic', true, ALL),
|
|
247
|
+
clause('NOT DETERMINISTIC', 'deterministic', false, ALL),
|
|
248
|
+
clause('NOT VARIANT', 'deterministic', true, ALL),
|
|
249
|
+
clause('VARIANT', 'deterministic', false, ALL),
|
|
250
|
+
clause('MODIFIES SQL DATA', 'sqlDataAccess', 'MODIFIES SQL DATA', ALL),
|
|
251
|
+
clause('READS SQL DATA', 'sqlDataAccess', 'READS SQL DATA', ALL),
|
|
252
|
+
clause('CONTAINS SQL', 'sqlDataAccess', 'CONTAINS SQL', ALL),
|
|
253
|
+
clause('NO SQL', 'sqlDataAccess', 'NO SQL', 'EF'),
|
|
254
|
+
clause('CALLED ON NULL INPUT', 'calledOnNullInput', true, ALL),
|
|
255
|
+
clause('NULL CALL', 'calledOnNullInput', true, 'NEFS'),
|
|
256
|
+
clause('RETURNS NULL ON NULL INPUT', 'calledOnNullInput', false, 'FS'),
|
|
257
|
+
clause('NOT NULL CALL', 'calledOnNullInput', false, 'FS'),
|
|
258
|
+
clause('DYNAMIC RESULT SETS', 'dynamicResultSets', readInteger, 'NEX'),
|
|
259
|
+
clause('DYNAMIC RESULT SET', 'dynamicResultSets', readInteger, 'NEX'),
|
|
260
|
+
clause('RESULT SETS', 'dynamicResultSets', readInteger, 'NEX'),
|
|
261
|
+
clause('RESULT SET', 'dynamicResultSets', readInteger, 'NEX'),
|
|
262
|
+
...['ALLOW', 'DISALLOW', 'DISABLE'].map((v) => clause(`${v} DEBUG MODE`, 'debugMode', v, 'NES')),
|
|
263
|
+
...choice('PARAMETER CCSID', 'parameterCcsid', ENCODINGS, ALL),
|
|
264
|
+
...choice('PARAMETER VARCHAR', 'parameterVarchar', ['NULTERM', 'STRUCTURE'], 'EXFc'),
|
|
265
|
+
clause('PARAMETER STYLE SQL', 'parameterStyle', 'SQL', 'EF'),
|
|
266
|
+
clause('PARAMETER STYLE DB2SQL', 'parameterStyle', 'SQL', 'EF'),
|
|
267
|
+
clause('PARAMETER STYLE STANDARD CALL', 'parameterStyle', 'SQL', 'E'),
|
|
268
|
+
clause('PARAMETER STYLE GENERAL', 'parameterStyle', 'GENERAL', 'E'),
|
|
269
|
+
clause('PARAMETER STYLE SIMPLE CALL', 'parameterStyle', 'GENERAL', 'E'),
|
|
270
|
+
clause('PARAMETER STYLE GENERAL WITH NULLS', 'parameterStyle', 'GENERAL WITH NULLS', 'Ec'),
|
|
271
|
+
clause('PARAMETER STYLE SIMPLE CALL WITH NULLS', 'parameterStyle', 'GENERAL WITH NULLS', 'E'),
|
|
272
|
+
clause('PARAMETER STYLE JAVA', 'parameterStyle', 'JAVA', 'EF'),
|
|
273
|
+
clause('PARAMETER STYLE', 'parameterStyle', failing('SQL, GENERAL, GENERAL WITH NULLS or JAVA'), ALL),
|
|
274
|
+
clause('PARAMETER', 'parameter', failing('CCSID, VARCHAR or STYLE'), ALL),
|
|
275
|
+
clause('QUALIFIER', 'qualifier', identifier, 'NS'),
|
|
276
|
+
clause('PACKAGE OWNER', 'packageOwner', readPackageOwner, 'NS'),
|
|
277
|
+
clause('ASUTIME NO LIMIT', 'asutime', 'NO LIMIT', ALL),
|
|
278
|
+
clause('ASUTIME LIMIT', 'asutime', readInteger, ALL),
|
|
279
|
+
clause('ASUTIME', 'asutime', failing('NO LIMIT or LIMIT'), ALL),
|
|
280
|
+
clause('COMMIT ON RETURN NO', 'commitOnReturn', 'NO', 'NEX'),
|
|
281
|
+
clause('COMMIT ON RETURN YES', 'commitOnReturn', 'YES', 'NEX'),
|
|
282
|
+
clause('COMMIT ON RETURN', 'commitOnReturn', failing('YES or NO'), 'NEX'),
|
|
283
|
+
clause('AUTONOMOUS', 'commitOnReturn', 'AUTONOMOUS', 'N'),
|
|
284
|
+
clause('INHERIT SPECIAL REGISTERS', 'specialRegisters', 'INHERIT', ALL),
|
|
285
|
+
clause('DEFAULT SPECIAL REGISTERS', 'specialRegisters', 'DEFAULT', ALL),
|
|
286
|
+
clause('WLM ENVIRONMENT FOR DEBUG MODE', 'debugWlmEnvironment', identifier, 'NS'),
|
|
287
|
+
clause('WLM ENVIRONMENT', 'wlmEnvironment', readWlmEnvironment, 'EXF'),
|
|
288
|
+
clause('DEFER PREPARE', 'deferPrepare', true, 'N'),
|
|
289
|
+
clause('NODEFER PREPARE', 'deferPrepare', false, 'N'),
|
|
290
|
+
...yesNo('CURRENT DATA', 'currentData', 'NS'),
|
|
291
|
+
clause('DEGREE', 'degree', readDegree, 'NS'),
|
|
292
|
+
...choice('CONCURRENT ACCESS RESOLUTION', 'concurrentAccessResolution', ['USE CURRENTLY COMMITTED', 'WAIT FOR OUTCOME'], 'NS'),
|
|
293
|
+
...choice('DYNAMICRULES', 'dynamicRules', ['RUN', 'BIND', 'DEFINEBIND', 'DEFINERUN', 'INVOKEBIND', 'INVOKERUN'], 'NS'),
|
|
294
|
+
...choice('APPLICATION ENCODING SCHEME', 'applicationEncodingScheme', ENCODINGS, 'NS'),
|
|
295
|
+
...withWithout('EXPLAIN', 'explain', 'NS'),
|
|
296
|
+
...withWithout('IMMEDIATE WRITE', 'immediateWrite', 'NS'),
|
|
297
|
+
...withWithout('KEEP DYNAMIC', 'keepDynamic', 'N'),
|
|
298
|
+
...choice('ISOLATION LEVEL', 'isolation', ['CS', 'RS', 'RR', 'UR'], 'NS'),
|
|
299
|
+
clause('OPTHINT', 'optHint', string, 'NS'),
|
|
300
|
+
clause('SQL PATH', 'sqlPath', readSqlPath, 'NS'),
|
|
301
|
+
...choice('QUERY ACCELERATION', 'queryAcceleration', ['NONE', 'ENABLE WITH FAILBACK', 'ENABLE', 'ELIGIBLE', 'ALL'], 'NS'),
|
|
302
|
+
...yesNo('GET_ACCEL_ARCHIVE', 'getAccelArchive', 'NS'),
|
|
303
|
+
clause('ACCELERATION WAITFORDATA', 'accelerationWaitForData', (c) => Number(readNumber(c)), 'NS'),
|
|
304
|
+
clause('ACCELERATOR', 'accelerator', identifier, 'NS'),
|
|
305
|
+
...choice('RELEASE AT', 'releaseAt', ['COMMIT', 'DEALLOCATE'], 'N'),
|
|
306
|
+
...choice('REOPT', 'reopt', ['NONE', 'ALWAYS', 'ONCE'], 'NS'),
|
|
307
|
+
...choice('VALIDATE', 'validate', ['RUN', 'BIND'], 'NS'),
|
|
308
|
+
...choice('ROUNDING', 'rounding', ['DEC_ROUND_CEILING', 'DEC_ROUND_DOWN', 'DEC_ROUND_FLOOR', 'DEC_ROUND_HALF_DOWN', 'DEC_ROUND_HALF_EVEN', 'DEC_ROUND_HALF_UP', 'DEC_ROUND_UP'], 'NS'),
|
|
309
|
+
...choice('DATE FORMAT', 'dateFormat', FORMATS, 'NS'),
|
|
310
|
+
...choice('TIME FORMAT', 'timeFormat', FORMATS, 'NS'),
|
|
311
|
+
clause('DECIMAL', 'decimalArithmetic', readDecimalArithmetic, 'NS'),
|
|
312
|
+
...choice('FOR UPDATE CLAUSE', 'forUpdateClause', ['REQUIRED', 'OPTIONAL'], 'NS'),
|
|
313
|
+
...yesNo('BUSINESS_TIME SENSITIVE', 'businessTimeSensitive', 'NS'),
|
|
314
|
+
...yesNo('SYSTEM_TIME SENSITIVE', 'systemTimeSensitive', 'NS'),
|
|
315
|
+
...yesNo('ARCHIVE SENSITIVE', 'archiveSensitive', 'NS'),
|
|
316
|
+
clause('APPLCOMPAT', 'applcompat', identifier, 'NS'),
|
|
317
|
+
...choice('CONCENTRATE STATEMENTS', 'concentrateStatements', ['OFF', 'WITH LITERALS'], 'NS'),
|
|
318
|
+
clause('EXTERNAL NAME', 'external', readExternalName, 'EXF'),
|
|
319
|
+
clause('EXTERNAL', 'external', () => ({ name: null, implicit: true }), 'EF'),
|
|
320
|
+
clause('FENCED', 'fenced', true, 'EXF'),
|
|
321
|
+
clause('PACKAGE PATH', 'packagePath', readPackagePath, 'EF'),
|
|
322
|
+
clause('NO PACKAGE PATH', 'packagePath', null, 'EF'),
|
|
323
|
+
clause('DBINFO', 'dbinfo', true, 'EF'),
|
|
324
|
+
clause('NO DBINFO', 'dbinfo', false, 'EXFc'),
|
|
325
|
+
clause('COLLID', 'collid', identifier, 'EXFc'),
|
|
326
|
+
clause('NO COLLID', 'collid', null, 'EXFc'),
|
|
327
|
+
...yesNo('STAY RESIDENT', 'stayResident', 'EXFc'),
|
|
328
|
+
...choice('PROGRAM TYPE', 'programType', ['SUB', 'MAIN'], 'EXFc'),
|
|
329
|
+
...choice('SECURITY', 'security', ['DB2', 'USER', 'DEFINER'], 'EXFc'),
|
|
330
|
+
clause('STOP AFTER SYSTEM DEFAULT FAILURES', 'stopAfterFailures', 'SYSTEM DEFAULT', 'EXFc'),
|
|
331
|
+
clause('STOP AFTER', 'stopAfterFailures', readStopAfter, 'EXFc'),
|
|
332
|
+
clause('CONTINUE AFTER FAILURE', 'stopAfterFailures', false, 'EXFc'),
|
|
333
|
+
clause('CONTINUE AFTER FAILURES', 'stopAfterFailures', false, 'c'),
|
|
334
|
+
clause('RUN OPTIONS', 'runOptions', string, 'EXFc'),
|
|
335
|
+
clause('RETURNS', 'returns', (c, ctx) => readReturns(c, ctx.checks), 'FS'),
|
|
336
|
+
clause('EXTERNAL ACTION', 'externalAction', true, 'FS'),
|
|
337
|
+
clause('NO EXTERNAL ACTION', 'externalAction', false, 'FS'),
|
|
338
|
+
clause('SCRATCHPAD', 'scratchpad', (c) => (c.peek() && c.peek().t === 'num' ? readInteger(c) : 100), 'F'),
|
|
339
|
+
clause('NO SCRATCHPAD', 'scratchpad', false, 'F'),
|
|
340
|
+
clause('FINAL CALL', 'finalCall', true, 'F'),
|
|
341
|
+
clause('NO FINAL CALL', 'finalCall', false, 'F'),
|
|
342
|
+
clause('ALLOW PARALLEL', 'parallel', true, 'FS'),
|
|
343
|
+
clause('DISALLOW PARALLEL', 'parallel', false, 'FS'),
|
|
344
|
+
clause('STATIC DISPATCH', 'staticDispatch', true, 'FS'),
|
|
345
|
+
clause('SECURED', 'secured', true, 'FS'),
|
|
346
|
+
clause('NOT SECURED', 'secured', false, 'FS'),
|
|
347
|
+
].sort((a, b) => b.words.length - a.words.length);
|
|
348
|
+
|
|
349
|
+
// Clauses in any order, each at most once; VERSION only first in a procedure and straight after a
|
|
350
|
+
// leading RETURNS in a function, as the reference places it.
|
|
351
|
+
function readClauses(c, ctx) {
|
|
352
|
+
const options = {};
|
|
353
|
+
const used = [];
|
|
354
|
+
for (;;) {
|
|
355
|
+
const k = CLAUSES.find((x) => x.words.every((w, i) => c.isWord(w, i)));
|
|
356
|
+
if (!k) return { options, used };
|
|
357
|
+
const tok = c.peek();
|
|
358
|
+
if (k.key in options) c.fail(`no second ${k.key}`);
|
|
359
|
+
if (k.key === 'version' && (ctx.procedure ? used.length > 0 : used.length !== 1 || used[0].k.key !== 'returns')) {
|
|
360
|
+
throw new Db2Syntax(`VERSION comes ${ctx.procedure ? 'before the other clauses' : 'straight after a leading RETURNS'}`, tok);
|
|
361
|
+
}
|
|
362
|
+
c.pos += k.words.length;
|
|
363
|
+
options[k.key] = typeof k.value === 'function' ? k.value(c, ctx) : k.value;
|
|
364
|
+
used.push({ k, tok });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const TYPE_FORMS = { ROWID: 'EFS', XML: 'NS', 'USER-DEFINED': 'NEFS', BIGINT: 'NEFS', DECFLOAT: 'NEFS', BINARY: 'NEFS', VARBINARY: 'NEFS' };
|
|
369
|
+
const LOCATABLE = ['BLOB', 'CLOB', 'DBCLOB', 'USER-DEFINED'];
|
|
370
|
+
|
|
371
|
+
function checkTypes(checks, code) {
|
|
372
|
+
for (const { type, locator, named, tok, returns } of checks) {
|
|
373
|
+
const where = returns ? 'RETURNS' : 'a parameter';
|
|
374
|
+
if (named === false && 'NXS'.includes(code)) throw new Db2Syntax(`expected a parameter name: ${A[code]} names each parameter`, tok);
|
|
375
|
+
const forms = TYPE_FORMS[type.name];
|
|
376
|
+
if ((forms && !forms.includes(code)) || (code === 'X' && (type.timeZone || SIZED.has(type)))) {
|
|
377
|
+
throw new Db2Syntax(`${type.name === 'USER-DEFINED' ? 'a user-defined type' : type.name} is not a type for ${where} of ${A[code]}`, tok);
|
|
378
|
+
}
|
|
379
|
+
if (locator && type.name !== 'TABLE LIKE') {
|
|
380
|
+
// "AS LOCATOR cannot be specified for SQL procedures" (CREATE PROCEDURE overview), nor SQL functions.
|
|
381
|
+
if (!'EF'.includes(code)) throw new Db2Syntax(`AS LOCATOR is not allowed on ${where} of ${A[code]}`, tok);
|
|
382
|
+
if (!LOCATABLE.includes(type.name)) throw new Db2Syntax(`AS LOCATOR takes a LOB or distinct type, not ${type.name}`, tok);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Clauses the form does not take are refused, except those a native SQL procedure accepts for
|
|
388
|
+
// compatibility and ignores, which move to options.ignored.
|
|
389
|
+
function settle(options, used, code) {
|
|
390
|
+
for (const { k, tok } of used) {
|
|
391
|
+
if (k.forms.includes(code)) continue;
|
|
392
|
+
if (code === 'N' && k.forms.includes('c')) {
|
|
393
|
+
(options.ignored ||= {})[k.key] = options[k.key];
|
|
394
|
+
delete options[k.key];
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
throw new Db2Syntax(`${k.words.join(' ')} is not a clause of ${A[code]} in Db2 for z/OS`, tok);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const CONTROL = ['BEGIN', 'CALL', 'CASE', 'FOR', 'GET', 'GOTO', 'IF', 'ITERATE', 'LEAVE', 'LOOP', 'REPEAT', 'RESIGNAL', 'RETURN', 'SET', 'SIGNAL', 'WHILE'];
|
|
402
|
+
const STATEMENTS = [...CONTROL, 'ALTER', 'COMMENT', 'COMMIT', 'CONNECT', 'CREATE', 'DECLARE', 'DELETE', 'DROP', 'EXCHANGE', 'EXECUTE', 'GRANT', 'INSERT', 'LABEL', 'LOCK', 'MERGE', 'REFRESH', 'RELEASE', 'RENAME', 'REVOKE', 'ROLLBACK', 'SAVEPOINT', 'SELECT', 'TRUNCATE', 'UPDATE', 'VALUES'];
|
|
403
|
+
const BODY_STARTS = { N: STATEMENTS, X: STATEMENTS.filter((w) => w !== 'FOR'), S: CONTROL };
|
|
404
|
+
// The word after the first, where the first alone also opens another dialect's clause.
|
|
405
|
+
const SECOND = { COMMENT: 'ON', LABEL: 'ON', DECLARE: 'GLOBAL' };
|
|
406
|
+
|
|
407
|
+
// The extent of a compound statement: BEGIN and CASE open, END closes the latest, and END IF, END
|
|
408
|
+
// LOOP, END WHILE, END REPEAT and END FOR close statements that opened nothing here.
|
|
409
|
+
function readCompound(c) {
|
|
410
|
+
const open = [];
|
|
411
|
+
do {
|
|
412
|
+
const t = c.next();
|
|
413
|
+
if (!t) c.fail('END closing the routine body');
|
|
414
|
+
if (t.t !== 'word') continue;
|
|
415
|
+
if (t.u === 'BEGIN' || t.u === 'CASE') open.push(t.u);
|
|
416
|
+
else if (t.u === 'END') {
|
|
417
|
+
if (c.isWord(['IF', 'LOOP', 'WHILE', 'REPEAT', 'FOR'])) c.next();
|
|
418
|
+
else { c.word('CASE'); open.pop(); }
|
|
419
|
+
}
|
|
420
|
+
} while (open.length);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const sameName = (a, b) => a.t === b.t && (a.t === 'word' ? a.u === b.u : a.v === b.v);
|
|
424
|
+
|
|
425
|
+
// The SQL-routine-body: one statement, optionally labelled, kept as tokens. A compound statement must
|
|
426
|
+
// end the routine; any other statement runs to the end of it.
|
|
427
|
+
function readBody(c, code) {
|
|
428
|
+
const at = c.pos;
|
|
429
|
+
const label = isName(c.peek()) && c.isOp(':', 1) ? c.peek() : null;
|
|
430
|
+
if (label) c.pos += 2;
|
|
431
|
+
const t = c.peek();
|
|
432
|
+
if (!t || t.t !== 'word' || !BODY_STARTS[code].includes(t.u) || (SECOND[t.u] && !c.isWord(SECOND[t.u], 1))) {
|
|
433
|
+
c.pos = at;
|
|
434
|
+
refuse(c, `a Db2 for z/OS ${LABELS[code]} clause or an SQL routine body`);
|
|
435
|
+
}
|
|
436
|
+
if (t.u !== 'BEGIN') { c.drain(); return c.slice(at); }
|
|
437
|
+
readCompound(c);
|
|
438
|
+
if (label && c.peek() && sameName(c.peek(), label)) c.next();
|
|
439
|
+
if (!c.done()) refuse(c, 'the end of the statement after the routine body');
|
|
440
|
+
return c.slice(at);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function routineName(c) {
|
|
444
|
+
const parts = readName(c);
|
|
445
|
+
if (c.isOp('/')) c.fail("'(' or a clause ('/' qualifies names in Db2 for i system naming, not Db2 for z/OS)");
|
|
446
|
+
return parts;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function newNode(kind, orReplace, parts) {
|
|
450
|
+
const node = { kind, orReplace, routine: null, name: parts.join('.'), parameters: [] };
|
|
451
|
+
if (kind === 'CREATE FUNCTION') node.returns = null;
|
|
452
|
+
return { ...node, language: null, external: null, security: null, wlmEnvironment: null, version: null, options: {}, body: null, wrapped: null };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// The obfuscated text of a WRAPPED statement stands for everything after the parameters.
|
|
456
|
+
function readWrapped(c, node, code, checks) {
|
|
457
|
+
if (c.done()) c.fail('the obfuscated statement text');
|
|
458
|
+
node.routine = ROUTINES[code];
|
|
459
|
+
node.language = 'SQL';
|
|
460
|
+
node.wrapped = c.drain();
|
|
461
|
+
checkTypes(checks, code);
|
|
462
|
+
return node;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Moves the clauses a security reading needs onto the node: the language, the program that runs
|
|
466
|
+
// (EXTERNAL NAME, or the routine's own name when NAME is left out), SECURITY and WLM ENVIRONMENT.
|
|
467
|
+
// SQLCODE -449: left out, NAME defaults to the routine's name, which must then be at most eight
|
|
468
|
+
// characters; a Java routine always names its method in a string.
|
|
469
|
+
function lift(c, node, options, code, parts) {
|
|
470
|
+
const external = 'EXF'.includes(code);
|
|
471
|
+
node.routine = ROUTINES[code];
|
|
472
|
+
node.language = options.language ?? 'SQL';
|
|
473
|
+
if (external) {
|
|
474
|
+
const ext = options.external || { name: null, implicit: true };
|
|
475
|
+
const own = parts[parts.length - 1];
|
|
476
|
+
if (node.language === 'JAVA' && !QUOTED.has(ext)) c.fail("EXTERNAL NAME 'class.method' (a Java routine names its method in a string)");
|
|
477
|
+
if (ext.implicit && own.length > 8) c.fail(`EXTERNAL NAME (${own} is longer than eight characters, so it cannot name the load module)`);
|
|
478
|
+
node.external = ext.implicit ? { name: own, implicit: true } : ext;
|
|
479
|
+
node.security = options.security ?? 'DB2';
|
|
480
|
+
node.wlmEnvironment = options.wlmEnvironment ?? null;
|
|
481
|
+
}
|
|
482
|
+
node.version = options.version ?? null;
|
|
483
|
+
for (const key of ['language', 'external', 'security', 'wlmEnvironment', 'version']) delete options[key];
|
|
484
|
+
node.options = options;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function readProcedure(c) {
|
|
488
|
+
c.expectWord('CREATE');
|
|
489
|
+
const orReplace = c.isWord('OR') && c.isWord('REPLACE', 1);
|
|
490
|
+
if (orReplace) c.pos += 2;
|
|
491
|
+
c.expectWord('PROCEDURE');
|
|
492
|
+
const parts = routineName(c);
|
|
493
|
+
const node = newNode('CREATE PROCEDURE', orReplace, parts);
|
|
494
|
+
const ctx = { procedure: true, checks: [] };
|
|
495
|
+
if (c.isOp('(')) node.parameters = c.items().map((item) => readParameter(item, true, ctx.checks));
|
|
496
|
+
if (c.word('WRAPPED')) return readWrapped(c, node, 'N', ctx.checks);
|
|
497
|
+
const { options, used } = readClauses(c, ctx);
|
|
498
|
+
const language = options.language;
|
|
499
|
+
let code = 'N';
|
|
500
|
+
if (language && language !== 'SQL') code = 'E';
|
|
501
|
+
else if ('external' in options || 'fenced' in options) {
|
|
502
|
+
if (!language) c.fail('LANGUAGE');
|
|
503
|
+
code = 'X';
|
|
504
|
+
}
|
|
505
|
+
settle(options, used, code);
|
|
506
|
+
if (code === 'E') {
|
|
507
|
+
if (!c.done()) refuse(c, `a Db2 for z/OS ${LABELS[code]} clause`);
|
|
508
|
+
} else node.body = readBody(c, code);
|
|
509
|
+
if (code === 'X' && orReplace) throw new Db2Syntax('an external SQL procedure takes no OR REPLACE', c.slice(0, 1)[0]);
|
|
510
|
+
if (code === 'E' && !('external' in options)) c.fail('EXTERNAL');
|
|
511
|
+
checkTypes(ctx.checks, code);
|
|
512
|
+
lift(c, node, options, code, parts);
|
|
513
|
+
return node;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// The clauses of an inlined SQL scalar function, the one form whose RETURNS may come anywhere; a
|
|
517
|
+
// statement that fits it is inlined, any other is compiled (CREATE FUNCTION, compiled SQL scalar).
|
|
518
|
+
const INLINED = new Set(['RETURNS', 'LANGUAGE SQL', 'SPECIFIC', 'PARAMETER CCSID', 'DETERMINISTIC', 'NOT DETERMINISTIC', 'VARIANT', 'NOT VARIANT',
|
|
519
|
+
'EXTERNAL ACTION', 'NO EXTERNAL ACTION', 'READS SQL DATA', 'CONTAINS SQL', 'STATIC DISPATCH', 'CALLED ON NULL INPUT', 'NULL CALL', 'SECURED', 'NOT SECURED']);
|
|
520
|
+
const inlined = (k) => INLINED.has(k.words.join(' ')) || INLINED.has(k.words.slice(0, 2).join(' '));
|
|
521
|
+
|
|
522
|
+
function readFunction(c) {
|
|
523
|
+
c.expectWord('CREATE');
|
|
524
|
+
if (c.isWord('OR') && c.isWord('REPLACE', 1)) c.fail('FUNCTION (Db2 for z/OS has no CREATE OR REPLACE FUNCTION)');
|
|
525
|
+
c.expectWord('FUNCTION');
|
|
526
|
+
const parts = routineName(c);
|
|
527
|
+
const node = newNode('CREATE FUNCTION', false, parts);
|
|
528
|
+
const ctx = { procedure: false, checks: [] };
|
|
529
|
+
if (!c.isOp('(')) c.fail("'(' (a function lists its parameters, if only as ())");
|
|
530
|
+
node.parameters = c.items().map((item) => readParameter(item, false, ctx.checks));
|
|
531
|
+
if (c.word('WRAPPED')) return readWrapped(c, node, 'S', ctx.checks);
|
|
532
|
+
const { options, used } = readClauses(c, ctx);
|
|
533
|
+
if (c.isWord('SOURCE')) c.fail('an external or SQL scalar function clause (sourced functions are not read)');
|
|
534
|
+
const language = options.language;
|
|
535
|
+
let code = 'S';
|
|
536
|
+
if (language && language !== 'SQL') code = 'F';
|
|
537
|
+
else if ('external' in options) {
|
|
538
|
+
if (!language) c.fail('LANGUAGE');
|
|
539
|
+
code = 'F';
|
|
540
|
+
}
|
|
541
|
+
settle(options, used, code);
|
|
542
|
+
if (code === 'F') {
|
|
543
|
+
if (!c.done()) refuse(c, `a Db2 for z/OS ${LABELS[code]} clause`);
|
|
544
|
+
} else node.body = readBody(c, code);
|
|
545
|
+
const { returns } = options;
|
|
546
|
+
if (!returns) c.fail('RETURNS');
|
|
547
|
+
const outside = used.find((u) => !inlined(u.k));
|
|
548
|
+
if (code === 'S' && used[0].k.key !== 'returns' && (outside || !(node.body[0].t === 'word' && node.body[0].u === 'RETURN'))) {
|
|
549
|
+
throw new Db2Syntax(`RETURNS comes first in a compiled SQL scalar function; only an inlined one, a single RETURN statement with inlined clauses${outside ? ` (not ${outside.k.words.join(' ')})` : ''}, takes them in any order`, used[0].tok);
|
|
550
|
+
}
|
|
551
|
+
if (code === 'S' && (returns.castFrom || returns.locator)) throw new Db2Syntax('CAST FROM and AS LOCATOR in RETURNS are for external functions', used.find((u) => u.k.key === 'returns').tok);
|
|
552
|
+
if (code === 'F' && !('external' in options)) c.fail('EXTERNAL');
|
|
553
|
+
if (code === 'F' && !('parameterStyle' in options)) c.fail('PARAMETER STYLE');
|
|
554
|
+
checkTypes(ctx.checks, code);
|
|
555
|
+
node.returns = returns;
|
|
556
|
+
delete options.returns;
|
|
557
|
+
lift(c, node, options, code, parts);
|
|
558
|
+
return node;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export const parsers = {
|
|
562
|
+
'CREATE PROCEDURE': (c) => readProcedure(c),
|
|
563
|
+
'CREATE FUNCTION': (c) => readFunction(c),
|
|
564
|
+
};
|