@rex0220/kintone-sql-tools 3.2.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 +437 -106
- package/dist-mcp/ksql-mcp.js +440 -109
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -122,6 +122,9 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
122
122
|
["SUBSTR", "SUBSTR" /* SUBSTR */],
|
|
123
123
|
["CONCAT", "CONCAT" /* CONCAT */],
|
|
124
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 */],
|
|
125
128
|
["COALESCE", "COALESCE" /* COALESCE */],
|
|
126
129
|
["NULLIF", "NULLIF" /* NULLIF */],
|
|
127
130
|
["ISNULL", "ISNULL" /* ISNULL */],
|
|
@@ -254,7 +257,7 @@ var Lexer = class {
|
|
|
254
257
|
);
|
|
255
258
|
}
|
|
256
259
|
// ----------------------------------------------------------
|
|
257
|
-
// 数値:
|
|
260
|
+
// 数値: digits[.digits][e[+-]digits](先頭/末尾 dot は受理しない)
|
|
258
261
|
// ----------------------------------------------------------
|
|
259
262
|
readNumber(start) {
|
|
260
263
|
while (this.pos < this.input.length && isDigit(this.input[this.pos])) {
|
|
@@ -266,6 +269,16 @@ var Lexer = class {
|
|
|
266
269
|
this.pos++;
|
|
267
270
|
}
|
|
268
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
|
+
}
|
|
269
282
|
return this.makeToken(
|
|
270
283
|
"NUMBER" /* NUMBER */,
|
|
271
284
|
this.input.slice(start, this.pos),
|
|
@@ -465,8 +478,94 @@ function isJapanese(cp) {
|
|
|
465
478
|
return cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
|
|
466
479
|
}
|
|
467
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
|
+
|
|
468
560
|
// src/types/ast.ts
|
|
469
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
|
+
}
|
|
470
569
|
|
|
471
570
|
// src/parser/parser.ts
|
|
472
571
|
var MAX_BATCH_STATEMENTS = 20;
|
|
@@ -499,6 +598,9 @@ var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
|
499
598
|
"COALESCE" /* COALESCE */,
|
|
500
599
|
"NULLIF" /* NULLIF */,
|
|
501
600
|
"ISNULL" /* ISNULL */,
|
|
601
|
+
"REGEXP_LIKE" /* REGEXP_LIKE */,
|
|
602
|
+
"REGEXP_REPLACE" /* REGEXP_REPLACE */,
|
|
603
|
+
"REGEXP_SUBSTR" /* REGEXP_SUBSTR */,
|
|
502
604
|
"LEFT" /* LEFT */,
|
|
503
605
|
"RIGHT" /* RIGHT */,
|
|
504
606
|
"INSTR" /* INSTR */,
|
|
@@ -1262,12 +1364,12 @@ var Parser = class {
|
|
|
1262
1364
|
if (this.peek().kind === "-" /* MINUS */) {
|
|
1263
1365
|
this.advance();
|
|
1264
1366
|
const operand = this.parseAggPrimary();
|
|
1265
|
-
if (operand.type === "NUMBER") return {
|
|
1266
|
-
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 };
|
|
1267
1369
|
}
|
|
1268
1370
|
if (this.peek().kind === "NUMBER" /* NUMBER */) {
|
|
1269
1371
|
const tok = this.advance();
|
|
1270
|
-
return
|
|
1372
|
+
return makeNumberLiteral(tok.value);
|
|
1271
1373
|
}
|
|
1272
1374
|
const aggFunc = this.tryAggregateFunc();
|
|
1273
1375
|
if (aggFunc !== null) {
|
|
@@ -1319,7 +1421,7 @@ var Parser = class {
|
|
|
1319
1421
|
if (this.allowUnaryPlusNumber && this.peek().kind === "+" /* PLUS */) {
|
|
1320
1422
|
this.advance();
|
|
1321
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");
|
|
1322
|
-
return {
|
|
1424
|
+
return makeNumberLiteral(`+${number.value}`);
|
|
1323
1425
|
}
|
|
1324
1426
|
if (this.peek().kind === "-" /* MINUS */) {
|
|
1325
1427
|
this.advance();
|
|
@@ -1327,8 +1429,8 @@ var Parser = class {
|
|
|
1327
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());
|
|
1328
1430
|
}
|
|
1329
1431
|
const operand = this.parseArithPrimary();
|
|
1330
|
-
if (operand.type === "NUMBER") return {
|
|
1331
|
-
return { type: "ARITH", left:
|
|
1432
|
+
if (operand.type === "NUMBER") return makeNumberLiteral(`-${numberLiteralText(operand)}`);
|
|
1433
|
+
return { type: "ARITH", left: makeNumberLiteral("0"), op: "-", right: operand };
|
|
1332
1434
|
}
|
|
1333
1435
|
if (this.tryStringFuncName() !== null) {
|
|
1334
1436
|
return this.parseStringFuncExpr();
|
|
@@ -1336,7 +1438,7 @@ var Parser = class {
|
|
|
1336
1438
|
const tok = this.peek();
|
|
1337
1439
|
if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
1338
1440
|
this.advance();
|
|
1339
|
-
return
|
|
1441
|
+
return makeNumberLiteral(tok.value);
|
|
1340
1442
|
}
|
|
1341
1443
|
if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
|
|
1342
1444
|
this.advance();
|
|
@@ -1440,6 +1542,9 @@ var Parser = class {
|
|
|
1440
1542
|
["SUBSTR" /* SUBSTR */]: "SUBSTRING",
|
|
1441
1543
|
["CONCAT" /* CONCAT */]: "CONCAT",
|
|
1442
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",
|
|
1443
1548
|
["TRANSLATE" /* TRANSLATE */]: "TRANSLATE",
|
|
1444
1549
|
["COALESCE" /* COALESCE */]: "COALESCE",
|
|
1445
1550
|
["NULLIF" /* NULLIF */]: "NULLIF",
|
|
@@ -1979,7 +2084,7 @@ var Parser = class {
|
|
|
1979
2084
|
}
|
|
1980
2085
|
if (tok.kind === "NUMBER" /* NUMBER */ || tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */ || tok.kind === "(" /* LPAREN */ || tok.kind === "-" /* MINUS */ || this.tryStringFuncName() !== null) {
|
|
1981
2086
|
const expr = this.parseArithAddSub();
|
|
1982
|
-
if (expr.type === "NUMBER") return
|
|
2087
|
+
if (expr.type === "NUMBER") return expr;
|
|
1983
2088
|
return { type: "ARITH_VALUE", expr };
|
|
1984
2089
|
}
|
|
1985
2090
|
throw new ParseError(
|
|
@@ -2006,15 +2111,15 @@ var Parser = class {
|
|
|
2006
2111
|
if (tok.kind === "STRING" /* STRING */) {
|
|
2007
2112
|
values.push({ type: "STRING", value: tok.value });
|
|
2008
2113
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
2009
|
-
values.push(
|
|
2114
|
+
values.push(makeNumberLiteral(tok.value));
|
|
2010
2115
|
} else if (tok.kind === "-" /* MINUS */ || tok.kind === "+" /* PLUS */) {
|
|
2011
2116
|
const number = this.peek();
|
|
2012
2117
|
if (number.kind !== "NUMBER" /* NUMBER */) {
|
|
2013
2118
|
throw new ParseError(invalidValueMessage, tok);
|
|
2014
2119
|
}
|
|
2015
2120
|
this.advance();
|
|
2016
|
-
const sign = tok.kind === "-" /* MINUS */ ? -
|
|
2017
|
-
values.push({
|
|
2121
|
+
const sign = tok.kind === "-" /* MINUS */ ? "-" : "+";
|
|
2122
|
+
values.push(makeNumberLiteral(`${sign}${number.value}`));
|
|
2018
2123
|
} else if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
2019
2124
|
values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
|
|
2020
2125
|
} else {
|
|
@@ -2204,14 +2309,13 @@ var Parser = class {
|
|
|
2204
2309
|
} else if (this.peek().kind === "-" /* MINUS */ || this.peek().kind === "+" /* PLUS */) {
|
|
2205
2310
|
const sign = this.advance();
|
|
2206
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");
|
|
2207
|
-
|
|
2208
|
-
row.push({ type: "NUMBER", value: sign.kind === "-" /* MINUS */ ? -value : value });
|
|
2312
|
+
row.push(makeNumberLiteral(`${sign.kind === "-" /* MINUS */ ? "-" : "+"}${number.value}`));
|
|
2209
2313
|
} else {
|
|
2210
2314
|
const tok = this.advance();
|
|
2211
2315
|
if (tok.kind === "STRING" /* STRING */) {
|
|
2212
2316
|
row.push({ type: "STRING", value: tok.value });
|
|
2213
2317
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
2214
|
-
row.push(
|
|
2318
|
+
row.push(makeNumberLiteral(tok.value));
|
|
2215
2319
|
} else {
|
|
2216
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);
|
|
2217
2321
|
}
|
|
@@ -3045,7 +3149,7 @@ function convertValue(value, op) {
|
|
|
3045
3149
|
case "STRING":
|
|
3046
3150
|
return convertString(value);
|
|
3047
3151
|
case "NUMBER":
|
|
3048
|
-
return
|
|
3152
|
+
return numberLiteralText(value);
|
|
3049
3153
|
case "KINTONE_FUNC":
|
|
3050
3154
|
return convertKintoneFunc(value);
|
|
3051
3155
|
case "IN_LIST":
|
|
@@ -3074,7 +3178,7 @@ function convertInList(v, op) {
|
|
|
3074
3178
|
}
|
|
3075
3179
|
assertResolvedInListValues(v.values);
|
|
3076
3180
|
const values = v.values.map(
|
|
3077
|
-
(item) => item.type === "STRING" ? convertString(item) :
|
|
3181
|
+
(item) => item.type === "STRING" ? convertString(item) : numberLiteralText(item)
|
|
3078
3182
|
).join(",");
|
|
3079
3183
|
return `(${values})`;
|
|
3080
3184
|
}
|
|
@@ -3587,7 +3691,7 @@ function aggregateSyntheticName(func, distinct, arg) {
|
|
|
3587
3691
|
}
|
|
3588
3692
|
function arithNodeLabel(node) {
|
|
3589
3693
|
if (node.type === "FIELD_REF") return node.field;
|
|
3590
|
-
if (node.type === "NUMBER") return
|
|
3694
|
+
if (node.type === "NUMBER") return numberLiteralText(node);
|
|
3591
3695
|
if (node.type === "STRING_FUNC") return stringFuncLabel(node);
|
|
3592
3696
|
return `(${arithNodeLabel(node.left)}${node.op}${arithNodeLabel(node.right)})`;
|
|
3593
3697
|
}
|
|
@@ -3744,7 +3848,7 @@ function isNumericCandidate(expr, options) {
|
|
|
3744
3848
|
if (!isTargetField(expr.left, options)) return false;
|
|
3745
3849
|
if (expr.right.type !== "NUMBER") return false;
|
|
3746
3850
|
if (expr.op === "=") return true;
|
|
3747
|
-
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);
|
|
3748
3852
|
}
|
|
3749
3853
|
function isSelectionInCandidate(expr, options) {
|
|
3750
3854
|
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
@@ -4349,9 +4453,10 @@ function triCompare(left, right) {
|
|
|
4349
4453
|
}
|
|
4350
4454
|
function numberKey(value) {
|
|
4351
4455
|
if (value === "") return { band: 0 };
|
|
4456
|
+
const decimal = parseExactDecimal(value);
|
|
4457
|
+
if (decimal !== null) return { band: 2, value: decimal };
|
|
4352
4458
|
const numeric = Number(value);
|
|
4353
4459
|
if (numeric === Number.NEGATIVE_INFINITY) return { band: 1 };
|
|
4354
|
-
if (Number.isFinite(numeric)) return { band: 2, value: numeric };
|
|
4355
4460
|
if (numeric === Number.POSITIVE_INFINITY) return { band: 3 };
|
|
4356
4461
|
if (value === "NaN") return { band: 4 };
|
|
4357
4462
|
return { band: 5, value };
|
|
@@ -4360,7 +4465,7 @@ function compareNumbers(left, right) {
|
|
|
4360
4465
|
const a = numberKey(left);
|
|
4361
4466
|
const b = numberKey(right);
|
|
4362
4467
|
if (a.band !== b.band) return a.band < b.band ? -1 : 1;
|
|
4363
|
-
if (a.band === 2 && b.band === 2) return
|
|
4468
|
+
if (a.band === 2 && b.band === 2) return compareExactDecimal(a.value, b.value);
|
|
4364
4469
|
if (a.band === 5 && b.band === 5) return compareCodePointStrings(a.value, b.value);
|
|
4365
4470
|
return 0;
|
|
4366
4471
|
}
|
|
@@ -4460,7 +4565,9 @@ function selectScalarExtreme(values, extreme) {
|
|
|
4460
4565
|
const numeric = candidates.every((value) => !Number.isNaN(Number(value)));
|
|
4461
4566
|
const compare = (left, right) => {
|
|
4462
4567
|
if (numeric) {
|
|
4463
|
-
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));
|
|
4464
4571
|
if (numericCmp !== 0) return numericCmp;
|
|
4465
4572
|
}
|
|
4466
4573
|
return compareCodePointStrings(left, right);
|
|
@@ -4583,6 +4690,112 @@ function makeSafePadding(pad, gap) {
|
|
|
4583
4690
|
const repeated = pad.repeat(Math.ceil(gap / pad.length));
|
|
4584
4691
|
return sliceSafePrefix(repeated, gap);
|
|
4585
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
|
+
}
|
|
4586
4799
|
function evalStringFunc(expr, row) {
|
|
4587
4800
|
const args = expr.args.map((a) => evalStringFuncArg(a, row));
|
|
4588
4801
|
switch (expr.func) {
|
|
@@ -4646,6 +4859,21 @@ function evalStringFunc(expr, row) {
|
|
|
4646
4859
|
const to = args[2] ?? "";
|
|
4647
4860
|
return from === "" ? str : str.split(from).join(to);
|
|
4648
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
|
+
}
|
|
4649
4877
|
case "TRANSLATE": {
|
|
4650
4878
|
assertArity("TRANSLATE", args, 3, 3);
|
|
4651
4879
|
const from = [...args[1]];
|
|
@@ -4824,7 +5052,7 @@ function evalStringFuncArg(arg, row) {
|
|
|
4824
5052
|
if (arg.type === "STRING") return arg.value;
|
|
4825
5053
|
if (arg.type === "STRING_FUNC") return evalStringFunc(arg, row);
|
|
4826
5054
|
if (arg.type === "FIELD_REF") return resolveFieldRef(row, arg.field);
|
|
4827
|
-
if (arg.type === "NUMBER") return
|
|
5055
|
+
if (arg.type === "NUMBER") return numberLiteralText(arg);
|
|
4828
5056
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return "";
|
|
4829
5057
|
return String(evalArithExpr(arg, row));
|
|
4830
5058
|
}
|
|
@@ -4873,7 +5101,7 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType, semantics
|
|
|
4873
5101
|
let values = null;
|
|
4874
5102
|
if (right.type === "IN_LIST") {
|
|
4875
5103
|
assertResolvedInListValues2(right.values);
|
|
4876
|
-
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));
|
|
4877
5105
|
}
|
|
4878
5106
|
if (right.type === "SUBQUERY_IN_LIST") {
|
|
4879
5107
|
values = right.resolved;
|
|
@@ -4953,6 +5181,10 @@ var SINGLE_OBJECT_FIELD_TYPES = /* @__PURE__ */ new Set(["CREATOR", "MODIFIER"])
|
|
|
4953
5181
|
function typedInContains(leftStr, values, fieldType) {
|
|
4954
5182
|
const fallback = () => values.has(leftStr);
|
|
4955
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
|
+
}
|
|
4956
5188
|
let parsed;
|
|
4957
5189
|
if (STRING_ARRAY_FIELD_TYPES.has(fieldType) || OBJECT_ARRAY_FIELD_TYPES.has(fieldType) || SINGLE_OBJECT_FIELD_TYPES.has(fieldType)) {
|
|
4958
5190
|
try {
|
|
@@ -5011,7 +5243,7 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
5011
5243
|
case "STRING":
|
|
5012
5244
|
return value.value;
|
|
5013
5245
|
case "NUMBER":
|
|
5014
|
-
return
|
|
5246
|
+
return numberLiteralText(value);
|
|
5015
5247
|
case "KINTONE_FUNC":
|
|
5016
5248
|
return resolveKintoneFunc(value.name);
|
|
5017
5249
|
case "IN_LIST":
|
|
@@ -5451,7 +5683,7 @@ function convertDmlSqlValue(value, fieldType) {
|
|
|
5451
5683
|
case "STRING":
|
|
5452
5684
|
return convertString2(value.value, fieldType);
|
|
5453
5685
|
case "NUMBER":
|
|
5454
|
-
return
|
|
5686
|
+
return numberLiteralText(value);
|
|
5455
5687
|
case "ARRAY":
|
|
5456
5688
|
return convertArray(value.elements.map((e) => e.value), fieldType);
|
|
5457
5689
|
case "KINTONE_FUNC":
|
|
@@ -6083,7 +6315,7 @@ function evalAggArithExpr(node, rows, resolveAggSortKind) {
|
|
|
6083
6315
|
}
|
|
6084
6316
|
}
|
|
6085
6317
|
function aggArithDefaultKey(node) {
|
|
6086
|
-
if (node.type === "NUMBER") return
|
|
6318
|
+
if (node.type === "NUMBER") return numberLiteralText(node);
|
|
6087
6319
|
if (node.type === "AGG_REF") return aggregateSyntheticName2(node.func, node.distinct, node.arg);
|
|
6088
6320
|
return `${aggArithDefaultKey(node.left)}${node.op}${aggArithDefaultKey(node.right)}`;
|
|
6089
6321
|
}
|
|
@@ -6433,7 +6665,7 @@ function stripParentShortcutColumns(row) {
|
|
|
6433
6665
|
function arithColDefaultKey(expr) {
|
|
6434
6666
|
const nodeLabel = (n) => {
|
|
6435
6667
|
if (n.type === "FIELD_REF") return n.field;
|
|
6436
|
-
if (n.type === "NUMBER") return
|
|
6668
|
+
if (n.type === "NUMBER") return numberLiteralText(n);
|
|
6437
6669
|
if (n.type === "STRING_FUNC") return stringFuncDefaultKey(n);
|
|
6438
6670
|
return `(${nodeLabel(n.left)}${n.op}${nodeLabel(n.right)})`;
|
|
6439
6671
|
};
|
|
@@ -6462,10 +6694,11 @@ function hasAggregateInStringFuncExpr2(expr) {
|
|
|
6462
6694
|
function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
|
|
6463
6695
|
if (arg.type === "AGG_REF") {
|
|
6464
6696
|
const value = evalAggregate(arg.func, arg.distinct, arg.arg, arg.separator, rows, resolveAggSortKind);
|
|
6465
|
-
return typeof value === "number" ? { type: "NUMBER", value } : { type: "STRING", value };
|
|
6697
|
+
return typeof value === "number" ? { type: "NUMBER", value, raw: String(value) } : { type: "STRING", value };
|
|
6466
6698
|
}
|
|
6467
6699
|
if (arg.type === "AGG_ARITH") {
|
|
6468
|
-
|
|
6700
|
+
const value = evalAggArithExpr(arg, rows, resolveAggSortKind);
|
|
6701
|
+
return { type: "NUMBER", value, raw: String(value) };
|
|
6469
6702
|
}
|
|
6470
6703
|
if (arg.type === "STRING_FUNC") {
|
|
6471
6704
|
return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
|
|
@@ -6583,10 +6816,43 @@ function toFlatString(value) {
|
|
|
6583
6816
|
}
|
|
6584
6817
|
}
|
|
6585
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
|
+
|
|
6586
6852
|
// src/core/dmlValidation.ts
|
|
6587
6853
|
var ARRAY_TYPES2 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
6588
6854
|
var CHOICE_TYPES = /* @__PURE__ */ new Set(["DROP_DOWN", "RADIO_BUTTON", "CHECK_BOX", "MULTI_SELECT"]);
|
|
6589
|
-
function validateAndNormalizeDmlValue(raw, field) {
|
|
6855
|
+
function validateAndNormalizeDmlValue(raw, field, numberPrecision) {
|
|
6590
6856
|
if (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME") {
|
|
6591
6857
|
const original = rawScalarText(raw);
|
|
6592
6858
|
if (original !== "" && !isValidTemporalInput(original, field.fieldType)) {
|
|
@@ -6605,7 +6871,8 @@ function validateAndNormalizeDmlValue(raw, field) {
|
|
|
6605
6871
|
}
|
|
6606
6872
|
if (!isEmpty(value) && field.fieldType === "NUMBER") {
|
|
6607
6873
|
const text = String(value);
|
|
6608
|
-
|
|
6874
|
+
const decimal = parseExactDecimal(text);
|
|
6875
|
+
if (decimal === null) {
|
|
6609
6876
|
return { ok: false, code: "ERR_TYPE_NUMBER", message: `${field.code} \u306F\u6570\u5024\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
6610
6877
|
}
|
|
6611
6878
|
if (field.minValue != null && compareDecimal(text, field.minValue) < 0) {
|
|
@@ -6614,6 +6881,17 @@ function validateAndNormalizeDmlValue(raw, field) {
|
|
|
6614
6881
|
if (field.maxValue != null && compareDecimal(text, field.maxValue) > 0) {
|
|
6615
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` };
|
|
6616
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
|
+
}
|
|
6617
6895
|
}
|
|
6618
6896
|
if (!isEmpty(value) && (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME")) {
|
|
6619
6897
|
if (!isValidTemporal(String(value), field.fieldType)) {
|
|
@@ -6641,7 +6919,8 @@ function validateAndNormalizeDmlValue(raw, field) {
|
|
|
6641
6919
|
}
|
|
6642
6920
|
function rawScalarText(raw) {
|
|
6643
6921
|
if (raw == null) return "";
|
|
6644
|
-
if (isSqlValue(raw) &&
|
|
6922
|
+
if (isSqlValue(raw) && raw.type === "NUMBER") return numberLiteralText(raw);
|
|
6923
|
+
if (isSqlValue(raw) && raw.type === "STRING") return raw.value;
|
|
6645
6924
|
return typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
6646
6925
|
}
|
|
6647
6926
|
function isValidTemporalInput(value, type) {
|
|
@@ -6691,34 +6970,6 @@ function isEmpty(value) {
|
|
|
6691
6970
|
function typeCode(type) {
|
|
6692
6971
|
return type === "NUMBER" ? "ERR_TYPE_NUMBER" : "ERR_TYPE_DATE";
|
|
6693
6972
|
}
|
|
6694
|
-
function isFiniteDecimal(value) {
|
|
6695
|
-
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim());
|
|
6696
|
-
}
|
|
6697
|
-
function compareDecimal(left, right) {
|
|
6698
|
-
const normalize = (input) => {
|
|
6699
|
-
let s = input.trim();
|
|
6700
|
-
let sign = 1;
|
|
6701
|
-
if (s.startsWith("-")) {
|
|
6702
|
-
sign = -1;
|
|
6703
|
-
s = s.slice(1);
|
|
6704
|
-
} else if (s.startsWith("+")) s = s.slice(1);
|
|
6705
|
-
let [whole, fraction = ""] = s.split(".");
|
|
6706
|
-
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
6707
|
-
fraction = fraction.replace(/0+$/, "");
|
|
6708
|
-
if (/^0*$/.test(whole) && fraction === "") sign = 1;
|
|
6709
|
-
return { sign, whole, fraction };
|
|
6710
|
-
};
|
|
6711
|
-
const a = normalize(left);
|
|
6712
|
-
const b = normalize(right);
|
|
6713
|
-
if (a.sign !== b.sign) return a.sign < b.sign ? -1 : 1;
|
|
6714
|
-
const direction = a.sign;
|
|
6715
|
-
if (a.whole.length !== b.whole.length) return a.whole.length < b.whole.length ? -direction : direction;
|
|
6716
|
-
if (a.whole !== b.whole) return a.whole < b.whole ? -direction : direction;
|
|
6717
|
-
const width = Math.max(a.fraction.length, b.fraction.length);
|
|
6718
|
-
const af = a.fraction.padEnd(width, "0");
|
|
6719
|
-
const bf = b.fraction.padEnd(width, "0");
|
|
6720
|
-
return af === bf ? 0 : af < bf ? -direction : direction;
|
|
6721
|
-
}
|
|
6722
6973
|
function isValidTemporal(value, type) {
|
|
6723
6974
|
if (type === "TIME") {
|
|
6724
6975
|
const m2 = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
@@ -6746,7 +6997,7 @@ var VALIDATION_META_COLUMNS = [
|
|
|
6746
6997
|
"$err_code",
|
|
6747
6998
|
"$err_message"
|
|
6748
6999
|
];
|
|
6749
|
-
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber) {
|
|
7000
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber, numberPrecision) {
|
|
6750
7001
|
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
6751
7002
|
const errors = [];
|
|
6752
7003
|
const invalid = /* @__PURE__ */ new Set();
|
|
@@ -6754,7 +7005,7 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
6754
7005
|
candidate.record ??= {};
|
|
6755
7006
|
const rowErrors = [...candidate.preErrors];
|
|
6756
7007
|
for (const code of targetFields) {
|
|
6757
|
-
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code));
|
|
7008
|
+
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
|
|
6758
7009
|
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
6759
7010
|
else candidate.record[code] = { value: result.value };
|
|
6760
7011
|
}
|
|
@@ -6764,14 +7015,14 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
6764
7015
|
if (candidate.payload.has(info.code)) continue;
|
|
6765
7016
|
const emptyDefault = isEmptyDmlValue(info.defaultValue);
|
|
6766
7017
|
if (!emptyDefault) {
|
|
6767
|
-
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info);
|
|
7018
|
+
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info, numberPrecision);
|
|
6768
7019
|
if (!defaultResult.ok) rowErrors.push({
|
|
6769
7020
|
field: info.code,
|
|
6770
7021
|
code: defaultResult.code,
|
|
6771
7022
|
message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
|
|
6772
7023
|
});
|
|
6773
7024
|
} else {
|
|
6774
|
-
const emptyResult = validateAndNormalizeDmlValue("", info);
|
|
7025
|
+
const emptyResult = validateAndNormalizeDmlValue("", info, numberPrecision);
|
|
6775
7026
|
if (!emptyResult.ok) {
|
|
6776
7027
|
rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
|
|
6777
7028
|
} else if (info.required) {
|
|
@@ -6799,7 +7050,8 @@ function renderValidationValue(value) {
|
|
|
6799
7050
|
if (value == null) return "";
|
|
6800
7051
|
if (typeof value === "object" && "type" in value) {
|
|
6801
7052
|
const sql = value;
|
|
6802
|
-
if (sql.type === "
|
|
7053
|
+
if (sql.type === "NUMBER") return sql.raw ?? String(sql.value ?? "");
|
|
7054
|
+
if (sql.type === "STRING") return String(sql.value ?? "");
|
|
6803
7055
|
if (sql.type === "ARRAY") return JSON.stringify(sql.elements?.map((e) => e.value) ?? []);
|
|
6804
7056
|
}
|
|
6805
7057
|
if (Array.isArray(value)) return JSON.stringify(value);
|
|
@@ -7041,6 +7293,7 @@ function createEmptyMetrics() {
|
|
|
7041
7293
|
putCalls: 0,
|
|
7042
7294
|
deleteCalls: 0,
|
|
7043
7295
|
fieldCalls: 0,
|
|
7296
|
+
numberPrecisionCalls: 0,
|
|
7044
7297
|
appsCalls: 0,
|
|
7045
7298
|
processStatusCalls: 0,
|
|
7046
7299
|
cursorCreateCalls: 0,
|
|
@@ -7126,6 +7379,10 @@ function wrapClientWithMetrics(client, metrics) {
|
|
|
7126
7379
|
metrics.fieldCalls += 1;
|
|
7127
7380
|
return client.getFields(appId);
|
|
7128
7381
|
},
|
|
7382
|
+
getNumberPrecision: (appId) => {
|
|
7383
|
+
metrics.numberPrecisionCalls += 1;
|
|
7384
|
+
return client.getNumberPrecision(appId);
|
|
7385
|
+
},
|
|
7129
7386
|
getProcessStatuses: (appId) => {
|
|
7130
7387
|
metrics.processStatusCalls += 1;
|
|
7131
7388
|
return client.getProcessStatuses(appId);
|
|
@@ -7393,7 +7650,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
7393
7650
|
const first = resolvedStmt2.expr.query.columns[0];
|
|
7394
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");
|
|
7395
7652
|
const numberValue = numeric ? Number(value) : Number.NaN;
|
|
7396
|
-
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 });
|
|
7397
7654
|
} catch (e) {
|
|
7398
7655
|
if (e instanceof ScalarSubqueryError) {
|
|
7399
7656
|
throw new Error(`ArgumentError: ${e.message}`);
|
|
@@ -7411,7 +7668,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
7411
7668
|
variables.set(stmt.name, { type: "string", value: injected[stmt.name] });
|
|
7412
7669
|
} else {
|
|
7413
7670
|
const value = evaluateScalarExpr(stmt.default);
|
|
7414
|
-
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
|
+
});
|
|
7415
7675
|
}
|
|
7416
7676
|
return {};
|
|
7417
7677
|
}
|
|
@@ -7587,7 +7847,7 @@ function evaluateScalarExpr(expr) {
|
|
|
7587
7847
|
case "STRING":
|
|
7588
7848
|
return { type: "string", value: expr.value };
|
|
7589
7849
|
case "NUMBER":
|
|
7590
|
-
return { type: "number", value: expr.value };
|
|
7850
|
+
return { type: "number", value: expr.value, raw: numberLiteralText(expr) };
|
|
7591
7851
|
case "KINTONE_FUNC":
|
|
7592
7852
|
return { type: "string", value: resolveKintoneFunc(expr.name) };
|
|
7593
7853
|
case "STRING_FUNC":
|
|
@@ -7597,7 +7857,7 @@ function evaluateScalarExpr(expr) {
|
|
|
7597
7857
|
if (!Number.isFinite(value)) {
|
|
7598
7858
|
throw new Error("ArgumentError: SET scalar arithmetic produced a non-finite number.");
|
|
7599
7859
|
}
|
|
7600
|
-
return { type: "number", value };
|
|
7860
|
+
return { type: "number", value, raw: String(value) };
|
|
7601
7861
|
}
|
|
7602
7862
|
}
|
|
7603
7863
|
}
|
|
@@ -7612,7 +7872,7 @@ function resolveVariableRefs(node, variables) {
|
|
|
7612
7872
|
if (value === void 0) {
|
|
7613
7873
|
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
7614
7874
|
}
|
|
7615
|
-
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 };
|
|
7616
7876
|
}
|
|
7617
7877
|
return Object.fromEntries(
|
|
7618
7878
|
Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
|
|
@@ -7678,7 +7938,7 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
7678
7938
|
case "VARIABLE":
|
|
7679
7939
|
throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
|
|
7680
7940
|
case "NUMBER":
|
|
7681
|
-
return
|
|
7941
|
+
return numberLiteralText(operand);
|
|
7682
7942
|
case "STRING":
|
|
7683
7943
|
return operand.value;
|
|
7684
7944
|
case "ARITH":
|
|
@@ -8961,8 +9221,8 @@ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, par
|
|
|
8961
9221
|
}
|
|
8962
9222
|
var UPSERT_IN_CHUNK_SIZE = 50;
|
|
8963
9223
|
function normalizeKeyPart(v) {
|
|
8964
|
-
const
|
|
8965
|
-
if (
|
|
9224
|
+
const decimal = parseExactDecimal(v);
|
|
9225
|
+
if (decimal !== null) return JSON.stringify(decimal);
|
|
8966
9226
|
return v;
|
|
8967
9227
|
}
|
|
8968
9228
|
function upsertCompositeKey(parts) {
|
|
@@ -9114,6 +9374,7 @@ var optionOrderCache = /* @__PURE__ */ new Map();
|
|
|
9114
9374
|
var sortKindCache = /* @__PURE__ */ new Map();
|
|
9115
9375
|
var fieldInfoCache = /* @__PURE__ */ new Map();
|
|
9116
9376
|
var processStatusCache = /* @__PURE__ */ new Map();
|
|
9377
|
+
var numberPrecisionCache = /* @__PURE__ */ new Map();
|
|
9117
9378
|
function getScopedCacheValue(root, cacheContext, appId) {
|
|
9118
9379
|
return root.get(cacheContext)?.get(appId);
|
|
9119
9380
|
}
|
|
@@ -9135,6 +9396,13 @@ async function getFieldsCached(appId, client, cacheContext) {
|
|
|
9135
9396
|
setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
|
|
9136
9397
|
return loading;
|
|
9137
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
|
+
}
|
|
9138
9406
|
async function getProcessStatusesCached(appId, client, cacheContext) {
|
|
9139
9407
|
const cached = getScopedCacheValue(processStatusCache, cacheContext, appId);
|
|
9140
9408
|
if (cached) return cached;
|
|
@@ -9409,6 +9677,23 @@ async function loadWritableTopLevelDmlFields(appId, targetFields, client, cacheC
|
|
|
9409
9677
|
assertWritableTopLevelDmlFields(appId, targetFields, fieldInfos);
|
|
9410
9678
|
return fieldInfos;
|
|
9411
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
|
+
}
|
|
9412
9697
|
async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
9413
9698
|
return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
|
|
9414
9699
|
}
|
|
@@ -9436,6 +9721,13 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
|
|
|
9436
9721
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9437
9722
|
}
|
|
9438
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
|
+
);
|
|
9439
9731
|
const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
|
|
9440
9732
|
const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
|
|
9441
9733
|
candidates,
|
|
@@ -9443,7 +9735,8 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
|
|
|
9443
9735
|
payloadFields,
|
|
9444
9736
|
targetFields,
|
|
9445
9737
|
fieldInfos,
|
|
9446
|
-
statementNumber
|
|
9738
|
+
statementNumber,
|
|
9739
|
+
numberPrecision
|
|
9447
9740
|
);
|
|
9448
9741
|
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
9449
9742
|
const result = {
|
|
@@ -9667,6 +9960,7 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9667
9960
|
tempTables
|
|
9668
9961
|
);
|
|
9669
9962
|
const sourceByKey = /* @__PURE__ */ new Map();
|
|
9963
|
+
const sourceQueryByKey = /* @__PURE__ */ new Map();
|
|
9670
9964
|
for (const row of sourceRows) {
|
|
9671
9965
|
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
9672
9966
|
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
@@ -9676,6 +9970,7 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9676
9970
|
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for normalized key ${key}.`);
|
|
9677
9971
|
}
|
|
9678
9972
|
sourceByKey.set(key, row);
|
|
9973
|
+
sourceQueryByKey.set(key, String(row[from.joinKeyField]).trim());
|
|
9679
9974
|
}
|
|
9680
9975
|
if (sourceByKey.size === 0) return [];
|
|
9681
9976
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
@@ -9684,7 +9979,7 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9684
9979
|
const targetRecords = [];
|
|
9685
9980
|
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
9686
9981
|
let fetchedTargetCount = 0;
|
|
9687
|
-
for (const keys of splitChunks([...
|
|
9982
|
+
for (const keys of splitChunks([...sourceQueryByKey.values()], UPDATE_FROM_KEY_CHUNK_SIZE)) {
|
|
9688
9983
|
const keyQuery = `${from.targetJoinField} in (${keys.map(sqlQuote).join(",")})`;
|
|
9689
9984
|
const query = filterQuery ? `(${keyQuery}) and (${filterQuery})` : keyQuery;
|
|
9690
9985
|
const resolved = await fetchRecordsForSharedPlan(
|
|
@@ -9785,37 +10080,28 @@ function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
|
9785
10080
|
}
|
|
9786
10081
|
if (kind === "number" && side === "target" && raw === "") return null;
|
|
9787
10082
|
if (kind === "id") {
|
|
9788
|
-
const
|
|
9789
|
-
const id = Number(
|
|
9790
|
-
if (
|
|
10083
|
+
const text = raw.trim();
|
|
10084
|
+
const id = Number(text);
|
|
10085
|
+
if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
9791
10086
|
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a positive safe integer: ${raw}`);
|
|
9792
10087
|
}
|
|
9793
10088
|
return String(id);
|
|
9794
10089
|
}
|
|
9795
|
-
const
|
|
9796
|
-
if (
|
|
10090
|
+
const decimal = parseExactDecimal(raw);
|
|
10091
|
+
if (decimal === null) {
|
|
9797
10092
|
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a finite decimal: ${raw}`);
|
|
9798
10093
|
}
|
|
9799
|
-
|
|
9800
|
-
let negative = false;
|
|
9801
|
-
if (unsigned.startsWith("-") || unsigned.startsWith("+")) {
|
|
9802
|
-
negative = unsigned[0] === "-";
|
|
9803
|
-
unsigned = unsigned.slice(1);
|
|
9804
|
-
}
|
|
9805
|
-
let [whole, fraction = ""] = unsigned.split(".");
|
|
9806
|
-
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
9807
|
-
fraction = fraction.replace(/0+$/, "");
|
|
9808
|
-
const zero = /^0*$/.test(whole) && fraction === "";
|
|
9809
|
-
const canonical = fraction === "" ? whole : `${whole}.${fraction}`;
|
|
9810
|
-
return negative && !zero ? `-${canonical}` : canonical;
|
|
10094
|
+
return JSON.stringify(decimal);
|
|
9811
10095
|
}
|
|
9812
10096
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
9813
10097
|
if (stmt.subtableCode) {
|
|
9814
10098
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
9815
10099
|
}
|
|
9816
|
-
await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10100
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10101
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
9817
10102
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9818
10103
|
const batches = insertToPostBatches(stmt, fieldTypes);
|
|
10104
|
+
assertValidDmlRecords(batches.flatMap((batch) => batch.records), stmt.fields, fieldInfos, numberPrecision);
|
|
9819
10105
|
const createdIds = [];
|
|
9820
10106
|
for (const batch of batches) {
|
|
9821
10107
|
const res = await client.postRecords(batch);
|
|
@@ -9828,7 +10114,8 @@ async function executeInsert(stmt, client, options, cacheContext) {
|
|
|
9828
10114
|
};
|
|
9829
10115
|
}
|
|
9830
10116
|
async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
9831
|
-
await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10117
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10118
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
9832
10119
|
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
9833
10120
|
const { rows, columns } = selectResult;
|
|
9834
10121
|
if (columns.length !== stmt.fields.length) {
|
|
@@ -9850,6 +10137,7 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
9850
10137
|
});
|
|
9851
10138
|
return record;
|
|
9852
10139
|
});
|
|
10140
|
+
assertValidDmlRecords(allRecords, stmt.fields, fieldInfos, numberPrecision);
|
|
9853
10141
|
const createdIds = [];
|
|
9854
10142
|
for (let i = 0; i < allRecords.length; i += 100) {
|
|
9855
10143
|
const batch = allRecords.slice(i, i + 100);
|
|
@@ -9867,12 +10155,20 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9867
10155
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9868
10156
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
9869
10157
|
}
|
|
9870
|
-
await loadWritableTopLevelDmlFields(
|
|
10158
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(
|
|
9871
10159
|
stmt.appId,
|
|
9872
10160
|
stmt.assignments.map((assignment) => assignment.field),
|
|
9873
10161
|
client,
|
|
9874
10162
|
cacheContext
|
|
9875
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
|
+
);
|
|
9876
10172
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
9877
10173
|
if (stmt.from != null) {
|
|
9878
10174
|
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
@@ -9890,11 +10186,12 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9890
10186
|
{ maxRecords, parallel: options.fetchParallel ?? 1 }
|
|
9891
10187
|
);
|
|
9892
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);
|
|
9893
10191
|
if (options.confirm) {
|
|
9894
10192
|
const ok = await options.confirm(records.length, "UPDATE");
|
|
9895
10193
|
if (!ok) throw new OperationCancelledError("UPDATE", records.length);
|
|
9896
10194
|
}
|
|
9897
|
-
const batches2 = updateToPutBatchesArith(stmt, records, fieldTypes);
|
|
9898
10195
|
for (const batch of batches2) {
|
|
9899
10196
|
await client.putRecords(batch);
|
|
9900
10197
|
}
|
|
@@ -9908,11 +10205,12 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9908
10205
|
{ maxRecords, parallel: options.fetchParallel ?? 1 }
|
|
9909
10206
|
);
|
|
9910
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);
|
|
9911
10210
|
if (options.confirm) {
|
|
9912
10211
|
const ok = await options.confirm(ids.length, "UPDATE");
|
|
9913
10212
|
if (!ok) throw new OperationCancelledError("UPDATE", ids.length);
|
|
9914
10213
|
}
|
|
9915
|
-
const batches = updateToPutBatches(stmt, ids, fieldTypes);
|
|
9916
10214
|
for (const batch of batches) {
|
|
9917
10215
|
await client.putRecords(batch);
|
|
9918
10216
|
}
|
|
@@ -9920,12 +10218,16 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
9920
10218
|
}
|
|
9921
10219
|
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
9922
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);
|
|
9923
10227
|
if (options.confirm) {
|
|
9924
10228
|
const ok = await options.confirm(matched.length, "UPDATE");
|
|
9925
10229
|
if (!ok) throw new OperationCancelledError("UPDATE", matched.length);
|
|
9926
10230
|
}
|
|
9927
|
-
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9928
|
-
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
9929
10231
|
for (const batch of batches) await client.putRecords(batch);
|
|
9930
10232
|
return { type: "UPDATE", updatedCount: matched.length };
|
|
9931
10233
|
}
|
|
@@ -9974,7 +10276,8 @@ async function executeDelete(stmt, client, options, cacheContext) {
|
|
|
9974
10276
|
return { type: "DELETE", deletedCount: ids.length };
|
|
9975
10277
|
}
|
|
9976
10278
|
async function executeUpsert(stmt, client, options, cacheContext) {
|
|
9977
|
-
await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10279
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10280
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
9978
10281
|
const toInsert = [];
|
|
9979
10282
|
const toUpdate = [];
|
|
9980
10283
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
@@ -9983,7 +10286,7 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
9983
10286
|
const idx = stmt.fields.indexOf(key);
|
|
9984
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`);
|
|
9985
10288
|
const val = row[idx];
|
|
9986
|
-
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(",");
|
|
9987
10290
|
})
|
|
9988
10291
|
);
|
|
9989
10292
|
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
@@ -10004,6 +10307,12 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
10004
10307
|
toInsert.push(record);
|
|
10005
10308
|
}
|
|
10006
10309
|
});
|
|
10310
|
+
assertValidDmlRecords(
|
|
10311
|
+
[...toInsert, ...toUpdate.map((entry) => entry.record)],
|
|
10312
|
+
stmt.fields,
|
|
10313
|
+
fieldInfos,
|
|
10314
|
+
numberPrecision
|
|
10315
|
+
);
|
|
10007
10316
|
if (options.confirm && toInsert.length + toUpdate.length > 0) {
|
|
10008
10317
|
const total = toInsert.length + toUpdate.length;
|
|
10009
10318
|
const ok = await options.confirm(total, "UPDATE");
|
|
@@ -10279,14 +10588,14 @@ function buildSubtableReorderPutParams(appId, parentId, revision, subtableCode,
|
|
|
10279
10588
|
}
|
|
10280
10589
|
function evalAssignmentValueForSubtable(value, row, resolveFieldType) {
|
|
10281
10590
|
if (value.type === "STRING") return value.value;
|
|
10282
|
-
if (value.type === "NUMBER") return
|
|
10591
|
+
if (value.type === "NUMBER") return numberLiteralText(value);
|
|
10283
10592
|
if (value.type === "ARITH") return String(evalArithExpr(value, row));
|
|
10284
10593
|
if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, row, resolveFieldType);
|
|
10285
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`);
|
|
10286
10595
|
}
|
|
10287
10596
|
function valueToString(value) {
|
|
10288
10597
|
if (value.type === "STRING") return value.value;
|
|
10289
|
-
if (value.type === "NUMBER") return
|
|
10598
|
+
if (value.type === "NUMBER") return numberLiteralText(value);
|
|
10290
10599
|
if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, {});
|
|
10291
10600
|
return value.elements.map((e) => e.value).join(",");
|
|
10292
10601
|
}
|
|
@@ -10401,7 +10710,8 @@ function evalOrderKeyForRow(key, row) {
|
|
|
10401
10710
|
}
|
|
10402
10711
|
}
|
|
10403
10712
|
async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
10404
|
-
await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10713
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10714
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
10405
10715
|
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
10406
10716
|
const { rows, columns } = selectResult;
|
|
10407
10717
|
if (columns.length !== stmt.fields.length) {
|
|
@@ -10424,6 +10734,7 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
10424
10734
|
});
|
|
10425
10735
|
return record;
|
|
10426
10736
|
});
|
|
10737
|
+
assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
|
|
10427
10738
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
10428
10739
|
const rowKeyValues = records.map(
|
|
10429
10740
|
(record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
|
|
@@ -11182,7 +11493,7 @@ function formatArithExprStr(expr) {
|
|
|
11182
11493
|
}
|
|
11183
11494
|
function formatArithNodeStr(node) {
|
|
11184
11495
|
if (node.type === "FIELD_REF") return node.field;
|
|
11185
|
-
if (node.type === "NUMBER") return
|
|
11496
|
+
if (node.type === "NUMBER") return numberLiteralText(node);
|
|
11186
11497
|
if (node.type === "ARITH") return `(${formatArithExprStr(node)})`;
|
|
11187
11498
|
return "...";
|
|
11188
11499
|
}
|
|
@@ -11651,6 +11962,7 @@ function withRequestGate(client, gate) {
|
|
|
11651
11962
|
},
|
|
11652
11963
|
getApps: () => gate.runReadOnly(() => client.getApps()),
|
|
11653
11964
|
getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
|
|
11965
|
+
getNumberPrecision: (appId) => gate.runReadOnly(() => client.getNumberPrecision(appId)),
|
|
11654
11966
|
getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
|
|
11655
11967
|
postRecords: (params) => gate.runMutation(() => client.postRecords(params)),
|
|
11656
11968
|
putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
|
|
@@ -12212,6 +12524,16 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
|
12212
12524
|
);
|
|
12213
12525
|
return flattenFormFieldProperties(res.properties);
|
|
12214
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
|
+
},
|
|
12215
12537
|
async getProcessStatuses(appId) {
|
|
12216
12538
|
const qs = new URLSearchParams();
|
|
12217
12539
|
qs.set("app", String(appId));
|
|
@@ -13342,6 +13664,9 @@ function createDryRunClient() {
|
|
|
13342
13664
|
getFields: notUsed,
|
|
13343
13665
|
async getProcessStatuses() {
|
|
13344
13666
|
return { enable: false, states: [] };
|
|
13667
|
+
},
|
|
13668
|
+
async getNumberPrecision() {
|
|
13669
|
+
return { digits: 30, decimalPlaces: 10, roundingMode: "HALF_EVEN" };
|
|
13345
13670
|
}
|
|
13346
13671
|
};
|
|
13347
13672
|
}
|
|
@@ -14359,6 +14684,12 @@ async function run() {
|
|
|
14359
14684
|
if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${appId}.`);
|
|
14360
14685
|
return routed.getFields(binding.appId);
|
|
14361
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
|
+
},
|
|
14362
14693
|
getProcessStatuses: (appId) => {
|
|
14363
14694
|
const binding = appBindingByMappedApp.get(appId) ?? { appId, profile: profileName.toLowerCase() };
|
|
14364
14695
|
const pName = binding.profile;
|