callman-core 1.44.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/ai/prompts/scenarioNodeReference.d.ts.map +1 -1
  2. package/dist/ai/prompts/scenarioNodeReference.js +17 -6
  3. package/dist/ai/prompts/scenarioNodeReference.js.map +1 -1
  4. package/dist/host/connections.d.ts.map +1 -1
  5. package/dist/host/connections.js +7 -0
  6. package/dist/host/connections.js.map +1 -1
  7. package/dist/host/db/db-query-reference.d.ts.map +1 -1
  8. package/dist/host/db/db-query-reference.js +22 -5
  9. package/dist/host/db/db-query-reference.js.map +1 -1
  10. package/dist/host/db/dbBinds.d.ts +20 -0
  11. package/dist/host/db/dbBinds.d.ts.map +1 -0
  12. package/dist/host/db/dbBinds.js +61 -0
  13. package/dist/host/db/dbBinds.js.map +1 -0
  14. package/dist/host/db/dbSessionManager.d.ts +13 -0
  15. package/dist/host/db/dbSessionManager.d.ts.map +1 -0
  16. package/dist/host/db/dbSessionManager.js +165 -0
  17. package/dist/host/db/dbSessionManager.js.map +1 -0
  18. package/dist/host/db/introspection/index.d.ts +1 -0
  19. package/dist/host/db/introspection/index.d.ts.map +1 -1
  20. package/dist/host/db/introspection/index.js +1 -0
  21. package/dist/host/db/introspection/index.js.map +1 -1
  22. package/dist/host/db/introspection/routines.d.ts +38 -0
  23. package/dist/host/db/introspection/routines.d.ts.map +1 -0
  24. package/dist/host/db/introspection/routines.js +439 -0
  25. package/dist/host/db/introspection/routines.js.map +1 -0
  26. package/dist/host/db/mssqlAdapter.d.ts +10 -4
  27. package/dist/host/db/mssqlAdapter.d.ts.map +1 -1
  28. package/dist/host/db/mssqlAdapter.js +144 -34
  29. package/dist/host/db/mssqlAdapter.js.map +1 -1
  30. package/dist/host/db/mysqlAdapter.d.ts +10 -4
  31. package/dist/host/db/mysqlAdapter.d.ts.map +1 -1
  32. package/dist/host/db/mysqlAdapter.js +66 -16
  33. package/dist/host/db/mysqlAdapter.js.map +1 -1
  34. package/dist/host/db/oracleAdapter.d.ts +10 -4
  35. package/dist/host/db/oracleAdapter.d.ts.map +1 -1
  36. package/dist/host/db/oracleAdapter.js +301 -28
  37. package/dist/host/db/oracleAdapter.js.map +1 -1
  38. package/dist/host/db/pgClientConfig.d.ts +10 -1
  39. package/dist/host/db/pgClientConfig.d.ts.map +1 -1
  40. package/dist/host/db/pgClientConfig.js +59 -0
  41. package/dist/host/db/pgClientConfig.js.map +1 -1
  42. package/dist/host/db/pgSession.d.ts +11 -0
  43. package/dist/host/db/pgSession.d.ts.map +1 -0
  44. package/dist/host/db/pgSession.js +62 -0
  45. package/dist/host/db/pgSession.js.map +1 -0
  46. package/dist/host/db/pgSessionManager.d.ts +5 -14
  47. package/dist/host/db/pgSessionManager.d.ts.map +1 -1
  48. package/dist/host/db/pgSessionManager.js +9 -161
  49. package/dist/host/db/pgSessionManager.js.map +1 -1
  50. package/dist/host/db/postgresAdapter.d.ts +2 -4
  51. package/dist/host/db/postgresAdapter.d.ts.map +1 -1
  52. package/dist/host/db/postgresAdapter.js +26 -6
  53. package/dist/host/db/postgresAdapter.js.map +1 -1
  54. package/dist/host/db/queryExecutor.d.ts +2 -4
  55. package/dist/host/db/queryExecutor.d.ts.map +1 -1
  56. package/dist/host/db/queryExecutor.js +35 -23
  57. package/dist/host/db/queryExecutor.js.map +1 -1
  58. package/dist/host/db/sqlState.d.ts +1 -0
  59. package/dist/host/db/sqlState.d.ts.map +1 -1
  60. package/dist/host/db/sqlState.js +20 -0
  61. package/dist/host/db/sqlState.js.map +1 -1
  62. package/dist/host/db/types.d.ts +38 -0
  63. package/dist/host/db/types.d.ts.map +1 -1
  64. package/dist/host/index.d.ts +5 -3
  65. package/dist/host/index.d.ts.map +1 -1
  66. package/dist/host/index.js +8 -6
  67. package/dist/host/index.js.map +1 -1
  68. package/dist/scenario-runner/adapters/db-executor.d.ts +17 -3
  69. package/dist/scenario-runner/adapters/db-executor.d.ts.map +1 -1
  70. package/dist/scenario-runner/adapters/db-executor.js +57 -22
  71. package/dist/scenario-runner/adapters/db-executor.js.map +1 -1
  72. package/dist/scenario-runner/adapters/index.d.ts +1 -1
  73. package/dist/scenario-runner/adapters/index.d.ts.map +1 -1
  74. package/dist/scenario-runner/adapters/index.js +1 -1
  75. package/dist/scenario-runner/adapters/index.js.map +1 -1
  76. package/dist/scenario-runner/adapters/types.d.ts +7 -0
  77. package/dist/scenario-runner/adapters/types.d.ts.map +1 -1
  78. package/dist/scenario-runner/index.d.ts +1 -1
  79. package/dist/scenario-runner/index.d.ts.map +1 -1
  80. package/dist/scenario-runner/index.js +1 -1
  81. package/dist/scenario-runner/index.js.map +1 -1
  82. package/dist/scenario-runner/nodePolicy.d.ts.map +1 -1
  83. package/dist/scenario-runner/nodePolicy.js +3 -0
  84. package/dist/scenario-runner/nodePolicy.js.map +1 -1
  85. package/dist/scenario-runner/runScenario.d.ts +4 -2
  86. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  87. package/dist/scenario-runner/runScenario.js +47 -12
  88. package/dist/scenario-runner/runScenario.js.map +1 -1
  89. package/dist/scenario-runner/runScenarioWithDataset.d.ts.map +1 -1
  90. package/dist/scenario-runner/runScenarioWithDataset.js +65 -1
  91. package/dist/scenario-runner/runScenarioWithDataset.js.map +1 -1
  92. package/dist/scenario-runner/types.d.ts +21 -1
  93. package/dist/scenario-runner/types.d.ts.map +1 -1
  94. package/dist/script/features/db.d.ts +7 -0
  95. package/dist/script/features/db.d.ts.map +1 -1
  96. package/dist/script/features/db.js +8 -1
  97. package/dist/script/features/db.js.map +1 -1
  98. package/dist/script/pm-api-declarations.d.ts.map +1 -1
  99. package/dist/script/pm-api-declarations.js +24 -0
  100. package/dist/script/pm-api-declarations.js.map +1 -1
  101. package/dist/script/pm-api-metadata.js +1 -1
  102. package/dist/script/pm-api-metadata.js.map +1 -1
  103. package/dist/script/types.d.ts +7 -0
  104. package/dist/script/types.d.ts.map +1 -1
  105. package/dist-cjs/ai/prompts/scenarioNodeReference.js +17 -6
  106. package/dist-cjs/ai/prompts/scenarioNodeReference.js.map +1 -1
  107. package/dist-cjs/host/connections.js +7 -0
  108. package/dist-cjs/host/connections.js.map +1 -1
  109. package/dist-cjs/host/db/db-query-reference.js +22 -5
  110. package/dist-cjs/host/db/db-query-reference.js.map +1 -1
  111. package/dist-cjs/host/db/dbBinds.js +71 -0
  112. package/dist-cjs/host/db/dbBinds.js.map +1 -0
  113. package/dist-cjs/host/db/dbSessionManager.js +172 -0
  114. package/dist-cjs/host/db/dbSessionManager.js.map +1 -0
  115. package/dist-cjs/host/db/introspection/index.js +4 -1
  116. package/dist-cjs/host/db/introspection/index.js.map +1 -1
  117. package/dist-cjs/host/db/introspection/routines.js +444 -0
  118. package/dist-cjs/host/db/introspection/routines.js.map +1 -0
  119. package/dist-cjs/host/db/mssqlAdapter.js +146 -35
  120. package/dist-cjs/host/db/mssqlAdapter.js.map +1 -1
  121. package/dist-cjs/host/db/mysqlAdapter.js +68 -17
  122. package/dist-cjs/host/db/mysqlAdapter.js.map +1 -1
  123. package/dist-cjs/host/db/oracleAdapter.js +303 -29
  124. package/dist-cjs/host/db/oracleAdapter.js.map +1 -1
  125. package/dist-cjs/host/db/pgClientConfig.js +61 -1
  126. package/dist-cjs/host/db/pgClientConfig.js.map +1 -1
  127. package/dist-cjs/host/db/pgSession.js +67 -0
  128. package/dist-cjs/host/db/pgSession.js.map +1 -0
  129. package/dist-cjs/host/db/pgSessionManager.js +10 -166
  130. package/dist-cjs/host/db/pgSessionManager.js.map +1 -1
  131. package/dist-cjs/host/db/postgresAdapter.js +25 -5
  132. package/dist-cjs/host/db/postgresAdapter.js.map +1 -1
  133. package/dist-cjs/host/db/queryExecutor.js +34 -22
  134. package/dist-cjs/host/db/queryExecutor.js.map +1 -1
  135. package/dist-cjs/host/db/sqlState.js +22 -1
  136. package/dist-cjs/host/db/sqlState.js.map +1 -1
  137. package/dist-cjs/host/index.js +15 -6
  138. package/dist-cjs/host/index.js.map +1 -1
  139. package/dist-cjs/scenario-runner/adapters/db-executor.js +57 -21
  140. package/dist-cjs/scenario-runner/adapters/db-executor.js.map +1 -1
  141. package/dist-cjs/scenario-runner/adapters/index.js +3 -1
  142. package/dist-cjs/scenario-runner/adapters/index.js.map +1 -1
  143. package/dist-cjs/scenario-runner/index.js +2 -1
  144. package/dist-cjs/scenario-runner/index.js.map +1 -1
  145. package/dist-cjs/scenario-runner/nodePolicy.js +3 -0
  146. package/dist-cjs/scenario-runner/nodePolicy.js.map +1 -1
  147. package/dist-cjs/scenario-runner/runScenario.js +49 -13
  148. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  149. package/dist-cjs/scenario-runner/runScenarioWithDataset.js +65 -1
  150. package/dist-cjs/scenario-runner/runScenarioWithDataset.js.map +1 -1
  151. package/dist-cjs/script/features/db.js +8 -1
  152. package/dist-cjs/script/features/db.js.map +1 -1
  153. package/dist-cjs/script/pm-api-declarations.js +24 -0
  154. package/dist-cjs/script/pm-api-declarations.js.map +1 -1
  155. package/dist-cjs/script/pm-api-metadata.js +1 -1
  156. package/dist-cjs/script/pm-api-metadata.js.map +1 -1
  157. package/package.json +1 -1
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ // Run-scoped DB sessions for scenario db nodes (`useSession`), on every SQL
3
+ // engine.
4
+ //
5
+ // One live connection per (runId, connection identity). All session-joined
6
+ // nodes of a run share it, so transactions, temp tables, package state and
7
+ // session variables survive between nodes — the foundation of the
8
+ // seed → call procedure → assert → ROLLBACK testing pattern.
9
+ //
10
+ // Modeled on callman-backend's kafka-client-pool: lazy open, per-session
11
+ // serialization, idle sweeper, explicit shutdown hook.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.__getDbSessionCountForTests = exports.closeAllDbSessions = exports.releaseDbSessionsForRun = exports.executeInDbSession = void 0;
14
+ const mssqlAdapter_js_1 = require("./mssqlAdapter.js");
15
+ const mysqlAdapter_js_1 = require("./mysqlAdapter.js");
16
+ const oracleAdapter_js_1 = require("./oracleAdapter.js");
17
+ const pgSession_js_1 = require("./pgSession.js");
18
+ const IDLE_SWEEP_INTERVAL_MS = 60_000;
19
+ const IDLE_SESSION_TTL_MS = 5 * 60_000;
20
+ const sessions = new Map();
21
+ let sweeper = null;
22
+ // Connection identity participates in the key so two different connections
23
+ // in one run never share a client, even if their `id`s degenerate to the
24
+ // same normalized placeholder.
25
+ const sessionKey = (runId, connection) => [
26
+ runId,
27
+ connection.type,
28
+ connection.id,
29
+ connection.connectionString ?? "",
30
+ connection.host,
31
+ String(connection.port),
32
+ connection.database ?? "",
33
+ connection.username,
34
+ ].join("::");
35
+ const ensureSweeper = () => {
36
+ if (sweeper)
37
+ return;
38
+ sweeper = setInterval(() => {
39
+ const now = Date.now();
40
+ for (const [key, entry] of sessions) {
41
+ if (now - entry.lastUsedAtMs > IDLE_SESSION_TTL_MS) {
42
+ sessions.delete(key);
43
+ void destroyEntry(entry, "rollback");
44
+ }
45
+ }
46
+ if (sessions.size === 0 && sweeper) {
47
+ clearInterval(sweeper);
48
+ sweeper = null;
49
+ }
50
+ }, IDLE_SWEEP_INTERVAL_MS);
51
+ // Never keep the host process alive just for the sweeper.
52
+ sweeper.unref?.();
53
+ };
54
+ const destroyEntry = async (entry, outcome) => {
55
+ try {
56
+ const client = await entry.clientPromise;
57
+ if (entry.transactionOpen && !entry.poisoned) {
58
+ try {
59
+ await (outcome === "commit" ? client.commit() : client.rollback());
60
+ }
61
+ catch {
62
+ // The transaction dies with the connection either way.
63
+ }
64
+ }
65
+ try {
66
+ await client.close();
67
+ }
68
+ catch {
69
+ // Best-effort teardown.
70
+ }
71
+ }
72
+ catch {
73
+ // Client never connected — nothing to tear down.
74
+ }
75
+ };
76
+ const openClient = (connection, entry) => {
77
+ switch (connection.type) {
78
+ case "postgres":
79
+ return (0, pgSession_js_1.openPgSession)(connection, (reason) => {
80
+ entry.poisoned = reason;
81
+ });
82
+ case "oracle":
83
+ return (0, oracleAdapter_js_1.openOracleSession)(connection);
84
+ case "mysql":
85
+ return (0, mysqlAdapter_js_1.openMysqlSession)(connection);
86
+ case "mssql":
87
+ return (0, mssqlAdapter_js_1.openMssqlSession)(connection);
88
+ default:
89
+ return Promise.reject(new Error(`Scenario DB sessions are only supported for SQL connections (got "${connection.type}"). Disable 'Run in scenario transaction' on this node.`));
90
+ }
91
+ };
92
+ const openEntry = (runId, connection) => {
93
+ const entry = {
94
+ chain: Promise.resolve(),
95
+ transactionOpen: false,
96
+ poisoned: null,
97
+ lastUsedAtMs: Date.now(),
98
+ runId,
99
+ };
100
+ entry.clientPromise = openClient(connection, entry);
101
+ // A failed connect reaches the caller through the chain below.
102
+ entry.clientPromise.catch(() => undefined);
103
+ return entry;
104
+ };
105
+ const executeInDbSession = async (input) => {
106
+ const key = sessionKey(input.runId, input.connection);
107
+ let entry = sessions.get(key);
108
+ if (!entry) {
109
+ entry = openEntry(input.runId, input.connection);
110
+ sessions.set(key, entry);
111
+ ensureSweeper();
112
+ }
113
+ const run = entry.chain.then(async () => {
114
+ const client = await entry.clientPromise;
115
+ if (entry.poisoned) {
116
+ throw new Error(`${client.label} session is unusable: ${entry.poisoned}`);
117
+ }
118
+ const inTransaction = input.transactionMode !== "none";
119
+ if (inTransaction && !entry.transactionOpen) {
120
+ await client.begin();
121
+ entry.transactionOpen = true;
122
+ }
123
+ const { connection: _connection, runId: _runId, transactionMode: _mode, query, ...options } = input;
124
+ try {
125
+ return await client.exec(query, options, inTransaction);
126
+ }
127
+ catch (error) {
128
+ if (error?.dbTimeout) {
129
+ entry.poisoned = error instanceof Error ? error.message : "statement cancelled";
130
+ }
131
+ throw error;
132
+ }
133
+ });
134
+ // The chain must survive a failed query (SQL errors are normal in
135
+ // expectError flows) — swallow for chaining purposes only.
136
+ entry.chain = run.then(() => undefined, () => undefined).then(() => {
137
+ entry.lastUsedAtMs = Date.now();
138
+ });
139
+ return run;
140
+ };
141
+ exports.executeInDbSession = executeInDbSession;
142
+ // Close every session belonging to `runId` — COMMIT/ROLLBACK per the run
143
+ // outcome, then close the connection. Queued statements finish (or fail) first
144
+ // thanks to the serialization chain.
145
+ const releaseDbSessionsForRun = async (runId, outcome) => {
146
+ const owned = [];
147
+ for (const [key, entry] of sessions) {
148
+ if (entry.runId === runId)
149
+ owned.push([key, entry]);
150
+ }
151
+ await Promise.all(owned.map(async ([key, entry]) => {
152
+ sessions.delete(key);
153
+ await entry.chain;
154
+ await destroyEntry(entry, outcome);
155
+ }));
156
+ };
157
+ exports.releaseDbSessionsForRun = releaseDbSessionsForRun;
158
+ // Shutdown hook for hosts (backend process exit, CLI end, electron quit).
159
+ const closeAllDbSessions = async () => {
160
+ const all = Array.from(sessions.values());
161
+ sessions.clear();
162
+ if (sweeper) {
163
+ clearInterval(sweeper);
164
+ sweeper = null;
165
+ }
166
+ await Promise.all(all.map((entry) => destroyEntry(entry, "rollback")));
167
+ };
168
+ exports.closeAllDbSessions = closeAllDbSessions;
169
+ // Introspection for tests.
170
+ const __getDbSessionCountForTests = () => sessions.size;
171
+ exports.__getDbSessionCountForTests = __getDbSessionCountForTests;
172
+ //# sourceMappingURL=dbSessionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dbSessionManager.js","sourceRoot":"","sources":["../../../src/host/db/dbSessionManager.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.introspectSchema = exports.formatOracleType = exports.formatMssqlType = exports.bsonTypeOf = exports.fieldsFromSample = exports.tableKey = exports.tableMatchesFilter = void 0;
21
+ exports.introspectSchema = exports.formatOracleType = exports.formatMssqlType = exports.bsonTypeOf = exports.fieldsFromSample = exports.tableKey = exports.tableMatchesFilter = exports.introspectRoutines = exports.buildRoutineCall = void 0;
22
22
  const queryExecutor_js_1 = require("../queryExecutor.js");
23
23
  const mongo_js_1 = require("./mongo.js");
24
24
  const mssql_js_1 = require("./mssql.js");
@@ -27,6 +27,9 @@ const oracle_js_1 = require("./oracle.js");
27
27
  const postgres_js_1 = require("./postgres.js");
28
28
  const shared_js_1 = require("./shared.js");
29
29
  __exportStar(require("./types.js"), exports);
30
+ var routines_js_1 = require("./routines.js");
31
+ Object.defineProperty(exports, "buildRoutineCall", { enumerable: true, get: function () { return routines_js_1.buildRoutineCall; } });
32
+ Object.defineProperty(exports, "introspectRoutines", { enumerable: true, get: function () { return routines_js_1.introspectRoutines; } });
30
33
  var shared_js_2 = require("./shared.js");
31
34
  Object.defineProperty(exports, "tableMatchesFilter", { enumerable: true, get: function () { return shared_js_2.tableMatchesFilter; } });
32
35
  Object.defineProperty(exports, "tableKey", { enumerable: true, get: function () { return shared_js_2.tableKey; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/host/db/introspection/index.ts"],"names":[],"mappings":";AAAA,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,uDAAuD;;;;;;;;;;;;;;;;;AAEvD,0DAA+D;AAE/D,yCAA6C;AAC7C,yCAA6C;AAC7C,yCAA6C;AAC7C,2CAA+C;AAC/C,+CAAmD;AACnD,2CAAuD;AAGvD,6CAA2B;AAC3B,yCAA2D;AAAlD,+GAAA,kBAAkB,OAAA;AAAE,qGAAA,QAAQ,OAAA;AACrC,uCAA0D;AAAjD,4GAAA,gBAAgB,OAAA;AAAE,sGAAA,UAAU,OAAA;AACrC,uCAA6C;AAApC,2GAAA,eAAe,OAAA;AACxB,yCAA+C;AAAtC,6GAAA,gBAAgB,OAAA;AAElB,MAAM,gBAAgB,GAAG,KAAK,EAAE,eAAkC,EAAE,UAA+B,EAAE,EAAqB,EAAE;IACjI,MAAM,UAAU,GAAG,IAAA,2CAAwB,EAAC,EAAE,GAAG,eAAe,EAAE,EAAE,EAAE,eAAe,CAAC,EAAE,IAAI,YAAY,EAAE,CAAC,CAAC;IAC5G,MAAM,QAAQ,GAAG,IAAA,oCAAwB,EAAC,OAAO,CAAC,CAAC;IACnD,QAAQ,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,UAAU;YACb,OAAO,IAAA,gCAAkB,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClD,KAAK,OAAO;YACV,OAAO,IAAA,0BAAe,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,KAAK,OAAO;YACV,OAAO,IAAA,0BAAe,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,KAAK,OAAO;YACV,OAAO,IAAA,0BAAe,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,KAAK,QAAQ;YACX,OAAO,IAAA,4BAAgB,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAChD;YACE,MAAM,IAAI,KAAK,CAAC,8CAA8C,MAAM,CAAE,UAA+B,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACjI,CAAC;AACH,CAAC,CAAC;AAjBW,QAAA,gBAAgB,oBAiB3B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/host/db/introspection/index.ts"],"names":[],"mappings":";AAAA,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,uDAAuD;;;;;;;;;;;;;;;;;AAEvD,0DAA+D;AAE/D,yCAA6C;AAC7C,yCAA6C;AAC7C,yCAA6C;AAC7C,2CAA+C;AAC/C,+CAAmD;AACnD,2CAAuD;AAGvD,6CAA2B;AAC3B,6CAOuB;AANrB,+GAAA,gBAAgB,OAAA;AAChB,iHAAA,kBAAkB,OAAA;AAMpB,yCAA2D;AAAlD,+GAAA,kBAAkB,OAAA;AAAE,qGAAA,QAAQ,OAAA;AACrC,uCAA0D;AAAjD,4GAAA,gBAAgB,OAAA;AAAE,sGAAA,UAAU,OAAA;AACrC,uCAA6C;AAApC,2GAAA,eAAe,OAAA;AACxB,yCAA+C;AAAtC,6GAAA,gBAAgB,OAAA;AAElB,MAAM,gBAAgB,GAAG,KAAK,EAAE,eAAkC,EAAE,UAA+B,EAAE,EAAqB,EAAE;IACjI,MAAM,UAAU,GAAG,IAAA,2CAAwB,EAAC,EAAE,GAAG,eAAe,EAAE,EAAE,EAAE,eAAe,CAAC,EAAE,IAAI,YAAY,EAAE,CAAC,CAAC;IAC5G,MAAM,QAAQ,GAAG,IAAA,oCAAwB,EAAC,OAAO,CAAC,CAAC;IACnD,QAAQ,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,UAAU;YACb,OAAO,IAAA,gCAAkB,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClD,KAAK,OAAO;YACV,OAAO,IAAA,0BAAe,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,KAAK,OAAO;YACV,OAAO,IAAA,0BAAe,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,KAAK,OAAO;YACV,OAAO,IAAA,0BAAe,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,KAAK,QAAQ;YACX,OAAO,IAAA,4BAAgB,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAChD;YACE,MAAM,IAAI,KAAK,CAAC,8CAA8C,MAAM,CAAE,UAA+B,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACjI,CAAC;AACH,CAAC,CAAC;AAjBW,QAAA,gBAAgB,oBAiB3B"}
@@ -0,0 +1,444 @@
1
+ "use strict";
2
+ // Stored routine catalog: the procedures and functions a connection can call,
3
+ // with their arguments, read from the database's own dictionary. What a
4
+ // routine does is the database's business; this only says how to call it.
5
+ //
6
+ // `buildRoutineCall` turns a routine into a ready statement plus its binds, so
7
+ // a test can be started from the catalog on any engine.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.buildRoutineCall = exports.introspectRoutines = void 0;
10
+ const mssqlAdapter_js_1 = require("../mssqlAdapter.js");
11
+ const mysqlAdapter_js_1 = require("../mysqlAdapter.js");
12
+ const oracleAdapter_js_1 = require("../oracleAdapter.js");
13
+ const postgresAdapter_js_1 = require("../postgresAdapter.js");
14
+ const queryExecutor_js_1 = require("../queryExecutor.js");
15
+ const DEFAULT_ROUTINE_LIMIT = 500;
16
+ const text = (value) => (value === null || value === undefined ? "" : String(value));
17
+ // ── Oracle ──────────────────────────────────────────────────────────────────
18
+ const ORACLE_ROUTINES_SQL = `
19
+ SELECT OWNER, OBJECT_NAME, PROCEDURE_NAME, OBJECT_TYPE, OVERLOAD
20
+ FROM ALL_PROCEDURES
21
+ WHERE OWNER = :owner
22
+ AND ((OBJECT_TYPE = 'PACKAGE' AND PROCEDURE_NAME IS NOT NULL)
23
+ OR OBJECT_TYPE IN ('PROCEDURE', 'FUNCTION'))
24
+ ORDER BY OBJECT_NAME, PROCEDURE_NAME, OVERLOAD`;
25
+ // DATA_LEVEL 0 = the arguments themselves, not the fields of a record type.
26
+ const ORACLE_ARGUMENTS_SQL = `
27
+ SELECT PACKAGE_NAME, OBJECT_NAME, OVERLOAD, ARGUMENT_NAME, POSITION, DATA_TYPE, IN_OUT, DEFAULTED,
28
+ TYPE_OWNER, TYPE_NAME, TYPE_SUBNAME
29
+ FROM ALL_ARGUMENTS
30
+ WHERE OWNER = :owner AND DATA_LEVEL = 0 AND DATA_TYPE IS NOT NULL
31
+ ORDER BY PACKAGE_NAME, OBJECT_NAME, OVERLOAD, POSITION`;
32
+ const oracleBindType = (dataType) => {
33
+ const type = dataType.toUpperCase();
34
+ if (type === "REF CURSOR")
35
+ return "cursor";
36
+ if (["NUMBER", "FLOAT", "BINARY_INTEGER", "PLS_INTEGER", "BINARY_FLOAT", "BINARY_DOUBLE"].includes(type))
37
+ return "numeric";
38
+ if (type === "DATE")
39
+ return "date";
40
+ if (type.startsWith("TIMESTAMP"))
41
+ return "timestamp";
42
+ if (type === "CLOB" || type === "NCLOB")
43
+ return "clob";
44
+ if (type === "BLOB")
45
+ return "blob";
46
+ if (type === "PL/SQL BOOLEAN" || type === "BOOLEAN")
47
+ return "boolean";
48
+ if (["PL/SQL TABLE", "PL/SQL RECORD", "OBJECT", "TABLE", "VARRAY"].includes(type))
49
+ return "object";
50
+ return "text";
51
+ };
52
+ const oracleDirection = (inOut) => inOut === "OUT" ? "out" : inOut === "IN/OUT" ? "inout" : "in";
53
+ const oracleTypeName = (row) => {
54
+ const parts = [row.TYPE_OWNER, row.TYPE_NAME, row.TYPE_SUBNAME].map(text).filter(Boolean);
55
+ return parts.length > 0 ? parts.join(".") : undefined;
56
+ };
57
+ const listOracleRoutines = (connection, options) => (0, oracleAdapter_js_1.withOracleConnection)(connection, async (client, oracledb) => {
58
+ const outFormat = { outFormat: oracledb.OUT_FORMAT_OBJECT };
59
+ let owners = options.schemas?.map((owner) => owner.toUpperCase());
60
+ if (!owners || owners.length === 0) {
61
+ const current = await client.execute("SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS SCH FROM DUAL", [], outFormat);
62
+ owners = [text(current.rows[0]?.SCH)];
63
+ }
64
+ const routines = [];
65
+ for (const owner of owners) {
66
+ const objects = ((await client.execute(ORACLE_ROUTINES_SQL, { owner }, outFormat)).rows ?? []);
67
+ const argumentRows = ((await client.execute(ORACLE_ARGUMENTS_SQL, { owner }, outFormat)).rows ?? []);
68
+ const argsByRoutine = new Map();
69
+ for (const row of argumentRows) {
70
+ const key = [text(row.PACKAGE_NAME), text(row.OBJECT_NAME), text(row.OVERLOAD)].join("|");
71
+ argsByRoutine.set(key, [...(argsByRoutine.get(key) ?? []), row]);
72
+ }
73
+ for (const object of objects) {
74
+ const isPackage = text(object.OBJECT_TYPE) === "PACKAGE";
75
+ const name = text(isPackage ? object.PROCEDURE_NAME : object.OBJECT_NAME);
76
+ const pkg = isPackage ? text(object.OBJECT_NAME) : "";
77
+ const overload = text(object.OVERLOAD);
78
+ const rows = argsByRoutine.get([pkg, name, overload].join("|")) ?? [];
79
+ // Position 0 is a function's return value.
80
+ const returned = rows.find((row) => Number(row.POSITION) === 0 && !row.ARGUMENT_NAME);
81
+ routines.push({
82
+ schema: owner,
83
+ ...(pkg ? { package: pkg } : {}),
84
+ name,
85
+ kind: returned || text(object.OBJECT_TYPE) === "FUNCTION" ? "function" : "procedure",
86
+ ...(overload ? { overload } : {}),
87
+ arguments: rows
88
+ .filter((row) => row !== returned && row.ARGUMENT_NAME)
89
+ .map((row) => {
90
+ const dataType = text(row.DATA_TYPE);
91
+ const typeName = oracleBindType(dataType) === "object" ? oracleTypeName(row) : undefined;
92
+ return {
93
+ name: text(row.ARGUMENT_NAME),
94
+ position: Number(row.POSITION),
95
+ direction: oracleDirection(text(row.IN_OUT)),
96
+ dataType,
97
+ bindType: oracleBindType(dataType),
98
+ ...(typeName ? { typeName } : {}),
99
+ hasDefault: text(row.DEFAULTED) === "Y",
100
+ };
101
+ }),
102
+ ...(returned
103
+ ? {
104
+ returnType: text(returned.DATA_TYPE),
105
+ returnBindType: oracleBindType(text(returned.DATA_TYPE)),
106
+ ...(oracleBindType(text(returned.DATA_TYPE)) === "object" && oracleTypeName(returned)
107
+ ? { returnTypeName: oracleTypeName(returned) }
108
+ : {}),
109
+ }
110
+ : {}),
111
+ });
112
+ }
113
+ }
114
+ return routines;
115
+ });
116
+ // ── PostgreSQL ──────────────────────────────────────────────────────────────
117
+ const POSTGRES_ROUTINES_SQL = `
118
+ SELECT n.nspname AS schema, p.proname AS name, p.prokind AS kind, p.oid::text AS overload,
119
+ p.proretset AS returns_set, format_type(p.prorettype, NULL) AS return_type,
120
+ p.pronargdefaults AS defaults, p.pronargs AS in_args,
121
+ p.proargnames AS names, p.proargmodes::text[] AS modes,
122
+ ARRAY(SELECT format_type(t, NULL)
123
+ FROM unnest(COALESCE(p.proallargtypes, p.proargtypes::oid[])) WITH ORDINALITY AS a(t, i)
124
+ ORDER BY a.i) AS types
125
+ FROM pg_proc p
126
+ JOIN pg_namespace n ON n.oid = p.pronamespace
127
+ WHERE p.prokind IN ('f', 'p')
128
+ AND n.nspname NOT IN ('pg_catalog', 'information_schema')
129
+ AND n.nspname NOT LIKE 'pg\\_%'
130
+ AND p.prorettype <> 'trigger'::regtype
131
+ ORDER BY 1, 2`;
132
+ const postgresBindType = (dataType) => {
133
+ const type = dataType.toLowerCase();
134
+ if (type === "refcursor")
135
+ return "cursor";
136
+ if (/^(smallint|integer|bigint)$/.test(type))
137
+ return "int";
138
+ if (/^(numeric|real|double precision|money)/.test(type))
139
+ return "numeric";
140
+ if (type === "boolean")
141
+ return "boolean";
142
+ if (type === "date")
143
+ return "date";
144
+ if (type.startsWith("timestamp"))
145
+ return "timestamp";
146
+ if (type === "json" || type === "jsonb")
147
+ return "json";
148
+ return "text";
149
+ };
150
+ const listPostgresRoutines = (connection, options) => (0, postgresAdapter_js_1.withPostgresClient)(connection, async (client) => {
151
+ const { rows } = (await client.query(POSTGRES_ROUTINES_SQL));
152
+ return rows
153
+ .filter((row) => !options.schemas?.length || options.schemas.includes(text(row.schema)))
154
+ .map((row) => {
155
+ const types = (row.types ?? []);
156
+ const names = (row.names ?? []);
157
+ const modes = (row.modes ?? null);
158
+ const defaults = Number(row.defaults ?? 0);
159
+ const inTotal = types.filter((_, index) => {
160
+ const mode = modes?.[index] ?? "i";
161
+ return mode === "i" || mode === "b" || mode === "v";
162
+ }).length;
163
+ let inSeen = 0;
164
+ const args = types.flatMap((dataType, index) => {
165
+ const mode = modes?.[index] ?? "i";
166
+ // "t" columns of RETURNS TABLE are not arguments.
167
+ if (mode === "t")
168
+ return [];
169
+ const isIn = mode === "i" || mode === "b" || mode === "v";
170
+ if (isIn)
171
+ inSeen += 1;
172
+ return [
173
+ {
174
+ name: names[index] || `arg${index + 1}`,
175
+ position: index + 1,
176
+ direction: mode === "o" ? "out" : mode === "b" ? "inout" : "in",
177
+ dataType,
178
+ bindType: postgresBindType(dataType),
179
+ hasDefault: isIn && inSeen > inTotal - defaults,
180
+ },
181
+ ];
182
+ });
183
+ const isFunction = text(row.kind) === "f";
184
+ const returnType = text(row.return_type);
185
+ return {
186
+ schema: text(row.schema),
187
+ name: text(row.name),
188
+ kind: isFunction ? "function" : "procedure",
189
+ overload: text(row.overload),
190
+ arguments: args,
191
+ ...(isFunction && returnType !== "void"
192
+ ? { returnType, returnBindType: postgresBindType(returnType) }
193
+ : {}),
194
+ ...(row.returns_set === true || returnType === "record" ? { returnsTable: true } : {}),
195
+ };
196
+ });
197
+ });
198
+ // ── MySQL ───────────────────────────────────────────────────────────────────
199
+ const MYSQL_ROUTINES_SQL = `
200
+ SELECT r.ROUTINE_SCHEMA AS sch, r.ROUTINE_NAME AS name, r.ROUTINE_TYPE AS kind,
201
+ r.DTD_IDENTIFIER AS return_type,
202
+ p.PARAMETER_NAME AS arg_name, p.ORDINAL_POSITION AS pos, p.PARAMETER_MODE AS mode, p.DTD_IDENTIFIER AS arg_type
203
+ FROM information_schema.ROUTINES r
204
+ LEFT JOIN information_schema.PARAMETERS p
205
+ ON p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA AND p.SPECIFIC_NAME = r.ROUTINE_NAME AND p.ROUTINE_TYPE = r.ROUTINE_TYPE AND p.ORDINAL_POSITION > 0
206
+ WHERE r.ROUTINE_SCHEMA = COALESCE(?, DATABASE())
207
+ ORDER BY r.ROUTINE_NAME, p.ORDINAL_POSITION`;
208
+ const mysqlBindType = (dataType) => {
209
+ const type = dataType.toLowerCase();
210
+ if (/^(tinyint\(1\)|bool)/.test(type))
211
+ return "boolean";
212
+ if (/^(tinyint|smallint|mediumint|int|bigint)/.test(type))
213
+ return "int";
214
+ if (/^(decimal|numeric|float|double)/.test(type))
215
+ return "numeric";
216
+ if (type.startsWith("date") && !type.startsWith("datetime"))
217
+ return "date";
218
+ if (/^(datetime|timestamp)/.test(type))
219
+ return "timestamp";
220
+ if (type.startsWith("json"))
221
+ return "json";
222
+ return "text";
223
+ };
224
+ const listMysqlRoutines = (connection, options) => (0, mysqlAdapter_js_1.withMysqlConnection)(connection, async (conn) => {
225
+ const schemas = options.schemas?.length ? options.schemas : [null];
226
+ const routines = new Map();
227
+ for (const schema of schemas) {
228
+ const [rows] = (await conn.query(MYSQL_ROUTINES_SQL, [schema]));
229
+ for (const row of rows) {
230
+ const key = `${text(row.sch)}.${text(row.name)}.${text(row.kind)}`;
231
+ const isFunction = text(row.kind) === "FUNCTION";
232
+ let routine = routines.get(key);
233
+ if (!routine) {
234
+ routine = {
235
+ schema: text(row.sch),
236
+ name: text(row.name),
237
+ kind: isFunction ? "function" : "procedure",
238
+ arguments: [],
239
+ ...(isFunction
240
+ ? { returnType: text(row.return_type), returnBindType: mysqlBindType(text(row.return_type)) }
241
+ : {}),
242
+ };
243
+ routines.set(key, routine);
244
+ }
245
+ if (row.arg_name) {
246
+ const dataType = text(row.arg_type);
247
+ routine.arguments.push({
248
+ name: text(row.arg_name),
249
+ position: Number(row.pos),
250
+ direction: row.mode === "OUT" ? "out" : row.mode === "INOUT" ? "inout" : "in",
251
+ dataType,
252
+ bindType: mysqlBindType(dataType),
253
+ hasDefault: false,
254
+ });
255
+ }
256
+ }
257
+ }
258
+ return [...routines.values()];
259
+ });
260
+ // ── SQL Server ──────────────────────────────────────────────────────────────
261
+ const MSSQL_ROUTINES_SQL = `
262
+ SELECT s.name AS sch, o.name AS name, RTRIM(o.type) AS kind, o.object_id AS id,
263
+ p.name AS arg_name, p.parameter_id AS pos, p.is_output, p.has_default_value,
264
+ TYPE_NAME(p.user_type_id) AS arg_type
265
+ FROM sys.objects o
266
+ JOIN sys.schemas s ON s.schema_id = o.schema_id
267
+ LEFT JOIN sys.parameters p ON p.object_id = o.object_id
268
+ WHERE o.type IN ('P', 'FN', 'IF', 'TF') AND o.is_ms_shipped = 0
269
+ ORDER BY s.name, o.name, p.parameter_id`;
270
+ const mssqlBindType = (dataType) => {
271
+ const type = dataType.toLowerCase();
272
+ if (/^(tinyint|smallint|int|bigint)$/.test(type))
273
+ return "int";
274
+ if (/^(decimal|numeric|money|smallmoney|float|real)$/.test(type))
275
+ return "numeric";
276
+ if (type === "bit")
277
+ return "boolean";
278
+ if (type === "date")
279
+ return "date";
280
+ if (/^(datetime|datetime2|smalldatetime|datetimeoffset)$/.test(type))
281
+ return "timestamp";
282
+ if (/^(varbinary|binary|image)$/.test(type))
283
+ return "blob";
284
+ return "text";
285
+ };
286
+ const listMssqlRoutines = (connection, options) => (0, mssqlAdapter_js_1.withMssqlPool)(connection, async (pool) => {
287
+ const { recordset } = (await pool.request().query(MSSQL_ROUTINES_SQL));
288
+ const routines = new Map();
289
+ for (const row of recordset) {
290
+ if (options.schemas?.length && !options.schemas.includes(text(row.sch)))
291
+ continue;
292
+ const key = text(row.id);
293
+ const kind = text(row.kind);
294
+ let routine = routines.get(key);
295
+ if (!routine) {
296
+ routine = {
297
+ schema: text(row.sch),
298
+ name: text(row.name),
299
+ kind: kind === "P" ? "procedure" : "function",
300
+ arguments: [],
301
+ ...(kind === "IF" || kind === "TF" ? { returnsTable: true } : {}),
302
+ };
303
+ routines.set(key, routine);
304
+ }
305
+ const argName = text(row.arg_name).replace(/^@/, "");
306
+ const position = Number(row.pos ?? 0);
307
+ const dataType = text(row.arg_type);
308
+ if (position === 0 && !argName) {
309
+ // Parameter 0 of a scalar function is its return value.
310
+ if (dataType) {
311
+ routine.returnType = dataType;
312
+ routine.returnBindType = mssqlBindType(dataType);
313
+ }
314
+ }
315
+ else if (argName) {
316
+ routine.arguments.push({
317
+ name: argName,
318
+ position,
319
+ // T-SQL has no pure OUT: every OUTPUT parameter can also take a value.
320
+ direction: row.is_output === true ? "inout" : "in",
321
+ dataType,
322
+ bindType: mssqlBindType(dataType),
323
+ // Set only for CLR routines: SQL Server does not report whether a
324
+ // T-SQL parameter has a default, so such a call lists it too.
325
+ hasDefault: row.has_default_value === true,
326
+ });
327
+ }
328
+ }
329
+ return [...routines.values()];
330
+ });
331
+ // ── Entry points ────────────────────────────────────────────────────────────
332
+ const qualifiedName = (routine) => [routine.schema, routine.package, routine.name].filter(Boolean).join(".");
333
+ const introspectRoutines = async (connectionInput, options = {}) => {
334
+ const connection = (0, queryExecutor_js_1.normalizeConnectionInput)({ ...connectionInput, id: connectionInput.id ?? "introspect" });
335
+ const load = () => {
336
+ switch (connection.type) {
337
+ case "oracle":
338
+ return listOracleRoutines(connection, options);
339
+ case "postgres":
340
+ return listPostgresRoutines(connection, options);
341
+ case "mysql":
342
+ return listMysqlRoutines(connection, options);
343
+ case "mssql":
344
+ return listMssqlRoutines(connection, options);
345
+ default:
346
+ throw new Error(`"${connection.type}" connections have no stored procedures to list.`);
347
+ }
348
+ };
349
+ const search = options.search?.trim().toLowerCase();
350
+ const matching = (await load()).filter((routine) => !search || qualifiedName(routine).toLowerCase().includes(search));
351
+ const limit = options.limit && options.limit > 0 ? options.limit : DEFAULT_ROUTINE_LIMIT;
352
+ return {
353
+ dialect: connection.type,
354
+ routines: matching.slice(0, limit),
355
+ truncated: matching.length > limit,
356
+ };
357
+ };
358
+ exports.introspectRoutines = introspectRoutines;
359
+ // The statement and binds that call a routine. Arguments with a default are
360
+ // left out, so the call starts from what the routine requires; a tester adds
361
+ // any of them back by editing the statement.
362
+ const buildRoutineCall = (dialect, routine) => {
363
+ const args = routine.arguments.filter((arg) => !arg.hasDefault || arg.direction !== "in");
364
+ const name = qualifiedName(routine);
365
+ const toBind = (arg) => ({
366
+ name: dialect === "mysql" || dialect === "postgres" ? arg.name : arg.name.toLowerCase(),
367
+ value: "",
368
+ type: arg.bindType,
369
+ direction: arg.bindType === "cursor" ? "out" : arg.direction,
370
+ ...(arg.typeName ? { typeName: arg.typeName } : {}),
371
+ });
372
+ const binds = args.map(toBind);
373
+ const returnBind = routine.returnBindType
374
+ ? {
375
+ name: "return_value",
376
+ value: "",
377
+ type: routine.returnBindType,
378
+ direction: "out",
379
+ ...(routine.returnTypeName ? { typeName: routine.returnTypeName } : {}),
380
+ }
381
+ : null;
382
+ switch (dialect) {
383
+ case "oracle": {
384
+ const list = args.map((arg) => `${arg.name} => :${arg.name.toLowerCase()}`).join(", ");
385
+ const call = `${name}${list ? `(${list})` : ""}`;
386
+ return routine.kind === "function" && returnBind
387
+ ? { query: `BEGIN\n :return_value := ${call};\nEND;`, binds: [returnBind, ...binds] }
388
+ : { query: `BEGIN\n ${call};\nEND;`, binds };
389
+ }
390
+ case "postgres": {
391
+ // OUT arguments take NULL in CALL and nothing in a function call; either
392
+ // way they come back as columns of the returned row.
393
+ let slot = 0;
394
+ if (routine.kind === "procedure") {
395
+ const list = args
396
+ .map((arg) => (arg.direction === "out" ? "NULL" : `$${(slot += 1)}`))
397
+ .join(", ");
398
+ return { query: `CALL ${name}(${list})`, binds };
399
+ }
400
+ const list = args
401
+ .filter((arg) => arg.direction !== "out")
402
+ .map(() => `$${(slot += 1)}`)
403
+ .join(", ");
404
+ return {
405
+ query: `SELECT * FROM ${name}(${list})`,
406
+ binds: returnBind && routine.returnBindType === "cursor"
407
+ ? [...binds, { ...returnBind, name: routine.name.toLowerCase() }]
408
+ : binds,
409
+ };
410
+ }
411
+ case "mysql": {
412
+ if (routine.kind === "function") {
413
+ return {
414
+ query: `SELECT ${name}(${args.map(() => "?").join(", ")}) AS return_value`,
415
+ binds,
416
+ };
417
+ }
418
+ return {
419
+ query: `CALL ${name}(${args
420
+ .map((arg) => (arg.direction === "in" ? "?" : `@${arg.name}`))
421
+ .join(", ")})`,
422
+ binds,
423
+ };
424
+ }
425
+ default: {
426
+ const quoted = [routine.schema, routine.name].filter(Boolean).map((part) => `[${part}]`).join(".");
427
+ if (routine.kind === "function") {
428
+ const list = args.map((arg) => `@${arg.name.toLowerCase()}`).join(", ");
429
+ return {
430
+ query: routine.returnsTable
431
+ ? `SELECT * FROM ${quoted}(${list})`
432
+ : `SELECT ${quoted}(${list}) AS return_value`,
433
+ binds,
434
+ };
435
+ }
436
+ const list = args
437
+ .map((arg) => `@${arg.name} = @${arg.name.toLowerCase()}${arg.direction === "in" ? "" : " OUTPUT"}`)
438
+ .join(", ");
439
+ return { query: `EXEC ${quoted}${list ? ` ${list}` : ""}`, binds };
440
+ }
441
+ }
442
+ };
443
+ exports.buildRoutineCall = buildRoutineCall;
444
+ //# sourceMappingURL=routines.js.map