@rex0220/kintone-sql-tools 3.55.0 → 3.56.1

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/dist-cli/ksql.js CHANGED
@@ -17931,7 +17931,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
17931
17931
  maxRecords: options.maxRecords ?? 1e4,
17932
17932
  hasKlike: whereHasKlike(stmt.where)
17933
17933
  }) : null;
17934
- await validateSelectFieldCodes(
17934
+ const subtableFieldWarnings = await validateSelectFieldCodes(
17935
17935
  stmt,
17936
17936
  orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : mode,
17937
17937
  client,
@@ -17976,11 +17976,31 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
17976
17976
  await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache, forLibraryCapture)
17977
17977
  );
17978
17978
  }
17979
- return mergeSelectWarnings(result, defaultRangeWarnings);
17979
+ return mergeSelectWarnings(result, [...defaultRangeWarnings, ...subtableFieldWarnings]);
17980
+ }
17981
+ function subtableGroupingAdvice(fieldName, owners) {
17982
+ if (owners.length === 1) {
17983
+ const { appId, owner } = owners[0];
17984
+ return `ArgumentError: ${fieldName} \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u300C${owner}\u300D\uFF08APP${appId}\uFF09\u306E\u4E2D\u306E\u9805\u76EE\u3067\u3059\u3002\u89AA\u30C6\u30FC\u30D6\u30EB\u306E GROUP BY \u306B\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002APP${appId}$${owner} \u304B\u3089\u96C6\u8A08\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u89AA\u306E\u30EC\u30B3\u30FC\u30C9 ID \u306F _pid\u3001\u89AA\u9805\u76EE\u306F _p.<\u30D5\u30A3\u30FC\u30EB\u30C9\u30B3\u30FC\u30C9> \u306B\u306A\u308A\u307E\u3059\uFF09\u3002`;
17985
+ }
17986
+ const list = owners.map(({ appId, owner }) => `APP${appId}$${owner}`).join(" / ");
17987
+ return `ArgumentError: ${fieldName} \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u306E\u4E2D\u306E\u9805\u76EE\u3067\u3059\uFF08${list}\uFF09\u3002\u89AA\u30C6\u30FC\u30D6\u30EB\u306E GROUP BY \u306B\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002\u3069\u306E\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u304B\u3089\u96C6\u8A08\u3059\u308B\u304B\u3092 FROM \u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`;
17988
+ }
17989
+ function dedupeSubtableOwners(owners) {
17990
+ const seen = /* @__PURE__ */ new Set();
17991
+ const result = [];
17992
+ for (const entry of owners) {
17993
+ const key = `${entry.appId}\0${entry.owner}`;
17994
+ if (seen.has(key)) continue;
17995
+ seen.add(key);
17996
+ result.push(entry);
17997
+ }
17998
+ return result;
17980
17999
  }
17981
18000
  async function buildRuntimePlainGroupByPlan(stmt, client, cacheContext, materializedTables) {
17982
18001
  if (stmt.groupBy.length === 0) return void 0;
17983
18002
  const sources = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
18003
+ const subtableOwners = /* @__PURE__ */ new Map();
17984
18004
  const inputs = await Promise.all(sources.map(async (source) => {
17985
18005
  if (source.cteName !== null) {
17986
18006
  const materialized = materializedTables?.get(source.cteName);
@@ -17991,6 +18011,12 @@ async function buildRuntimePlainGroupByPlan(stmt, client, cacheContext, material
17991
18011
  }
17992
18012
  const fields = await getFieldsCached(source.appId, client, cacheContext);
17993
18013
  if (!source.subtableCode) {
18014
+ for (const field of fields) {
18015
+ if (field.inSubtable !== true || !field.subtableCode) continue;
18016
+ const entries = subtableOwners.get(field.code) ?? [];
18017
+ entries.push({ appId: source.appId, owner: field.subtableCode });
18018
+ subtableOwners.set(field.code, dedupeSubtableOwners(entries));
18019
+ }
17994
18020
  return {
17995
18021
  kind: "APP",
17996
18022
  fieldCodes: fields.filter((field) => !field.inSubtable).map((field) => field.code)
@@ -18012,10 +18038,10 @@ async function buildRuntimePlainGroupByPlan(stmt, client, cacheContext, material
18012
18038
  );
18013
18039
  const groupBy = stmt.groupBy;
18014
18040
  const plan = planPlainGroupByResolution(groupBy, stmt.columns, schemas);
18015
- assertRuntimePlainGroupByPlan(stmt, groupBy, plan);
18041
+ assertRuntimePlainGroupByPlan(stmt, groupBy, plan, subtableOwners);
18016
18042
  return plan;
18017
18043
  }
18018
- function assertRuntimePlainGroupByPlan(stmt, groupBy, plan) {
18044
+ function assertRuntimePlainGroupByPlan(stmt, groupBy, plan, subtableOwners) {
18019
18045
  const physicalAppIds = [
18020
18046
  stmt.from,
18021
18047
  ...stmt.joins.map((join2) => join2.table)
@@ -18028,6 +18054,12 @@ function assertRuntimePlainGroupByPlan(stmt, groupBy, plan) {
18028
18054
  const name = key?.type === "FIELD_NAME" ? key.name : "(expression)";
18029
18055
  if (item.kind === "UNKNOWN") {
18030
18056
  const appSuffix = primaryPhysicalAppId === null ? "" : ` (APP${primaryPhysicalAppId})`;
18057
+ const dot = item.name.indexOf(".");
18058
+ const bare = dot >= 0 ? item.name.slice(dot + 1) : item.name;
18059
+ const owners = subtableOwners.get(item.name) ?? subtableOwners.get(bare) ?? [];
18060
+ if (owners.length > 0) {
18061
+ throw new Error(subtableGroupingAdvice(item.name, owners));
18062
+ }
18031
18063
  throw new Error(`ArgumentError: unknown field code(s): ${item.name}${appSuffix}`);
18032
18064
  }
18033
18065
  if (item.kind === "ALIAS_REJECT") {
@@ -18078,9 +18110,15 @@ async function buildGroupingFieldResolver(stmt, client, cacheContext, materializ
18078
18110
  return [appId, new Map(infos.map((info) => [info.code, info]))];
18079
18111
  }))
18080
18112
  );
18113
+ const subtableFieldOf = (table, code) => {
18114
+ if (table.subtableCode) return null;
18115
+ const info = infosByApp.get(table.appId)?.get(code);
18116
+ return info?.inSubtable === true ? info.subtableCode ?? "" : null;
18117
+ };
18081
18118
  const physicalMatch = (table, field) => {
18082
18119
  if (field === "$id") return "$id";
18083
18120
  const code = fieldCodeForTypeLookup(table, field);
18121
+ if (subtableFieldOf(table, code) !== null) return null;
18084
18122
  return infosByApp.get(table.appId)?.has(code) ? code : null;
18085
18123
  };
18086
18124
  const materializedHas = (table, field) => {
@@ -18115,6 +18153,10 @@ async function buildGroupingFieldResolver(stmt, client, cacheContext, materializ
18115
18153
  }
18116
18154
  const code = physicalMatch(table, field.field);
18117
18155
  if (code === null) {
18156
+ const owner = subtableFieldOf(table, fieldCodeForTypeLookup(table, field.field));
18157
+ if (owner !== null && owner !== "") {
18158
+ throw new Error(subtableGroupingAdvice(field.field, [{ appId: table.appId, owner }]));
18159
+ }
18118
18160
  throw new Error(`ArgumentError: B65 field ${field.tableAlias}.${field.field} does not exist in APP${table.appId}.`);
18119
18161
  }
18120
18162
  return resolved(table, tableIndex, field, code);
@@ -18137,6 +18179,15 @@ async function buildGroupingFieldResolver(stmt, client, cacheContext, materializ
18137
18179
  `ArgumentError: B65 field ${field.field} resolves to a materialized CTE/temp column; physical APP fields are required.`
18138
18180
  );
18139
18181
  }
18182
+ const owners = tables.flatMap((table) => {
18183
+ if (table.cteName !== null) return [];
18184
+ const owner = subtableFieldOf(table, fieldCodeForTypeLookup(table, field.field));
18185
+ return owner === null || owner === "" ? [] : [{ appId: table.appId, owner }];
18186
+ });
18187
+ const uniqueOwners = dedupeSubtableOwners(owners);
18188
+ if (uniqueOwners.length > 0) {
18189
+ throw new Error(subtableGroupingAdvice(field.field, uniqueOwners));
18190
+ }
18140
18191
  throw new Error(`ArgumentError: B65 field ${field.field} does not exist in a physical APP source.`);
18141
18192
  };
18142
18193
  }
@@ -18369,6 +18420,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext, orderPla
18369
18420
  return { type: "SELECT", rows: projected, columns, rowCount: projected.length, warnings: [...warnings] };
18370
18421
  }
18371
18422
  async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
18423
+ const warnings = [];
18372
18424
  const appToFields = /* @__PURE__ */ new Map();
18373
18425
  const addFields = (appId, fields) => {
18374
18426
  if (fields.length === 0) return;
@@ -18379,13 +18431,25 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
18379
18431
  }
18380
18432
  for (const f of fields) target.add(f);
18381
18433
  };
18434
+ const parentTableFields = /* @__PURE__ */ new Map();
18435
+ const addParentFields = (appId, fields) => {
18436
+ let target = parentTableFields.get(appId);
18437
+ if (!target) {
18438
+ target = /* @__PURE__ */ new Set();
18439
+ parentTableFields.set(appId, target);
18440
+ }
18441
+ for (const f of fields) target.add(f);
18442
+ };
18382
18443
  if (mode === "SIMPLE") {
18383
18444
  const params = selectToKintoneParams(stmt);
18384
18445
  addFields(params.app, params.fields);
18446
+ if (!stmt.from.subtableCode) addParentFields(params.app, params.fields);
18385
18447
  } else {
18386
18448
  const tables = [stmt.from, ...stmt.joins.map((j) => j.table)].filter((t) => t.cteName === null);
18387
18449
  for (const table of tables) {
18388
- addFields(table.appId, selectToFetchAllFields(stmt, table));
18450
+ const fields = selectToFetchAllFields(stmt, table);
18451
+ addFields(table.appId, fields);
18452
+ if (!table.subtableCode) addParentFields(table.appId, fields);
18389
18453
  }
18390
18454
  }
18391
18455
  for (const [appId, fields] of appToFields.entries()) {
@@ -18398,7 +18462,21 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
18398
18462
  if (unknown.length > 0) {
18399
18463
  throw new Error(`ArgumentError: unknown field code(s): ${unknown.join(", ")} (APP${appId})`);
18400
18464
  }
18465
+ const referenced = parentTableFields.get(appId);
18466
+ if (referenced) {
18467
+ const subtableFields = defs.filter(
18468
+ (d) => d.inSubtable === true && referenced.has(d.code)
18469
+ );
18470
+ for (const field of subtableFields) {
18471
+ const owner = field.subtableCode ?? "";
18472
+ const source = owner ? `\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u300C${owner}\u300D` : "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB";
18473
+ warnings.push(
18474
+ `${field.code} \u306F${source}\u306E\u4E2D\u306E\u9805\u76EE\u3067\u3059\u3002APP${appId} \u304B\u3089\u9078\u3076\u3068\u3001\u30A8\u30E9\u30FC\u306B\u306A\u3089\u305A\u5168\u884C\u304C\u7A7A\u306B\u306A\u308A\u307E\u3059\u3002` + (owner ? `APP${appId}$${owner} \u304B\u3089\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u89AA\u306E\u30EC\u30B3\u30FC\u30C9 ID \u306F _pid\u3001\u89AA\u9805\u76EE\u306F _p.<\u30D5\u30A3\u30FC\u30EB\u30C9\u30B3\u30FC\u30C9> \u306B\u306A\u308A\u307E\u3059\uFF09\u3002` : "")
18475
+ );
18476
+ }
18477
+ }
18401
18478
  }
18479
+ return warnings;
18402
18480
  }
18403
18481
  function b86SourceLabel(table) {
18404
18482
  return table.cteName ?? `APP${table.appId}`;
@@ -23130,9 +23208,12 @@ var SHOW_APPS_COLUMNS = Object.freeze([
23130
23208
  "\u8AAC\u660E"
23131
23209
  ]);
23132
23210
  var DESCRIBE_COLUMNS = Object.freeze([
23211
+ // B145: サブテーブル列は「その項目がどの表にあるか」を示す。無いと明細項目を
23212
+ // 親項目と取り違え、親から SELECT して全行空という結果になる(エラーも警告も出ない)。
23133
23213
  "\u30D5\u30A3\u30FC\u30EB\u30C9\u30B3\u30FC\u30C9",
23134
23214
  "\u30E9\u30D9\u30EB",
23135
23215
  "\u30BF\u30A4\u30D7",
23216
+ "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB",
23136
23217
  "\u30EB\u30C3\u30AF\u30A2\u30C3\u30D7",
23137
23218
  "\u30B3\u30D4\u30FC\u5143",
23138
23219
  "\u91CD\u8907\u7981\u6B62",
@@ -23155,6 +23236,8 @@ async function executeDescribe(stmt, client, cacheContext) {
23155
23236
  "\u30D5\u30A3\u30FC\u30EB\u30C9\u30B3\u30FC\u30C9": f.code,
23156
23237
  "\u30E9\u30D9\u30EB": f.label,
23157
23238
  "\u30BF\u30A4\u30D7": f.fieldType,
23239
+ // 明細項目はサブテーブルのフィールドコード、親項目は空文字。
23240
+ "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB": f.inSubtable === true ? f.subtableCode ?? "" : "",
23158
23241
  "\u30EB\u30C3\u30AF\u30A2\u30C3\u30D7": f.hasLookup === true ? "YES" : "",
23159
23242
  "\u30B3\u30D4\u30FC\u5143": f.isLookupCopyTarget === true ? "YES" : "",
23160
23243
  "\u91CD\u8907\u7981\u6B62": f.isUnique === true ? "YES" : "",