@lumifai/harness-tool-pack-postgres 0.0.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/README.md +84 -0
- package/dist/cast-types.d.ts +1 -0
- package/dist/cast-types.js +24 -0
- package/dist/cast-types.js.map +1 -0
- package/dist/catalog-cache.d.ts +9 -0
- package/dist/catalog-cache.js +49 -0
- package/dist/catalog-cache.js.map +1 -0
- package/dist/catalog.d.ts +45 -0
- package/dist/catalog.js +221 -0
- package/dist/catalog.js.map +1 -0
- package/dist/constants.d.ts +45 -0
- package/dist/constants.js +94 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/identifiers.d.ts +1 -0
- package/dist/identifiers.js +21 -0
- package/dist/identifiers.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/insert-compiler.d.ts +21 -0
- package/dist/insert-compiler.js +126 -0
- package/dist/insert-compiler.js.map +1 -0
- package/dist/insert-executor.d.ts +25 -0
- package/dist/insert-executor.js +101 -0
- package/dist/insert-executor.js.map +1 -0
- package/dist/insert-language.d.ts +56 -0
- package/dist/insert-language.js +218 -0
- package/dist/insert-language.js.map +1 -0
- package/dist/postgres.d.ts +39 -0
- package/dist/postgres.js +503 -0
- package/dist/postgres.js.map +1 -0
- package/dist/query-compiler.d.ts +16 -0
- package/dist/query-compiler.js +504 -0
- package/dist/query-compiler.js.map +1 -0
- package/dist/query-executor.d.ts +27 -0
- package/dist/query-executor.js +223 -0
- package/dist/query-executor.js.map +1 -0
- package/dist/query-language.d.ts +208 -0
- package/dist/query-language.js +500 -0
- package/dist/query-language.js.map +1 -0
- package/dist/workspace-file.d.ts +6 -0
- package/dist/workspace-file.js +47 -0
- package/dist/workspace-file.js.map +1 -0
- package/package.json +43 -0
- package/skills/postgres/SKILL.md +144 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AA4CA,kDAEC;AAlCD,MAAa,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAwB;IAC5B,IAAI,CAAU;IACd,IAAI,CAAU;IAEvB,YACE,IAA2B,EAC3B,OAAe,EACf,OAA2D;QAE3D,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM;QAMJ,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAC;IACJ,CAAC;CACF;AA9BD,8CA8BC;AAED,SAAgB,mBAAmB,CAAC,KAAc;IAChD,OAAO,KAAK,YAAY,iBAAiB,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assertIdentifier(value: string, path: string): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertIdentifier = assertIdentifier;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
6
|
+
function assertIdentifier(value, path) {
|
|
7
|
+
if (value.length === 0 || value.length > 63) {
|
|
8
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `Invalid identifier at ${path}.`, {
|
|
9
|
+
path,
|
|
10
|
+
hint: 'Identifiers must be 1-63 characters.',
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
if (!IDENTIFIER_PATTERN.test(value)) {
|
|
14
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `Invalid identifier "${value}" at ${path}.`, {
|
|
15
|
+
path,
|
|
16
|
+
hint: 'Use unquoted SQL identifiers matching /^[a-zA-Z_][a-zA-Z0-9_]*$/.',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=identifiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.js","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":";;AAIA,4CAgBC;AApBD,qCAA6C;AAE7C,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAEtD,SAAgB,gBAAgB,CAAC,KAAa,EAAE,IAAY;IAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,0BAAiB,CAAC,eAAe,EAAE,yBAAyB,IAAI,GAAG,EAAE;YAC7E,IAAI;YACJ,IAAI,EAAE,sCAAsC;SAC7C,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,0BAAiB,CAAC,eAAe,EAAE,uBAAuB,KAAK,QAAQ,IAAI,GAAG,EAAE;YACxF,IAAI;YACJ,IAAI,EAAE,mEAAmE;SAC1E,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { DEFAULT_POSTGRES_QUERY_LIMIT, MAX_POSTGRES_QUERY_LIMIT, DEFAULT_MAX_INSERT_ROWS, MAX_INSERT_ROWS, createPostgresTools, createPostgresToolPack, resolvePostgresSkillPath, queryPlanSchema, previewInputSchema, schemaDetailsInputSchema, insertInputSchema, insertResultSchema, } from './postgres';
|
|
2
|
+
export type { PostgresToolsOptions } from './postgres';
|
|
3
|
+
export type { QueryPlan, QueryResult } from './query-language';
|
|
4
|
+
export type { InsertInput, InsertResult } from './insert-language';
|
|
5
|
+
export { QUERY_PLAN_VERSION } from './constants';
|
|
6
|
+
export { PostgresToolError, isPostgresToolError } from './errors';
|
|
7
|
+
export type { PostgresToolErrorCode } from './errors';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPostgresToolError = exports.PostgresToolError = exports.QUERY_PLAN_VERSION = exports.insertResultSchema = exports.insertInputSchema = exports.schemaDetailsInputSchema = exports.previewInputSchema = exports.queryPlanSchema = exports.resolvePostgresSkillPath = exports.createPostgresToolPack = exports.createPostgresTools = exports.MAX_INSERT_ROWS = exports.DEFAULT_MAX_INSERT_ROWS = exports.MAX_POSTGRES_QUERY_LIMIT = exports.DEFAULT_POSTGRES_QUERY_LIMIT = void 0;
|
|
4
|
+
var postgres_1 = require("./postgres");
|
|
5
|
+
Object.defineProperty(exports, "DEFAULT_POSTGRES_QUERY_LIMIT", { enumerable: true, get: function () { return postgres_1.DEFAULT_POSTGRES_QUERY_LIMIT; } });
|
|
6
|
+
Object.defineProperty(exports, "MAX_POSTGRES_QUERY_LIMIT", { enumerable: true, get: function () { return postgres_1.MAX_POSTGRES_QUERY_LIMIT; } });
|
|
7
|
+
Object.defineProperty(exports, "DEFAULT_MAX_INSERT_ROWS", { enumerable: true, get: function () { return postgres_1.DEFAULT_MAX_INSERT_ROWS; } });
|
|
8
|
+
Object.defineProperty(exports, "MAX_INSERT_ROWS", { enumerable: true, get: function () { return postgres_1.MAX_INSERT_ROWS; } });
|
|
9
|
+
Object.defineProperty(exports, "createPostgresTools", { enumerable: true, get: function () { return postgres_1.createPostgresTools; } });
|
|
10
|
+
Object.defineProperty(exports, "createPostgresToolPack", { enumerable: true, get: function () { return postgres_1.createPostgresToolPack; } });
|
|
11
|
+
Object.defineProperty(exports, "resolvePostgresSkillPath", { enumerable: true, get: function () { return postgres_1.resolvePostgresSkillPath; } });
|
|
12
|
+
Object.defineProperty(exports, "queryPlanSchema", { enumerable: true, get: function () { return postgres_1.queryPlanSchema; } });
|
|
13
|
+
Object.defineProperty(exports, "previewInputSchema", { enumerable: true, get: function () { return postgres_1.previewInputSchema; } });
|
|
14
|
+
Object.defineProperty(exports, "schemaDetailsInputSchema", { enumerable: true, get: function () { return postgres_1.schemaDetailsInputSchema; } });
|
|
15
|
+
Object.defineProperty(exports, "insertInputSchema", { enumerable: true, get: function () { return postgres_1.insertInputSchema; } });
|
|
16
|
+
Object.defineProperty(exports, "insertResultSchema", { enumerable: true, get: function () { return postgres_1.insertResultSchema; } });
|
|
17
|
+
var constants_1 = require("./constants");
|
|
18
|
+
Object.defineProperty(exports, "QUERY_PLAN_VERSION", { enumerable: true, get: function () { return constants_1.QUERY_PLAN_VERSION; } });
|
|
19
|
+
var errors_1 = require("./errors");
|
|
20
|
+
Object.defineProperty(exports, "PostgresToolError", { enumerable: true, get: function () { return errors_1.PostgresToolError; } });
|
|
21
|
+
Object.defineProperty(exports, "isPostgresToolError", { enumerable: true, get: function () { return errors_1.isPostgresToolError; } });
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAaoB;AAZlB,wHAAA,4BAA4B,OAAA;AAC5B,oHAAA,wBAAwB,OAAA;AACxB,mHAAA,uBAAuB,OAAA;AACvB,2GAAA,eAAe,OAAA;AACf,+GAAA,mBAAmB,OAAA;AACnB,kHAAA,sBAAsB,OAAA;AACtB,oHAAA,wBAAwB,OAAA;AACxB,2GAAA,eAAe,OAAA;AACf,8GAAA,kBAAkB,OAAA;AAClB,oHAAA,wBAAwB,OAAA;AACxB,6GAAA,iBAAiB,OAAA;AACjB,8GAAA,kBAAkB,OAAA;AAKpB,yCAAiD;AAAxC,+GAAA,kBAAkB,OAAA;AAC3B,mCAAkE;AAAzD,2GAAA,iBAAiB,OAAA;AAAE,6GAAA,mBAAmB,OAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CatalogRelation, PostgresCatalog } from './catalog';
|
|
2
|
+
import type { InsertRow } from './insert-language';
|
|
3
|
+
import type { CompiledQuery } from './query-compiler';
|
|
4
|
+
export type PreparedInsertRow = {
|
|
5
|
+
index: number;
|
|
6
|
+
columns: string[];
|
|
7
|
+
values: Array<string | number | boolean | null>;
|
|
8
|
+
compiled: CompiledQuery;
|
|
9
|
+
};
|
|
10
|
+
export type PrepareInsertBatchResult = {
|
|
11
|
+
relation: CatalogRelation;
|
|
12
|
+
prepared: PreparedInsertRow[];
|
|
13
|
+
rejected: Array<{
|
|
14
|
+
index: number;
|
|
15
|
+
path?: string;
|
|
16
|
+
code: 'INVALID_INPUT' | 'UNKNOWN_COLUMN' | 'UNKNOWN_RELATION';
|
|
17
|
+
message: string;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export declare function prepareInsertBatch(catalog: PostgresCatalog, schema: string, relationName: string, rows: InsertRow[]): PrepareInsertBatchResult;
|
|
21
|
+
export declare function compileInsertRow(schema: string, relation: string, columns: string[], values: Array<string | number | boolean | null>): CompiledQuery;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.prepareInsertBatch = prepareInsertBatch;
|
|
7
|
+
exports.compileInsertRow = compileInsertRow;
|
|
8
|
+
const pg_sql2_1 = __importDefault(require("pg-sql2"));
|
|
9
|
+
const catalog_1 = require("./catalog");
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
const identifiers_1 = require("./identifiers");
|
|
12
|
+
function prepareInsertBatch(catalog, schema, relationName, rows) {
|
|
13
|
+
const relation = (0, catalog_1.getCatalogRelation)(catalog, schema, relationName);
|
|
14
|
+
if (!relation) {
|
|
15
|
+
throw new errors_1.PostgresToolError('UNKNOWN_RELATION', `Unknown relation ${schema}.${relationName}.`, {
|
|
16
|
+
path: 'relation',
|
|
17
|
+
hint: 'Use postgres_get_schema_details to inspect tables visible to the connection role.',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (relation.kind !== 'table') {
|
|
21
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `Cannot insert into ${schema}.${relationName} because it is a ${relation.kind}.`, {
|
|
22
|
+
path: 'relation',
|
|
23
|
+
hint: 'Inserts are only supported against physical tables.',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const catalogColumns = new Map(relation.columns.map((column) => [column.name, column]));
|
|
27
|
+
const prepared = [];
|
|
28
|
+
const rejected = [];
|
|
29
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
30
|
+
const row = rows[index];
|
|
31
|
+
try {
|
|
32
|
+
const preparedRow = prepareInsertRow(schema, relationName, catalogColumns, row, index);
|
|
33
|
+
prepared.push(preparedRow);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof errors_1.PostgresToolError) {
|
|
37
|
+
const code = error.code === 'UNKNOWN_COLUMN' || error.code === 'INVALID_INPUT'
|
|
38
|
+
? error.code
|
|
39
|
+
: 'INVALID_INPUT';
|
|
40
|
+
rejected.push({
|
|
41
|
+
index,
|
|
42
|
+
path: error.path,
|
|
43
|
+
code,
|
|
44
|
+
message: error.message,
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { relation, prepared, rejected };
|
|
52
|
+
}
|
|
53
|
+
function prepareInsertRow(schema, relationName, catalogColumns, row, index) {
|
|
54
|
+
const keys = Object.keys(row);
|
|
55
|
+
if (keys.length === 0) {
|
|
56
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', 'Insert row must include at least one column.', {
|
|
57
|
+
path: `rows[${index}]`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const columns = [];
|
|
61
|
+
const values = [];
|
|
62
|
+
// Stable catalog ordinal order keeps SQL fingerprints predictable across rows
|
|
63
|
+
// that share the same column set.
|
|
64
|
+
const orderedKeys = [...keys].sort((left, right) => {
|
|
65
|
+
const leftPos = catalogColumns.get(left)?.ordinalPosition ?? Number.MAX_SAFE_INTEGER;
|
|
66
|
+
const rightPos = catalogColumns.get(right)?.ordinalPosition ?? Number.MAX_SAFE_INTEGER;
|
|
67
|
+
if (leftPos !== rightPos) {
|
|
68
|
+
return leftPos - rightPos;
|
|
69
|
+
}
|
|
70
|
+
return left.localeCompare(right);
|
|
71
|
+
});
|
|
72
|
+
for (const key of orderedKeys) {
|
|
73
|
+
(0, identifiers_1.assertIdentifier)(key, `rows[${index}].${key}`);
|
|
74
|
+
if (!catalogColumns.has(key)) {
|
|
75
|
+
throw new errors_1.PostgresToolError('UNKNOWN_COLUMN', `Unknown column "${key}" for ${schema}.${relationName}.`, {
|
|
76
|
+
path: `rows[${index}].${key}`,
|
|
77
|
+
hint: 'Use postgres_get_schema_details to inspect available columns.',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
columns.push(key);
|
|
81
|
+
values.push(row[key] ?? null);
|
|
82
|
+
}
|
|
83
|
+
const compiled = compileInsertRow(schema, relationName, columns, values);
|
|
84
|
+
return { index, columns, values, compiled };
|
|
85
|
+
}
|
|
86
|
+
function compileInsertRow(schema, relation, columns, values) {
|
|
87
|
+
if (columns.length === 0) {
|
|
88
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', 'Insert requires at least one column.');
|
|
89
|
+
}
|
|
90
|
+
if (columns.length !== values.length) {
|
|
91
|
+
throw new errors_1.PostgresToolError('COMPILATION_ERROR', 'Insert column count does not match value count.');
|
|
92
|
+
}
|
|
93
|
+
(0, identifiers_1.assertIdentifier)(schema, 'schema');
|
|
94
|
+
(0, identifiers_1.assertIdentifier)(relation, 'relation');
|
|
95
|
+
for (const column of columns) {
|
|
96
|
+
(0, identifiers_1.assertIdentifier)(column, 'columns');
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const columnSql = pg_sql2_1.default.join(columns.map((column) => pg_sql2_1.default.identifier(column)), ', ');
|
|
100
|
+
const valueSql = pg_sql2_1.default.join(values.map((value) => pg_sql2_1.default.value(value)), ', ');
|
|
101
|
+
const fragment = pg_sql2_1.default.query `INSERT INTO ${pg_sql2_1.default.identifier(schema, relation)} (${columnSql}) VALUES (${valueSql})`;
|
|
102
|
+
const compiled = pg_sql2_1.default.compile(fragment);
|
|
103
|
+
const referencedRelations = [`${schema}.${relation}`];
|
|
104
|
+
return {
|
|
105
|
+
text: compiled.text,
|
|
106
|
+
values: compiled.values,
|
|
107
|
+
fingerprint: hashFingerprint(compiled.text, referencedRelations),
|
|
108
|
+
referencedRelations,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof errors_1.PostgresToolError) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
throw new errors_1.PostgresToolError('COMPILATION_ERROR', error instanceof Error ? error.message : String(error), { cause: error });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function hashFingerprint(text, relations) {
|
|
119
|
+
const input = `${text}::${[...relations].sort().join(',')}`;
|
|
120
|
+
let hash = 0;
|
|
121
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
122
|
+
hash = (hash * 31 + input.charCodeAt(index)) >>> 0;
|
|
123
|
+
}
|
|
124
|
+
return hash.toString(16);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=insert-compiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insert-compiler.js","sourceRoot":"","sources":["../src/insert-compiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Client } from 'pg';
|
|
2
|
+
import type { PreparedInsertRow } from './insert-compiler';
|
|
3
|
+
import type { InsertRejection, InsertSourceKind } from './insert-language';
|
|
4
|
+
import { type ExecutionOptions } from './query-executor';
|
|
5
|
+
export type InsertAuditRecord = {
|
|
6
|
+
relation: string;
|
|
7
|
+
sourceKind: InsertSourceKind;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
sourceCount: number;
|
|
10
|
+
insertedCount: number;
|
|
11
|
+
rejectedCount: number;
|
|
12
|
+
};
|
|
13
|
+
export type InsertAuditCallback = (record: InsertAuditRecord) => void;
|
|
14
|
+
export type ExecuteInsertBatchInput = {
|
|
15
|
+
schema: string;
|
|
16
|
+
relation: string;
|
|
17
|
+
sourceKind: InsertSourceKind;
|
|
18
|
+
sourceCount: number;
|
|
19
|
+
prepared: PreparedInsertRow[];
|
|
20
|
+
preflightRejected: InsertRejection[];
|
|
21
|
+
};
|
|
22
|
+
export declare function executeInsertBatch(client: Client, input: ExecuteInsertBatchInput, options?: ExecutionOptions, audit?: InsertAuditCallback): Promise<{
|
|
23
|
+
inserted_count: number;
|
|
24
|
+
rejected: InsertRejection[];
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeInsertBatch = executeInsertBatch;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const query_executor_1 = require("./query-executor");
|
|
6
|
+
async function executeInsertBatch(client, input, options = {}, audit) {
|
|
7
|
+
const started = Date.now();
|
|
8
|
+
const rejected = [...input.preflightRejected];
|
|
9
|
+
let insertedCount = 0;
|
|
10
|
+
await beginReadWriteTransaction(client, options);
|
|
11
|
+
try {
|
|
12
|
+
await client.query('SET CONSTRAINTS ALL IMMEDIATE');
|
|
13
|
+
for (const row of input.prepared) {
|
|
14
|
+
const savepoint = `row_${row.index}`;
|
|
15
|
+
await client.query(`SAVEPOINT ${savepoint}`);
|
|
16
|
+
try {
|
|
17
|
+
await client.query(row.compiled.text, row.compiled.values);
|
|
18
|
+
await client.query(`RELEASE SAVEPOINT ${savepoint}`);
|
|
19
|
+
insertedCount += 1;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
await client.query(`ROLLBACK TO SAVEPOINT ${savepoint}`).catch(() => undefined);
|
|
23
|
+
rejected.push(mapRowExecutionError(error, row.index));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
await client.query('COMMIT');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
await client.query('ROLLBACK').catch(() => undefined);
|
|
30
|
+
throw (0, query_executor_1.mapExecutionError)(error);
|
|
31
|
+
}
|
|
32
|
+
audit?.({
|
|
33
|
+
relation: `${input.schema}.${input.relation}`,
|
|
34
|
+
sourceKind: input.sourceKind,
|
|
35
|
+
durationMs: Date.now() - started,
|
|
36
|
+
sourceCount: input.sourceCount,
|
|
37
|
+
insertedCount,
|
|
38
|
+
rejectedCount: rejected.length,
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
inserted_count: insertedCount,
|
|
42
|
+
rejected: rejected.sort((left, right) => left.index - right.index),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function beginReadWriteTransaction(client, options) {
|
|
46
|
+
const statementTimeout = options.statementTimeoutMs ?? constants_1.DEFAULT_STATEMENT_TIMEOUT_MS;
|
|
47
|
+
const lockTimeout = options.lockTimeoutMs ?? constants_1.DEFAULT_LOCK_TIMEOUT_MS;
|
|
48
|
+
// Inserts with many savepoints can outlive the read-path idle default.
|
|
49
|
+
const idleTimeout = options.idleInTransactionTimeoutMs ?? Math.max(constants_1.DEFAULT_IDLE_IN_TRANSACTION_TIMEOUT_MS, 30_000);
|
|
50
|
+
await client.query('BEGIN');
|
|
51
|
+
await client.query(`SET LOCAL statement_timeout = ${statementTimeout}`);
|
|
52
|
+
await client.query(`SET LOCAL lock_timeout = ${lockTimeout}`);
|
|
53
|
+
await client.query(`SET LOCAL idle_in_transaction_session_timeout = ${idleTimeout}`);
|
|
54
|
+
const tempFileLimitKb = Math.min(Math.max(options.tempFileLimitKb ?? constants_1.DEFAULT_TEMP_FILE_LIMIT_KB, 1), constants_1.MAX_TEMP_FILE_LIMIT_KB);
|
|
55
|
+
await client.query(`SET LOCAL temp_file_limit = '${tempFileLimitKb}kB'`);
|
|
56
|
+
if (options.searchPath && options.searchPath.length > 0) {
|
|
57
|
+
const path = options.searchPath.map((entry) => `"${entry.replace(/"/g, '""')}"`).join(', ');
|
|
58
|
+
await client.query(`SET LOCAL search_path = ${path}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function mapRowExecutionError(error, index) {
|
|
62
|
+
const mapped = (0, query_executor_1.mapExecutionError)(error);
|
|
63
|
+
const pgCode = typeof error === 'object' && error !== null && 'code' in error
|
|
64
|
+
? String(error.code ?? '')
|
|
65
|
+
: '';
|
|
66
|
+
let code = mapped.code;
|
|
67
|
+
let message = mapped.message;
|
|
68
|
+
let path = `rows[${index}]`;
|
|
69
|
+
if (pgCode === '23505') {
|
|
70
|
+
code = 'EXECUTION_ERROR';
|
|
71
|
+
message = `Unique constraint violated for row ${index}.`;
|
|
72
|
+
}
|
|
73
|
+
else if (pgCode === '23503') {
|
|
74
|
+
code = 'EXECUTION_ERROR';
|
|
75
|
+
message = `Foreign key constraint violated for row ${index}.`;
|
|
76
|
+
}
|
|
77
|
+
else if (pgCode === '23502') {
|
|
78
|
+
code = 'EXECUTION_ERROR';
|
|
79
|
+
message = `NOT NULL constraint violated for row ${index}.`;
|
|
80
|
+
}
|
|
81
|
+
else if (pgCode === '42501') {
|
|
82
|
+
code = 'POLICY_DENIED';
|
|
83
|
+
message = `Permission denied inserting row ${index}.`;
|
|
84
|
+
}
|
|
85
|
+
else if (mapped.code === 'TIMEOUT') {
|
|
86
|
+
throw mapped;
|
|
87
|
+
}
|
|
88
|
+
const column = typeof error === 'object' && error !== null && 'column' in error
|
|
89
|
+
? error.column
|
|
90
|
+
: undefined;
|
|
91
|
+
if (typeof column === 'string' && column.length > 0) {
|
|
92
|
+
path = `rows[${index}].${column}`;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
index,
|
|
96
|
+
path,
|
|
97
|
+
code,
|
|
98
|
+
message,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=insert-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insert-executor.js","sourceRoot":"","sources":["../src/insert-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ToolExecutionContext } from '@mastra/core/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { type PostgresToolErrorCode } from './errors';
|
|
4
|
+
export declare const insertInputSchema: z.ZodObject<{
|
|
5
|
+
schema: z.ZodString;
|
|
6
|
+
relation: z.ZodString;
|
|
7
|
+
source: z.ZodObject<{
|
|
8
|
+
kind: z.ZodEnum<{
|
|
9
|
+
json: "json";
|
|
10
|
+
csv: "csv";
|
|
11
|
+
}>;
|
|
12
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
13
|
+
rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>>>;
|
|
14
|
+
path: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.core.$strict>;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
export type InsertInput = z.infer<typeof insertInputSchema>;
|
|
18
|
+
export type InsertRow = Record<string, string | number | boolean | null>;
|
|
19
|
+
export type InsertSourceKind = 'json' | 'csv';
|
|
20
|
+
export type InsertRejection = {
|
|
21
|
+
index: number;
|
|
22
|
+
path?: string;
|
|
23
|
+
code: PostgresToolErrorCode;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
export declare const insertResultSchema: z.ZodObject<{
|
|
27
|
+
schema: z.ZodString;
|
|
28
|
+
relation: z.ZodString;
|
|
29
|
+
source_kind: z.ZodEnum<{
|
|
30
|
+
json: "json";
|
|
31
|
+
csv: "csv";
|
|
32
|
+
}>;
|
|
33
|
+
source_count: z.ZodNumber;
|
|
34
|
+
inserted_count: z.ZodNumber;
|
|
35
|
+
rejected_count: z.ZodNumber;
|
|
36
|
+
rejected: z.ZodArray<z.ZodObject<{
|
|
37
|
+
index: z.ZodNumber;
|
|
38
|
+
path: z.ZodOptional<z.ZodString>;
|
|
39
|
+
code: z.ZodString;
|
|
40
|
+
message: z.ZodString;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
rejection_details_truncated: z.ZodBoolean;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type InsertResult = z.infer<typeof insertResultSchema>;
|
|
45
|
+
export type ResolveInsertRowsOptions = {
|
|
46
|
+
maxRows?: number;
|
|
47
|
+
maxCsvBytes?: number;
|
|
48
|
+
};
|
|
49
|
+
export declare function resolveInsertRows(input: InsertInput, context: ToolExecutionContext, options?: ResolveInsertRowsOptions): Promise<{
|
|
50
|
+
sourceKind: InsertSourceKind;
|
|
51
|
+
rows: InsertRow[];
|
|
52
|
+
}>;
|
|
53
|
+
export declare function boundRejectionDetails(rejected: InsertRejection[], maxDetails?: number): {
|
|
54
|
+
rejected: InsertRejection[];
|
|
55
|
+
rejection_details_truncated: boolean;
|
|
56
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.insertResultSchema = exports.insertInputSchema = void 0;
|
|
4
|
+
exports.resolveInsertRows = resolveInsertRows;
|
|
5
|
+
exports.boundRejectionDetails = boundRejectionDetails;
|
|
6
|
+
const sync_1 = require("csv-parse/sync");
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const constants_1 = require("./constants");
|
|
9
|
+
const errors_1 = require("./errors");
|
|
10
|
+
const workspace_file_1 = require("./workspace-file");
|
|
11
|
+
const identifierSchema = zod_1.z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(63)
|
|
15
|
+
.regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/);
|
|
16
|
+
const cellValueSchema = zod_1.z.union([
|
|
17
|
+
zod_1.z.string().max(10_000),
|
|
18
|
+
zod_1.z.number().finite(),
|
|
19
|
+
zod_1.z.boolean(),
|
|
20
|
+
zod_1.z.null(),
|
|
21
|
+
]);
|
|
22
|
+
// Tabular shape (columns + value arrays) instead of keyed row objects:
|
|
23
|
+
// freeform-key objects compile to additionalProperties with a value schema,
|
|
24
|
+
// which OpenAI's strict-mode constraint grammar cannot represent (the request
|
|
25
|
+
// crashes server-side while computing constraint tokens). Arrays of closed
|
|
26
|
+
// unions are fully grammar-representable. Note Mastra promotes any per-tool
|
|
27
|
+
// strict flag to a provider-wide strictJsonSchema, so this schema must stay
|
|
28
|
+
// strict-safe even if the insert tool itself is not marked strict.
|
|
29
|
+
//
|
|
30
|
+
// Flat object instead of a discriminated union: strict tool schemas also
|
|
31
|
+
// reject the oneOf construct unions compile to. Exclusivity between the json
|
|
32
|
+
// and csv fields is enforced by the refinement below (not emitted to JSON
|
|
33
|
+
// schema).
|
|
34
|
+
const insertSourceSchema = zod_1.z
|
|
35
|
+
.object({
|
|
36
|
+
kind: zod_1.z
|
|
37
|
+
.enum(['json', 'csv'])
|
|
38
|
+
.describe('Row source: inline "json" rows or a workspace "csv" file.'),
|
|
39
|
+
columns: zod_1.z
|
|
40
|
+
.array(identifierSchema)
|
|
41
|
+
.min(1)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Column names, in the same order as each row. Required when kind is "json"; omit when kind is "csv".'),
|
|
44
|
+
rows: zod_1.z
|
|
45
|
+
.array(zod_1.z.array(cellValueSchema).min(1))
|
|
46
|
+
.min(1)
|
|
47
|
+
.max(constants_1.MAX_INSERT_ROWS)
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Rows to insert; each row is an array of values aligned with columns. Required when kind is "json"; omit when kind is "csv".'),
|
|
50
|
+
path: zod_1.z
|
|
51
|
+
.string()
|
|
52
|
+
.min(1)
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Absolute workspace path to a CSV file, e.g. /data/rows.csv. Required when kind is "csv"; omit when kind is "json".'),
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
.superRefine((value, ctx) => {
|
|
58
|
+
if (value.kind === 'json') {
|
|
59
|
+
if (!value.columns) {
|
|
60
|
+
ctx.addIssue({
|
|
61
|
+
code: 'custom',
|
|
62
|
+
path: ['columns'],
|
|
63
|
+
message: 'source.columns is required when kind is "json".',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!value.rows) {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: 'custom',
|
|
69
|
+
path: ['rows'],
|
|
70
|
+
message: 'source.rows is required when kind is "json".',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (value.path !== undefined) {
|
|
74
|
+
ctx.addIssue({
|
|
75
|
+
code: 'custom',
|
|
76
|
+
path: ['path'],
|
|
77
|
+
message: 'source.path is not allowed when kind is "json".',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!value.path) {
|
|
83
|
+
ctx.addIssue({
|
|
84
|
+
code: 'custom',
|
|
85
|
+
path: ['path'],
|
|
86
|
+
message: 'source.path is required when kind is "csv".',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (value.columns !== undefined) {
|
|
90
|
+
ctx.addIssue({
|
|
91
|
+
code: 'custom',
|
|
92
|
+
path: ['columns'],
|
|
93
|
+
message: 'source.columns is not allowed when kind is "csv".',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (value.rows !== undefined) {
|
|
97
|
+
ctx.addIssue({
|
|
98
|
+
code: 'custom',
|
|
99
|
+
path: ['rows'],
|
|
100
|
+
message: 'source.rows is not allowed when kind is "csv".',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
exports.insertInputSchema = zod_1.z
|
|
105
|
+
.object({
|
|
106
|
+
schema: identifierSchema,
|
|
107
|
+
relation: identifierSchema,
|
|
108
|
+
source: insertSourceSchema,
|
|
109
|
+
})
|
|
110
|
+
.strict();
|
|
111
|
+
exports.insertResultSchema = zod_1.z.object({
|
|
112
|
+
schema: zod_1.z.string(),
|
|
113
|
+
relation: zod_1.z.string(),
|
|
114
|
+
source_kind: zod_1.z.enum(['json', 'csv']),
|
|
115
|
+
source_count: zod_1.z.number().int().nonnegative(),
|
|
116
|
+
inserted_count: zod_1.z.number().int().nonnegative(),
|
|
117
|
+
rejected_count: zod_1.z.number().int().nonnegative(),
|
|
118
|
+
rejected: zod_1.z.array(zod_1.z.object({
|
|
119
|
+
index: zod_1.z.number().int().nonnegative(),
|
|
120
|
+
path: zod_1.z.string().optional(),
|
|
121
|
+
code: zod_1.z.string(),
|
|
122
|
+
message: zod_1.z.string(),
|
|
123
|
+
})),
|
|
124
|
+
rejection_details_truncated: zod_1.z.boolean(),
|
|
125
|
+
});
|
|
126
|
+
async function resolveInsertRows(input, context, options = {}) {
|
|
127
|
+
const maxRows = clamp(options.maxRows ?? constants_1.DEFAULT_MAX_INSERT_ROWS, 1, constants_1.MAX_INSERT_ROWS);
|
|
128
|
+
const maxCsvBytes = clamp(options.maxCsvBytes ?? constants_1.DEFAULT_MAX_INSERT_CSV_BYTES, 1, constants_1.MAX_INSERT_CSV_BYTES);
|
|
129
|
+
if (input.source.kind === 'json') {
|
|
130
|
+
const columns = input.source.columns;
|
|
131
|
+
const jsonRows = input.source.rows;
|
|
132
|
+
if (!columns || columns.length === 0) {
|
|
133
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', 'source.columns is required when kind is "json".', { path: 'source.columns' });
|
|
134
|
+
}
|
|
135
|
+
if (!jsonRows || jsonRows.length === 0) {
|
|
136
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', 'source.rows is required when kind is "json".', {
|
|
137
|
+
path: 'source.rows',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (jsonRows.length > maxRows) {
|
|
141
|
+
throw new errors_1.PostgresToolError('COMPLEXITY_EXCEEDED', `JSON source has ${jsonRows.length} rows, exceeding the ${maxRows} row limit.`, { path: 'source.rows' });
|
|
142
|
+
}
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
for (const column of columns) {
|
|
145
|
+
if (seen.has(column)) {
|
|
146
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `Duplicate column "${column}" in source.columns.`, { path: 'source.columns' });
|
|
147
|
+
}
|
|
148
|
+
seen.add(column);
|
|
149
|
+
}
|
|
150
|
+
const rows = jsonRows.map((values, index) => {
|
|
151
|
+
if (values.length !== columns.length) {
|
|
152
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `Row ${index} has ${values.length} values but source.columns has ${columns.length}.`, { path: `source.rows[${index}]` });
|
|
153
|
+
}
|
|
154
|
+
const row = {};
|
|
155
|
+
for (let position = 0; position < columns.length; position += 1) {
|
|
156
|
+
row[columns[position]] = values[position] ?? null;
|
|
157
|
+
}
|
|
158
|
+
return row;
|
|
159
|
+
});
|
|
160
|
+
return { sourceKind: 'json', rows };
|
|
161
|
+
}
|
|
162
|
+
const csvPath = input.source.path;
|
|
163
|
+
if (!csvPath) {
|
|
164
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', 'source.path is required when kind is "csv".', {
|
|
165
|
+
path: 'source.path',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const { content } = await (0, workspace_file_1.readWorkspaceCsvText)(context, csvPath, maxCsvBytes);
|
|
169
|
+
const rows = parseCsvRows(content, csvPath);
|
|
170
|
+
if (rows.length === 0) {
|
|
171
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `CSV at ${csvPath} has a header but no data rows.`, { path: 'source.path' });
|
|
172
|
+
}
|
|
173
|
+
if (rows.length > maxRows) {
|
|
174
|
+
throw new errors_1.PostgresToolError('COMPLEXITY_EXCEEDED', `CSV at ${csvPath} has ${rows.length} rows, exceeding the ${maxRows} row limit.`, { path: 'source.path' });
|
|
175
|
+
}
|
|
176
|
+
return { sourceKind: 'csv', rows };
|
|
177
|
+
}
|
|
178
|
+
function boundRejectionDetails(rejected, maxDetails = constants_1.DEFAULT_MAX_INSERT_REJECTION_DETAILS) {
|
|
179
|
+
const limit = clamp(maxDetails, 1, constants_1.MAX_INSERT_REJECTION_DETAILS);
|
|
180
|
+
return {
|
|
181
|
+
rejected: rejected.slice(0, limit),
|
|
182
|
+
rejection_details_truncated: rejected.length > limit,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function parseCsvRows(content, path) {
|
|
186
|
+
let records;
|
|
187
|
+
try {
|
|
188
|
+
records = (0, sync_1.parse)(content, {
|
|
189
|
+
columns: true,
|
|
190
|
+
skip_empty_lines: true,
|
|
191
|
+
trim: true,
|
|
192
|
+
relax_column_count: true,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `File at ${path} is not valid CSV: ${error instanceof Error ? error.message : String(error)}`, { path: 'source.path', cause: error });
|
|
197
|
+
}
|
|
198
|
+
if (records.length === 0) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
const headers = Object.keys(records[0] ?? {});
|
|
202
|
+
if (headers.length === 0) {
|
|
203
|
+
throw new errors_1.PostgresToolError('INVALID_INPUT', `CSV at ${path} has no header columns.`, {
|
|
204
|
+
path: 'source.path',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return records.map((record) => {
|
|
208
|
+
const row = {};
|
|
209
|
+
for (const [key, value] of Object.entries(record)) {
|
|
210
|
+
row[key] = value === '' ? null : value;
|
|
211
|
+
}
|
|
212
|
+
return row;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function clamp(value, min, max) {
|
|
216
|
+
return Math.min(Math.max(value, min), max);
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=insert-language.js.map
|