@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.
Files changed (163) hide show
  1. package/README.md +89 -26
  2. package/STABILITY.md +76 -0
  3. package/bin/cobolwork.mjs +56 -14
  4. package/lib/baseline.mjs +9 -0
  5. package/lib/bms.mjs +21 -7
  6. package/lib/build.mjs +133 -53
  7. package/lib/capabilities.mjs +13 -11
  8. package/lib/cics-commands.mjs +501 -9
  9. package/lib/compliance.mjs +11 -0
  10. package/lib/consequence.mjs +17 -0
  11. package/lib/control-reuse.mjs +113 -0
  12. package/lib/control-workers.mjs +161 -0
  13. package/lib/control.mjs +77 -4
  14. package/lib/dataflow.mjs +402 -152
  15. package/lib/db2/cursor.mjs +15 -0
  16. package/lib/db2/read.mjs +170 -0
  17. package/lib/db2/rules.mjs +77 -0
  18. package/lib/db2/stmt/alter.mjs +473 -0
  19. package/lib/db2/stmt/grant.mjs +125 -0
  20. package/lib/db2/stmt/index.mjs +141 -0
  21. package/lib/db2/stmt/misc.mjs +325 -0
  22. package/lib/db2/stmt/routine.mjs +564 -0
  23. package/lib/db2/stmt/storage.mjs +146 -0
  24. package/lib/db2/stmt/table.mjs +540 -0
  25. package/lib/db2/stmt/view.mjs +146 -0
  26. package/lib/diff.mjs +17 -5
  27. package/lib/equivalence.mjs +44 -5
  28. package/lib/evidence/cli.mjs +26 -7
  29. package/lib/evidence/record.mjs +16 -6
  30. package/lib/evidence/seal.mjs +17 -0
  31. package/lib/evidence/store.mjs +44 -15
  32. package/lib/evidence/timestamp.mjs +83 -0
  33. package/lib/evidence/verify.mjs +120 -42
  34. package/lib/exec-reading.mjs +51 -0
  35. package/lib/execution.mjs +3 -2
  36. package/lib/explain.mjs +2 -0
  37. package/lib/exploitability.mjs +11 -2
  38. package/lib/hlasm/asm/data.mjs +128 -0
  39. package/lib/hlasm/asm/listing.mjs +43 -0
  40. package/lib/hlasm/asm/output.mjs +45 -0
  41. package/lib/hlasm/asm/sections.mjs +161 -0
  42. package/lib/hlasm/asm/symbols.mjs +78 -0
  43. package/lib/hlasm/exec.mjs +27 -0
  44. package/lib/hlasm/expr.mjs +167 -0
  45. package/lib/hlasm/instr.mjs +73 -0
  46. package/lib/hlasm/locate.mjs +333 -0
  47. package/lib/hlasm/macro/authorization.mjs +146 -0
  48. package/lib/hlasm/macro/datasets.mjs +107 -0
  49. package/lib/hlasm/macro/io.mjs +162 -0
  50. package/lib/hlasm/macro/le.mjs +53 -0
  51. package/lib/hlasm/macro/linkage.mjs +143 -0
  52. package/lib/hlasm/macro/operator.mjs +77 -0
  53. package/lib/hlasm/macro/program.mjs +184 -0
  54. package/lib/hlasm/macro/recovery.mjs +85 -0
  55. package/lib/hlasm/macro/storage.mjs +167 -0
  56. package/lib/hlasm/macro/structured.mjs +131 -0
  57. package/lib/hlasm/model.mjs +97 -0
  58. package/lib/hlasm/mvs38.mjs +47 -0
  59. package/lib/hlasm/operands.mjs +59 -0
  60. package/lib/hlasm/optable.mjs +61 -0
  61. package/lib/hlasm/read.mjs +130 -0
  62. package/lib/hlasm.mjs +44 -7
  63. package/lib/ims/dli.mjs +37 -0
  64. package/lib/ims/macro/dbd.mjs +299 -0
  65. package/lib/ims/macro/psb.mjs +286 -0
  66. package/lib/ims/model.mjs +149 -0
  67. package/lib/ims/operands.mjs +23 -0
  68. package/lib/ims/read.mjs +37 -0
  69. package/lib/ims/rules.mjs +135 -0
  70. package/lib/inventory.mjs +10 -7
  71. package/lib/ironwork-ids.mjs +24 -0
  72. package/lib/ironwork.mjs +20 -13
  73. package/lib/kernel/pds-archive.mjs +256 -0
  74. package/lib/kernel/registry.mjs +32 -23
  75. package/lib/kernel/shared-pass.mjs +163 -0
  76. package/lib/kernel/source-tree.mjs +104 -36
  77. package/lib/kernel/version-key.mjs +17 -0
  78. package/lib/layout.mjs +26 -31
  79. package/lib/options.mjs +26 -4
  80. package/lib/parser.mjs +136 -17
  81. package/lib/pli/cursor.mjs +15 -0
  82. package/lib/pli/expr.mjs +101 -0
  83. package/lib/pli/include.mjs +82 -0
  84. package/lib/pli/layout.mjs +125 -0
  85. package/lib/pli/lex.mjs +198 -0
  86. package/lib/pli/program.mjs +280 -0
  87. package/lib/pli/rules/based.mjs +95 -0
  88. package/lib/pli/rules/conditions.mjs +68 -0
  89. package/lib/pli/rules/entry.mjs +130 -0
  90. package/lib/pli/rules/index.mjs +24 -0
  91. package/lib/pli/rules/preprocessor.mjs +55 -0
  92. package/lib/pli/statements.mjs +130 -0
  93. package/lib/pli/stmt/alloc.mjs +45 -0
  94. package/lib/pli/stmt/assignment.mjs +56 -0
  95. package/lib/pli/stmt/call.mjs +104 -0
  96. package/lib/pli/stmt/conditions.mjs +94 -0
  97. package/lib/pli/stmt/control.mjs +219 -0
  98. package/lib/pli/stmt/declare.mjs +149 -0
  99. package/lib/pli/stmt/exec.mjs +55 -0
  100. package/lib/pli/stmt/io.mjs +239 -0
  101. package/lib/pli/stmt/misc.mjs +4 -0
  102. package/lib/pli/stmt/preprocessor.mjs +242 -0
  103. package/lib/pli/stmt/procedure.mjs +258 -0
  104. package/lib/pli/stmt/stream.mjs +283 -0
  105. package/lib/pli/storage.mjs +129 -0
  106. package/lib/policy.mjs +6 -0
  107. package/lib/precompile-check.mjs +124 -0
  108. package/lib/precompile-cics.mjs +8 -4
  109. package/lib/reach.mjs +11 -2
  110. package/lib/revision.json +1 -1
  111. package/lib/sarif.mjs +44 -3
  112. package/lib/scan.mjs +28 -12
  113. package/lib/sets/abend.mjs +82 -8
  114. package/lib/sets/build.mjs +43 -37
  115. package/lib/sets/cics.mjs +20 -38
  116. package/lib/sets/compile.mjs +46 -18
  117. package/lib/sets/copybook.mjs +5 -3
  118. package/lib/sets/crypto.mjs +5 -3
  119. package/lib/sets/ddl.mjs +36 -0
  120. package/lib/sets/flow.mjs +32 -8
  121. package/lib/sets/hidden.mjs +5 -3
  122. package/lib/sets/hlasm.mjs +124 -8
  123. package/lib/sets/ims.mjs +139 -0
  124. package/lib/sets/log.mjs +11 -9
  125. package/lib/sets/opaque.mjs +27 -7
  126. package/lib/sets/pli.mjs +40 -0
  127. package/lib/sets/priv.mjs +5 -3
  128. package/lib/sets/recon.mjs +5 -3
  129. package/lib/sets/secrets.mjs +112 -0
  130. package/lib/sets/semantics.mjs +8 -3
  131. package/lib/sets/web.mjs +39 -23
  132. package/lib/site.mjs +10 -0
  133. package/lib/sources.mjs +80 -20
  134. package/lib/statement-cursor.mjs +67 -0
  135. package/lib/verify.mjs +3 -2
  136. package/lib/version.mjs +6 -0
  137. package/package.json +3 -2
  138. package/rules/compliance-cobit2019.json +520 -5
  139. package/rules/compliance-dora.json +509 -5
  140. package/rules/compliance-ffiec.json +505 -1
  141. package/rules/compliance-nist80053.json +557 -1
  142. package/rules/gitleaks-mainframe.toml +46 -15
  143. package/rules/hlasm-instructions.json +2396 -0
  144. package/rules/hlasm-optables.json +8024 -0
  145. package/schema/cobolwork-baseline.schema.json +36 -0
  146. package/schema/cobolwork-build-provenance.schema.json +187 -0
  147. package/schema/cobolwork-build.schema.json +382 -0
  148. package/schema/cobolwork-capabilities.schema.json +239 -0
  149. package/schema/cobolwork-diff.schema.json +217 -0
  150. package/schema/cobolwork-evidence.schema.json +161 -0
  151. package/schema/cobolwork-execution.schema.json +53 -0
  152. package/schema/cobolwork-explain.schema.json +360 -0
  153. package/schema/cobolwork-finding.schema.json +465 -0
  154. package/schema/cobolwork-flow.schema.json +465 -0
  155. package/schema/cobolwork-gate.schema.json +211 -0
  156. package/schema/cobolwork-inventory.schema.json +206 -0
  157. package/schema/cobolwork-parse.schema.json +105 -0
  158. package/schema/cobolwork-reach.schema.json +74 -0
  159. package/schema/cobolwork-report.schema.json +559 -0
  160. package/schema/cobolwork-witness.schema.json +107 -0
  161. package/schema/cobolwork.baseline.schema.json +101 -0
  162. package/schema/cobolwork.policy.schema.json +7 -0
  163. package/schema/cobolwork.site.schema.json +116 -0
@@ -0,0 +1,473 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Db2 for z/OS ALTER TABLE, after the Db2 13 SQL Reference: each clause as { action, ... } in the order
3
+ // written, then the reference's limits on which clauses may share one statement.
4
+
5
+ import { cursor, Db2Syntax } from '../cursor.mjs';
6
+ import {
7
+ readName, readInteger, readNumber, readNameList, only, readDataType, readColumnDefinition, readDefault,
8
+ readTableConstraint, readPeriod, readPartitioning,
9
+ } from './table.mjs';
10
+
11
+ const isName = (t) => !!t && (t.t === 'word' || t.t === 'ident');
12
+ const isWord = (t, u) => !!t && t.t === 'word' && (Array.isArray(u) ? u.includes(t.u) : t.u === u);
13
+ const isOp = (t, v) => !!t && t.t === 'op' && t.v === v;
14
+ const name = (c) => readName(c).join('.');
15
+ const yes = (c) => only(c, ['YES', 'NO']) === 'YES';
16
+
17
+ // What another dialect's ALTER TABLE looks like at the token where a Db2 for z/OS one stops.
18
+ const HINTS = [
19
+ [(t) => isOp(t, ','), 'Db2 for z/OS lists ALTER TABLE clauses without commas'],
20
+ [(t) => isOp(t, '/'), "'/' qualifies names in Db2 for i system naming, not Db2 for z/OS"],
21
+ [(t, n) => isWord(t, 'IF') && isWord(n, ['EXISTS', 'NOT']), 'IF [NOT] EXISTS is not Db2 for z/OS syntax'],
22
+ [(t, n) => isWord(t, 'OWNER') && isWord(n, 'TO'), 'OWNER TO is PostgreSQL, not Db2 for z/OS'],
23
+ [(t) => isWord(t, 'MODIFY'), 'MODIFY is Oracle or MySQL; Db2 for z/OS changes a column with ALTER COLUMN'],
24
+ [(t) => isWord(t, 'CHANGE'), 'CHANGE is MySQL; Db2 for z/OS renames a column with RENAME COLUMN'],
25
+ [(t, n) => isWord(t, 'RENAME') && isWord(n, 'TO'), 'Db2 for z/OS renames a table with the RENAME statement'],
26
+ [(t, n) => isWord(t, 'ON') && isWord(n, 'UPDATE'), 'Db2 for z/OS has no ON UPDATE rule; a foreign key takes ON DELETE only'],
27
+ [(t, n) => isWord(t, 'NOT') && isWord(n, 'VALID'), 'NOT VALID is PostgreSQL, not Db2 for z/OS'],
28
+ [(t, n) => isWord(t, ['VALIDATE', 'NOVALIDATE']) || (isWord(t, ['ENABLE', 'DISABLE']) && isWord(n, ['VALIDATE', 'NOVALIDATE'])), 'VALIDATE and NOVALIDATE are Oracle, not Db2 for z/OS'],
29
+ [(t, n) => isWord(t, ['DEFERRABLE', 'INITIALLY']) || (isWord(t, 'NOT') && isWord(n, 'DEFERRABLE')), 'a Db2 for z/OS constraint is not deferrable'],
30
+ [(t) => isWord(t, 'USING'), 'USING here is not Db2 for z/OS syntax'],
31
+ [(t) => isWord(t, ['AFTER', 'FIRST', 'AUTO_INCREMENT', 'ENGINE']), 'a MySQL clause, not Db2 for z/OS'],
32
+ [(t) => isWord(t, 'TYPE'), 'ALTER COLUMN … TYPE is PostgreSQL; Db2 for z/OS writes SET DATA TYPE'],
33
+ [(t, n) => isWord(t, ['SET', 'DROP']) && isWord(n, 'NOT'), 'a Db2 for z/OS column alteration has no SET NOT NULL or DROP NOT NULL'],
34
+ [(t, n) => isWord(t, 'SET') && (isWord(n, ['STATISTICS', 'STORAGE', 'COMPRESSION', 'SCHEMA', 'TABLESPACE', 'LOGGED', 'UNLOGGED']) || isOp(n, '(')), 'a PostgreSQL clause, not Db2 for z/OS'],
35
+ [(t, n) => isWord(t, 'ACTIVATE') && isWord(n, 'NOT'), 'ACTIVATE NOT LOGGED INITIALLY is Db2 for LUW, not Db2 for z/OS'],
36
+ [(t) => isWord(t, ['LOCKSIZE', 'PCTFREE', 'COMPRESS']), 'Db2 for z/OS sets this on the table space, with ALTER TABLESPACE'],
37
+ ];
38
+
39
+ function hint(c) {
40
+ const t = c.peek();
41
+ const found = t && HINTS.find(([test]) => test(t, c.peek(1)));
42
+ return found ? ` (${found[1]})` : '';
43
+ }
44
+
45
+ function refuse(c, what) {
46
+ const t = c.peek();
47
+ throw new Db2Syntax(`expected ${what}${t ? ` at '${t.v}'` : ' at end of statement'}${hint(c)}`, t || c.peek(-1));
48
+ }
49
+
50
+ function setter(c, into) {
51
+ return (key, value) => {
52
+ if (key in into) c.fail(`no second ${key}`);
53
+ into[key] = value;
54
+ };
55
+ }
56
+
57
+ function columnName(c) {
58
+ const t = c.next();
59
+ if (!isName(t)) { c.pos -= t ? 1 : 0; refuse(c, 'a column name'); }
60
+ if (c.isOp('.')) refuse(c, 'an unqualified column name');
61
+ return t.v;
62
+ }
63
+
64
+ // Whether t (n after it) opens another clause, so an added column's definition ends before it.
65
+ function opensClause(t, n) {
66
+ if (!t || isOp(t, ',')) return true;
67
+ if (t.t !== 'word') return false;
68
+ if (['ADD', 'ALTER', 'DROP', 'RENAME', 'ROTATE', 'VOLATILE', 'ACTIVATE', 'DEACTIVATE', 'APPEND', 'AUDIT', 'VALIDPROC'].includes(t.u)) return true;
69
+ if (t.u === 'DATA') return isWord(n, 'CAPTURE');
70
+ if (t.u === 'NOT') return isWord(n, 'VOLATILE');
71
+ if (t.u === 'ENABLE' || t.u === 'DISABLE') return isWord(n, 'ARCHIVE');
72
+ if (t.u === 'KEY') return isWord(n, 'LABEL');
73
+ if (t.u === 'NO') return isWord(n, 'KEY');
74
+ if (t.u === 'SET') return isWord(n, ['MATERIALIZED', 'SUMMARY']);
75
+ return false;
76
+ }
77
+
78
+ // An added column's references-clause or check-constraint; PRIMARY KEY and UNIQUE are clauses of their own.
79
+ function readAddedColumn(c) {
80
+ if (c.isWord('IF')) refuse(c, 'a column definition');
81
+ const rest = c.rest();
82
+ let end = 1;
83
+ for (let depth = 0; end < rest.length; end++) {
84
+ if (depth === 0 && opensClause(rest[end], rest[end + 1])) break;
85
+ if (isOp(rest[end], '(')) depth++;
86
+ else if (isOp(rest[end], ')')) depth--;
87
+ }
88
+ if (isOp(rest[1], '.')) { c.next(); refuse(c, 'an unqualified column name'); }
89
+ const s = cursor(rest.slice(0, end));
90
+ let column;
91
+ try {
92
+ column = readColumnDefinition(s);
93
+ } catch (e) {
94
+ if (e instanceof Db2Syntax && hint(s)) throw new Db2Syntax(e.message + hint(s), s.peek() || s.peek(-1));
95
+ throw e;
96
+ }
97
+ const key = column.constraints.find((k) => k.type === 'PRIMARY KEY' || k.type === 'UNIQUE');
98
+ if (key) throw new Db2Syntax(`an added column takes REFERENCES or CHECK, not ${key.type}; ADD ${key.type} is a clause of its own`, rest[0]);
99
+ c.pos += end;
100
+ return { action: 'ADD COLUMN', column };
101
+ }
102
+
103
+ function readAlteredType(c) {
104
+ const at = c.peek();
105
+ const type = readDataType(c);
106
+ const refused = type.name === 'DISTINCT' ? `a distinct type (${type.distinct})`
107
+ : ['DATE', 'TIME', 'ROWID'].includes(type.name) ? type.name
108
+ : type.long ? `LONG ${type.name}`
109
+ : 'ccsid' in type ? 'a CCSID'
110
+ : type.name === 'CLOB' && type.forData === 'BIT' ? 'CLOB FOR BIT DATA' : null;
111
+ if (refused) throw new Db2Syntax(`SET DATA TYPE does not take ${refused} in Db2 for z/OS`, at);
112
+ return type;
113
+ }
114
+
115
+ const NO_OPTION = { MINVALUE: 'minValue', MAXVALUE: 'maxValue', CYCLE: 'cycle', CACHE: 'cache', ORDER: 'order' };
116
+ const IDENTITY_SET = ['INCREMENT', 'NO', ...Object.keys(NO_OPTION), ...Object.keys(NO_OPTION).map((w) => `NO${w}`)];
117
+ const startsIdentityAlteration = (c) => c.isWord('RESTART') || (c.isWord('SET') && c.isWord(IDENTITY_SET, 1));
118
+
119
+ // RESTART [WITH n] and SET INCREMENT BY, SET [NO] MINVALUE and the rest, each at most once; NOCACHE and
120
+ // the other one-word forms are IBM's synonyms. restart: null restarts at the original START WITH.
121
+ function readIdentityAlteration(c) {
122
+ const identity = {};
123
+ const set = setter(c, identity);
124
+ while (startsIdentityAlteration(c)) {
125
+ if (c.word('RESTART')) { set('restart', c.word('WITH') ? readNumber(c) : null); continue; }
126
+ c.next();
127
+ if (c.word('INCREMENT')) { c.expectWord('BY'); set('incrementBy', readNumber(c)); }
128
+ else if (c.word('MINVALUE')) set('minValue', readNumber(c));
129
+ else if (c.word('MAXVALUE')) set('maxValue', readNumber(c));
130
+ else if (c.word('CYCLE')) set('cycle', true);
131
+ else if (c.word('ORDER')) set('order', true);
132
+ else if (c.word('CACHE')) set('cache', readInteger(c));
133
+ else if (c.word('NO')) set(NO_OPTION[only(c, Object.keys(NO_OPTION))], false);
134
+ else set(NO_OPTION[c.next().u.slice(2)], false);
135
+ }
136
+ return identity;
137
+ }
138
+
139
+ function readRowGenerated(c) {
140
+ c.expectWord('AS');
141
+ if (c.word('TRANSACTION')) { c.expectWord('START'); c.expectWord('ID'); return 'TRANSACTION START ID'; }
142
+ c.expectWord('ROW');
143
+ const which = only(c, ['BEGIN', 'START', 'END']);
144
+ return `ROW ${which === 'START' ? 'BEGIN' : which}`;
145
+ }
146
+
147
+ function readColumnAlteration(c) {
148
+ const clause = { action: 'ALTER COLUMN', column: columnName(c), change: null };
149
+ if (c.isWord('SET') && c.isWord('DATA', 1) && c.isWord('TYPE', 2)) {
150
+ c.pos += 3;
151
+ clause.change = 'SET DATA TYPE';
152
+ clause.type = readAlteredType(c);
153
+ if (c.isWord('INLINE') && c.isWord('LENGTH', 1)) { c.pos += 2; clause.inlineLength = readInteger(c); }
154
+ } else if (c.isWord('SET') && (c.isWord('DEFAULT', 1) || (c.isWord('WITH', 1) && c.isWord('DEFAULT', 2)))) {
155
+ c.pos += c.isWord('WITH', 1) ? 3 : 2;
156
+ clause.change = 'SET DEFAULT';
157
+ clause.default = readDefault(c);
158
+ if (clause.default.implicit && !c.done() && !opensClause(c.peek(), c.peek(1))) refuse(c, 'a constant, SESSION_USER, USER, CURRENT SQLID or NULL after DEFAULT');
159
+ } else if (c.isWord('SET') && c.isWord('INLINE', 1)) {
160
+ c.pos += 2;
161
+ c.expectWord('LENGTH');
162
+ clause.change = 'SET INLINE LENGTH';
163
+ clause.inlineLength = readInteger(c);
164
+ } else if (c.isWord('SET') && c.isWord('GENERATED', 1)) {
165
+ c.pos += 2;
166
+ clause.change = 'SET GENERATED';
167
+ const when = c.word('ALWAYS') ? 'ALWAYS' : c.word('BY') ? (c.expectWord('DEFAULT'), 'BY DEFAULT') : null;
168
+ if (when !== 'BY DEFAULT' && c.isWord('AS')) clause.generated = { when: 'ALWAYS', as: readRowGenerated(c) };
169
+ else {
170
+ if (!when) refuse(c, 'ALWAYS, BY DEFAULT or AS');
171
+ clause.generated = { when };
172
+ if (startsIdentityAlteration(c)) clause.identity = readIdentityAlteration(c);
173
+ }
174
+ } else if (c.isWord('DROP') && c.isWord('DEFAULT', 1)) {
175
+ c.pos += 2;
176
+ clause.change = 'DROP DEFAULT';
177
+ } else if (startsIdentityAlteration(c)) {
178
+ clause.change = 'IDENTITY';
179
+ clause.identity = readIdentityAlteration(c);
180
+ } else refuse(c, 'SET DATA TYPE, SET DEFAULT, SET INLINE LENGTH, SET GENERATED, DROP DEFAULT, RESTART or SET and an identity attribute');
181
+ return clause;
182
+ }
183
+
184
+ function readLimits(c) {
185
+ const limits = c.items().map((v) => {
186
+ const s = cursor(v);
187
+ const limit = s.word(['MAXVALUE', 'MINVALUE']) ? v[0].u : s.peek() && s.peek().t === 'lit' ? s.next().v : readNumber(s);
188
+ if (!s.done()) s.fail("',' or ')'");
189
+ return limit;
190
+ });
191
+ if (!limits.length) c.fail('a limit key value');
192
+ return limits;
193
+ }
194
+
195
+ // ENDING [AT] (…) [INCLUSIVE]; VALUES is IBM's synonym for ENDING AT.
196
+ function readBoundary(c) {
197
+ if (!c.word('VALUES')) { c.expectWord('ENDING'); c.word('AT'); }
198
+ const boundary = { limits: readLimits(c) };
199
+ if (c.word('INCLUSIVE')) boundary.inclusive = true;
200
+ return boundary;
201
+ }
202
+
203
+ function readHashSpace(c) {
204
+ c.expectWord('HASH');
205
+ c.expectWord('SPACE');
206
+ const n = readInteger(c);
207
+ return `${n}${only(c, ['K', 'M', 'G'])}`;
208
+ }
209
+
210
+ function readQueryOptions(c, into, required) {
211
+ const set = setter(c, into);
212
+ let read = false;
213
+ for (;;) {
214
+ if (c.word('MAINTAINED')) { c.expectWord('BY'); set('maintainedBy', only(c, ['SYSTEM', 'USER'])); }
215
+ else if (c.isWord(['ENABLE', 'DISABLE']) && c.isWord('QUERY', 1)) { set('queryOptimization', c.next().u === 'ENABLE'); c.next(); c.expectWord('OPTIMIZATION'); }
216
+ else break;
217
+ read = true;
218
+ }
219
+ if (required && !read) refuse(c, 'MAINTAINED BY, ENABLE QUERY OPTIMIZATION or DISABLE QUERY OPTIMIZATION');
220
+ }
221
+
222
+ function readMaterializedQuery(c) {
223
+ if (!c.isOp('(')) refuse(c, "'(' and the materialized query's fullselect");
224
+ const clause = { action: 'ADD MATERIALIZED QUERY', fullselect: c.group() };
225
+ for (const w of ['DATA', 'INITIALLY', 'DEFERRED', 'REFRESH', 'DEFERRED']) c.expectWord(w);
226
+ readQueryOptions(c, clause, false);
227
+ return clause;
228
+ }
229
+
230
+ // SET MATERIALIZED QUERY AS and SET SUMMARY AS: IBM's synonyms for ADD and DROP MATERIALIZED QUERY.
231
+ function readSetQuery(c) {
232
+ c.next();
233
+ if (c.next().u === 'MATERIALIZED') c.expectWord('QUERY');
234
+ c.expectWord('AS');
235
+ if (c.word('DEFINITION')) { c.expectWord('ONLY'); return { action: 'DROP MATERIALIZED QUERY' }; }
236
+ return readMaterializedQuery(c);
237
+ }
238
+
239
+ const startsConstraint = (c) => c.isWord(['CONSTRAINT', 'PRIMARY', 'FOREIGN']) || (c.isWord(['UNIQUE', 'CHECK']) && c.isOp('(', 1));
240
+
241
+ const readConstraint = (c) => ({ action: 'ADD CONSTRAINT', constraint: readTableConstraint(c) });
242
+
243
+ function readAdd(c) {
244
+ if (c.word('COLUMN')) return readAddedColumn(c);
245
+ if (c.isWord('PERIOD') && c.isWord(['FOR', 'SYSTEM_TIME', 'BUSINESS_TIME'], 1)) return { action: 'ADD PERIOD', period: readPeriod(c) };
246
+ if (startsConstraint(c)) return readConstraint(c);
247
+ if (c.isWord('PARTITION') && c.isWord('BY', 1)) {
248
+ if (c.isWord(['SIZE', 'GROWTH'], 2)) { c.pos += 2; refuse(c, 'RANGE or the partitioning columns'); }
249
+ return { action: 'ADD PARTITION BY RANGE', partitioning: readPartitioning(c) };
250
+ }
251
+ if (c.word('PARTITION')) {
252
+ if (c.isWord('HASH')) refuse(c, 'ENDING AT (an added partition takes no HASH SPACE)');
253
+ return { action: 'ADD PARTITION', ...(c.isWord(['ENDING', 'VALUES']) ? readBoundary(c) : {}) };
254
+ }
255
+ if ((c.isWord('SYSTEM') && c.isWord('VERSIONING', 1)) || (c.isWord('VERSIONING') && c.isWord('USE', 1))) {
256
+ c.word('SYSTEM');
257
+ for (const w of ['VERSIONING', 'USE', 'HISTORY', 'TABLE']) c.expectWord(w);
258
+ const clause = { action: 'ADD VERSIONING', historyTable: name(c), extraRow: false };
259
+ if (c.isWord('ON') && c.isWord('DELETE', 1)) {
260
+ c.pos += 2;
261
+ for (const w of ['ADD', 'EXTRA', 'ROW']) c.expectWord(w);
262
+ clause.extraRow = true;
263
+ }
264
+ return clause;
265
+ }
266
+ if ((c.isWord('MATERIALIZED') && c.isWord('QUERY', 1)) || (c.isWord('QUERY') && c.isOp('(', 1))) {
267
+ c.word('MATERIALIZED');
268
+ c.next();
269
+ return readMaterializedQuery(c);
270
+ }
271
+ if (c.isOp('(')) {
272
+ const at = c.pos;
273
+ c.group();
274
+ const query = c.isWord('DATA') && c.isWord('INITIALLY', 1);
275
+ c.pos = at;
276
+ if (!query) refuse(c, 'a column definition (a parenthesised list after ADD is Oracle, not Db2 for z/OS)');
277
+ return readMaterializedQuery(c);
278
+ }
279
+ if (c.isWord('ORGANIZE') && c.isWord('BY', 1)) {
280
+ c.pos += 2;
281
+ c.expectWord('HASH');
282
+ c.expectWord('UNIQUE');
283
+ const clause = { action: 'ADD ORGANIZATION', columns: readNameList(c) };
284
+ if (c.isWord('HASH')) clause.hashSpace = readHashSpace(c);
285
+ return clause;
286
+ }
287
+ if (c.word('CLONE')) return { action: 'ADD CLONE', clone: name(c) };
288
+ if (c.isWord('RESTRICT') && c.isWord('ON', 1)) { c.pos += 2; c.expectWord('DROP'); return { action: 'ADD RESTRICT ON DROP' }; }
289
+ if (c.isWord(['INDEX', 'KEY', 'FULLTEXT', 'SPATIAL']) || (c.isWord('UNIQUE') && c.isWord(['INDEX', 'KEY'], 1))) {
290
+ refuse(c, 'an ALTER TABLE clause (an index added by ALTER TABLE is MySQL; Db2 for z/OS uses CREATE INDEX)');
291
+ }
292
+ return readAddedColumn(c);
293
+ }
294
+
295
+ function readAlter(c) {
296
+ if (c.word('PARTITIONING')) {
297
+ c.expectWord('TO');
298
+ if (!c.isWord('PARTITION') || !c.isWord('BY', 1)) refuse(c, 'PARTITION BY');
299
+ if (!c.isWord(['GROWTH', 'SIZE'], 2)) return { action: 'ALTER PARTITIONING', to: readPartitioning(c) };
300
+ c.pos += 3;
301
+ const to = { by: 'GROWTH' };
302
+ const set = setter(c, to);
303
+ for (;;) {
304
+ if (c.isWord(['DSSIZE', 'EVERY'])) { c.next(); const n = readInteger(c); c.expectWord('G'); set('dssize', `${n}G`); }
305
+ else if (c.word('MAXPARTITIONS')) set('maxpartitions', readInteger(c));
306
+ else return { action: 'ALTER PARTITIONING', to };
307
+ }
308
+ }
309
+ if (c.isWord(['PARTITION', 'PART']) && c.peek(1) && c.peek(1).t === 'num') {
310
+ c.next();
311
+ const clause = { action: 'ALTER PARTITION', partition: readInteger(c) };
312
+ return c.isWord('HASH') ? { ...clause, hashSpace: readHashSpace(c) } : { ...clause, ...readBoundary(c) };
313
+ }
314
+ if ((c.isWord('MATERIALIZED') && c.isWord('QUERY', 1)) || (c.isWord('QUERY') && c.isWord('SET', 1) && c.isWord(['MAINTAINED', 'ENABLE', 'DISABLE'], 2))) {
315
+ c.word('MATERIALIZED');
316
+ c.next();
317
+ c.expectWord('SET');
318
+ const clause = { action: 'ALTER MATERIALIZED QUERY' };
319
+ readQueryOptions(c, clause, true);
320
+ return clause;
321
+ }
322
+ if (c.isWord('ORGANIZATION') && c.isWord('SET', 1) && c.isWord('HASH', 2)) {
323
+ c.pos += 2;
324
+ return { action: 'ALTER ORGANIZATION', hashSpace: readHashSpace(c) };
325
+ }
326
+ c.word('COLUMN');
327
+ return readColumnAlteration(c);
328
+ }
329
+
330
+ function readDrop(c) {
331
+ if (c.isWord('PRIMARY') && c.isWord('KEY', 1)) { c.pos += 2; return { action: 'DROP CONSTRAINT', type: 'PRIMARY KEY', name: null }; }
332
+ if (c.isWord('FOREIGN') && c.isWord('KEY', 1)) { c.pos += 2; return { action: 'DROP CONSTRAINT', type: 'FOREIGN KEY', name: name(c) }; }
333
+ if (c.isWord(['UNIQUE', 'CHECK', 'CONSTRAINT'])) {
334
+ const type = c.next().u;
335
+ return { action: 'DROP CONSTRAINT', type: type === 'CONSTRAINT' ? null : type, name: name(c) };
336
+ }
337
+ if ((c.isWord('SYSTEM') && c.isWord('VERSIONING', 1)) || (c.isWord('VERSIONING') && !c.isWord('RESTRICT', 1))) {
338
+ c.word('SYSTEM');
339
+ c.next();
340
+ return { action: 'DROP VERSIONING' };
341
+ }
342
+ if ((c.isWord('MATERIALIZED') && c.isWord('QUERY', 1)) || (c.isWord('QUERY') && !c.isWord('RESTRICT', 1))) {
343
+ c.word('MATERIALIZED');
344
+ c.next();
345
+ return { action: 'DROP MATERIALIZED QUERY' };
346
+ }
347
+ if (c.isWord(['ORGANIZATION', 'CLONE']) && !c.isWord('RESTRICT', 1)) return { action: `DROP ${c.next().u}` };
348
+ if (c.isWord('RESTRICT') && c.isWord('ON', 1)) { c.pos += 2; c.expectWord('DROP'); return { action: 'DROP RESTRICT ON DROP' }; }
349
+ if (c.isWord(['INDEX', 'KEY']) && !c.isWord('RESTRICT', 1)) refuse(c, 'an ALTER TABLE clause (DROP INDEX in ALTER TABLE is MySQL; Db2 for z/OS uses DROP INDEX on its own)');
350
+ c.word('COLUMN');
351
+ if (c.isWord('IF')) refuse(c, 'a column name');
352
+ const column = columnName(c);
353
+ if (!c.word('RESTRICT')) refuse(c, 'RESTRICT (Db2 for z/OS drops a column only with RESTRICT)');
354
+ return { action: 'DROP COLUMN', column };
355
+ }
356
+
357
+ function readRotate(c) {
358
+ c.expectWord('PARTITION');
359
+ const partition = c.word('FIRST') ? 'FIRST' : readInteger(c);
360
+ c.expectWord('TO');
361
+ c.expectWord('LAST');
362
+ const clause = { action: 'ROTATE PARTITION', partition, ...readBoundary(c) };
363
+ c.expectWord('RESET');
364
+ return clause;
365
+ }
366
+
367
+ // ADD may be left out before a unique or referential constraint that is the statement's first clause.
368
+ const startsKeyConstraint = (c) => (c.isWord('CONSTRAINT') && c.isWord(['PRIMARY', 'UNIQUE', 'FOREIGN'], 2)) ||
369
+ (c.isWord('PRIMARY') && c.isWord('KEY', 1)) || (c.isWord('UNIQUE') && c.isOp('(', 1)) || (c.isWord('FOREIGN') && c.isWord('KEY', 1));
370
+
371
+ function readClause(c, first) {
372
+ if (c.word('ADD')) return readAdd(c);
373
+ if (c.word('ALTER')) return readAlter(c);
374
+ if (c.word('DROP')) return readDrop(c);
375
+ if (c.isWord('RENAME') && c.isWord('COLUMN', 1)) {
376
+ c.pos += 2;
377
+ const from = columnName(c);
378
+ c.expectWord('TO');
379
+ return { action: 'RENAME COLUMN', from, to: columnName(c) };
380
+ }
381
+ if (c.word('ROTATE')) return readRotate(c);
382
+ if (c.isWord('DATA') && c.isWord('CAPTURE', 1)) { c.pos += 2; return { action: 'DATA CAPTURE', value: only(c, ['NONE', 'CHANGES']) }; }
383
+ if (c.word('VOLATILE')) { c.word('CARDINALITY'); return { action: 'VOLATILE', value: true }; }
384
+ if (c.isWord('NOT') && c.isWord('VOLATILE', 1)) { c.pos += 2; c.word('CARDINALITY'); return { action: 'VOLATILE', value: false }; }
385
+ if (c.isWord(['ACTIVATE', 'DEACTIVATE']) && c.isWord(['ROW', 'COLUMN'], 1)) {
386
+ const verb = c.next().u;
387
+ const what = c.next().u;
388
+ c.expectWord('ACCESS');
389
+ c.expectWord('CONTROL');
390
+ return { action: `${verb} ${what} ACCESS CONTROL` };
391
+ }
392
+ if (c.word('APPEND')) {
393
+ if (c.isWord(['ON', 'OFF'])) refuse(c, 'YES or NO (APPEND ON and APPEND OFF are Db2 for LUW)');
394
+ return { action: 'APPEND', value: yes(c) };
395
+ }
396
+ if (c.word('AUDIT')) return { action: 'AUDIT', value: only(c, ['NONE', 'CHANGES', 'ALL']) };
397
+ if (c.word('VALIDPROC')) return { action: 'VALIDPROC', program: c.word('NULL') ? null : name(c) };
398
+ if (c.isWord('ENABLE') && c.isWord('ARCHIVE', 1)) { c.pos += 2; c.expectWord('USE'); return { action: 'ENABLE ARCHIVE', archiveTable: name(c) }; }
399
+ if (c.isWord('DISABLE') && c.isWord('ARCHIVE', 1)) { c.pos += 2; return { action: 'DISABLE ARCHIVE' }; }
400
+ if (c.isWord('KEY') && c.isWord('LABEL', 1)) { c.pos += 2; return { action: 'KEY LABEL', keyLabel: name(c) }; }
401
+ if (c.isWord('NO') && c.isWord('KEY', 1)) { c.pos += 2; c.expectWord('LABEL'); return { action: 'KEY LABEL', keyLabel: null }; }
402
+ if (c.isWord('SET') && c.isWord(['MATERIALIZED', 'SUMMARY'], 1)) return readSetQuery(c);
403
+ if (first && startsKeyConstraint(c)) return readConstraint(c);
404
+ if (first && (c.isWord('CHECK') || c.isWord('CONSTRAINT'))) refuse(c, 'ADD before a check constraint');
405
+ if (startsKeyConstraint(c)) refuse(c, 'ADD (only a first clause may leave it out before a key)');
406
+ refuse(c, 'an ALTER TABLE clause');
407
+ }
408
+
409
+ const ALONE = new Set(['ADD CLONE', 'DROP CLONE', 'RENAME COLUMN', 'ALTER ORGANIZATION', 'DROP ORGANIZATION', 'ADD VERSIONING', 'DROP VERSIONING', 'DROP COLUMN',
410
+ 'ENABLE ARCHIVE', 'DISABLE ARCHIVE', 'ADD MATERIALIZED QUERY', 'DROP MATERIALIZED QUERY']);
411
+ // Row and column access control share a statement only with each other, each once (SQLCODE -637).
412
+ const ACCESS_CONTROL = /^(?:DE)?ACTIVATE (ROW|COLUMN) ACCESS CONTROL$/;
413
+ const REPEATABLE = new Set(['ADD COLUMN', 'ALTER COLUMN', 'ADD CONSTRAINT', 'DROP CONSTRAINT']);
414
+ const EXCLUSIVE = ['ALTER COLUMN', 'ADD PARTITION', 'ALTER PARTITION', 'ALTER PARTITIONING', 'ROTATE PARTITION'];
415
+
416
+ // The reference's notes on the clause list and SQLCODE -628's mutually exclusive clauses: some clauses
417
+ // stand alone, the rest appear at most once (ADD COLUMN, ALTER COLUMN and constraints aside), a column
418
+ // is added, altered or renamed once, SET DATA TYPE leads, and the partition clauses and ALTER COLUMN
419
+ // exclude one another.
420
+ function checkClauses(clauses, at) {
421
+ const fail = (message, k) => { throw new Db2Syntax(message, at[k]); };
422
+ const actions = clauses.map((x) => x.action);
423
+ const seen = new Set();
424
+ const columns = new Set();
425
+ let other = false;
426
+ clauses.forEach((x, k) => {
427
+ if (ALONE.has(x.action) && clauses.length > 1) fail(`${x.action} takes no other clause in the same ALTER TABLE statement`, k);
428
+ const access = ACCESS_CONTROL.exec(x.action);
429
+ if (access && clauses.some((y) => !ACCESS_CONTROL.test(y.action))) fail(`${x.action} takes no clause but the other access control clause`, k);
430
+ const key = x.action === 'ADD PERIOD' ? `ADD PERIOD ${x.period.name}` : access ? `ACTIVATE or DEACTIVATE ${access[1]} ACCESS CONTROL` : REPEATABLE.has(x.action) ? null : x.action;
431
+ if (key && seen.has(key)) fail(`no second ${key} in one ALTER TABLE statement`, k);
432
+ seen.add(key);
433
+ const column = x.action === 'ADD COLUMN' ? x.column.name : x.action === 'ALTER COLUMN' ? x.column : null;
434
+ if (column !== null && columns.has(column.toUpperCase())) fail(`column ${column} is added or altered by a second clause`, k);
435
+ if (column !== null) columns.add(column.toUpperCase());
436
+ const setsType = x.action === 'ALTER COLUMN' && x.change === 'SET DATA TYPE';
437
+ if (setsType && other) fail('ALTER COLUMN … SET DATA TYPE comes before every other clause', k);
438
+ if (!setsType) other = true;
439
+ });
440
+ const partitioning = [...new Set(actions.filter((a) => EXCLUSIVE.includes(a)))];
441
+ if (partitioning.length > 1 && !(partitioning.length === 2 && partitioning.includes('ADD PARTITION') && partitioning.includes('ALTER PARTITION'))) {
442
+ fail(`${partitioning[0]} and ${partitioning[1]} do not share an ALTER TABLE statement`, actions.indexOf(partitioning[1]));
443
+ }
444
+ if (actions.includes('ALTER PARTITIONING') && actions.includes('ADD PARTITION BY RANGE')) fail('ALTER PARTITIONING and ADD PARTITION BY do not share an ALTER TABLE statement', actions.indexOf('ADD PARTITION BY RANGE'));
445
+ if (actions.includes('ALTER COLUMN') && actions.includes('VALIDPROC')) fail('ALTER COLUMN and VALIDPROC do not share an ALTER TABLE statement', actions.indexOf('VALIDPROC'));
446
+ if (actions.includes('ADD ORGANIZATION') && actions.includes('APPEND')) fail('ADD ORGANIZE BY HASH and APPEND do not share an ALTER TABLE statement', actions.indexOf('APPEND'));
447
+ const referencing = clauses.filter((x) => x.action === 'ADD COLUMN' && x.column.constraints.some((k) => k.type === 'FOREIGN KEY'));
448
+ if (referencing.length > 1) fail('at most one added column carries a REFERENCES clause', clauses.indexOf(referencing[1]));
449
+ const drops = clauses.filter((x) => x.action === 'DROP CONSTRAINT');
450
+ if (drops.some((x) => x.type === null) && drops.some((x) => x.type !== null)) {
451
+ fail('DROP CONSTRAINT does not share an ALTER TABLE statement with DROP PRIMARY KEY, UNIQUE, FOREIGN KEY or CHECK', clauses.indexOf(drops[drops.length - 1]));
452
+ }
453
+ }
454
+
455
+ function readAlterTable(c) {
456
+ c.expectWord('ALTER');
457
+ c.expectWord('TABLE');
458
+ if (c.isWord('IF') && c.isWord('EXISTS', 1)) refuse(c, 'a table name');
459
+ const parts = readName(c);
460
+ if (parts.length > 3) refuse(c, 'a table name of at most three parts');
461
+ const node = { kind: 'ALTER TABLE', name: parts.join('.'), clauses: [] };
462
+ const at = [];
463
+ do {
464
+ at.push(c.peek());
465
+ node.clauses.push(readClause(c, node.clauses.length === 0));
466
+ } while (!c.done());
467
+ checkClauses(node.clauses, at);
468
+ return node;
469
+ }
470
+
471
+ export const parsers = {
472
+ 'ALTER TABLE': (c) => readAlterTable(c),
473
+ };
@@ -0,0 +1,125 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Parsers for Db2 GRANT and REVOKE statements.
3
+
4
+ const PRIVILEGES = new Set([
5
+ 'ALL', 'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'ALTER', 'INDEX', 'REFERENCES', 'TRIGGER',
6
+ 'EXECUTE', 'USAGE', 'BIND', 'COPY', 'CREATEIN', 'ALTERIN', 'DROPIN', 'CONTROL',
7
+ 'DBADM', 'DBCTRL', 'DBMAINT', 'SYSADM', 'SYSCTRL', 'SYSOPR', 'SECADM', 'ACCESSCTRL',
8
+ 'DATAACCESS', 'BINDADD', 'BINDAGENT', 'CREATETAB', 'CREATETS', 'CREATEDBA', 'CREATEDBC',
9
+ 'CREATESG', 'CREATEALIAS', 'DISPLAYDB', 'DROP', 'IMAGCOPY', 'LOAD', 'RECOVERDB', 'REORG',
10
+ 'REPAIR', 'STARTDB', 'STATS', 'STOPDB', 'MONITOR1', 'MONITOR2', 'TRACE', 'ARCHIVE', 'BSDS',
11
+ 'DISPLAY', 'RECOVER', 'STOPALL', 'STOSPACE', 'CONNECT', 'CREATE_NOT_FENCED_ROUTINE',
12
+ 'IMPLICIT_SCHEMA', 'QUIESCE_CONNECT'
13
+ ]);
14
+
15
+ const OBJECT_TYPES = new Set(['TABLE', 'DATABASE', 'PLAN', 'PACKAGE', 'COLLECTION', 'SCHEMA', 'PROCEDURE', 'FUNCTION', 'SEQUENCE', 'VARIABLE', 'TYPE', 'JAR']);
16
+ const USE_OF = new Set(['STOGROUP', 'TABLESPACE', 'BUFFERPOOL']);
17
+
18
+ function readName(c) {
19
+ const t = c.peek();
20
+ if (!t) c.fail('a name');
21
+ if (t.t === 'word' || t.t === 'ident') {
22
+ c.next();
23
+ const parts = [t.v];
24
+ while (c.isOp('.')) {
25
+ c.next();
26
+ const nt = c.peek();
27
+ if (!nt || (nt.t !== 'word' && nt.t !== 'ident')) c.fail('a name part');
28
+ c.next();
29
+ parts.push(nt.v);
30
+ }
31
+ return parts;
32
+ }
33
+ c.fail('a name');
34
+ }
35
+
36
+ // UPDATE and REFERENCES may name columns; USE OF STOGROUP, TABLESPACE or BUFFERPOOL is a privilege
37
+ // whose objects follow it directly, with no ON.
38
+ function readPrivilege(c) {
39
+ const t = c.peek();
40
+ if (!t || t.t !== 'word') c.fail('a privilege');
41
+ if (t.u === 'USE' && c.isWord('OF', 1)) {
42
+ c.next();
43
+ c.next();
44
+ if (c.isWord('ALL') && c.isWord('BUFFERPOOLS', 1)) { c.next(); c.next(); return { name: 'USE', of: 'ALL BUFFERPOOLS', columns: null }; }
45
+ const of = c.expectWord([...USE_OF]).u;
46
+ return { name: 'USE', of, columns: null };
47
+ }
48
+ if (!PRIVILEGES.has(t.u)) c.fail(`privilege '${t.v}'`);
49
+ c.next();
50
+ if (t.u === 'ALL') c.word('PRIVILEGES');
51
+ let columns = null;
52
+ if ((t.u === 'UPDATE' || t.u === 'REFERENCES') && c.isOp('(')) {
53
+ columns = c.items().map((toks) => {
54
+ if (toks.length !== 1 || (toks[0].t !== 'word' && toks[0].t !== 'ident')) c.fail('a column name');
55
+ return toks[0].v;
56
+ });
57
+ }
58
+ return { name: t.u, columns };
59
+ }
60
+
61
+ // A function or procedure may carry its parameter types after its name.
62
+ function readObject(c) {
63
+ const parts = readName(c);
64
+ if (c.isOp('(')) c.group();
65
+ return parts;
66
+ }
67
+
68
+ function readGrantee(c) {
69
+ const t = c.peek();
70
+ if (!t || t.t !== 'word') c.fail('a grantee');
71
+ if (t.u === 'PUBLIC') { c.next(); return { type: 'PUBLIC', name: 'PUBLIC' }; }
72
+ if ((t.u === 'ROLE' || t.u === 'USER' || t.u === 'GROUP') && c.peek(1) && (c.peek(1).t === 'word' || c.peek(1).t === 'ident')) {
73
+ c.next();
74
+ return { type: t.u, name: readName(c).join('.') };
75
+ }
76
+ return { type: 'NAME', name: readName(c).join('.') };
77
+ }
78
+
79
+ const list = (c, read) => { const out = [read(c)]; while (c.op(',')) out.push(read(c)); return out; };
80
+
81
+ // GRANT and REVOKE share everything up to TO or FROM: privileges, then ON an optional object type
82
+ // and the objects (TABLE when no type is written), or the objects of USE OF.
83
+ function grantBody(c, verb) {
84
+ c.expectWord(verb);
85
+ const privileges = list(c, readPrivilege);
86
+ let objectType = null;
87
+ let objects = null;
88
+ const use = privileges.find((p) => p.name === 'USE');
89
+ if (use) {
90
+ objectType = use.of;
91
+ if (use.of !== 'ALL BUFFERPOOLS') objects = list(c, readObject);
92
+ } else if (c.word('ON')) {
93
+ if (c.isWord('SPECIFIC') && c.isWord('FUNCTION', 1)) { c.next(); c.next(); objectType = 'SPECIFIC FUNCTION'; }
94
+ else if (c.isWord('DISTINCT') && c.isWord('TYPE', 1)) { c.next(); c.next(); objectType = 'DISTINCT TYPE'; }
95
+ else if (c.isWord([...OBJECT_TYPES]) && c.peek(1) && c.peek(1).t !== 'op') objectType = c.next().u;
96
+ else objectType = 'TABLE';
97
+ objects = list(c, readObject);
98
+ }
99
+ c.expectWord(verb === 'GRANT' ? 'TO' : 'FROM');
100
+ return { kind: verb, privileges, objectType, objects, grantees: list(c, readGrantee) };
101
+ }
102
+
103
+ function parseGrant(c) {
104
+ const node = { ...grantBody(c, 'GRANT'), withGrantOption: false, atAllLocations: false };
105
+ for (;;) {
106
+ if (c.isWord('WITH') && c.isWord('GRANT', 1)) { c.next(); c.next(); c.expectWord('OPTION'); node.withGrantOption = true; continue; }
107
+ if (c.isWord('AT') && c.isWord('ALL', 1)) { c.next(); c.next(); c.expectWord('LOCATIONS'); node.atAllLocations = true; continue; }
108
+ return node;
109
+ }
110
+ }
111
+
112
+ function parseRevoke(c) {
113
+ const node = { ...grantBody(c, 'REVOKE'), by: null, dependent: null };
114
+ if (c.word('BY')) node.by = c.word('ALL') ? 'ALL' : list(c, (x) => readName(x).join('.'));
115
+ if (c.word('RESTRICT')) node.dependent = 'RESTRICT';
116
+ else if (c.isWord('INCLUDING') || (c.isWord('NOT') && c.isWord('INCLUDING', 1))) {
117
+ node.dependent = c.word('NOT') ? 'NOT INCLUDING' : 'INCLUDING';
118
+ c.expectWord('INCLUDING');
119
+ c.expectWord('DEPENDENT');
120
+ c.expectWord('PRIVILEGES');
121
+ }
122
+ return node;
123
+ }
124
+
125
+ export const parsers = { GRANT: parseGrant, REVOKE: parseRevoke };