@abaplint/runtime 2.13.94 → 2.13.95
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/build/src/builtin/find.js +33 -2
- package/build/src/compare/cp.js +42 -29
- package/build/src/statements/concatenate.js +4 -2
- package/build/src/statements/loop.js +13 -5
- package/build/src/statements/update_database.js +14 -0
- package/build/src/types/table.d.ts +1 -1
- package/build/src/types/table.js +16 -6
- package/package.json +3 -3
|
@@ -32,7 +32,7 @@ function find(input) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
|
|
35
|
+
let sub = typeof input.sub === "string" ? input.sub : input.sub?.get() || "";
|
|
36
36
|
let off = (0, _position_1.position)(input.off) || 0;
|
|
37
37
|
let occ = (0, _position_1.position)(input.occ);
|
|
38
38
|
if (occ === 0) {
|
|
@@ -41,6 +41,26 @@ function find(input) {
|
|
|
41
41
|
else if (occ === undefined) {
|
|
42
42
|
occ = 1;
|
|
43
43
|
}
|
|
44
|
+
const caseInput = typeof input.case === "string" ? input.case : input.case?.get();
|
|
45
|
+
if (caseInput !== undefined && caseInput !== "X") {
|
|
46
|
+
val = foldCase(val);
|
|
47
|
+
sub = foldCase(sub);
|
|
48
|
+
}
|
|
49
|
+
if (occ < 0 && sub !== "" && off === 0) {
|
|
50
|
+
// search from the right, on the string as it is, so a "sub" of any length
|
|
51
|
+
// matches and the offset needs no mapping back
|
|
52
|
+
let from = val.length - sub.length;
|
|
53
|
+
let found = -1;
|
|
54
|
+
for (let i = 0; i < Math.abs(occ); i++) {
|
|
55
|
+
found = from < 0 ? -1 : val.lastIndexOf(sub, from);
|
|
56
|
+
if (found < 0) {
|
|
57
|
+
return new types_1.Integer().set(-1);
|
|
58
|
+
}
|
|
59
|
+
from = found - 1;
|
|
60
|
+
}
|
|
61
|
+
return new types_1.Integer().set(found);
|
|
62
|
+
}
|
|
63
|
+
// a negative "occ" with an empty "sub" or an "off" still comes here, unchanged
|
|
44
64
|
let negative = false;
|
|
45
65
|
if (occ < 0) {
|
|
46
66
|
negative = true;
|
|
@@ -54,7 +74,7 @@ function find(input) {
|
|
|
54
74
|
}
|
|
55
75
|
let found = -1;
|
|
56
76
|
for (let i = 0; i < occ; i++) {
|
|
57
|
-
found = val.indexOf(sub
|
|
77
|
+
found = val.indexOf(sub, off);
|
|
58
78
|
if (found >= 0) {
|
|
59
79
|
off = found + 1;
|
|
60
80
|
}
|
|
@@ -65,4 +85,15 @@ function find(input) {
|
|
|
65
85
|
return new types_1.Integer().set(found);
|
|
66
86
|
}
|
|
67
87
|
}
|
|
88
|
+
// lower case one character at a time and keep a character whose lower case form
|
|
89
|
+
// has another length, so every offset stays valid - a plain toLowerCase( )
|
|
90
|
+
// turns e.g. "\u0130" into two code units
|
|
91
|
+
function foldCase(str) {
|
|
92
|
+
let ret = "";
|
|
93
|
+
for (const character of str) {
|
|
94
|
+
const lower = character.toLowerCase();
|
|
95
|
+
ret += lower.length === character.length ? lower : character;
|
|
96
|
+
}
|
|
97
|
+
return ret;
|
|
98
|
+
}
|
|
68
99
|
//# sourceMappingURL=find.js.map
|
package/build/src/compare/cp.js
CHANGED
|
@@ -5,6 +5,41 @@ const types_1 = require("../types");
|
|
|
5
5
|
function escapeRegExpCharacter(input) {
|
|
6
6
|
return input.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
7
7
|
}
|
|
8
|
+
const CACHE_SIZE = 1000;
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
/** `end` is the pattern's length after the last token that is not a `*`: what
|
|
11
|
+
* follows is a run of trailing `[\s\S]*`, which matches any rest of the
|
|
12
|
+
* string - so it goes, and so does the `$` that made the engine walk there */
|
|
13
|
+
function compile(r) {
|
|
14
|
+
let pattern = "";
|
|
15
|
+
let end = 0;
|
|
16
|
+
for (let i = 0; i < r.length; i++) {
|
|
17
|
+
const current = r[i];
|
|
18
|
+
if (current === "*") {
|
|
19
|
+
pattern += "[\\s\\S]*";
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (current === "#") {
|
|
23
|
+
if (i + 1 < r.length) {
|
|
24
|
+
const next = r[i + 1];
|
|
25
|
+
pattern += next === "#" ? "#" : escapeRegExpCharacter(next);
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
pattern += "#";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (current === "+") {
|
|
33
|
+
pattern += "[\\s\\S]";
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
pattern += escapeRegExpCharacter(current);
|
|
37
|
+
}
|
|
38
|
+
end = pattern.length;
|
|
39
|
+
}
|
|
40
|
+
const open = end < pattern.length;
|
|
41
|
+
return new RegExp("^" + pattern.slice(0, end) + (open ? "" : "$"), "iu");
|
|
42
|
+
}
|
|
8
43
|
function cp(left, right) {
|
|
9
44
|
let l = "";
|
|
10
45
|
if (typeof left === "number" || typeof left === "string") {
|
|
@@ -35,36 +70,14 @@ function cp(left, right) {
|
|
|
35
70
|
else {
|
|
36
71
|
r = right.get().toString().trimEnd();
|
|
37
72
|
}
|
|
38
|
-
let
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
const next = r[i + 1];
|
|
44
|
-
if (next === "#") {
|
|
45
|
-
pattern += "#";
|
|
46
|
-
i++;
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
pattern += escapeRegExpCharacter(next);
|
|
50
|
-
i++;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
pattern += "#";
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
else if (current === "*") {
|
|
58
|
-
pattern += "[\\s\\S]*";
|
|
59
|
-
}
|
|
60
|
-
else if (current === "+") {
|
|
61
|
-
pattern += "[\\s\\S]";
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
pattern += escapeRegExpCharacter(current);
|
|
73
|
+
let reg = cache.get(r);
|
|
74
|
+
if (reg === undefined) {
|
|
75
|
+
reg = compile(r);
|
|
76
|
+
if (cache.size >= CACHE_SIZE) {
|
|
77
|
+
cache.delete(cache.keys().next().value);
|
|
65
78
|
}
|
|
79
|
+
cache.set(r, reg);
|
|
66
80
|
}
|
|
67
|
-
|
|
68
|
-
return l.match(reg) !== null;
|
|
81
|
+
return reg.test(l);
|
|
69
82
|
}
|
|
70
83
|
//# sourceMappingURL=cp.js.map
|
|
@@ -38,8 +38,10 @@ function concatenate(input) {
|
|
|
38
38
|
if (hasSeparator === true && i > 0) {
|
|
39
39
|
result += sep;
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
41
|
+
const row = array[i];
|
|
42
|
+
const value = row.get();
|
|
43
|
+
// Preserve trailing blanks in string rows even without RESPECTING BLANKS.
|
|
44
|
+
result += respectingBlanks === true || row instanceof types_1.String ? value : value.trimEnd();
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
setTarget(input.target, result);
|
|
@@ -8,13 +8,17 @@ function determineFromTo(array, topEquals, key) {
|
|
|
8
8
|
// if there is no WHERE supplied, its using the sorting of the secondary key
|
|
9
9
|
return { from: 1, to: array.length };
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
// 1-based, as the branch without topEquals above answers: when the key's
|
|
12
|
+
// first field is not in topEquals (a key over a substructure component,
|
|
13
|
+
// s-x, is never put there) nothing is narrowed, and the loop must not start
|
|
14
|
+
// at row index -1
|
|
15
|
+
let from = 1;
|
|
12
16
|
let to = array.length;
|
|
13
17
|
// todo: multi field
|
|
14
18
|
const keyField = key.keyFields[0].toLowerCase();
|
|
15
19
|
const keyValue = topEquals[keyField];
|
|
16
20
|
if (keyField && keyValue) {
|
|
17
|
-
from = (0, binary_search_1.binarySearchFrom)(array,
|
|
21
|
+
from = (0, binary_search_1.binarySearchFrom)(array, 0, to, keyField, keyValue);
|
|
18
22
|
to = (0, binary_search_1.binarySearchTo)(array, from, to, keyField, keyValue);
|
|
19
23
|
// console.dir("from: " + from + ", to: " + to);
|
|
20
24
|
}
|
|
@@ -99,7 +103,9 @@ async function* loop(table, options) {
|
|
|
99
103
|
let loopFrom = options?.from && options?.from.get() > 0 ? options.from.get() - 1 : 0;
|
|
100
104
|
let loopTo = options?.to && options.to.get() < length ? options.to.get() : length;
|
|
101
105
|
let array = [];
|
|
102
|
-
|
|
106
|
+
// a dynamic key name may come in any case, USING KEY ('PRIMARY_KEY') is the primary key too
|
|
107
|
+
const isPrimaryKey = options?.usingKey === undefined || options.usingKey.toLowerCase() === "primary_key";
|
|
108
|
+
if (options?.usingKey && isPrimaryKey === false) {
|
|
103
109
|
array = table.getSecondaryIndex(options.usingKey);
|
|
104
110
|
const { from, to } = determineFromTo(array, options.topEquals, table.getKeyByName(options.usingKey));
|
|
105
111
|
loopFrom = Math.max(loopFrom, from) - 1;
|
|
@@ -122,7 +128,7 @@ async function* loop(table, options) {
|
|
|
122
128
|
// The same goes for a loop that reads an index table through a hash
|
|
123
129
|
// secondary key. Handing out 1, 2, 3 there looks helpful and is a lie the
|
|
124
130
|
// caller cannot tell from the truth.
|
|
125
|
-
const usedKey = options?.usingKey === undefined ||
|
|
131
|
+
const usedKey = options?.usingKey === undefined || isPrimaryKey
|
|
126
132
|
? undefined
|
|
127
133
|
: table.getKeyByName(options.usingKey);
|
|
128
134
|
const hasRowNumber = usedKey !== undefined
|
|
@@ -131,7 +137,9 @@ async function* loop(table, options) {
|
|
|
131
137
|
try {
|
|
132
138
|
const isStructured = array[0] instanceof types_1.Structure;
|
|
133
139
|
while (loopController.index < loopController.loopTo) {
|
|
134
|
-
|
|
140
|
+
// the body may have deleted rows: never read past the end, where
|
|
141
|
+
// array[array.length] is undefined
|
|
142
|
+
if (loopController.index >= array.length) {
|
|
135
143
|
break;
|
|
136
144
|
}
|
|
137
145
|
const current = array[loopController.index];
|
|
@@ -15,6 +15,20 @@ async function updateDatabase(table, options, context) {
|
|
|
15
15
|
if (typeof table !== "string") {
|
|
16
16
|
table = table.get();
|
|
17
17
|
}
|
|
18
|
+
if (options.table !== undefined) {
|
|
19
|
+
// row by row, as a work area each: sy-subrc 4 when a row found nothing
|
|
20
|
+
// to update, sy-dbcnt the rows updated
|
|
21
|
+
let subrc = 0;
|
|
22
|
+
let dbcnt = 0;
|
|
23
|
+
for (const row of options.table.array()) {
|
|
24
|
+
await updateDatabase(table, { from: row }, context);
|
|
25
|
+
subrc = Math.max(subrc, abap.builtin.sy.get().subrc.get());
|
|
26
|
+
dbcnt += abap.builtin.sy.get().dbcnt.get();
|
|
27
|
+
}
|
|
28
|
+
abap.builtin.sy.get().subrc.set(subrc);
|
|
29
|
+
abap.builtin.sy.get().dbcnt.set(dbcnt);
|
|
30
|
+
return subrc;
|
|
31
|
+
}
|
|
18
32
|
const tabl = abap.DDIC[table.toUpperCase()];
|
|
19
33
|
if (tabl === undefined) {
|
|
20
34
|
await (0, throw_error_1.throwErrorWithParameters)("CX_SY_DYNAMIC_OSQL_SEMANTICS", { sqlmsg: new types_1.String().set(`Table ${table} not found`) });
|
|
@@ -97,7 +97,7 @@ export declare class Table implements ITable {
|
|
|
97
97
|
clone(): Table;
|
|
98
98
|
getArrayLength(): number;
|
|
99
99
|
getKeyByName(name: string): ITableKey | undefined;
|
|
100
|
-
getSecondaryIndex(name: string):
|
|
100
|
+
getSecondaryIndex(name: string): any[];
|
|
101
101
|
getQualifiedName(): string | undefined;
|
|
102
102
|
getOptions(): ITableOptions;
|
|
103
103
|
startLoop(from: number, to: number, array: any[]): LoopController;
|
package/build/src/types/table.js
CHANGED
|
@@ -27,6 +27,15 @@ var TableKeyType;
|
|
|
27
27
|
TableKeyType["user"] = "USER";
|
|
28
28
|
TableKeyType["empty"] = "EMPTY";
|
|
29
29
|
})(TableKeyType || (exports.TableKeyType = TableKeyType = {}));
|
|
30
|
+
/* rows are handed to a sorted secondary key in table order, and a duplicate of a
|
|
31
|
+
* non-unique key goes in front of its equals, as INSERT ... INTO TABLE does: reversed
|
|
32
|
+
* before the stable sort, the later of two equal rows comes first */
|
|
33
|
+
function secondaryDuplicatesFirst(rows, key) {
|
|
34
|
+
if (key.type === TableAccessType.sorted && key.isUnique !== true) {
|
|
35
|
+
rows.reverse();
|
|
36
|
+
}
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
30
39
|
class LoopController {
|
|
31
40
|
index;
|
|
32
41
|
loopTo;
|
|
@@ -109,7 +118,7 @@ class HashedTable {
|
|
|
109
118
|
throw `Table, secondary key "${name}" not found`;
|
|
110
119
|
}
|
|
111
120
|
// note, array() already is a copy, so it can be used,
|
|
112
|
-
const copy = this.array();
|
|
121
|
+
const copy = secondaryDuplicatesFirst(this.array(), secondary);
|
|
113
122
|
(0, sort_1.sort)(copy, { by: secondary.keyFields.map(k => { return { component: k.toLowerCase() }; }) });
|
|
114
123
|
this.secondaryIndexes[name.toUpperCase()] = copy;
|
|
115
124
|
return copy;
|
|
@@ -365,7 +374,7 @@ class Table {
|
|
|
365
374
|
if (secondary === undefined) {
|
|
366
375
|
throw `Table, secondary key "${name}" not found`;
|
|
367
376
|
}
|
|
368
|
-
const copy = [...this.value];
|
|
377
|
+
const copy = secondaryDuplicatesFirst([...this.value], secondary);
|
|
369
378
|
(0, sort_1.sort)(copy, { by: secondary.keyFields.map(k => { return { component: k.toLowerCase() }; }), skipSortedCheck: true });
|
|
370
379
|
this.secondaryIndexes[name.toUpperCase()] = copy;
|
|
371
380
|
return copy;
|
|
@@ -482,24 +491,25 @@ class Table {
|
|
|
482
491
|
return { value: this.insertIndex(val, 0, true), subrc: 0 };
|
|
483
492
|
}
|
|
484
493
|
const lastComparison = compare(this.value[lastIndex], val);
|
|
485
|
-
if (lastComparison < 0
|
|
494
|
+
if (lastComparison < 0) {
|
|
486
495
|
return { value: this.insertIndex(val, this.value.length, true), subrc: 0 };
|
|
487
496
|
}
|
|
488
|
-
else if (lastComparison === 0) {
|
|
497
|
+
else if (lastComparison === 0 && unique === true) {
|
|
489
498
|
return { value: undefined, subrc: 4 };
|
|
490
499
|
}
|
|
491
500
|
let low = 0;
|
|
492
501
|
let high = this.value.length;
|
|
493
502
|
while (low < high) {
|
|
494
503
|
const middle = Math.floor((low + high) / 2);
|
|
495
|
-
|
|
504
|
+
// a duplicate of a non-unique key goes in front of the rows it duplicates
|
|
505
|
+
if (compare(this.value[middle], val) < 0) {
|
|
496
506
|
low = middle + 1;
|
|
497
507
|
}
|
|
498
508
|
else {
|
|
499
509
|
high = middle;
|
|
500
510
|
}
|
|
501
511
|
}
|
|
502
|
-
if (unique === true && low
|
|
512
|
+
if (unique === true && low < this.value.length && compare(this.value[low], val) === 0) {
|
|
503
513
|
return { value: undefined, subrc: 4 };
|
|
504
514
|
}
|
|
505
515
|
return { value: this.insertIndex(val, low, true), subrc: 0 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/runtime",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.95",
|
|
4
4
|
"description": "Transpiler - Runtime",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"typings": "build/src/index.d.ts",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"@types/mocha": "^10.0.10",
|
|
34
34
|
"@types/node": "^26.4.1",
|
|
35
35
|
"chai": "^6.2.2",
|
|
36
|
-
"mocha": "^12.0.
|
|
36
|
+
"mocha": "^12.0.3",
|
|
37
37
|
"source-map-support": "^0.5.21",
|
|
38
38
|
"typescript": "^6.0.3"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"temporal-polyfill": "^1.0.
|
|
41
|
+
"temporal-polyfill": "^1.0.5"
|
|
42
42
|
}
|
|
43
43
|
}
|