@rex0220/kintone-sql-tools 3.1.0 → 3.3.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 +5 -4
- package/dist-cli/ksql.js +614 -123
- package/dist-mcp/ksql-mcp.js +617 -126
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -117,10 +117,14 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
117
117
|
["LTRIM", "LTRIM" /* LTRIM */],
|
|
118
118
|
["RTRIM", "RTRIM" /* RTRIM */],
|
|
119
119
|
["LENGTH", "LENGTH" /* LENGTH */],
|
|
120
|
+
["LENGTH_CHAR", "LENGTH_CHAR" /* LENGTH_CHAR */],
|
|
120
121
|
["SUBSTRING", "SUBSTRING" /* SUBSTRING */],
|
|
121
122
|
["SUBSTR", "SUBSTR" /* SUBSTR */],
|
|
122
123
|
["CONCAT", "CONCAT" /* CONCAT */],
|
|
123
124
|
["REPLACE", "REPLACE" /* REPLACE */],
|
|
125
|
+
["REGEXP_LIKE", "REGEXP_LIKE" /* REGEXP_LIKE */],
|
|
126
|
+
["REGEXP_REPLACE", "REGEXP_REPLACE" /* REGEXP_REPLACE */],
|
|
127
|
+
["REGEXP_SUBSTR", "REGEXP_SUBSTR" /* REGEXP_SUBSTR */],
|
|
124
128
|
["COALESCE", "COALESCE" /* COALESCE */],
|
|
125
129
|
["NULLIF", "NULLIF" /* NULLIF */],
|
|
126
130
|
["ISNULL", "ISNULL" /* ISNULL */],
|
|
@@ -129,6 +133,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
129
133
|
["LEAST", "LEAST" /* LEAST */],
|
|
130
134
|
["LPAD", "LPAD" /* LPAD */],
|
|
131
135
|
["RPAD", "RPAD" /* RPAD */],
|
|
136
|
+
["TRANSLATE", "TRANSLATE" /* TRANSLATE */],
|
|
132
137
|
["CAST", "CAST" /* CAST */],
|
|
133
138
|
["CONVERT", "CONVERT" /* CONVERT */],
|
|
134
139
|
["FORMAT", "FORMAT" /* FORMAT */],
|
|
@@ -252,7 +257,7 @@ var Lexer = class {
|
|
|
252
257
|
);
|
|
253
258
|
}
|
|
254
259
|
// ----------------------------------------------------------
|
|
255
|
-
// 数値:
|
|
260
|
+
// 数値: digits[.digits][e[+-]digits](先頭/末尾 dot は受理しない)
|
|
256
261
|
// ----------------------------------------------------------
|
|
257
262
|
readNumber(start) {
|
|
258
263
|
while (this.pos < this.input.length && isDigit(this.input[this.pos])) {
|
|
@@ -264,6 +269,16 @@ var Lexer = class {
|
|
|
264
269
|
this.pos++;
|
|
265
270
|
}
|
|
266
271
|
}
|
|
272
|
+
if (this.pos < this.input.length && (this.input[this.pos] === "e" || this.input[this.pos] === "E")) {
|
|
273
|
+
this.pos++;
|
|
274
|
+
if (this.pos < this.input.length && (this.input[this.pos] === "+" || this.input[this.pos] === "-")) {
|
|
275
|
+
this.pos++;
|
|
276
|
+
}
|
|
277
|
+
if (this.pos >= this.input.length || !isDigit(this.input[this.pos])) {
|
|
278
|
+
throw new LexError("\u6307\u6570\u90E8\u306B\u306F\u6570\u5B57\u304C\u5FC5\u8981\u3067\u3059", start, this.input, this.pos >= this.input.length);
|
|
279
|
+
}
|
|
280
|
+
while (this.pos < this.input.length && isDigit(this.input[this.pos])) this.pos++;
|
|
281
|
+
}
|
|
267
282
|
return this.makeToken(
|
|
268
283
|
"NUMBER" /* NUMBER */,
|
|
269
284
|
this.input.slice(start, this.pos),
|
|
@@ -463,8 +478,94 @@ function isJapanese(cp) {
|
|
|
463
478
|
return cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
|
|
464
479
|
}
|
|
465
480
|
|
|
481
|
+
// src/core/exactDecimal.ts
|
|
482
|
+
var DECIMAL_PATTERN = /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?)(\d+))?$/;
|
|
483
|
+
function parseSafeExponent(sign, digits) {
|
|
484
|
+
if (digits === void 0) return 0;
|
|
485
|
+
let value = 0;
|
|
486
|
+
for (const digit of digits) {
|
|
487
|
+
value = value * 10 + (digit.charCodeAt(0) - 48);
|
|
488
|
+
if (!Number.isSafeInteger(value)) return null;
|
|
489
|
+
}
|
|
490
|
+
return sign === "-" ? -value : value;
|
|
491
|
+
}
|
|
492
|
+
function parseExactDecimal(input) {
|
|
493
|
+
const match = DECIMAL_PATTERN.exec(input.trim());
|
|
494
|
+
if (match === null) return null;
|
|
495
|
+
const exponent = parseSafeExponent(match[5], match[6]);
|
|
496
|
+
if (exponent === null) return null;
|
|
497
|
+
const fraction = match[3] ?? match[4] ?? "";
|
|
498
|
+
let coefficient = `${match[2] ?? ""}${fraction}`.replace(/^0+/, "");
|
|
499
|
+
if (coefficient === "") return { sign: 0, coefficient: "0", scale: 0 };
|
|
500
|
+
let scale = fraction.length - exponent;
|
|
501
|
+
if (!Number.isSafeInteger(scale)) return null;
|
|
502
|
+
const trailingZeros = /0+$/.exec(coefficient)?.[0].length ?? 0;
|
|
503
|
+
if (trailingZeros > 0) {
|
|
504
|
+
coefficient = coefficient.slice(0, -trailingZeros);
|
|
505
|
+
scale -= trailingZeros;
|
|
506
|
+
if (!Number.isSafeInteger(scale)) return null;
|
|
507
|
+
}
|
|
508
|
+
if (!Number.isSafeInteger(coefficient.length - scale)) return null;
|
|
509
|
+
const sign = match[1] === "-" ? -1 : 1;
|
|
510
|
+
return { sign, coefficient, scale };
|
|
511
|
+
}
|
|
512
|
+
function formatPlainDecimal(dec) {
|
|
513
|
+
if (dec.sign === 0) return "0";
|
|
514
|
+
const digits = dec.coefficient;
|
|
515
|
+
let magnitude;
|
|
516
|
+
if (dec.scale <= 0) {
|
|
517
|
+
magnitude = `${digits}${"0".repeat(-dec.scale)}`;
|
|
518
|
+
} else if (digits.length > dec.scale) {
|
|
519
|
+
const point = digits.length - dec.scale;
|
|
520
|
+
magnitude = `${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
521
|
+
} else {
|
|
522
|
+
magnitude = `0.${"0".repeat(dec.scale - digits.length)}${digits}`;
|
|
523
|
+
}
|
|
524
|
+
return dec.sign === -1 ? `-${magnitude}` : magnitude;
|
|
525
|
+
}
|
|
526
|
+
function toPlainDecimal(input) {
|
|
527
|
+
const dec = parseExactDecimal(input);
|
|
528
|
+
return dec === null ? null : formatPlainDecimal(dec);
|
|
529
|
+
}
|
|
530
|
+
function compareMagnitudes(left, right) {
|
|
531
|
+
const leftPoint = left.coefficient.length - left.scale;
|
|
532
|
+
const rightPoint = right.coefficient.length - right.scale;
|
|
533
|
+
if (!Number.isSafeInteger(leftPoint) || !Number.isSafeInteger(rightPoint)) {
|
|
534
|
+
throw new Error("ArgumentError: exact decimal scale is outside the supported range.");
|
|
535
|
+
}
|
|
536
|
+
if (leftPoint !== rightPoint) return leftPoint < rightPoint ? -1 : 1;
|
|
537
|
+
const width = Math.max(left.coefficient.length, right.coefficient.length);
|
|
538
|
+
for (let index = 0; index < width; index++) {
|
|
539
|
+
const a = index < left.coefficient.length ? left.coefficient.charCodeAt(index) : 48;
|
|
540
|
+
const b = index < right.coefficient.length ? right.coefficient.charCodeAt(index) : 48;
|
|
541
|
+
if (a !== b) return a < b ? -1 : 1;
|
|
542
|
+
}
|
|
543
|
+
return 0;
|
|
544
|
+
}
|
|
545
|
+
function compareExactDecimal(left, right) {
|
|
546
|
+
if (left.sign !== right.sign) return left.sign < right.sign ? -1 : 1;
|
|
547
|
+
if (left.sign === 0) return 0;
|
|
548
|
+
const magnitude = compareMagnitudes(left, right);
|
|
549
|
+
return left.sign === -1 ? magnitude === 0 ? 0 : magnitude === -1 ? 1 : -1 : magnitude;
|
|
550
|
+
}
|
|
551
|
+
function compareDecimal(left, right) {
|
|
552
|
+
const a = parseExactDecimal(left);
|
|
553
|
+
const b = parseExactDecimal(right);
|
|
554
|
+
if (a === null || b === null) {
|
|
555
|
+
throw new Error("ArgumentError: compareDecimal requires finite decimal inputs.");
|
|
556
|
+
}
|
|
557
|
+
return compareExactDecimal(a, b);
|
|
558
|
+
}
|
|
559
|
+
|
|
466
560
|
// src/types/ast.ts
|
|
467
561
|
var NO_FROM_CTE_NAME = "__NO_FROM__";
|
|
562
|
+
function makeNumberLiteral(raw) {
|
|
563
|
+
return { type: "NUMBER", value: Number(raw), raw };
|
|
564
|
+
}
|
|
565
|
+
function numberLiteralText(node) {
|
|
566
|
+
const source = node.raw ?? String(node.value);
|
|
567
|
+
return toPlainDecimal(source) ?? source;
|
|
568
|
+
}
|
|
468
569
|
|
|
469
570
|
// src/parser/parser.ts
|
|
470
571
|
var MAX_BATCH_STATEMENTS = 20;
|
|
@@ -488,13 +589,18 @@ var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
|
488
589
|
"LTRIM" /* LTRIM */,
|
|
489
590
|
"RTRIM" /* RTRIM */,
|
|
490
591
|
"LENGTH" /* LENGTH */,
|
|
592
|
+
"LENGTH_CHAR" /* LENGTH_CHAR */,
|
|
491
593
|
"SUBSTRING" /* SUBSTRING */,
|
|
492
594
|
"SUBSTR" /* SUBSTR */,
|
|
493
595
|
"CONCAT" /* CONCAT */,
|
|
494
596
|
"REPLACE" /* REPLACE */,
|
|
597
|
+
"TRANSLATE" /* TRANSLATE */,
|
|
495
598
|
"COALESCE" /* COALESCE */,
|
|
496
599
|
"NULLIF" /* NULLIF */,
|
|
497
600
|
"ISNULL" /* ISNULL */,
|
|
601
|
+
"REGEXP_LIKE" /* REGEXP_LIKE */,
|
|
602
|
+
"REGEXP_REPLACE" /* REGEXP_REPLACE */,
|
|
603
|
+
"REGEXP_SUBSTR" /* REGEXP_SUBSTR */,
|
|
498
604
|
"LEFT" /* LEFT */,
|
|
499
605
|
"RIGHT" /* RIGHT */,
|
|
500
606
|
"INSTR" /* INSTR */,
|
|
@@ -543,6 +649,7 @@ var ParseError = class extends Error {
|
|
|
543
649
|
var Parser = class {
|
|
544
650
|
constructor(tokens) {
|
|
545
651
|
this.tokens = tokens;
|
|
652
|
+
this.allowUnaryPlusNumber = false;
|
|
546
653
|
this.pos = 0;
|
|
547
654
|
/** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
|
|
548
655
|
this.cteNames = /* @__PURE__ */ new Set();
|
|
@@ -1257,12 +1364,12 @@ var Parser = class {
|
|
|
1257
1364
|
if (this.peek().kind === "-" /* MINUS */) {
|
|
1258
1365
|
this.advance();
|
|
1259
1366
|
const operand = this.parseAggPrimary();
|
|
1260
|
-
if (operand.type === "NUMBER") return {
|
|
1261
|
-
return { type: "AGG_ARITH", left:
|
|
1367
|
+
if (operand.type === "NUMBER") return makeNumberLiteral(`-${numberLiteralText(operand)}`);
|
|
1368
|
+
return { type: "AGG_ARITH", left: makeNumberLiteral("0"), op: "-", right: operand };
|
|
1262
1369
|
}
|
|
1263
1370
|
if (this.peek().kind === "NUMBER" /* NUMBER */) {
|
|
1264
1371
|
const tok = this.advance();
|
|
1265
|
-
return
|
|
1372
|
+
return makeNumberLiteral(tok.value);
|
|
1266
1373
|
}
|
|
1267
1374
|
const aggFunc = this.tryAggregateFunc();
|
|
1268
1375
|
if (aggFunc !== null) {
|
|
@@ -1311,11 +1418,19 @@ var Parser = class {
|
|
|
1311
1418
|
this.expect(")" /* RPAREN */);
|
|
1312
1419
|
return expr;
|
|
1313
1420
|
}
|
|
1421
|
+
if (this.allowUnaryPlusNumber && this.peek().kind === "+" /* PLUS */) {
|
|
1422
|
+
this.advance();
|
|
1423
|
+
const number = this.expect("NUMBER" /* NUMBER */, "\u5358\u9805 + \u306E\u76F4\u5F8C\u306B\u306F\u6570\u5024\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
|
|
1424
|
+
return makeNumberLiteral(`+${number.value}`);
|
|
1425
|
+
}
|
|
1314
1426
|
if (this.peek().kind === "-" /* MINUS */) {
|
|
1315
1427
|
this.advance();
|
|
1428
|
+
if (this.peek().kind === "-" /* MINUS */ || this.peek().kind === "+" /* PLUS */) {
|
|
1429
|
+
throw new ParseError("\u5358\u9805\u7B26\u53F7\u3092\u91CD\u306D\u3066\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093", this.peek());
|
|
1430
|
+
}
|
|
1316
1431
|
const operand = this.parseArithPrimary();
|
|
1317
|
-
if (operand.type === "NUMBER") return {
|
|
1318
|
-
return { type: "ARITH", left:
|
|
1432
|
+
if (operand.type === "NUMBER") return makeNumberLiteral(`-${numberLiteralText(operand)}`);
|
|
1433
|
+
return { type: "ARITH", left: makeNumberLiteral("0"), op: "-", right: operand };
|
|
1319
1434
|
}
|
|
1320
1435
|
if (this.tryStringFuncName() !== null) {
|
|
1321
1436
|
return this.parseStringFuncExpr();
|
|
@@ -1323,7 +1438,7 @@ var Parser = class {
|
|
|
1323
1438
|
const tok = this.peek();
|
|
1324
1439
|
if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
1325
1440
|
this.advance();
|
|
1326
|
-
return
|
|
1441
|
+
return makeNumberLiteral(tok.value);
|
|
1327
1442
|
}
|
|
1328
1443
|
if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
|
|
1329
1444
|
this.advance();
|
|
@@ -1422,10 +1537,15 @@ var Parser = class {
|
|
|
1422
1537
|
["LTRIM" /* LTRIM */]: "LTRIM",
|
|
1423
1538
|
["RTRIM" /* RTRIM */]: "RTRIM",
|
|
1424
1539
|
["LENGTH" /* LENGTH */]: "LENGTH",
|
|
1540
|
+
["LENGTH_CHAR" /* LENGTH_CHAR */]: "LENGTH_CHAR",
|
|
1425
1541
|
["SUBSTRING" /* SUBSTRING */]: "SUBSTRING",
|
|
1426
1542
|
["SUBSTR" /* SUBSTR */]: "SUBSTRING",
|
|
1427
1543
|
["CONCAT" /* CONCAT */]: "CONCAT",
|
|
1428
1544
|
["REPLACE" /* REPLACE */]: "REPLACE",
|
|
1545
|
+
["REGEXP_LIKE" /* REGEXP_LIKE */]: "REGEXP_LIKE",
|
|
1546
|
+
["REGEXP_REPLACE" /* REGEXP_REPLACE */]: "REGEXP_REPLACE",
|
|
1547
|
+
["REGEXP_SUBSTR" /* REGEXP_SUBSTR */]: "REGEXP_SUBSTR",
|
|
1548
|
+
["TRANSLATE" /* TRANSLATE */]: "TRANSLATE",
|
|
1429
1549
|
["COALESCE" /* COALESCE */]: "COALESCE",
|
|
1430
1550
|
["NULLIF" /* NULLIF */]: "NULLIF",
|
|
1431
1551
|
["ISNULL" /* ISNULL */]: "ISNULL",
|
|
@@ -1964,7 +2084,7 @@ var Parser = class {
|
|
|
1964
2084
|
}
|
|
1965
2085
|
if (tok.kind === "NUMBER" /* NUMBER */ || tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */ || tok.kind === "(" /* LPAREN */ || tok.kind === "-" /* MINUS */ || this.tryStringFuncName() !== null) {
|
|
1966
2086
|
const expr = this.parseArithAddSub();
|
|
1967
|
-
if (expr.type === "NUMBER") return
|
|
2087
|
+
if (expr.type === "NUMBER") return expr;
|
|
1968
2088
|
return { type: "ARITH_VALUE", expr };
|
|
1969
2089
|
}
|
|
1970
2090
|
throw new ParseError(
|
|
@@ -1991,15 +2111,15 @@ var Parser = class {
|
|
|
1991
2111
|
if (tok.kind === "STRING" /* STRING */) {
|
|
1992
2112
|
values.push({ type: "STRING", value: tok.value });
|
|
1993
2113
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
1994
|
-
values.push(
|
|
2114
|
+
values.push(makeNumberLiteral(tok.value));
|
|
1995
2115
|
} else if (tok.kind === "-" /* MINUS */ || tok.kind === "+" /* PLUS */) {
|
|
1996
2116
|
const number = this.peek();
|
|
1997
2117
|
if (number.kind !== "NUMBER" /* NUMBER */) {
|
|
1998
2118
|
throw new ParseError(invalidValueMessage, tok);
|
|
1999
2119
|
}
|
|
2000
2120
|
this.advance();
|
|
2001
|
-
const sign = tok.kind === "-" /* MINUS */ ? -
|
|
2002
|
-
values.push({
|
|
2121
|
+
const sign = tok.kind === "-" /* MINUS */ ? "-" : "+";
|
|
2122
|
+
values.push(makeNumberLiteral(`${sign}${number.value}`));
|
|
2003
2123
|
} else if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
2004
2124
|
values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
|
|
2005
2125
|
} else {
|
|
@@ -2186,12 +2306,16 @@ var Parser = class {
|
|
|
2186
2306
|
} else if (this.peek().kind === "IF" /* IF */) {
|
|
2187
2307
|
const expr = this.parseIfExpr();
|
|
2188
2308
|
row.push({ type: "CASE_VALUE", expr });
|
|
2309
|
+
} else if (this.peek().kind === "-" /* MINUS */ || this.peek().kind === "+" /* PLUS */) {
|
|
2310
|
+
const sign = this.advance();
|
|
2311
|
+
const number = this.expect("NUMBER" /* NUMBER */, "INSERT \u306E\u5358\u9805\u7B26\u53F7\u306E\u76F4\u5F8C\u306B\u306F\u6570\u5024\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
|
|
2312
|
+
row.push(makeNumberLiteral(`${sign.kind === "-" /* MINUS */ ? "-" : "+"}${number.value}`));
|
|
2189
2313
|
} else {
|
|
2190
2314
|
const tok = this.advance();
|
|
2191
2315
|
if (tok.kind === "STRING" /* STRING */) {
|
|
2192
2316
|
row.push({ type: "STRING", value: tok.value });
|
|
2193
2317
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
2194
|
-
row.push(
|
|
2318
|
+
row.push(makeNumberLiteral(tok.value));
|
|
2195
2319
|
} else {
|
|
2196
2320
|
throw new ParseError("INSERT \u306E\u5024\u306B\u306F\u6587\u5B57\u5217\u30FB\u6570\u5024\u30FB\u914D\u5217\u30EA\u30C6\u30E9\u30EB\u30FBCASE WHEN \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
2197
2321
|
}
|
|
@@ -2215,6 +2339,12 @@ var Parser = class {
|
|
|
2215
2339
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
2216
2340
|
this.expect("SET" /* SET */);
|
|
2217
2341
|
const assignments = this.parseAssignments();
|
|
2342
|
+
if (subtableCode && assignments.some((a) => a.value.type === "STRING_FUNC")) {
|
|
2343
|
+
throw new ParseError(
|
|
2344
|
+
"\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
2345
|
+
this.prev()
|
|
2346
|
+
);
|
|
2347
|
+
}
|
|
2218
2348
|
let from = null;
|
|
2219
2349
|
if (this.consume("FROM" /* FROM */)) {
|
|
2220
2350
|
const table = this.parseTableRef();
|
|
@@ -2258,7 +2388,14 @@ var Parser = class {
|
|
|
2258
2388
|
from.targetFilter = decomposed.targetFilter;
|
|
2259
2389
|
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
2260
2390
|
throw new ParseError(
|
|
2261
|
-
"SET \u306E\u5024\u306B\
|
|
2391
|
+
"SET \u306E\u5024\u306B\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u5358\u72EC\u3067\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093",
|
|
2392
|
+
whereTok
|
|
2393
|
+
);
|
|
2394
|
+
} else if (assignments.some(
|
|
2395
|
+
(a) => a.value.type === "STRING_FUNC" && this.nodeContainsAnyQualifier(a.value)
|
|
2396
|
+
)) {
|
|
2397
|
+
throw new ParseError(
|
|
2398
|
+
"UPDATE SET \u306E\u6587\u5B57\u5217\u95A2\u6570\u3067\u306F\u66F4\u65B0\u5148\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044",
|
|
2262
2399
|
whereTok
|
|
2263
2400
|
);
|
|
2264
2401
|
}
|
|
@@ -2325,6 +2462,12 @@ var Parser = class {
|
|
|
2325
2462
|
}
|
|
2326
2463
|
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
2327
2464
|
for (const assignment of assignments) {
|
|
2465
|
+
if (assignment.value.type === "STRING_FUNC") {
|
|
2466
|
+
throw new ParseError(
|
|
2467
|
+
"UPDATE ... FROM \u306E SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
2468
|
+
tok
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2328
2471
|
if (assignment.value.type === "SOURCE_FIELD") {
|
|
2329
2472
|
if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
|
|
2330
2473
|
throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
@@ -2464,9 +2607,17 @@ var Parser = class {
|
|
|
2464
2607
|
this.expect(")" /* RPAREN */);
|
|
2465
2608
|
return { type: "SCALAR_SUBQUERY", query };
|
|
2466
2609
|
}
|
|
2467
|
-
const
|
|
2610
|
+
const previousAllowUnaryPlusNumber = this.allowUnaryPlusNumber;
|
|
2611
|
+
this.allowUnaryPlusNumber = true;
|
|
2612
|
+
let node;
|
|
2613
|
+
try {
|
|
2614
|
+
node = this.parseArithAddSub();
|
|
2615
|
+
} finally {
|
|
2616
|
+
this.allowUnaryPlusNumber = previousAllowUnaryPlusNumber;
|
|
2617
|
+
}
|
|
2468
2618
|
if (node.type === "NUMBER") return node;
|
|
2469
2619
|
if (node.type === "ARITH") return node;
|
|
2620
|
+
if (node.type === "STRING_FUNC") return node;
|
|
2470
2621
|
if (node.type === "FIELD_REF") {
|
|
2471
2622
|
const dot = node.field.indexOf(".");
|
|
2472
2623
|
if (dot > 0 && dot < node.field.length - 1) {
|
|
@@ -2474,7 +2625,7 @@ var Parser = class {
|
|
|
2474
2625
|
}
|
|
2475
2626
|
}
|
|
2476
2627
|
throw new ParseError(
|
|
2477
|
-
"SET \u306E\u5024\u306B\
|
|
2628
|
+
"SET \u306E\u5024\u306B\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u5358\u72EC\u3067\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093",
|
|
2478
2629
|
tok
|
|
2479
2630
|
);
|
|
2480
2631
|
}
|
|
@@ -2998,7 +3149,7 @@ function convertValue(value, op) {
|
|
|
2998
3149
|
case "STRING":
|
|
2999
3150
|
return convertString(value);
|
|
3000
3151
|
case "NUMBER":
|
|
3001
|
-
return
|
|
3152
|
+
return numberLiteralText(value);
|
|
3002
3153
|
case "KINTONE_FUNC":
|
|
3003
3154
|
return convertKintoneFunc(value);
|
|
3004
3155
|
case "IN_LIST":
|
|
@@ -3027,7 +3178,7 @@ function convertInList(v, op) {
|
|
|
3027
3178
|
}
|
|
3028
3179
|
assertResolvedInListValues(v.values);
|
|
3029
3180
|
const values = v.values.map(
|
|
3030
|
-
(item) => item.type === "STRING" ? convertString(item) :
|
|
3181
|
+
(item) => item.type === "STRING" ? convertString(item) : numberLiteralText(item)
|
|
3031
3182
|
).join(",");
|
|
3032
3183
|
return `(${values})`;
|
|
3033
3184
|
}
|
|
@@ -3540,7 +3691,7 @@ function aggregateSyntheticName(func, distinct, arg) {
|
|
|
3540
3691
|
}
|
|
3541
3692
|
function arithNodeLabel(node) {
|
|
3542
3693
|
if (node.type === "FIELD_REF") return node.field;
|
|
3543
|
-
if (node.type === "NUMBER") return
|
|
3694
|
+
if (node.type === "NUMBER") return numberLiteralText(node);
|
|
3544
3695
|
if (node.type === "STRING_FUNC") return stringFuncLabel(node);
|
|
3545
3696
|
return `(${arithNodeLabel(node.left)}${node.op}${arithNodeLabel(node.right)})`;
|
|
3546
3697
|
}
|
|
@@ -3697,7 +3848,7 @@ function isNumericCandidate(expr, options) {
|
|
|
3697
3848
|
if (!isTargetField(expr.left, options)) return false;
|
|
3698
3849
|
if (expr.right.type !== "NUMBER") return false;
|
|
3699
3850
|
if (expr.op === "=") return true;
|
|
3700
|
-
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
3851
|
+
return (expr.op === "<" || expr.op === ">") && /^[+-]?\d+$/.test(numberLiteralText(expr.right)) && Number.isSafeInteger(expr.right.value);
|
|
3701
3852
|
}
|
|
3702
3853
|
function isSelectionInCandidate(expr, options) {
|
|
3703
3854
|
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
@@ -4302,9 +4453,10 @@ function triCompare(left, right) {
|
|
|
4302
4453
|
}
|
|
4303
4454
|
function numberKey(value) {
|
|
4304
4455
|
if (value === "") return { band: 0 };
|
|
4456
|
+
const decimal = parseExactDecimal(value);
|
|
4457
|
+
if (decimal !== null) return { band: 2, value: decimal };
|
|
4305
4458
|
const numeric = Number(value);
|
|
4306
4459
|
if (numeric === Number.NEGATIVE_INFINITY) return { band: 1 };
|
|
4307
|
-
if (Number.isFinite(numeric)) return { band: 2, value: numeric };
|
|
4308
4460
|
if (numeric === Number.POSITIVE_INFINITY) return { band: 3 };
|
|
4309
4461
|
if (value === "NaN") return { band: 4 };
|
|
4310
4462
|
return { band: 5, value };
|
|
@@ -4313,7 +4465,7 @@ function compareNumbers(left, right) {
|
|
|
4313
4465
|
const a = numberKey(left);
|
|
4314
4466
|
const b = numberKey(right);
|
|
4315
4467
|
if (a.band !== b.band) return a.band < b.band ? -1 : 1;
|
|
4316
|
-
if (a.band === 2 && b.band === 2) return
|
|
4468
|
+
if (a.band === 2 && b.band === 2) return compareExactDecimal(a.value, b.value);
|
|
4317
4469
|
if (a.band === 5 && b.band === 5) return compareCodePointStrings(a.value, b.value);
|
|
4318
4470
|
return 0;
|
|
4319
4471
|
}
|
|
@@ -4413,7 +4565,9 @@ function selectScalarExtreme(values, extreme) {
|
|
|
4413
4565
|
const numeric = candidates.every((value) => !Number.isNaN(Number(value)));
|
|
4414
4566
|
const compare = (left, right) => {
|
|
4415
4567
|
if (numeric) {
|
|
4416
|
-
const
|
|
4568
|
+
const leftDecimal = parseExactDecimal(left);
|
|
4569
|
+
const rightDecimal = parseExactDecimal(right);
|
|
4570
|
+
const numericCmp = leftDecimal !== null && rightDecimal !== null ? compareExactDecimal(leftDecimal, rightDecimal) : triCompare(Number(left), Number(right));
|
|
4417
4571
|
if (numericCmp !== 0) return numericCmp;
|
|
4418
4572
|
}
|
|
4419
4573
|
return compareCodePointStrings(left, right);
|
|
@@ -4499,6 +4653,149 @@ function applyRoundOp(op, num, digits) {
|
|
|
4499
4653
|
if (digits > 0) return String(parseFloat(raw.toFixed(digits)));
|
|
4500
4654
|
return String(raw);
|
|
4501
4655
|
}
|
|
4656
|
+
function isHighSurrogate(codeUnit) {
|
|
4657
|
+
return codeUnit >= 55296 && codeUnit <= 56319;
|
|
4658
|
+
}
|
|
4659
|
+
function isLowSurrogate(codeUnit) {
|
|
4660
|
+
return codeUnit >= 56320 && codeUnit <= 57343;
|
|
4661
|
+
}
|
|
4662
|
+
function splitsSurrogatePair(value, index) {
|
|
4663
|
+
return index > 0 && index < value.length && isHighSurrogate(value.charCodeAt(index - 1)) && isLowSurrogate(value.charCodeAt(index));
|
|
4664
|
+
}
|
|
4665
|
+
function normalizeSliceIndex(index, length) {
|
|
4666
|
+
if (Number.isNaN(index) || index === Number.NEGATIVE_INFINITY) return 0;
|
|
4667
|
+
if (index === Number.POSITIVE_INFINITY) return length;
|
|
4668
|
+
const integer = Math.trunc(index);
|
|
4669
|
+
return integer < 0 ? Math.max(length + integer, 0) : Math.min(integer, length);
|
|
4670
|
+
}
|
|
4671
|
+
function sliceSafePrefix(value, budget) {
|
|
4672
|
+
let end = Math.min(Math.max(0, budget), value.length);
|
|
4673
|
+
if (splitsSurrogatePair(value, end)) end -= 1;
|
|
4674
|
+
return value.slice(0, end);
|
|
4675
|
+
}
|
|
4676
|
+
function sliceSafeSuffix(value, budget) {
|
|
4677
|
+
let start = Math.max(0, value.length - budget);
|
|
4678
|
+
if (splitsSurrogatePair(value, start)) start += 1;
|
|
4679
|
+
return value.slice(start);
|
|
4680
|
+
}
|
|
4681
|
+
function sliceSafeRange(value, rawStart, rawEnd) {
|
|
4682
|
+
let start = normalizeSliceIndex(rawStart, value.length);
|
|
4683
|
+
let end = normalizeSliceIndex(rawEnd, value.length);
|
|
4684
|
+
if (end <= start) return "";
|
|
4685
|
+
if (splitsSurrogatePair(value, start)) start += 1;
|
|
4686
|
+
if (splitsSurrogatePair(value, end)) end -= 1;
|
|
4687
|
+
return value.slice(start, Math.max(start, end));
|
|
4688
|
+
}
|
|
4689
|
+
function makeSafePadding(pad, gap) {
|
|
4690
|
+
const repeated = pad.repeat(Math.ceil(gap / pad.length));
|
|
4691
|
+
return sliceSafePrefix(repeated, gap);
|
|
4692
|
+
}
|
|
4693
|
+
var REGEXP_CACHE_MAX = 200;
|
|
4694
|
+
var regexpCache = /* @__PURE__ */ new Map();
|
|
4695
|
+
function normalizeRegexpFlags(flags) {
|
|
4696
|
+
if (/[^ims]/.test(flags)) {
|
|
4697
|
+
throw new Error("ArgumentError: regular expression flags may contain only i, m, or s.");
|
|
4698
|
+
}
|
|
4699
|
+
if (new Set(flags).size !== flags.length) {
|
|
4700
|
+
throw new Error("ArgumentError: regular expression flags must not contain duplicates.");
|
|
4701
|
+
}
|
|
4702
|
+
return `${flags}u`;
|
|
4703
|
+
}
|
|
4704
|
+
function compileRegexp(pattern, flags, global = false) {
|
|
4705
|
+
const normalizedFlags = normalizeRegexpFlags(flags) + (global ? "g" : "");
|
|
4706
|
+
const key = `${pattern}\0${normalizedFlags}`;
|
|
4707
|
+
const cached = regexpCache.get(key);
|
|
4708
|
+
if (cached !== void 0) {
|
|
4709
|
+
cached.lastIndex = 0;
|
|
4710
|
+
return cached;
|
|
4711
|
+
}
|
|
4712
|
+
let regexp;
|
|
4713
|
+
try {
|
|
4714
|
+
regexp = new RegExp(pattern, normalizedFlags);
|
|
4715
|
+
} catch (error) {
|
|
4716
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4717
|
+
throw new Error(`ArgumentError: invalid regular expression: ${detail}`);
|
|
4718
|
+
}
|
|
4719
|
+
if (regexpCache.size >= REGEXP_CACHE_MAX) {
|
|
4720
|
+
const oldest = regexpCache.keys().next().value;
|
|
4721
|
+
if (oldest !== void 0) regexpCache.delete(oldest);
|
|
4722
|
+
}
|
|
4723
|
+
regexpCache.set(key, regexp);
|
|
4724
|
+
return regexp;
|
|
4725
|
+
}
|
|
4726
|
+
function assertRegexpReplacement(replacement) {
|
|
4727
|
+
if (replacement.includes("$`") || replacement.includes("$'")) {
|
|
4728
|
+
throw new Error("ArgumentError: REGEXP_REPLACE replacement must not contain $` or $'.");
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
function parseRegexpOccurrence(arg) {
|
|
4732
|
+
if (arg === void 0) return 0;
|
|
4733
|
+
if (!/^\d+$/.test(arg)) {
|
|
4734
|
+
throw new Error("ArgumentError: REGEXP_REPLACE occurrence must be a non-negative integer.");
|
|
4735
|
+
}
|
|
4736
|
+
return Number(arg);
|
|
4737
|
+
}
|
|
4738
|
+
function expandRegexpReplacement(replacement, match, captures, namedGroups) {
|
|
4739
|
+
let result = "";
|
|
4740
|
+
for (let i = 0; i < replacement.length; i += 1) {
|
|
4741
|
+
const char = replacement[i];
|
|
4742
|
+
if (char !== "$" || i + 1 >= replacement.length) {
|
|
4743
|
+
result += char;
|
|
4744
|
+
continue;
|
|
4745
|
+
}
|
|
4746
|
+
const next = replacement[i + 1];
|
|
4747
|
+
if (next === "$") {
|
|
4748
|
+
result += "$";
|
|
4749
|
+
i += 1;
|
|
4750
|
+
continue;
|
|
4751
|
+
}
|
|
4752
|
+
if (next === "&") {
|
|
4753
|
+
result += match;
|
|
4754
|
+
i += 1;
|
|
4755
|
+
continue;
|
|
4756
|
+
}
|
|
4757
|
+
if (next === "<" && namedGroups !== void 0) {
|
|
4758
|
+
const end = replacement.indexOf(">", i + 2);
|
|
4759
|
+
if (end >= 0) {
|
|
4760
|
+
result += namedGroups[replacement.slice(i + 2, end)] ?? "";
|
|
4761
|
+
i = end;
|
|
4762
|
+
continue;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
if (/\d/.test(next)) {
|
|
4766
|
+
const secondDigit = replacement[i + 2];
|
|
4767
|
+
if (secondDigit !== void 0 && /\d/.test(secondDigit)) {
|
|
4768
|
+
const twoDigitIndex = Number(next + secondDigit);
|
|
4769
|
+
if (twoDigitIndex >= 1 && twoDigitIndex <= captures.length) {
|
|
4770
|
+
result += captures[twoDigitIndex - 1] ?? "";
|
|
4771
|
+
i += 2;
|
|
4772
|
+
continue;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
const oneDigitIndex = Number(next);
|
|
4776
|
+
if (oneDigitIndex >= 1 && oneDigitIndex <= captures.length) {
|
|
4777
|
+
result += captures[oneDigitIndex - 1] ?? "";
|
|
4778
|
+
i += 1;
|
|
4779
|
+
continue;
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
result += "$";
|
|
4783
|
+
}
|
|
4784
|
+
return result;
|
|
4785
|
+
}
|
|
4786
|
+
function replaceNthMatch(input, globalRe, replacement, n) {
|
|
4787
|
+
let matchCount = 0;
|
|
4788
|
+
return input.replace(globalRe, (match, ...callbackArgs) => {
|
|
4789
|
+
matchCount += 1;
|
|
4790
|
+
if (matchCount !== n) return match;
|
|
4791
|
+
const lastArg = callbackArgs[callbackArgs.length - 1];
|
|
4792
|
+
const hasNamedGroups = typeof lastArg === "object" && lastArg !== null;
|
|
4793
|
+
const capturesEnd = callbackArgs.length - (hasNamedGroups ? 3 : 2);
|
|
4794
|
+
const captures = callbackArgs.slice(0, capturesEnd);
|
|
4795
|
+
const namedGroups = hasNamedGroups ? lastArg : void 0;
|
|
4796
|
+
return expandRegexpReplacement(replacement, match, captures, namedGroups);
|
|
4797
|
+
});
|
|
4798
|
+
}
|
|
4502
4799
|
function evalStringFunc(expr, row) {
|
|
4503
4800
|
const args = expr.args.map((a) => evalStringFuncArg(a, row));
|
|
4504
4801
|
switch (expr.func) {
|
|
@@ -4514,23 +4811,26 @@ function evalStringFunc(expr, row) {
|
|
|
4514
4811
|
return (args[0] ?? "").trimEnd();
|
|
4515
4812
|
case "LENGTH":
|
|
4516
4813
|
return String((args[0] ?? "").length);
|
|
4814
|
+
case "LENGTH_CHAR":
|
|
4815
|
+
assertArity("LENGTH_CHAR", args, 1, 1);
|
|
4816
|
+
return String([...args[0] ?? ""].length);
|
|
4517
4817
|
case "SUBSTRING": {
|
|
4518
4818
|
const str = args[0] ?? "";
|
|
4519
4819
|
const start = Math.max(0, Number(args[1] ?? "1") - 1);
|
|
4520
4820
|
const len = args[2] !== void 0 ? Number(args[2]) : void 0;
|
|
4521
|
-
return len !== void 0 ?
|
|
4821
|
+
return sliceSafeRange(str, start, len !== void 0 ? start + len : str.length);
|
|
4522
4822
|
}
|
|
4523
4823
|
case "LEFT": {
|
|
4524
4824
|
assertArity("LEFT", args, 2, 2);
|
|
4525
4825
|
const str = args[0];
|
|
4526
4826
|
const n = Math.trunc(Number(args[1]));
|
|
4527
|
-
return Number.isNaN(n) || n <= 0 ? "" : str
|
|
4827
|
+
return Number.isNaN(n) || n <= 0 ? "" : sliceSafePrefix(str, n);
|
|
4528
4828
|
}
|
|
4529
4829
|
case "RIGHT": {
|
|
4530
4830
|
assertArity("RIGHT", args, 2, 2);
|
|
4531
4831
|
const str = args[0];
|
|
4532
4832
|
const n = Math.trunc(Number(args[1]));
|
|
4533
|
-
return Number.isNaN(n) || n <= 0 ? "" : str
|
|
4833
|
+
return Number.isNaN(n) || n <= 0 ? "" : sliceSafeSuffix(str, n);
|
|
4534
4834
|
}
|
|
4535
4835
|
case "INSTR":
|
|
4536
4836
|
assertArity("INSTR", args, 2, 2);
|
|
@@ -4541,10 +4841,11 @@ function evalStringFunc(expr, row) {
|
|
|
4541
4841
|
const str = args[0];
|
|
4542
4842
|
const n = Math.trunc(Number(args[1]));
|
|
4543
4843
|
if (Number.isNaN(n) || n <= 0) return "";
|
|
4544
|
-
if (str.length >= n) return str
|
|
4844
|
+
if (str.length >= n) return sliceSafePrefix(str, n);
|
|
4545
4845
|
const pad = args[2] ?? " ";
|
|
4546
4846
|
if (pad === "") return str;
|
|
4547
|
-
|
|
4847
|
+
const padding = makeSafePadding(pad, n - str.length);
|
|
4848
|
+
return expr.func === "LPAD" ? padding + str : str + padding;
|
|
4548
4849
|
}
|
|
4549
4850
|
case "GREATEST":
|
|
4550
4851
|
case "LEAST":
|
|
@@ -4558,6 +4859,36 @@ function evalStringFunc(expr, row) {
|
|
|
4558
4859
|
const to = args[2] ?? "";
|
|
4559
4860
|
return from === "" ? str : str.split(from).join(to);
|
|
4560
4861
|
}
|
|
4862
|
+
case "REGEXP_LIKE": {
|
|
4863
|
+
assertArity("REGEXP_LIKE", args, 2, 3);
|
|
4864
|
+
return compileRegexp(args[1], args[2] ?? "").test(args[0]) ? "1" : "0";
|
|
4865
|
+
}
|
|
4866
|
+
case "REGEXP_REPLACE": {
|
|
4867
|
+
assertArity("REGEXP_REPLACE", args, 3, 5);
|
|
4868
|
+
assertRegexpReplacement(args[2]);
|
|
4869
|
+
const occurrence = parseRegexpOccurrence(args[4]);
|
|
4870
|
+
const regexp = compileRegexp(args[1], args[3] ?? "", true);
|
|
4871
|
+
return occurrence === 0 ? args[0].replace(regexp, args[2]) : replaceNthMatch(args[0], regexp, args[2], occurrence);
|
|
4872
|
+
}
|
|
4873
|
+
case "REGEXP_SUBSTR": {
|
|
4874
|
+
assertArity("REGEXP_SUBSTR", args, 2, 3);
|
|
4875
|
+
return compileRegexp(args[1], args[2] ?? "").exec(args[0])?.[0] ?? "";
|
|
4876
|
+
}
|
|
4877
|
+
case "TRANSLATE": {
|
|
4878
|
+
assertArity("TRANSLATE", args, 3, 3);
|
|
4879
|
+
const from = [...args[1]];
|
|
4880
|
+
const to = [...args[2]];
|
|
4881
|
+
if (from.length !== to.length) {
|
|
4882
|
+
throw new Error(
|
|
4883
|
+
`ArgumentError: TRANSLATE \u306E from \u3068 to \u306F\u540C\u3058\u6587\u5B57\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08from=${from.length}, to=${to.length}\uFF09`
|
|
4884
|
+
);
|
|
4885
|
+
}
|
|
4886
|
+
const map = /* @__PURE__ */ new Map();
|
|
4887
|
+
from.forEach((ch, i) => {
|
|
4888
|
+
if (!map.has(ch)) map.set(ch, to[i]);
|
|
4889
|
+
});
|
|
4890
|
+
return [...args[0]].map((ch) => map.get(ch) ?? ch).join("");
|
|
4891
|
+
}
|
|
4561
4892
|
case "COALESCE":
|
|
4562
4893
|
return args.find((a) => a !== "") ?? "";
|
|
4563
4894
|
case "NULLIF":
|
|
@@ -4721,7 +5052,7 @@ function evalStringFuncArg(arg, row) {
|
|
|
4721
5052
|
if (arg.type === "STRING") return arg.value;
|
|
4722
5053
|
if (arg.type === "STRING_FUNC") return evalStringFunc(arg, row);
|
|
4723
5054
|
if (arg.type === "FIELD_REF") return resolveFieldRef(row, arg.field);
|
|
4724
|
-
if (arg.type === "NUMBER") return
|
|
5055
|
+
if (arg.type === "NUMBER") return numberLiteralText(arg);
|
|
4725
5056
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return "";
|
|
4726
5057
|
return String(evalArithExpr(arg, row));
|
|
4727
5058
|
}
|
|
@@ -4770,7 +5101,7 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType, semantics
|
|
|
4770
5101
|
let values = null;
|
|
4771
5102
|
if (right.type === "IN_LIST") {
|
|
4772
5103
|
assertResolvedInListValues2(right.values);
|
|
4773
|
-
values = new Set(right.values.map((v) => String(v.value)));
|
|
5104
|
+
values = new Set(right.values.map((v) => v.type === "NUMBER" ? fieldType === "NUMBER" ? numberLiteralText(v) : String(v.value) : v.value));
|
|
4774
5105
|
}
|
|
4775
5106
|
if (right.type === "SUBQUERY_IN_LIST") {
|
|
4776
5107
|
values = right.resolved;
|
|
@@ -4795,6 +5126,7 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType, semantics
|
|
|
4795
5126
|
}
|
|
4796
5127
|
var NUMERIC_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
4797
5128
|
"LENGTH",
|
|
5129
|
+
"LENGTH_CHAR",
|
|
4798
5130
|
"INSTR",
|
|
4799
5131
|
"ROUND",
|
|
4800
5132
|
"FLOOR",
|
|
@@ -4849,6 +5181,10 @@ var SINGLE_OBJECT_FIELD_TYPES = /* @__PURE__ */ new Set(["CREATOR", "MODIFIER"])
|
|
|
4849
5181
|
function typedInContains(leftStr, values, fieldType) {
|
|
4850
5182
|
const fallback = () => values.has(leftStr);
|
|
4851
5183
|
if (fieldType === void 0) return fallback();
|
|
5184
|
+
if (fieldType === "NUMBER") {
|
|
5185
|
+
const semantics = syntheticSemantics("number");
|
|
5186
|
+
return [...values].some((value) => compareScalarValues("=", leftStr, value, semantics));
|
|
5187
|
+
}
|
|
4852
5188
|
let parsed;
|
|
4853
5189
|
if (STRING_ARRAY_FIELD_TYPES.has(fieldType) || OBJECT_ARRAY_FIELD_TYPES.has(fieldType) || SINGLE_OBJECT_FIELD_TYPES.has(fieldType)) {
|
|
4854
5190
|
try {
|
|
@@ -4907,7 +5243,7 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
4907
5243
|
case "STRING":
|
|
4908
5244
|
return value.value;
|
|
4909
5245
|
case "NUMBER":
|
|
4910
|
-
return
|
|
5246
|
+
return numberLiteralText(value);
|
|
4911
5247
|
case "KINTONE_FUNC":
|
|
4912
5248
|
return resolveKintoneFunc(value.name);
|
|
4913
5249
|
case "IN_LIST":
|
|
@@ -5042,7 +5378,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
|
5042
5378
|
function buildUpdateRecord(assignments, fieldTypes) {
|
|
5043
5379
|
const record = {};
|
|
5044
5380
|
for (const { field, value } of assignments) {
|
|
5045
|
-
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
|
|
5381
|
+
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "STRING_FUNC" || value.type === "SOURCE_FIELD") continue;
|
|
5046
5382
|
record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
5047
5383
|
}
|
|
5048
5384
|
return record;
|
|
@@ -5052,12 +5388,19 @@ function hasArithAssignment(stmt) {
|
|
|
5052
5388
|
(a) => a.value.type === "ARITH" || a.value.type === "CASE_VALUE"
|
|
5053
5389
|
);
|
|
5054
5390
|
}
|
|
5391
|
+
function hasRowDependentAssignment(stmt) {
|
|
5392
|
+
return stmt.assignments.some(
|
|
5393
|
+
(a) => a.value.type === "ARITH" || a.value.type === "CASE_VALUE" || a.value.type === "STRING_FUNC"
|
|
5394
|
+
);
|
|
5395
|
+
}
|
|
5055
5396
|
function updateToGetQueryForArith(stmt) {
|
|
5056
5397
|
assertDmlWhereIsSafe(stmt.where);
|
|
5057
5398
|
const refFields = /* @__PURE__ */ new Set();
|
|
5058
5399
|
for (const { value } of stmt.assignments) {
|
|
5059
5400
|
if (value.type === "ARITH") {
|
|
5060
5401
|
collectArithFields2(value, refFields);
|
|
5402
|
+
} else if (value.type === "STRING_FUNC") {
|
|
5403
|
+
collectStringFuncFields2(value, refFields);
|
|
5061
5404
|
} else if (value.type === "CASE_VALUE") {
|
|
5062
5405
|
collectCaseFields(value.expr, refFields);
|
|
5063
5406
|
}
|
|
@@ -5144,6 +5487,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
5144
5487
|
for (const { field, value } of stmt.assignments) {
|
|
5145
5488
|
if (value.type === "ARITH") {
|
|
5146
5489
|
record[field] = { value: String(evalArith(value, raw)) };
|
|
5490
|
+
} else if (value.type === "STRING_FUNC") {
|
|
5491
|
+
record[field] = { value: evalStringFunc(value, row) };
|
|
5147
5492
|
} else if (value.type === "CASE_VALUE") {
|
|
5148
5493
|
record[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
|
|
5149
5494
|
} else if (value.type === "SOURCE_FIELD") {
|
|
@@ -5192,6 +5537,8 @@ function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new
|
|
|
5192
5537
|
throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
|
|
5193
5538
|
}
|
|
5194
5539
|
record[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
|
|
5540
|
+
} else if (value.type === "STRING_FUNC") {
|
|
5541
|
+
throw new DmlConvertError("UPDATE ... FROM \u306E SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
5195
5542
|
} else if (value.type === "ARITH") {
|
|
5196
5543
|
record[field] = { value: String(evalArith(value, target)) };
|
|
5197
5544
|
} else if (value.type === "CASE_VALUE") {
|
|
@@ -5336,7 +5683,7 @@ function convertDmlSqlValue(value, fieldType) {
|
|
|
5336
5683
|
case "STRING":
|
|
5337
5684
|
return convertString2(value.value, fieldType);
|
|
5338
5685
|
case "NUMBER":
|
|
5339
|
-
return
|
|
5686
|
+
return numberLiteralText(value);
|
|
5340
5687
|
case "ARRAY":
|
|
5341
5688
|
return convertArray(value.elements.map((e) => e.value), fieldType);
|
|
5342
5689
|
case "KINTONE_FUNC":
|
|
@@ -5968,7 +6315,7 @@ function evalAggArithExpr(node, rows, resolveAggSortKind) {
|
|
|
5968
6315
|
}
|
|
5969
6316
|
}
|
|
5970
6317
|
function aggArithDefaultKey(node) {
|
|
5971
|
-
if (node.type === "NUMBER") return
|
|
6318
|
+
if (node.type === "NUMBER") return numberLiteralText(node);
|
|
5972
6319
|
if (node.type === "AGG_REF") return aggregateSyntheticName2(node.func, node.distinct, node.arg);
|
|
5973
6320
|
return `${aggArithDefaultKey(node.left)}${node.op}${aggArithDefaultKey(node.right)}`;
|
|
5974
6321
|
}
|
|
@@ -6083,6 +6430,7 @@ function compareSortKeys(a, b, meta) {
|
|
|
6083
6430
|
}
|
|
6084
6431
|
var NUMERIC_ORDER_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
6085
6432
|
"LENGTH",
|
|
6433
|
+
"LENGTH_CHAR",
|
|
6086
6434
|
"INSTR",
|
|
6087
6435
|
"ROUND",
|
|
6088
6436
|
"FLOOR",
|
|
@@ -6317,7 +6665,7 @@ function stripParentShortcutColumns(row) {
|
|
|
6317
6665
|
function arithColDefaultKey(expr) {
|
|
6318
6666
|
const nodeLabel = (n) => {
|
|
6319
6667
|
if (n.type === "FIELD_REF") return n.field;
|
|
6320
|
-
if (n.type === "NUMBER") return
|
|
6668
|
+
if (n.type === "NUMBER") return numberLiteralText(n);
|
|
6321
6669
|
if (n.type === "STRING_FUNC") return stringFuncDefaultKey(n);
|
|
6322
6670
|
return `(${nodeLabel(n.left)}${n.op}${nodeLabel(n.right)})`;
|
|
6323
6671
|
};
|
|
@@ -6346,10 +6694,11 @@ function hasAggregateInStringFuncExpr2(expr) {
|
|
|
6346
6694
|
function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
|
|
6347
6695
|
if (arg.type === "AGG_REF") {
|
|
6348
6696
|
const value = evalAggregate(arg.func, arg.distinct, arg.arg, arg.separator, rows, resolveAggSortKind);
|
|
6349
|
-
return typeof value === "number" ? { type: "NUMBER", value } : { type: "STRING", value };
|
|
6697
|
+
return typeof value === "number" ? { type: "NUMBER", value, raw: String(value) } : { type: "STRING", value };
|
|
6350
6698
|
}
|
|
6351
6699
|
if (arg.type === "AGG_ARITH") {
|
|
6352
|
-
|
|
6700
|
+
const value = evalAggArithExpr(arg, rows, resolveAggSortKind);
|
|
6701
|
+
return { type: "NUMBER", value, raw: String(value) };
|
|
6353
6702
|
}
|
|
6354
6703
|
if (arg.type === "STRING_FUNC") {
|
|
6355
6704
|
return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
|
|
@@ -6467,10 +6816,43 @@ function toFlatString(value) {
|
|
|
6467
6816
|
}
|
|
6468
6817
|
}
|
|
6469
6818
|
|
|
6819
|
+
// src/core/numberPrecision.ts
|
|
6820
|
+
function parseIntegerSetting(value, name, min, max) {
|
|
6821
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) {
|
|
6822
|
+
throw new Error(`SettingsError: numberPrecision.${name} must be an integer string.`);
|
|
6823
|
+
}
|
|
6824
|
+
let parsed = 0;
|
|
6825
|
+
for (const digit of value) parsed = parsed * 10 + digit.charCodeAt(0) - 48;
|
|
6826
|
+
if (parsed < min || parsed > max) {
|
|
6827
|
+
throw new Error(`SettingsError: numberPrecision.${name} must be between ${min} and ${max}.`);
|
|
6828
|
+
}
|
|
6829
|
+
return parsed;
|
|
6830
|
+
}
|
|
6831
|
+
function parseNumberPrecisionSettings(response) {
|
|
6832
|
+
const raw = response.numberPrecision;
|
|
6833
|
+
if (raw === void 0 || raw === null || typeof raw !== "object") {
|
|
6834
|
+
throw new Error("SettingsError: numberPrecision is missing from app settings.");
|
|
6835
|
+
}
|
|
6836
|
+
const digits = parseIntegerSetting(raw.digits, "digits", 1, 30);
|
|
6837
|
+
const decimalPlaces = parseIntegerSetting(raw.decimalPlaces, "decimalPlaces", 0, 10);
|
|
6838
|
+
const roundingMode = raw.roundingMode;
|
|
6839
|
+
if (roundingMode !== "HALF_EVEN" && roundingMode !== "UP" && roundingMode !== "DOWN") {
|
|
6840
|
+
throw new Error("SettingsError: numberPrecision.roundingMode is unsupported.");
|
|
6841
|
+
}
|
|
6842
|
+
return { digits, decimalPlaces, roundingMode };
|
|
6843
|
+
}
|
|
6844
|
+
function exactDecimalDigitCounts(value) {
|
|
6845
|
+
if (value.sign === 0) return { integerDigits: 0, fractionDigits: 0 };
|
|
6846
|
+
return {
|
|
6847
|
+
integerDigits: Math.max(value.coefficient.length - value.scale, 0),
|
|
6848
|
+
fractionDigits: Math.max(value.scale, 0)
|
|
6849
|
+
};
|
|
6850
|
+
}
|
|
6851
|
+
|
|
6470
6852
|
// src/core/dmlValidation.ts
|
|
6471
6853
|
var ARRAY_TYPES2 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
6472
6854
|
var CHOICE_TYPES = /* @__PURE__ */ new Set(["DROP_DOWN", "RADIO_BUTTON", "CHECK_BOX", "MULTI_SELECT"]);
|
|
6473
|
-
function validateAndNormalizeDmlValue(raw, field) {
|
|
6855
|
+
function validateAndNormalizeDmlValue(raw, field, numberPrecision) {
|
|
6474
6856
|
if (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME") {
|
|
6475
6857
|
const original = rawScalarText(raw);
|
|
6476
6858
|
if (original !== "" && !isValidTemporalInput(original, field.fieldType)) {
|
|
@@ -6489,7 +6871,8 @@ function validateAndNormalizeDmlValue(raw, field) {
|
|
|
6489
6871
|
}
|
|
6490
6872
|
if (!isEmpty(value) && field.fieldType === "NUMBER") {
|
|
6491
6873
|
const text = String(value);
|
|
6492
|
-
|
|
6874
|
+
const decimal = parseExactDecimal(text);
|
|
6875
|
+
if (decimal === null) {
|
|
6493
6876
|
return { ok: false, code: "ERR_TYPE_NUMBER", message: `${field.code} \u306F\u6570\u5024\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
6494
6877
|
}
|
|
6495
6878
|
if (field.minValue != null && compareDecimal(text, field.minValue) < 0) {
|
|
@@ -6498,6 +6881,17 @@ function validateAndNormalizeDmlValue(raw, field) {
|
|
|
6498
6881
|
if (field.maxValue != null && compareDecimal(text, field.maxValue) > 0) {
|
|
6499
6882
|
return { ok: false, code: "ERR_RANGE_MAX", message: `${field.code} \u306F ${field.maxValue} \u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
6500
6883
|
}
|
|
6884
|
+
if (numberPrecision !== void 0) {
|
|
6885
|
+
const { integerDigits } = exactDecimalDigitCounts(decimal);
|
|
6886
|
+
const integerBudget = numberPrecision.digits - numberPrecision.decimalPlaces;
|
|
6887
|
+
if (integerDigits > integerBudget) {
|
|
6888
|
+
return {
|
|
6889
|
+
ok: false,
|
|
6890
|
+
code: "ERR_NUMBER_INTEGER_DIGITS",
|
|
6891
|
+
message: `${field.code} \u306E\u6574\u6570\u90E8\u306F ${integerDigits} \u6841\u3067\u3059\u3002\u8A31\u5BB9\u306F ${integerBudget} \u6841\u307E\u3067\u3067\u3059 (digits=${numberPrecision.digits}, decimalPlaces=${numberPrecision.decimalPlaces})`
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6501
6895
|
}
|
|
6502
6896
|
if (!isEmpty(value) && (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME")) {
|
|
6503
6897
|
if (!isValidTemporal(String(value), field.fieldType)) {
|
|
@@ -6525,7 +6919,8 @@ function validateAndNormalizeDmlValue(raw, field) {
|
|
|
6525
6919
|
}
|
|
6526
6920
|
function rawScalarText(raw) {
|
|
6527
6921
|
if (raw == null) return "";
|
|
6528
|
-
if (isSqlValue(raw) &&
|
|
6922
|
+
if (isSqlValue(raw) && raw.type === "NUMBER") return numberLiteralText(raw);
|
|
6923
|
+
if (isSqlValue(raw) && raw.type === "STRING") return raw.value;
|
|
6529
6924
|
return typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
6530
6925
|
}
|
|
6531
6926
|
function isValidTemporalInput(value, type) {
|
|
@@ -6575,34 +6970,6 @@ function isEmpty(value) {
|
|
|
6575
6970
|
function typeCode(type) {
|
|
6576
6971
|
return type === "NUMBER" ? "ERR_TYPE_NUMBER" : "ERR_TYPE_DATE";
|
|
6577
6972
|
}
|
|
6578
|
-
function isFiniteDecimal(value) {
|
|
6579
|
-
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim());
|
|
6580
|
-
}
|
|
6581
|
-
function compareDecimal(left, right) {
|
|
6582
|
-
const normalize = (input) => {
|
|
6583
|
-
let s = input.trim();
|
|
6584
|
-
let sign = 1;
|
|
6585
|
-
if (s.startsWith("-")) {
|
|
6586
|
-
sign = -1;
|
|
6587
|
-
s = s.slice(1);
|
|
6588
|
-
} else if (s.startsWith("+")) s = s.slice(1);
|
|
6589
|
-
let [whole, fraction = ""] = s.split(".");
|
|
6590
|
-
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
6591
|
-
fraction = fraction.replace(/0+$/, "");
|
|
6592
|
-
if (/^0*$/.test(whole) && fraction === "") sign = 1;
|
|
6593
|
-
return { sign, whole, fraction };
|
|
6594
|
-
};
|
|
6595
|
-
const a = normalize(left);
|
|
6596
|
-
const b = normalize(right);
|
|
6597
|
-
if (a.sign !== b.sign) return a.sign < b.sign ? -1 : 1;
|
|
6598
|
-
const direction = a.sign;
|
|
6599
|
-
if (a.whole.length !== b.whole.length) return a.whole.length < b.whole.length ? -direction : direction;
|
|
6600
|
-
if (a.whole !== b.whole) return a.whole < b.whole ? -direction : direction;
|
|
6601
|
-
const width = Math.max(a.fraction.length, b.fraction.length);
|
|
6602
|
-
const af = a.fraction.padEnd(width, "0");
|
|
6603
|
-
const bf = b.fraction.padEnd(width, "0");
|
|
6604
|
-
return af === bf ? 0 : af < bf ? -direction : direction;
|
|
6605
|
-
}
|
|
6606
6973
|
function isValidTemporal(value, type) {
|
|
6607
6974
|
if (type === "TIME") {
|
|
6608
6975
|
const m2 = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
@@ -6630,7 +6997,7 @@ var VALIDATION_META_COLUMNS = [
|
|
|
6630
6997
|
"$err_code",
|
|
6631
6998
|
"$err_message"
|
|
6632
6999
|
];
|
|
6633
|
-
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber) {
|
|
7000
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber, numberPrecision) {
|
|
6634
7001
|
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
6635
7002
|
const errors = [];
|
|
6636
7003
|
const invalid = /* @__PURE__ */ new Set();
|
|
@@ -6638,7 +7005,7 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
6638
7005
|
candidate.record ??= {};
|
|
6639
7006
|
const rowErrors = [...candidate.preErrors];
|
|
6640
7007
|
for (const code of targetFields) {
|
|
6641
|
-
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code));
|
|
7008
|
+
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
|
|
6642
7009
|
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
6643
7010
|
else candidate.record[code] = { value: result.value };
|
|
6644
7011
|
}
|
|
@@ -6648,14 +7015,14 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
6648
7015
|
if (candidate.payload.has(info.code)) continue;
|
|
6649
7016
|
const emptyDefault = isEmptyDmlValue(info.defaultValue);
|
|
6650
7017
|
if (!emptyDefault) {
|
|
6651
|
-
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info);
|
|
7018
|
+
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info, numberPrecision);
|
|
6652
7019
|
if (!defaultResult.ok) rowErrors.push({
|
|
6653
7020
|
field: info.code,
|
|
6654
7021
|
code: defaultResult.code,
|
|
6655
7022
|
message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
|
|
6656
7023
|
});
|
|
6657
7024
|
} else {
|
|
6658
|
-
const emptyResult = validateAndNormalizeDmlValue("", info);
|
|
7025
|
+
const emptyResult = validateAndNormalizeDmlValue("", info, numberPrecision);
|
|
6659
7026
|
if (!emptyResult.ok) {
|
|
6660
7027
|
rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
|
|
6661
7028
|
} else if (info.required) {
|
|
@@ -6683,7 +7050,8 @@ function renderValidationValue(value) {
|
|
|
6683
7050
|
if (value == null) return "";
|
|
6684
7051
|
if (typeof value === "object" && "type" in value) {
|
|
6685
7052
|
const sql = value;
|
|
6686
|
-
if (sql.type === "
|
|
7053
|
+
if (sql.type === "NUMBER") return sql.raw ?? String(sql.value ?? "");
|
|
7054
|
+
if (sql.type === "STRING") return String(sql.value ?? "");
|
|
6687
7055
|
if (sql.type === "ARRAY") return JSON.stringify(sql.elements?.map((e) => e.value) ?? []);
|
|
6688
7056
|
}
|
|
6689
7057
|
if (Array.isArray(value)) return JSON.stringify(value);
|
|
@@ -6925,6 +7293,7 @@ function createEmptyMetrics() {
|
|
|
6925
7293
|
putCalls: 0,
|
|
6926
7294
|
deleteCalls: 0,
|
|
6927
7295
|
fieldCalls: 0,
|
|
7296
|
+
numberPrecisionCalls: 0,
|
|
6928
7297
|
appsCalls: 0,
|
|
6929
7298
|
processStatusCalls: 0,
|
|
6930
7299
|
cursorCreateCalls: 0,
|
|
@@ -7010,6 +7379,10 @@ function wrapClientWithMetrics(client, metrics) {
|
|
|
7010
7379
|
metrics.fieldCalls += 1;
|
|
7011
7380
|
return client.getFields(appId);
|
|
7012
7381
|
},
|
|
7382
|
+
getNumberPrecision: (appId) => {
|
|
7383
|
+
metrics.numberPrecisionCalls += 1;
|
|
7384
|
+
return client.getNumberPrecision(appId);
|
|
7385
|
+
},
|
|
7013
7386
|
getProcessStatuses: (appId) => {
|
|
7014
7387
|
metrics.processStatusCalls += 1;
|
|
7015
7388
|
return client.getProcessStatuses(appId);
|
|
@@ -7277,7 +7650,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
7277
7650
|
const first = resolvedStmt2.expr.query.columns[0];
|
|
7278
7651
|
const numeric = first?.type === "ARITH_COL" || first?.type === "ARITH_AGG_COL" || first?.type === "WINDOW_COL" || first?.type === "AGGREGATE" && (first.func === "COUNT" || first.func === "SUM" || first.func === "AVG");
|
|
7279
7652
|
const numberValue = numeric ? Number(value) : Number.NaN;
|
|
7280
|
-
variables.set(stmt.name, numeric && Number.isFinite(numberValue) ? { type: "number", value: numberValue } : { type: "string", value });
|
|
7653
|
+
variables.set(stmt.name, numeric && Number.isFinite(numberValue) ? { type: "number", value: numberValue, raw: value } : { type: "string", value });
|
|
7281
7654
|
} catch (e) {
|
|
7282
7655
|
if (e instanceof ScalarSubqueryError) {
|
|
7283
7656
|
throw new Error(`ArgumentError: ${e.message}`);
|
|
@@ -7295,7 +7668,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
7295
7668
|
variables.set(stmt.name, { type: "string", value: injected[stmt.name] });
|
|
7296
7669
|
} else {
|
|
7297
7670
|
const value = evaluateScalarExpr(stmt.default);
|
|
7298
|
-
variables.set(stmt.name, {
|
|
7671
|
+
variables.set(stmt.name, {
|
|
7672
|
+
type: "string",
|
|
7673
|
+
value: value.type === "number" ? value.raw ?? String(value.value) : value.value
|
|
7674
|
+
});
|
|
7299
7675
|
}
|
|
7300
7676
|
return {};
|
|
7301
7677
|
}
|
|
@@ -7471,7 +7847,7 @@ function evaluateScalarExpr(expr) {
|
|
|
7471
7847
|
case "STRING":
|
|
7472
7848
|
return { type: "string", value: expr.value };
|
|
7473
7849
|
case "NUMBER":
|
|
7474
|
-
return { type: "number", value: expr.value };
|
|
7850
|
+
return { type: "number", value: expr.value, raw: numberLiteralText(expr) };
|
|
7475
7851
|
case "KINTONE_FUNC":
|
|
7476
7852
|
return { type: "string", value: resolveKintoneFunc(expr.name) };
|
|
7477
7853
|
case "STRING_FUNC":
|
|
@@ -7481,7 +7857,7 @@ function evaluateScalarExpr(expr) {
|
|
|
7481
7857
|
if (!Number.isFinite(value)) {
|
|
7482
7858
|
throw new Error("ArgumentError: SET scalar arithmetic produced a non-finite number.");
|
|
7483
7859
|
}
|
|
7484
|
-
return { type: "number", value };
|
|
7860
|
+
return { type: "number", value, raw: String(value) };
|
|
7485
7861
|
}
|
|
7486
7862
|
}
|
|
7487
7863
|
}
|
|
@@ -7496,7 +7872,7 @@ function resolveVariableRefs(node, variables) {
|
|
|
7496
7872
|
if (value === void 0) {
|
|
7497
7873
|
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
7498
7874
|
}
|
|
7499
|
-
return value.type === "number" ? { type: "NUMBER", value: value.value } : { type: "STRING", value: value.value };
|
|
7875
|
+
return value.type === "number" ? { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) } : { type: "STRING", value: value.value };
|
|
7500
7876
|
}
|
|
7501
7877
|
return Object.fromEntries(
|
|
7502
7878
|
Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
|
|
@@ -7562,7 +7938,7 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
7562
7938
|
case "VARIABLE":
|
|
7563
7939
|
throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
|
|
7564
7940
|
case "NUMBER":
|
|
7565
|
-
return
|
|
7941
|
+
return numberLiteralText(operand);
|
|
7566
7942
|
case "STRING":
|
|
7567
7943
|
return operand.value;
|
|
7568
7944
|
case "ARITH":
|
|
@@ -8303,6 +8679,7 @@ function systemColumnMeta(field) {
|
|
|
8303
8679
|
}
|
|
8304
8680
|
var NUMBER_RETURNING_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
8305
8681
|
"LENGTH",
|
|
8682
|
+
"LENGTH_CHAR",
|
|
8306
8683
|
"INSTR",
|
|
8307
8684
|
"ROUND",
|
|
8308
8685
|
"FLOOR",
|
|
@@ -8844,8 +9221,8 @@ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, par
|
|
|
8844
9221
|
}
|
|
8845
9222
|
var UPSERT_IN_CHUNK_SIZE = 50;
|
|
8846
9223
|
function normalizeKeyPart(v) {
|
|
8847
|
-
const
|
|
8848
|
-
if (
|
|
9224
|
+
const decimal = parseExactDecimal(v);
|
|
9225
|
+
if (decimal !== null) return JSON.stringify(decimal);
|
|
8849
9226
|
return v;
|
|
8850
9227
|
}
|
|
8851
9228
|
function upsertCompositeKey(parts) {
|
|
@@ -8997,6 +9374,7 @@ var optionOrderCache = /* @__PURE__ */ new Map();
|
|
|
8997
9374
|
var sortKindCache = /* @__PURE__ */ new Map();
|
|
8998
9375
|
var fieldInfoCache = /* @__PURE__ */ new Map();
|
|
8999
9376
|
var processStatusCache = /* @__PURE__ */ new Map();
|
|
9377
|
+
var numberPrecisionCache = /* @__PURE__ */ new Map();
|
|
9000
9378
|
function getScopedCacheValue(root, cacheContext, appId) {
|
|
9001
9379
|
return root.get(cacheContext)?.get(appId);
|
|
9002
9380
|
}
|
|
@@ -9018,6 +9396,13 @@ async function getFieldsCached(appId, client, cacheContext) {
|
|
|
9018
9396
|
setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
|
|
9019
9397
|
return loading;
|
|
9020
9398
|
}
|
|
9399
|
+
async function getNumberPrecisionCached(appId, client, cacheContext) {
|
|
9400
|
+
const cached = getScopedCacheValue(numberPrecisionCache, cacheContext, appId);
|
|
9401
|
+
if (cached) return cached;
|
|
9402
|
+
const loading = client.getNumberPrecision(appId);
|
|
9403
|
+
setScopedCacheValue(numberPrecisionCache, cacheContext, appId, loading);
|
|
9404
|
+
return loading;
|
|
9405
|
+
}
|
|
9021
9406
|
async function getProcessStatusesCached(appId, client, cacheContext) {
|
|
9022
9407
|
const cached = getScopedCacheValue(processStatusCache, cacheContext, appId);
|
|
9023
9408
|
if (cached) return cached;
|
|
@@ -9270,6 +9655,45 @@ var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
|
9270
9655
|
"CATEGORY",
|
|
9271
9656
|
"REFERENCE_TABLE"
|
|
9272
9657
|
]);
|
|
9658
|
+
function assertWritableTopLevelDmlFields(appId, targetFields, fieldInfos) {
|
|
9659
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
9660
|
+
for (const code of targetFields) {
|
|
9661
|
+
const info = infoByCode.get(code);
|
|
9662
|
+
if (!info) {
|
|
9663
|
+
throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
|
|
9664
|
+
}
|
|
9665
|
+
if (info.inSubtable) {
|
|
9666
|
+
throw new Error(
|
|
9667
|
+
`ArgumentError: DML target field ${code} is inside a subtable. Use subtable DML syntax (for example, APP${appId}$\u30C6\u30FC\u30D6\u30EB).`
|
|
9668
|
+
);
|
|
9669
|
+
}
|
|
9670
|
+
if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
|
|
9671
|
+
throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
|
|
9672
|
+
}
|
|
9673
|
+
}
|
|
9674
|
+
}
|
|
9675
|
+
async function loadWritableTopLevelDmlFields(appId, targetFields, client, cacheContext) {
|
|
9676
|
+
const fieldInfos = await getFieldsCached(appId, client, cacheContext);
|
|
9677
|
+
assertWritableTopLevelDmlFields(appId, targetFields, fieldInfos);
|
|
9678
|
+
return fieldInfos;
|
|
9679
|
+
}
|
|
9680
|
+
async function loadNumberPrecisionForTargets(appId, targetFields, fieldInfos, client, cacheContext) {
|
|
9681
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
9682
|
+
return targetFields.some((code) => infoByCode.get(code)?.fieldType === "NUMBER") ? getNumberPrecisionCached(appId, client, cacheContext) : void 0;
|
|
9683
|
+
}
|
|
9684
|
+
function assertValidDmlRecords(records, targetFields, fieldInfos, numberPrecision) {
|
|
9685
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
9686
|
+
records.forEach((record, rowIndex) => {
|
|
9687
|
+
for (const code of targetFields) {
|
|
9688
|
+
const info = infoByCode.get(code);
|
|
9689
|
+
const result = validateAndNormalizeDmlValue(record[code]?.value ?? "", info, numberPrecision);
|
|
9690
|
+
if (!result.ok) {
|
|
9691
|
+
throw new Error(`DmlValidationError: ${result.code} ${result.message} (row=${rowIndex + 1}, field=${code})`);
|
|
9692
|
+
}
|
|
9693
|
+
record[code] = { value: result.value };
|
|
9694
|
+
}
|
|
9695
|
+
});
|
|
9696
|
+
}
|
|
9273
9697
|
async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
9274
9698
|
return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
|
|
9275
9699
|
}
|
|
@@ -9281,24 +9705,29 @@ var RejectLimitExceededError = class extends Error {
|
|
|
9281
9705
|
}
|
|
9282
9706
|
};
|
|
9283
9707
|
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
9284
|
-
if (stmt.type === "UPDATE") {
|
|
9285
|
-
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9286
|
-
}
|
|
9287
9708
|
const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
|
|
9288
9709
|
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
|
|
9289
9710
|
if (new Set(payloadFields).size !== payloadFields.length) {
|
|
9290
9711
|
throw new Error("ArgumentError: DML target fields contain duplicates.");
|
|
9291
9712
|
}
|
|
9292
|
-
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
9293
|
-
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
9294
9713
|
const targetFields = stmt.type === "UPDATE" ? stmt.assignments.map((a) => a.field) : stmt.fields;
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9714
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(
|
|
9715
|
+
stmt.appId,
|
|
9716
|
+
targetFields,
|
|
9717
|
+
client,
|
|
9718
|
+
cacheContext
|
|
9719
|
+
);
|
|
9720
|
+
if (stmt.type === "UPDATE") {
|
|
9721
|
+
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9301
9722
|
}
|
|
9723
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
9724
|
+
const numberPrecision = await loadNumberPrecisionForTargets(
|
|
9725
|
+
stmt.appId,
|
|
9726
|
+
targetFields,
|
|
9727
|
+
fieldInfos,
|
|
9728
|
+
client,
|
|
9729
|
+
cacheContext
|
|
9730
|
+
);
|
|
9302
9731
|
const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
|
|
9303
9732
|
const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
|
|
9304
9733
|
candidates,
|
|
@@ -9306,7 +9735,8 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
|
|
|
9306
9735
|
payloadFields,
|
|
9307
9736
|
targetFields,
|
|
9308
9737
|
fieldInfos,
|
|
9309
|
-
statementNumber
|
|
9738
|
+
statementNumber,
|
|
9739
|
+
numberPrecision
|
|
9310
9740
|
);
|
|
9311
9741
|
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
9312
9742
|
const result = {
|
|
@@ -9467,7 +9897,7 @@ async function materializeUpdateValidationCandidates(stmt, client, options, cach
|
|
|
9467
9897
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
9468
9898
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9469
9899
|
let records;
|
|
9470
|
-
if (
|
|
9900
|
+
if (hasRowDependentAssignment(stmt)) {
|
|
9471
9901
|
const getParams = updateToGetQueryForArith(stmt);
|
|
9472
9902
|
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
|
|
9473
9903
|
maxRecords: options.maxRecords ?? 1e4,
|
|
@@ -9530,6 +9960,7 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9530
9960
|
tempTables
|
|
9531
9961
|
);
|
|
9532
9962
|
const sourceByKey = /* @__PURE__ */ new Map();
|
|
9963
|
+
const sourceQueryByKey = /* @__PURE__ */ new Map();
|
|
9533
9964
|
for (const row of sourceRows) {
|
|
9534
9965
|
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
9535
9966
|
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
@@ -9539,6 +9970,7 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9539
9970
|
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for normalized key ${key}.`);
|
|
9540
9971
|
}
|
|
9541
9972
|
sourceByKey.set(key, row);
|
|
9973
|
+
sourceQueryByKey.set(key, String(row[from.joinKeyField]).trim());
|
|
9542
9974
|
}
|
|
9543
9975
|
if (sourceByKey.size === 0) return [];
|
|
9544
9976
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
@@ -9547,7 +9979,7 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9547
9979
|
const targetRecords = [];
|
|
9548
9980
|
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
9549
9981
|
let fetchedTargetCount = 0;
|
|
9550
|
-
for (const keys of splitChunks([...
|
|
9982
|
+
for (const keys of splitChunks([...sourceQueryByKey.values()], UPDATE_FROM_KEY_CHUNK_SIZE)) {
|
|
9551
9983
|
const keyQuery = `${from.targetJoinField} in (${keys.map(sqlQuote).join(",")})`;
|
|
9552
9984
|
const query = filterQuery ? `(${keyQuery}) and (${filterQuery})` : keyQuery;
|
|
9553
9985
|
const resolved = await fetchRecordsForSharedPlan(
|
|
@@ -9648,36 +10080,28 @@ function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
|
9648
10080
|
}
|
|
9649
10081
|
if (kind === "number" && side === "target" && raw === "") return null;
|
|
9650
10082
|
if (kind === "id") {
|
|
9651
|
-
const
|
|
9652
|
-
const id = Number(
|
|
9653
|
-
if (
|
|
10083
|
+
const text = raw.trim();
|
|
10084
|
+
const id = Number(text);
|
|
10085
|
+
if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
9654
10086
|
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a positive safe integer: ${raw}`);
|
|
9655
10087
|
}
|
|
9656
10088
|
return String(id);
|
|
9657
10089
|
}
|
|
9658
|
-
const
|
|
9659
|
-
if (
|
|
10090
|
+
const decimal = parseExactDecimal(raw);
|
|
10091
|
+
if (decimal === null) {
|
|
9660
10092
|
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a finite decimal: ${raw}`);
|
|
9661
10093
|
}
|
|
9662
|
-
|
|
9663
|
-
let negative = false;
|
|
9664
|
-
if (unsigned.startsWith("-") || unsigned.startsWith("+")) {
|
|
9665
|
-
negative = unsigned[0] === "-";
|
|
9666
|
-
unsigned = unsigned.slice(1);
|
|
9667
|
-
}
|
|
9668
|
-
let [whole, fraction = ""] = unsigned.split(".");
|
|
9669
|
-
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
9670
|
-
fraction = fraction.replace(/0+$/, "");
|
|
9671
|
-
const zero = /^0*$/.test(whole) && fraction === "";
|
|
9672
|
-
const canonical = fraction === "" ? whole : `${whole}.${fraction}`;
|
|
9673
|
-
return negative && !zero ? `-${canonical}` : canonical;
|
|
10094
|
+
return JSON.stringify(decimal);
|
|
9674
10095
|
}
|
|
9675
10096
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
9676
10097
|
if (stmt.subtableCode) {
|
|
9677
10098
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
9678
10099
|
}
|
|
10100
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10101
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
9679
10102
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9680
10103
|
const batches = insertToPostBatches(stmt, fieldTypes);
|
|
10104
|
+
assertValidDmlRecords(batches.flatMap((batch) => batch.records), stmt.fields, fieldInfos, numberPrecision);
|
|
9681
10105
|
const createdIds = [];
|
|
9682
10106
|
for (const batch of batches) {
|
|
9683
10107
|
const res = await client.postRecords(batch);
|
|
@@ -9690,6 +10114,8 @@ async function executeInsert(stmt, client, options, cacheContext) {
|
|
|
9690
10114
|
};
|
|
9691
10115
|
}
|
|
9692
10116
|
async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
10117
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10118
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
9693
10119
|
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
9694
10120
|
const { rows, columns } = selectResult;
|
|
9695
10121
|
if (columns.length !== stmt.fields.length) {
|
|
@@ -9711,6 +10137,7 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
9711
10137
|
});
|
|
9712
10138
|
return record;
|
|
9713
10139
|
});
|
|
10140
|
+
assertValidDmlRecords(allRecords, stmt.fields, fieldInfos, numberPrecision);
|
|
9714
10141
|
const createdIds = [];
|
|
9715
10142
|
for (let i = 0; i < allRecords.length; i += 100) {
|
|
9716
10143
|
const batch = allRecords.slice(i, i + 100);
|
|
@@ -9724,17 +10151,32 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
9724
10151
|
};
|
|
9725
10152
|
}
|
|
9726
10153
|
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
9727
|
-
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9728
10154
|
if (stmt.subtableCode) {
|
|
10155
|
+
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9729
10156
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
9730
10157
|
}
|
|
10158
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(
|
|
10159
|
+
stmt.appId,
|
|
10160
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
10161
|
+
client,
|
|
10162
|
+
cacheContext
|
|
10163
|
+
);
|
|
10164
|
+
const targetFields = stmt.assignments.map((assignment) => assignment.field);
|
|
10165
|
+
const numberPrecision = await loadNumberPrecisionForTargets(
|
|
10166
|
+
stmt.appId,
|
|
10167
|
+
targetFields,
|
|
10168
|
+
fieldInfos,
|
|
10169
|
+
client,
|
|
10170
|
+
cacheContext
|
|
10171
|
+
);
|
|
10172
|
+
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9731
10173
|
if (stmt.from != null) {
|
|
9732
10174
|
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
9733
10175
|
}
|
|
9734
10176
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
9735
10177
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
9736
10178
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9737
|
-
if (
|
|
10179
|
+
if (hasRowDependentAssignment(stmt)) {
|
|
9738
10180
|
const getParams2 = updateToGetQueryForArith(stmt);
|
|
9739
10181
|
const resolved2 = await fetchRecordsForSharedPlan(
|
|
9740
10182
|
client.getRecords,
|
|
@@ -9744,11 +10186,12 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9744
10186
|
{ maxRecords, parallel: options.fetchParallel ?? 1 }
|
|
9745
10187
|
);
|
|
9746
10188
|
const records = resolved2.records;
|
|
10189
|
+
const batches2 = updateToPutBatchesArith(stmt, records, fieldTypes);
|
|
10190
|
+
assertValidDmlRecords(batches2.flatMap((batch) => batch.records.map((entry) => entry.record)), targetFields, fieldInfos, numberPrecision);
|
|
9747
10191
|
if (options.confirm) {
|
|
9748
10192
|
const ok = await options.confirm(records.length, "UPDATE");
|
|
9749
10193
|
if (!ok) throw new OperationCancelledError("UPDATE", records.length);
|
|
9750
10194
|
}
|
|
9751
|
-
const batches2 = updateToPutBatchesArith(stmt, records, fieldTypes);
|
|
9752
10195
|
for (const batch of batches2) {
|
|
9753
10196
|
await client.putRecords(batch);
|
|
9754
10197
|
}
|
|
@@ -9762,11 +10205,12 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9762
10205
|
{ maxRecords, parallel: options.fetchParallel ?? 1 }
|
|
9763
10206
|
);
|
|
9764
10207
|
const ids = resolved.ids;
|
|
10208
|
+
const batches = updateToPutBatches(stmt, ids, fieldTypes);
|
|
10209
|
+
assertValidDmlRecords(batches.flatMap((batch) => batch.records.map((entry) => entry.record)), targetFields, fieldInfos, numberPrecision);
|
|
9765
10210
|
if (options.confirm) {
|
|
9766
10211
|
const ok = await options.confirm(ids.length, "UPDATE");
|
|
9767
10212
|
if (!ok) throw new OperationCancelledError("UPDATE", ids.length);
|
|
9768
10213
|
}
|
|
9769
|
-
const batches = updateToPutBatches(stmt, ids, fieldTypes);
|
|
9770
10214
|
for (const batch of batches) {
|
|
9771
10215
|
await client.putRecords(batch);
|
|
9772
10216
|
}
|
|
@@ -9774,12 +10218,16 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9774
10218
|
}
|
|
9775
10219
|
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
9776
10220
|
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
10221
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
10222
|
+
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
10223
|
+
const targetFields = stmt.assignments.map((assignment) => assignment.field);
|
|
10224
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
10225
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, targetFields, fieldInfos, client, cacheContext);
|
|
10226
|
+
assertValidDmlRecords(batches.flatMap((batch) => batch.records.map((entry) => entry.record)), targetFields, fieldInfos, numberPrecision);
|
|
9777
10227
|
if (options.confirm) {
|
|
9778
10228
|
const ok = await options.confirm(matched.length, "UPDATE");
|
|
9779
10229
|
if (!ok) throw new OperationCancelledError("UPDATE", matched.length);
|
|
9780
10230
|
}
|
|
9781
|
-
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9782
|
-
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
9783
10231
|
for (const batch of batches) await client.putRecords(batch);
|
|
9784
10232
|
return { type: "UPDATE", updatedCount: matched.length };
|
|
9785
10233
|
}
|
|
@@ -9828,6 +10276,8 @@ async function executeDelete(stmt, client, options, cacheContext) {
|
|
|
9828
10276
|
return { type: "DELETE", deletedCount: ids.length };
|
|
9829
10277
|
}
|
|
9830
10278
|
async function executeUpsert(stmt, client, options, cacheContext) {
|
|
10279
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10280
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
9831
10281
|
const toInsert = [];
|
|
9832
10282
|
const toUpdate = [];
|
|
9833
10283
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
@@ -9836,7 +10286,7 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
9836
10286
|
const idx = stmt.fields.indexOf(key);
|
|
9837
10287
|
if (idx === -1) throw new Error(`ON DUPLICATE \u306E\u30AD\u30FC\u300C${key}\u300D\u304C INSERT \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093`);
|
|
9838
10288
|
const val = row[idx];
|
|
9839
|
-
return val.type === "STRING" ? val.value : val.type === "NUMBER" ?
|
|
10289
|
+
return val.type === "STRING" ? val.value : val.type === "NUMBER" ? numberLiteralText(val) : val.type === "CASE_VALUE" ? evalCaseWhen(val.expr, {}) : val.elements.map((e) => e.value).join(",");
|
|
9840
10290
|
})
|
|
9841
10291
|
);
|
|
9842
10292
|
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
@@ -9857,6 +10307,12 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
9857
10307
|
toInsert.push(record);
|
|
9858
10308
|
}
|
|
9859
10309
|
});
|
|
10310
|
+
assertValidDmlRecords(
|
|
10311
|
+
[...toInsert, ...toUpdate.map((entry) => entry.record)],
|
|
10312
|
+
stmt.fields,
|
|
10313
|
+
fieldInfos,
|
|
10314
|
+
numberPrecision
|
|
10315
|
+
);
|
|
9860
10316
|
if (options.confirm && toInsert.length + toUpdate.length > 0) {
|
|
9861
10317
|
const total = toInsert.length + toUpdate.length;
|
|
9862
10318
|
const ok = await options.confirm(total, "UPDATE");
|
|
@@ -10132,14 +10588,14 @@ function buildSubtableReorderPutParams(appId, parentId, revision, subtableCode,
|
|
|
10132
10588
|
}
|
|
10133
10589
|
function evalAssignmentValueForSubtable(value, row, resolveFieldType) {
|
|
10134
10590
|
if (value.type === "STRING") return value.value;
|
|
10135
|
-
if (value.type === "NUMBER") return
|
|
10591
|
+
if (value.type === "NUMBER") return numberLiteralText(value);
|
|
10136
10592
|
if (value.type === "ARITH") return String(evalArithExpr(value, row));
|
|
10137
10593
|
if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, row, resolveFieldType);
|
|
10138
10594
|
throw new Error(`${value.type} \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306E\u5024\u3068\u3057\u3066\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`);
|
|
10139
10595
|
}
|
|
10140
10596
|
function valueToString(value) {
|
|
10141
10597
|
if (value.type === "STRING") return value.value;
|
|
10142
|
-
if (value.type === "NUMBER") return
|
|
10598
|
+
if (value.type === "NUMBER") return numberLiteralText(value);
|
|
10143
10599
|
if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, {});
|
|
10144
10600
|
return value.elements.map((e) => e.value).join(",");
|
|
10145
10601
|
}
|
|
@@ -10254,6 +10710,8 @@ function evalOrderKeyForRow(key, row) {
|
|
|
10254
10710
|
}
|
|
10255
10711
|
}
|
|
10256
10712
|
async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
10713
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10714
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
10257
10715
|
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
10258
10716
|
const { rows, columns } = selectResult;
|
|
10259
10717
|
if (columns.length !== stmt.fields.length) {
|
|
@@ -10276,6 +10734,7 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
10276
10734
|
});
|
|
10277
10735
|
return record;
|
|
10278
10736
|
});
|
|
10737
|
+
assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
|
|
10279
10738
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
10280
10739
|
const rowKeyValues = records.map(
|
|
10281
10740
|
(record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
|
|
@@ -10889,6 +11348,8 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
|
10889
11348
|
}
|
|
10890
11349
|
function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
|
|
10891
11350
|
const isArith = hasArithAssignment(stmt);
|
|
11351
|
+
const isStringFunc = stmt.assignments.some((a) => a.value.type === "STRING_FUNC");
|
|
11352
|
+
const isRowDependent = hasRowDependentAssignment(stmt);
|
|
10892
11353
|
const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
|
|
10893
11354
|
const lines = [];
|
|
10894
11355
|
if (label) lines.push(label);
|
|
@@ -10905,10 +11366,11 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
|
|
|
10905
11366
|
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
10906
11367
|
const setTypes = [];
|
|
10907
11368
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
11369
|
+
if (isStringFunc) setTypes.push("\u6587\u5B57\u5217\u95A2\u6570 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A55\u4FA1\uFF09");
|
|
10908
11370
|
if (isSubq) setTypes.push("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA SET");
|
|
10909
|
-
if (!
|
|
11371
|
+
if (!isRowDependent && !isSubq) setTypes.push("\u5358\u7D14 SET");
|
|
10910
11372
|
lines.push(` set type: ${setTypes.join(", ")}`);
|
|
10911
|
-
if (
|
|
11373
|
+
if (isRowDependent) {
|
|
10912
11374
|
const refFields = collectArithRefFields(stmt);
|
|
10913
11375
|
if (refFields.length > 0) {
|
|
10914
11376
|
lines.push(` ref fields: ${refFields.join(", ")}\uFF08GET \u306B\u542B\u3081\u308B\uFF09`);
|
|
@@ -10994,6 +11456,7 @@ function collectArithRefFields(stmt) {
|
|
|
10994
11456
|
const refs = /* @__PURE__ */ new Set();
|
|
10995
11457
|
for (const { value } of stmt.assignments) {
|
|
10996
11458
|
if (value.type === "ARITH") collectArithNodeRefs(value, refs);
|
|
11459
|
+
if (value.type === "STRING_FUNC") collectArithNodeRefs(value, refs);
|
|
10997
11460
|
}
|
|
10998
11461
|
return [...refs];
|
|
10999
11462
|
}
|
|
@@ -11006,6 +11469,13 @@ function collectArithNodeRefs(node, out) {
|
|
|
11006
11469
|
collectArithNodeRefs(node.left, out);
|
|
11007
11470
|
collectArithNodeRefs(node.right, out);
|
|
11008
11471
|
}
|
|
11472
|
+
if (node.type === "STRING_FUNC") {
|
|
11473
|
+
for (const arg of node.args) {
|
|
11474
|
+
if (arg.type !== "STRING" && arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") {
|
|
11475
|
+
collectArithNodeRefs(arg, out);
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
}
|
|
11009
11479
|
}
|
|
11010
11480
|
function formatAssignment(a) {
|
|
11011
11481
|
const v = a.value;
|
|
@@ -11013,6 +11483,7 @@ function formatAssignment(a) {
|
|
|
11013
11483
|
if (v.type === "NUMBER") return `${a.field} = ${v.value}`;
|
|
11014
11484
|
if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
|
|
11015
11485
|
if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
|
|
11486
|
+
if (v.type === "STRING_FUNC") return `${a.field} = ${v.func}(...)`;
|
|
11016
11487
|
if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
|
|
11017
11488
|
if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
|
|
11018
11489
|
return `${a.field} = (${v.type})`;
|
|
@@ -11022,7 +11493,7 @@ function formatArithExprStr(expr) {
|
|
|
11022
11493
|
}
|
|
11023
11494
|
function formatArithNodeStr(node) {
|
|
11024
11495
|
if (node.type === "FIELD_REF") return node.field;
|
|
11025
|
-
if (node.type === "NUMBER") return
|
|
11496
|
+
if (node.type === "NUMBER") return numberLiteralText(node);
|
|
11026
11497
|
if (node.type === "ARITH") return `(${formatArithExprStr(node)})`;
|
|
11027
11498
|
return "...";
|
|
11028
11499
|
}
|
|
@@ -11491,6 +11962,7 @@ function withRequestGate(client, gate) {
|
|
|
11491
11962
|
},
|
|
11492
11963
|
getApps: () => gate.runReadOnly(() => client.getApps()),
|
|
11493
11964
|
getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
|
|
11965
|
+
getNumberPrecision: (appId) => gate.runReadOnly(() => client.getNumberPrecision(appId)),
|
|
11494
11966
|
getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
|
|
11495
11967
|
postRecords: (params) => gate.runMutation(() => client.postRecords(params)),
|
|
11496
11968
|
putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
|
|
@@ -12052,6 +12524,16 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
|
12052
12524
|
);
|
|
12053
12525
|
return flattenFormFieldProperties(res.properties);
|
|
12054
12526
|
},
|
|
12527
|
+
async getNumberPrecision(appId) {
|
|
12528
|
+
const qs = new URLSearchParams();
|
|
12529
|
+
qs.set("app", String(appId));
|
|
12530
|
+
const res = await requestJson(
|
|
12531
|
+
`${apiBasePath}/app/settings.json?${qs.toString()}`,
|
|
12532
|
+
{ method: "GET" },
|
|
12533
|
+
appId
|
|
12534
|
+
);
|
|
12535
|
+
return parseNumberPrecisionSettings(res);
|
|
12536
|
+
},
|
|
12055
12537
|
async getProcessStatuses(appId) {
|
|
12056
12538
|
const qs = new URLSearchParams();
|
|
12057
12539
|
qs.set("app", String(appId));
|
|
@@ -13182,6 +13664,9 @@ function createDryRunClient() {
|
|
|
13182
13664
|
getFields: notUsed,
|
|
13183
13665
|
async getProcessStatuses() {
|
|
13184
13666
|
return { enable: false, states: [] };
|
|
13667
|
+
},
|
|
13668
|
+
async getNumberPrecision() {
|
|
13669
|
+
return { digits: 30, decimalPlaces: 10, roundingMode: "HALF_EVEN" };
|
|
13185
13670
|
}
|
|
13186
13671
|
};
|
|
13187
13672
|
}
|
|
@@ -14199,6 +14684,12 @@ async function run() {
|
|
|
14199
14684
|
if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${appId}.`);
|
|
14200
14685
|
return routed.getFields(binding.appId);
|
|
14201
14686
|
},
|
|
14687
|
+
getNumberPrecision: (appId) => {
|
|
14688
|
+
const binding = appBindingByMappedApp.get(appId) ?? { appId, profile: profileName.toLowerCase() };
|
|
14689
|
+
const routed = profileClientMap.get(binding.profile);
|
|
14690
|
+
if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${appId}.`);
|
|
14691
|
+
return routed.getNumberPrecision(binding.appId);
|
|
14692
|
+
},
|
|
14202
14693
|
getProcessStatuses: (appId) => {
|
|
14203
14694
|
const binding = appBindingByMappedApp.get(appId) ?? { appId, profile: profileName.toLowerCase() };
|
|
14204
14695
|
const pName = binding.profile;
|