@slates-integrations/postgresql 0.2.0-rc.6 → 0.2.0-rc.9
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/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/sourcemap-register.cjs +1 -0
- package/package.json +9 -3
- package/src/auth.ts +0 -134
- package/src/config.ts +0 -16
- package/src/index.ts +0 -36
- package/src/lib/client.ts +0 -799
- package/src/lib/errors.ts +0 -55
- package/src/lib/helpers.ts +0 -48
- package/src/lib/protocol.ts +0 -336
- package/src/spec.ts +0 -13
- package/src/tools/delete-rows.ts +0 -84
- package/src/tools/describe-table.ts +0 -231
- package/src/tools/execute-query.ts +0 -95
- package/src/tools/index.ts +0 -12
- package/src/tools/insert-rows.ts +0 -133
- package/src/tools/list-schemas.ts +0 -80
- package/src/tools/list-tables.ts +0 -119
- package/src/tools/manage-indexes.ts +0 -99
- package/src/tools/manage-roles.ts +0 -214
- package/src/tools/manage-schemas.ts +0 -121
- package/src/tools/manage-table.ts +0 -267
- package/src/tools/manage-views.ts +0 -193
- package/src/tools/update-rows.ts +0 -98
- package/src/triggers/inbound-webhook.ts +0 -67
- package/src/triggers/index.ts +0 -2
- package/src/triggers/table-changes.ts +0 -123
- package/tsconfig.json +0 -23
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient, escapeIdentifier } from '../lib/helpers';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
export let describeTable = SlateTool.create(spec, {
|
|
7
|
-
name: 'Describe Table',
|
|
8
|
-
key: 'describe_table',
|
|
9
|
-
description: `Get detailed schema information for a specific table, including columns, data types, constraints, indexes, and foreign keys.
|
|
10
|
-
Useful for understanding table structure before building queries or modifying schemas.`,
|
|
11
|
-
tags: {
|
|
12
|
-
readOnly: true
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
.input(
|
|
16
|
-
z.object({
|
|
17
|
-
tableName: z.string().describe('Name of the table to describe'),
|
|
18
|
-
schemaName: z
|
|
19
|
-
.string()
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Schema containing the table. Defaults to the configured default schema.')
|
|
22
|
-
})
|
|
23
|
-
)
|
|
24
|
-
.output(
|
|
25
|
-
z.object({
|
|
26
|
-
tableName: z.string().describe('Name of the described table'),
|
|
27
|
-
schemaName: z.string().describe('Schema of the described table'),
|
|
28
|
-
columns: z
|
|
29
|
-
.array(
|
|
30
|
-
z.object({
|
|
31
|
-
columnName: z.string().describe('Column name'),
|
|
32
|
-
dataType: z.string().describe('PostgreSQL data type'),
|
|
33
|
-
isNullable: z.boolean().describe('Whether the column allows NULL values'),
|
|
34
|
-
defaultValue: z.string().nullable().describe('Default value expression'),
|
|
35
|
-
maxLength: z
|
|
36
|
-
.number()
|
|
37
|
-
.nullable()
|
|
38
|
-
.describe('Maximum character length for character types'),
|
|
39
|
-
isPrimaryKey: z
|
|
40
|
-
.boolean()
|
|
41
|
-
.describe('Whether this column is part of the primary key'),
|
|
42
|
-
ordinalPosition: z.number().describe('Column position in the table')
|
|
43
|
-
})
|
|
44
|
-
)
|
|
45
|
-
.describe('Column definitions'),
|
|
46
|
-
indexes: z
|
|
47
|
-
.array(
|
|
48
|
-
z.object({
|
|
49
|
-
indexName: z.string().describe('Name of the index'),
|
|
50
|
-
isUnique: z.boolean().describe('Whether this is a unique index'),
|
|
51
|
-
isPrimary: z.boolean().describe('Whether this is the primary key index'),
|
|
52
|
-
indexColumns: z.array(z.string()).describe('Columns included in the index'),
|
|
53
|
-
indexType: z
|
|
54
|
-
.string()
|
|
55
|
-
.describe('Index access method (btree, hash, gin, gist, etc.)')
|
|
56
|
-
})
|
|
57
|
-
)
|
|
58
|
-
.describe('Indexes on the table'),
|
|
59
|
-
foreignKeys: z
|
|
60
|
-
.array(
|
|
61
|
-
z.object({
|
|
62
|
-
constraintName: z.string().describe('Name of the foreign key constraint'),
|
|
63
|
-
columnName: z.string().describe('Local column name'),
|
|
64
|
-
referencedTable: z.string().describe('Referenced table (schema.table)'),
|
|
65
|
-
referencedColumn: z.string().describe('Referenced column name')
|
|
66
|
-
})
|
|
67
|
-
)
|
|
68
|
-
.describe('Foreign key relationships'),
|
|
69
|
-
estimatedRowCount: z.number().nullable().describe('Estimated number of rows'),
|
|
70
|
-
sizeFormatted: z.string().nullable().describe('Human-readable table size')
|
|
71
|
-
})
|
|
72
|
-
)
|
|
73
|
-
.handleInvocation(async ctx => {
|
|
74
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
75
|
-
let schema = ctx.input.schemaName || ctx.config.defaultSchema;
|
|
76
|
-
let table = ctx.input.tableName;
|
|
77
|
-
|
|
78
|
-
ctx.info(`Describing table: ${schema}.${table}`);
|
|
79
|
-
|
|
80
|
-
// Fetch columns
|
|
81
|
-
let columnsResult = await client.query(
|
|
82
|
-
`
|
|
83
|
-
SELECT
|
|
84
|
-
c.column_name,
|
|
85
|
-
c.data_type,
|
|
86
|
-
c.udt_name,
|
|
87
|
-
c.is_nullable,
|
|
88
|
-
c.column_default,
|
|
89
|
-
c.character_maximum_length,
|
|
90
|
-
c.ordinal_position,
|
|
91
|
-
COALESCE(
|
|
92
|
-
(SELECT true FROM information_schema.key_column_usage kcu
|
|
93
|
-
JOIN information_schema.table_constraints tc
|
|
94
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
95
|
-
AND tc.table_schema = kcu.table_schema
|
|
96
|
-
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
97
|
-
AND kcu.table_schema = c.table_schema
|
|
98
|
-
AND kcu.table_name = c.table_name
|
|
99
|
-
AND kcu.column_name = c.column_name
|
|
100
|
-
LIMIT 1),
|
|
101
|
-
false
|
|
102
|
-
) AS is_primary_key
|
|
103
|
-
FROM information_schema.columns c
|
|
104
|
-
WHERE c.table_schema = '${schema.replace(/'/g, "''")}'
|
|
105
|
-
AND c.table_name = '${table.replace(/'/g, "''")}'
|
|
106
|
-
ORDER BY c.ordinal_position
|
|
107
|
-
`,
|
|
108
|
-
ctx.config.queryTimeout
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Fetch indexes
|
|
112
|
-
let indexesResult = await client.query(
|
|
113
|
-
`
|
|
114
|
-
SELECT
|
|
115
|
-
i.relname AS index_name,
|
|
116
|
-
ix.indisunique AS is_unique,
|
|
117
|
-
ix.indisprimary AS is_primary,
|
|
118
|
-
am.amname AS index_type,
|
|
119
|
-
array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) AS index_columns
|
|
120
|
-
FROM pg_index ix
|
|
121
|
-
JOIN pg_class t ON t.oid = ix.indrelid
|
|
122
|
-
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
123
|
-
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
124
|
-
JOIN pg_am am ON am.oid = i.relam
|
|
125
|
-
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
126
|
-
WHERE t.relname = '${table.replace(/'/g, "''")}'
|
|
127
|
-
AND n.nspname = '${schema.replace(/'/g, "''")}'
|
|
128
|
-
GROUP BY i.relname, ix.indisunique, ix.indisprimary, am.amname
|
|
129
|
-
ORDER BY i.relname
|
|
130
|
-
`,
|
|
131
|
-
ctx.config.queryTimeout
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
// Fetch foreign keys
|
|
135
|
-
let fkResult = await client.query(
|
|
136
|
-
`
|
|
137
|
-
SELECT
|
|
138
|
-
tc.constraint_name,
|
|
139
|
-
kcu.column_name,
|
|
140
|
-
ccu.table_schema || '.' || ccu.table_name AS referenced_table,
|
|
141
|
-
ccu.column_name AS referenced_column
|
|
142
|
-
FROM information_schema.table_constraints tc
|
|
143
|
-
JOIN information_schema.key_column_usage kcu
|
|
144
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
145
|
-
AND tc.table_schema = kcu.table_schema
|
|
146
|
-
JOIN information_schema.constraint_column_usage ccu
|
|
147
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
148
|
-
AND ccu.table_schema = tc.table_schema
|
|
149
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
150
|
-
AND tc.table_schema = '${schema.replace(/'/g, "''")}'
|
|
151
|
-
AND tc.table_name = '${table.replace(/'/g, "''")}'
|
|
152
|
-
ORDER BY tc.constraint_name
|
|
153
|
-
`,
|
|
154
|
-
ctx.config.queryTimeout
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// Fetch size info
|
|
158
|
-
let sizeResult = await client.query(
|
|
159
|
-
`
|
|
160
|
-
SELECT
|
|
161
|
-
c.reltuples::bigint AS estimated_row_count,
|
|
162
|
-
pg_size_pretty(pg_total_relation_size(c.oid)) AS size_formatted
|
|
163
|
-
FROM pg_class c
|
|
164
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
165
|
-
WHERE c.relname = '${table.replace(/'/g, "''")}'
|
|
166
|
-
AND n.nspname = '${schema.replace(/'/g, "''")}'
|
|
167
|
-
LIMIT 1
|
|
168
|
-
`,
|
|
169
|
-
ctx.config.queryTimeout
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
let columns = columnsResult.rows.map((row: any) => ({
|
|
173
|
-
columnName: row.column_name as string,
|
|
174
|
-
dataType:
|
|
175
|
-
row.udt_name === 'int4'
|
|
176
|
-
? 'integer'
|
|
177
|
-
: row.udt_name === 'int8'
|
|
178
|
-
? 'bigint'
|
|
179
|
-
: row.udt_name === 'int2'
|
|
180
|
-
? 'smallint'
|
|
181
|
-
: row.udt_name === 'float4'
|
|
182
|
-
? 'real'
|
|
183
|
-
: row.udt_name === 'float8'
|
|
184
|
-
? 'double precision'
|
|
185
|
-
: row.udt_name === 'bool'
|
|
186
|
-
? 'boolean'
|
|
187
|
-
: (row.data_type as string),
|
|
188
|
-
isNullable: row.is_nullable === 'YES',
|
|
189
|
-
defaultValue: row.column_default as string | null,
|
|
190
|
-
maxLength:
|
|
191
|
-
row.character_maximum_length != null ? Number(row.character_maximum_length) : null,
|
|
192
|
-
isPrimaryKey: row.is_primary_key === true,
|
|
193
|
-
ordinalPosition: Number(row.ordinal_position)
|
|
194
|
-
}));
|
|
195
|
-
|
|
196
|
-
let indexes = indexesResult.rows.map((row: any) => ({
|
|
197
|
-
indexName: row.index_name as string,
|
|
198
|
-
isUnique: row.is_unique === true,
|
|
199
|
-
isPrimary: row.is_primary === true,
|
|
200
|
-
indexColumns: Array.isArray(row.index_columns)
|
|
201
|
-
? row.index_columns
|
|
202
|
-
: typeof row.index_columns === 'string'
|
|
203
|
-
? row.index_columns.replace(/[{}]/g, '').split(',')
|
|
204
|
-
: [],
|
|
205
|
-
indexType: row.index_type as string
|
|
206
|
-
}));
|
|
207
|
-
|
|
208
|
-
let foreignKeys = fkResult.rows.map((row: any) => ({
|
|
209
|
-
constraintName: row.constraint_name as string,
|
|
210
|
-
columnName: row.column_name as string,
|
|
211
|
-
referencedTable: row.referenced_table as string,
|
|
212
|
-
referencedColumn: row.referenced_column as string
|
|
213
|
-
}));
|
|
214
|
-
|
|
215
|
-
let sizeRow = sizeResult.rows[0] as any;
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
output: {
|
|
219
|
-
tableName: table,
|
|
220
|
-
schemaName: schema,
|
|
221
|
-
columns,
|
|
222
|
-
indexes,
|
|
223
|
-
foreignKeys,
|
|
224
|
-
estimatedRowCount:
|
|
225
|
-
sizeRow?.estimated_row_count != null ? Number(sizeRow.estimated_row_count) : null,
|
|
226
|
-
sizeFormatted: (sizeRow?.size_formatted as string) ?? null
|
|
227
|
-
},
|
|
228
|
-
message: `Table \`${schema}.${table}\` has **${columns.length}** column(s), **${indexes.length}** index(es), and **${foreignKeys.length}** foreign key(s).`
|
|
229
|
-
};
|
|
230
|
-
})
|
|
231
|
-
.build();
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient } from '../lib/helpers';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
export let executeQuery = SlateTool.create(spec, {
|
|
7
|
-
name: 'Execute SQL Query',
|
|
8
|
-
key: 'execute_query',
|
|
9
|
-
description: `Execute an arbitrary SQL query against the PostgreSQL database. Supports all SQL operations including SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, and more.
|
|
10
|
-
Returns column metadata and result rows for SELECT queries, or affected row counts for DML statements.
|
|
11
|
-
Supports complex queries with joins, subqueries, CTEs, window functions, and aggregations.`,
|
|
12
|
-
instructions: [
|
|
13
|
-
'Always validate SQL syntax before executing to avoid errors.',
|
|
14
|
-
'Use parameterized-style quoting for user-provided values to prevent SQL injection.',
|
|
15
|
-
'For large result sets, use LIMIT to control the number of returned rows.'
|
|
16
|
-
],
|
|
17
|
-
constraints: [
|
|
18
|
-
'Query execution is subject to the configured timeout.',
|
|
19
|
-
'Results are limited by the configured maxRows setting when no explicit LIMIT is provided.'
|
|
20
|
-
],
|
|
21
|
-
tags: {
|
|
22
|
-
destructive: false,
|
|
23
|
-
readOnly: false
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
.input(
|
|
27
|
-
z.object({
|
|
28
|
-
sql: z.string().describe('The SQL query to execute'),
|
|
29
|
-
maxRows: z
|
|
30
|
-
.number()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe(
|
|
33
|
-
'Maximum number of rows to return. Overrides the default maxRows config. Only applicable for SELECT queries.'
|
|
34
|
-
)
|
|
35
|
-
})
|
|
36
|
-
)
|
|
37
|
-
.output(
|
|
38
|
-
z.object({
|
|
39
|
-
columns: z
|
|
40
|
-
.array(
|
|
41
|
-
z.object({
|
|
42
|
-
name: z.string().describe('Column name'),
|
|
43
|
-
type: z.string().describe('PostgreSQL data type')
|
|
44
|
-
})
|
|
45
|
-
)
|
|
46
|
-
.describe('Column metadata for the result set'),
|
|
47
|
-
rows: z
|
|
48
|
-
.array(z.record(z.string(), z.any()))
|
|
49
|
-
.describe('Result rows as key-value objects'),
|
|
50
|
-
rowCount: z
|
|
51
|
-
.number()
|
|
52
|
-
.nullable()
|
|
53
|
-
.describe('Number of rows affected (for DML) or returned'),
|
|
54
|
-
command: z
|
|
55
|
-
.string()
|
|
56
|
-
.describe('The SQL command that was executed (e.g., SELECT, INSERT, UPDATE)')
|
|
57
|
-
})
|
|
58
|
-
)
|
|
59
|
-
.handleInvocation(async ctx => {
|
|
60
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
61
|
-
let sql = ctx.input.sql.trim();
|
|
62
|
-
let maxRows = ctx.input.maxRows ?? ctx.config.maxRows;
|
|
63
|
-
|
|
64
|
-
// If it's a SELECT query without LIMIT, add one
|
|
65
|
-
let isSelect = /^\s*(SELECT|WITH)\b/i.test(sql);
|
|
66
|
-
let hasLimit = /\bLIMIT\s+\d+/i.test(sql);
|
|
67
|
-
|
|
68
|
-
let querySql = sql;
|
|
69
|
-
if (isSelect && !hasLimit && maxRows > 0) {
|
|
70
|
-
// Remove trailing semicolon if present before adding LIMIT
|
|
71
|
-
querySql = querySql.replace(/;\s*$/, '');
|
|
72
|
-
querySql = `${querySql} LIMIT ${maxRows}`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
ctx.info(
|
|
76
|
-
`Executing SQL query: ${querySql.substring(0, 200)}${querySql.length > 200 ? '...' : ''}`
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
let result = await client.query(querySql, ctx.config.queryTimeout);
|
|
80
|
-
|
|
81
|
-
let displayColumns = result.columns.map(c => ({ name: c.name, type: c.type }));
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
output: {
|
|
85
|
-
columns: displayColumns,
|
|
86
|
-
rows: result.rows,
|
|
87
|
-
rowCount: result.rowCount,
|
|
88
|
-
command: result.command
|
|
89
|
-
},
|
|
90
|
-
message: isSelect
|
|
91
|
-
? `Query returned **${result.rows.length}** row(s) with **${result.columns.length}** column(s).`
|
|
92
|
-
: `\`${result.command}\` completed. **${result.rowCount ?? 0}** row(s) affected.`
|
|
93
|
-
};
|
|
94
|
-
})
|
|
95
|
-
.build();
|
package/src/tools/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { executeQuery } from './execute-query';
|
|
2
|
-
export { listTables } from './list-tables';
|
|
3
|
-
export { describeTable } from './describe-table';
|
|
4
|
-
export { insertRows } from './insert-rows';
|
|
5
|
-
export { updateRows } from './update-rows';
|
|
6
|
-
export { deleteRows } from './delete-rows';
|
|
7
|
-
export { manageTable } from './manage-table';
|
|
8
|
-
export { manageIndexes } from './manage-indexes';
|
|
9
|
-
export { listSchemas } from './list-schemas';
|
|
10
|
-
export { manageRoles } from './manage-roles';
|
|
11
|
-
export { manageSchemas } from './manage-schemas';
|
|
12
|
-
export { manageViews } from './manage-views';
|
package/src/tools/insert-rows.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import {
|
|
4
|
-
createClient,
|
|
5
|
-
escapeIdentifier,
|
|
6
|
-
escapeLiteral,
|
|
7
|
-
qualifiedTableName
|
|
8
|
-
} from '../lib/helpers';
|
|
9
|
-
import { postgresServiceError } from '../lib/errors';
|
|
10
|
-
import { z } from 'zod';
|
|
11
|
-
|
|
12
|
-
export let insertRows = SlateTool.create(spec, {
|
|
13
|
-
name: 'Insert Rows',
|
|
14
|
-
key: 'insert_rows',
|
|
15
|
-
description: `Insert one or more rows into a PostgreSQL table. Provide the data as an array of objects where keys are column names and values are the data to insert.
|
|
16
|
-
Supports inserting multiple rows in a single operation and can optionally return the inserted rows.`,
|
|
17
|
-
instructions: [
|
|
18
|
-
'Column names in the row objects must exactly match the table column names.',
|
|
19
|
-
'Values are automatically escaped to prevent SQL injection.',
|
|
20
|
-
'Use null for NULL values.'
|
|
21
|
-
],
|
|
22
|
-
tags: {
|
|
23
|
-
destructive: false
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
.input(
|
|
27
|
-
z.object({
|
|
28
|
-
tableName: z.string().describe('Name of the table to insert into'),
|
|
29
|
-
schemaName: z.string().optional().describe('Schema containing the table'),
|
|
30
|
-
rows: z
|
|
31
|
-
.array(z.record(z.string(), z.any()))
|
|
32
|
-
.min(1)
|
|
33
|
-
.describe('Array of row objects to insert, where keys are column names'),
|
|
34
|
-
returning: z
|
|
35
|
-
.boolean()
|
|
36
|
-
.optional()
|
|
37
|
-
.default(true)
|
|
38
|
-
.describe('Whether to return the inserted rows using RETURNING *'),
|
|
39
|
-
onConflict: z
|
|
40
|
-
.enum(['error', 'ignore', 'update'])
|
|
41
|
-
.optional()
|
|
42
|
-
.default('error')
|
|
43
|
-
.describe(
|
|
44
|
-
'Behavior on unique constraint conflict: error (default), ignore (DO NOTHING), or update (DO UPDATE SET)'
|
|
45
|
-
),
|
|
46
|
-
conflictColumns: z
|
|
47
|
-
.array(z.string())
|
|
48
|
-
.optional()
|
|
49
|
-
.describe(
|
|
50
|
-
'Columns that define the conflict target (required when onConflict is "ignore" or "update")'
|
|
51
|
-
)
|
|
52
|
-
})
|
|
53
|
-
)
|
|
54
|
-
.output(
|
|
55
|
-
z.object({
|
|
56
|
-
insertedCount: z.number().describe('Number of rows inserted'),
|
|
57
|
-
returnedRows: z
|
|
58
|
-
.array(z.record(z.string(), z.any()))
|
|
59
|
-
.describe('Inserted rows (if returning was enabled)')
|
|
60
|
-
})
|
|
61
|
-
)
|
|
62
|
-
.handleInvocation(async ctx => {
|
|
63
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
64
|
-
let schema = ctx.input.schemaName || ctx.config.defaultSchema;
|
|
65
|
-
let fullTableName = qualifiedTableName(ctx.input.tableName, schema);
|
|
66
|
-
|
|
67
|
-
// Collect all unique column names from all rows
|
|
68
|
-
let columnSet = new Set<string>();
|
|
69
|
-
for (let row of ctx.input.rows) {
|
|
70
|
-
for (let key of Object.keys(row)) {
|
|
71
|
-
columnSet.add(key);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
let columns = Array.from(columnSet);
|
|
75
|
-
|
|
76
|
-
// Build values list
|
|
77
|
-
let valueClauses: string[] = [];
|
|
78
|
-
for (let row of ctx.input.rows) {
|
|
79
|
-
let values = columns.map(col => {
|
|
80
|
-
let val = row[col];
|
|
81
|
-
if (val === null || val === undefined) return 'NULL';
|
|
82
|
-
if (typeof val === 'number') return String(val);
|
|
83
|
-
if (typeof val === 'boolean') return val ? 'TRUE' : 'FALSE';
|
|
84
|
-
if (typeof val === 'object') return escapeLiteral(JSON.stringify(val));
|
|
85
|
-
return escapeLiteral(String(val));
|
|
86
|
-
});
|
|
87
|
-
valueClauses.push(`(${values.join(', ')})`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let columnList = columns.map(escapeIdentifier).join(', ');
|
|
91
|
-
let sql = `INSERT INTO ${fullTableName} (${columnList}) VALUES ${valueClauses.join(', ')}`;
|
|
92
|
-
|
|
93
|
-
// Handle conflict
|
|
94
|
-
if (ctx.input.onConflict === 'ignore') {
|
|
95
|
-
let conflictTarget = ctx.input.conflictColumns?.length
|
|
96
|
-
? `(${ctx.input.conflictColumns.map(escapeIdentifier).join(', ')})`
|
|
97
|
-
: '';
|
|
98
|
-
sql += ` ON CONFLICT ${conflictTarget} DO NOTHING`;
|
|
99
|
-
} else if (ctx.input.onConflict === 'update') {
|
|
100
|
-
let conflictCols = ctx.input.conflictColumns;
|
|
101
|
-
if (!conflictCols?.length) {
|
|
102
|
-
throw postgresServiceError(
|
|
103
|
-
'conflictColumns must be specified when onConflict is "update"'
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
let conflictTarget = `(${conflictCols.map(escapeIdentifier).join(', ')})`;
|
|
107
|
-
let updateCols = columns.filter(c => !conflictCols.includes(c));
|
|
108
|
-
let setClauses = updateCols.map(
|
|
109
|
-
c => `${escapeIdentifier(c)} = EXCLUDED.${escapeIdentifier(c)}`
|
|
110
|
-
);
|
|
111
|
-
if (setClauses.length > 0) {
|
|
112
|
-
sql += ` ON CONFLICT ${conflictTarget} DO UPDATE SET ${setClauses.join(', ')}`;
|
|
113
|
-
} else {
|
|
114
|
-
sql += ` ON CONFLICT ${conflictTarget} DO NOTHING`;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (ctx.input.returning) {
|
|
119
|
-
sql += ' RETURNING *';
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
ctx.info(`Inserting ${ctx.input.rows.length} row(s) into ${fullTableName}`);
|
|
123
|
-
let result = await client.query(sql, ctx.config.queryTimeout);
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
output: {
|
|
127
|
-
insertedCount: result.rowCount ?? ctx.input.rows.length,
|
|
128
|
-
returnedRows: result.rows
|
|
129
|
-
},
|
|
130
|
-
message: `Inserted **${result.rowCount ?? ctx.input.rows.length}** row(s) into \`${ctx.input.tableName}\`.`
|
|
131
|
-
};
|
|
132
|
-
})
|
|
133
|
-
.build();
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient } from '../lib/helpers';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
export let listSchemas = SlateTool.create(spec, {
|
|
7
|
-
name: 'List Schemas',
|
|
8
|
-
key: 'list_schemas',
|
|
9
|
-
description: `List all schemas in the PostgreSQL database with their table counts and sizes.
|
|
10
|
-
Useful for exploring the database structure and understanding the organization of tables across schemas.`,
|
|
11
|
-
tags: {
|
|
12
|
-
readOnly: true
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
.input(
|
|
16
|
-
z.object({
|
|
17
|
-
includeSystemSchemas: z
|
|
18
|
-
.boolean()
|
|
19
|
-
.optional()
|
|
20
|
-
.default(false)
|
|
21
|
-
.describe('Include system schemas (pg_catalog, information_schema, pg_toast)')
|
|
22
|
-
})
|
|
23
|
-
)
|
|
24
|
-
.output(
|
|
25
|
-
z.object({
|
|
26
|
-
schemas: z
|
|
27
|
-
.array(
|
|
28
|
-
z.object({
|
|
29
|
-
schemaName: z.string().describe('Name of the schema'),
|
|
30
|
-
schemaOwner: z.string().describe('Owner of the schema'),
|
|
31
|
-
tableCount: z.number().describe('Number of tables in the schema'),
|
|
32
|
-
sizeFormatted: z
|
|
33
|
-
.string()
|
|
34
|
-
.nullable()
|
|
35
|
-
.describe('Total size of all tables in the schema')
|
|
36
|
-
})
|
|
37
|
-
)
|
|
38
|
-
.describe('List of schemas in the database'),
|
|
39
|
-
totalCount: z.number().describe('Total number of schemas found')
|
|
40
|
-
})
|
|
41
|
-
)
|
|
42
|
-
.handleInvocation(async ctx => {
|
|
43
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
44
|
-
|
|
45
|
-
let systemFilter = ctx.input.includeSystemSchemas
|
|
46
|
-
? ''
|
|
47
|
-
: `WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND n.nspname NOT LIKE 'pg_temp_%' AND n.nspname NOT LIKE 'pg_toast_temp_%'`;
|
|
48
|
-
|
|
49
|
-
let sql = `
|
|
50
|
-
SELECT
|
|
51
|
-
n.nspname AS schema_name,
|
|
52
|
-
pg_catalog.pg_get_userbyid(n.nspowner) AS schema_owner,
|
|
53
|
-
COUNT(c.oid) FILTER (WHERE c.relkind IN ('r', 'p')) AS table_count,
|
|
54
|
-
pg_size_pretty(COALESCE(SUM(pg_total_relation_size(c.oid)) FILTER (WHERE c.relkind IN ('r', 'p', 'm')), 0)) AS size_formatted
|
|
55
|
-
FROM pg_catalog.pg_namespace n
|
|
56
|
-
LEFT JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid AND c.relkind IN ('r', 'p', 'm')
|
|
57
|
-
${systemFilter}
|
|
58
|
-
GROUP BY n.nspname, n.nspowner
|
|
59
|
-
ORDER BY n.nspname
|
|
60
|
-
`;
|
|
61
|
-
|
|
62
|
-
ctx.info('Listing database schemas');
|
|
63
|
-
let result = await client.query(sql, ctx.config.queryTimeout);
|
|
64
|
-
|
|
65
|
-
let schemas = result.rows.map((row: any) => ({
|
|
66
|
-
schemaName: row.schema_name as string,
|
|
67
|
-
schemaOwner: row.schema_owner as string,
|
|
68
|
-
tableCount: Number(row.table_count),
|
|
69
|
-
sizeFormatted: row.size_formatted as string | null
|
|
70
|
-
}));
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
output: {
|
|
74
|
-
schemas,
|
|
75
|
-
totalCount: schemas.length
|
|
76
|
-
},
|
|
77
|
-
message: `Found **${schemas.length}** schema(s) in the database.`
|
|
78
|
-
};
|
|
79
|
-
})
|
|
80
|
-
.build();
|
package/src/tools/list-tables.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient } from '../lib/helpers';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
export let listTables = SlateTool.create(spec, {
|
|
7
|
-
name: 'List Tables',
|
|
8
|
-
key: 'list_tables',
|
|
9
|
-
description: `List all tables in the PostgreSQL database, optionally filtered by schema. Returns table names, schemas, row estimates, and size information.
|
|
10
|
-
Also supports listing views and materialized views.`,
|
|
11
|
-
tags: {
|
|
12
|
-
readOnly: true
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
.input(
|
|
16
|
-
z.object({
|
|
17
|
-
schemaName: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe(
|
|
21
|
-
'Filter tables by schema name. If not specified, uses the default schema from config.'
|
|
22
|
-
),
|
|
23
|
-
includeViews: z
|
|
24
|
-
.boolean()
|
|
25
|
-
.optional()
|
|
26
|
-
.default(false)
|
|
27
|
-
.describe('Include views and materialized views in the results'),
|
|
28
|
-
includeSystemTables: z
|
|
29
|
-
.boolean()
|
|
30
|
-
.optional()
|
|
31
|
-
.default(false)
|
|
32
|
-
.describe('Include system tables from pg_catalog and information_schema')
|
|
33
|
-
})
|
|
34
|
-
)
|
|
35
|
-
.output(
|
|
36
|
-
z.object({
|
|
37
|
-
tables: z
|
|
38
|
-
.array(
|
|
39
|
-
z.object({
|
|
40
|
-
tableName: z.string().describe('Name of the table'),
|
|
41
|
-
schemaName: z.string().describe('Schema containing the table'),
|
|
42
|
-
tableType: z
|
|
43
|
-
.string()
|
|
44
|
-
.describe('Type of table (BASE TABLE, VIEW, MATERIALIZED VIEW)'),
|
|
45
|
-
estimatedRowCount: z
|
|
46
|
-
.number()
|
|
47
|
-
.nullable()
|
|
48
|
-
.describe('Estimated number of rows from pg_stat'),
|
|
49
|
-
sizeBytes: z
|
|
50
|
-
.number()
|
|
51
|
-
.nullable()
|
|
52
|
-
.describe('Table size in bytes including indexes and TOAST'),
|
|
53
|
-
sizeFormatted: z.string().nullable().describe('Human-readable table size')
|
|
54
|
-
})
|
|
55
|
-
)
|
|
56
|
-
.describe('List of tables in the database'),
|
|
57
|
-
totalCount: z.number().describe('Total number of tables found')
|
|
58
|
-
})
|
|
59
|
-
)
|
|
60
|
-
.handleInvocation(async ctx => {
|
|
61
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
62
|
-
let schema = ctx.input.schemaName || ctx.config.defaultSchema;
|
|
63
|
-
|
|
64
|
-
let typeFilter = `'BASE TABLE'`;
|
|
65
|
-
if (ctx.input.includeViews) {
|
|
66
|
-
typeFilter = `'BASE TABLE', 'VIEW'`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let schemaFilter = ctx.input.includeSystemTables
|
|
70
|
-
? ''
|
|
71
|
-
: `AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')`;
|
|
72
|
-
|
|
73
|
-
if (schema && !ctx.input.includeSystemTables) {
|
|
74
|
-
schemaFilter = `AND n.nspname = '${schema.replace(/'/g, "''")}'`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let sql = `
|
|
78
|
-
SELECT
|
|
79
|
-
c.relname AS table_name,
|
|
80
|
-
n.nspname AS schema_name,
|
|
81
|
-
CASE c.relkind
|
|
82
|
-
WHEN 'r' THEN 'BASE TABLE'
|
|
83
|
-
WHEN 'v' THEN 'VIEW'
|
|
84
|
-
WHEN 'm' THEN 'MATERIALIZED VIEW'
|
|
85
|
-
WHEN 'f' THEN 'FOREIGN TABLE'
|
|
86
|
-
WHEN 'p' THEN 'PARTITIONED TABLE'
|
|
87
|
-
END AS table_type,
|
|
88
|
-
c.reltuples::bigint AS estimated_row_count,
|
|
89
|
-
pg_total_relation_size(c.oid) AS size_bytes,
|
|
90
|
-
pg_size_pretty(pg_total_relation_size(c.oid)) AS size_formatted
|
|
91
|
-
FROM pg_class c
|
|
92
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
93
|
-
WHERE c.relkind IN (${ctx.input.includeViews ? "'r','v','m','p'" : "'r','p'"})
|
|
94
|
-
${schemaFilter}
|
|
95
|
-
ORDER BY n.nspname, c.relname
|
|
96
|
-
`;
|
|
97
|
-
|
|
98
|
-
ctx.info(`Listing tables in schema: ${schema || '(all schemas)'}`);
|
|
99
|
-
let result = await client.query(sql, ctx.config.queryTimeout);
|
|
100
|
-
|
|
101
|
-
let tables = result.rows.map((row: any) => ({
|
|
102
|
-
tableName: row.table_name as string,
|
|
103
|
-
schemaName: row.schema_name as string,
|
|
104
|
-
tableType: row.table_type as string,
|
|
105
|
-
estimatedRowCount:
|
|
106
|
-
row.estimated_row_count != null ? Number(row.estimated_row_count) : null,
|
|
107
|
-
sizeBytes: row.size_bytes != null ? Number(row.size_bytes) : null,
|
|
108
|
-
sizeFormatted: row.size_formatted as string | null
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
output: {
|
|
113
|
-
tables,
|
|
114
|
-
totalCount: tables.length
|
|
115
|
-
},
|
|
116
|
-
message: `Found **${tables.length}** table(s) in ${schema ? `schema \`${schema}\`` : 'the database'}.`
|
|
117
|
-
};
|
|
118
|
-
})
|
|
119
|
-
.build();
|