@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,99 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient, escapeIdentifier, qualifiedTableName } from '../lib/helpers';
|
|
4
|
-
import { postgresServiceError } from '../lib/errors';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
export let manageIndexes = SlateTool.create(spec, {
|
|
8
|
-
name: 'Manage Indexes',
|
|
9
|
-
key: 'manage_indexes',
|
|
10
|
-
description: `Create or drop indexes on PostgreSQL tables. Supports B-tree, Hash, GIN, GiST, and other index types.
|
|
11
|
-
Can create unique indexes, partial indexes with WHERE conditions, and multi-column indexes.`,
|
|
12
|
-
tags: {
|
|
13
|
-
destructive: true
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
.input(
|
|
17
|
-
z.object({
|
|
18
|
-
action: z.enum(['create', 'drop']).describe('Action to perform'),
|
|
19
|
-
indexName: z.string().describe('Name of the index'),
|
|
20
|
-
tableName: z.string().optional().describe('Name of the table (required for create)'),
|
|
21
|
-
schemaName: z.string().optional().describe('Schema containing the table'),
|
|
22
|
-
|
|
23
|
-
// Create options
|
|
24
|
-
columns: z
|
|
25
|
-
.array(z.string())
|
|
26
|
-
.optional()
|
|
27
|
-
.describe('Column names to include in the index (required for create)'),
|
|
28
|
-
unique: z
|
|
29
|
-
.boolean()
|
|
30
|
-
.optional()
|
|
31
|
-
.default(false)
|
|
32
|
-
.describe('Whether to create a UNIQUE index'),
|
|
33
|
-
method: z
|
|
34
|
-
.enum(['btree', 'hash', 'gin', 'gist', 'spgist', 'brin'])
|
|
35
|
-
.optional()
|
|
36
|
-
.default('btree')
|
|
37
|
-
.describe('Index access method'),
|
|
38
|
-
where: z.string().optional().describe('WHERE condition for a partial index'),
|
|
39
|
-
concurrently: z
|
|
40
|
-
.boolean()
|
|
41
|
-
.optional()
|
|
42
|
-
.default(false)
|
|
43
|
-
.describe('Create index concurrently (non-blocking)'),
|
|
44
|
-
ifNotExists: z.boolean().optional().default(false).describe('Add IF NOT EXISTS clause'),
|
|
45
|
-
|
|
46
|
-
// Drop options
|
|
47
|
-
cascade: z.boolean().optional().default(false).describe('Use CASCADE when dropping'),
|
|
48
|
-
ifExists: z.boolean().optional().default(false).describe('Add IF EXISTS clause for drop')
|
|
49
|
-
})
|
|
50
|
-
)
|
|
51
|
-
.output(
|
|
52
|
-
z.object({
|
|
53
|
-
success: z.boolean().describe('Whether the operation completed successfully'),
|
|
54
|
-
executedSql: z.string().describe('The SQL statement that was executed')
|
|
55
|
-
})
|
|
56
|
-
)
|
|
57
|
-
.handleInvocation(async ctx => {
|
|
58
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
59
|
-
let schema = ctx.input.schemaName || ctx.config.defaultSchema;
|
|
60
|
-
let sql: string;
|
|
61
|
-
|
|
62
|
-
if (ctx.input.action === 'create') {
|
|
63
|
-
if (!ctx.input.tableName) {
|
|
64
|
-
throw postgresServiceError('tableName is required for create action');
|
|
65
|
-
}
|
|
66
|
-
if (!ctx.input.columns || ctx.input.columns.length === 0) {
|
|
67
|
-
throw postgresServiceError('columns are required for create action');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let fullTableName = qualifiedTableName(ctx.input.tableName, schema);
|
|
71
|
-
let unique = ctx.input.unique ? 'UNIQUE ' : '';
|
|
72
|
-
let concurrently = ctx.input.concurrently ? 'CONCURRENTLY ' : '';
|
|
73
|
-
let ifNotExists = ctx.input.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
74
|
-
let method = ctx.input.method !== 'btree' ? ` USING ${ctx.input.method}` : '';
|
|
75
|
-
let columnList = ctx.input.columns.map(escapeIdentifier).join(', ');
|
|
76
|
-
let where = ctx.input.where ? ` WHERE ${ctx.input.where}` : '';
|
|
77
|
-
|
|
78
|
-
sql = `CREATE ${unique}INDEX ${concurrently}${ifNotExists}${escapeIdentifier(ctx.input.indexName)} ON ${fullTableName}${method} (${columnList})${where}`;
|
|
79
|
-
} else {
|
|
80
|
-
let ifExists = ctx.input.ifExists ? 'IF EXISTS ' : '';
|
|
81
|
-
let cascade = ctx.input.cascade ? ' CASCADE' : '';
|
|
82
|
-
let fullIndexName = qualifiedTableName(ctx.input.indexName, schema);
|
|
83
|
-
sql = `DROP INDEX ${ifExists}${fullIndexName}${cascade}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
ctx.info(`Executing: ${sql}`);
|
|
87
|
-
await client.query(sql, ctx.config.queryTimeout);
|
|
88
|
-
|
|
89
|
-
let actionLabel = ctx.input.action === 'create' ? 'Created' : 'Dropped';
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
output: {
|
|
93
|
-
success: true,
|
|
94
|
-
executedSql: sql
|
|
95
|
-
},
|
|
96
|
-
message: `${actionLabel} index \`${ctx.input.indexName}\`.`
|
|
97
|
-
};
|
|
98
|
-
})
|
|
99
|
-
.build();
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient, escapeIdentifier, qualifiedTableName } from '../lib/helpers';
|
|
4
|
-
import { postgresServiceError } from '../lib/errors';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
export let manageRoles = SlateTool.create(spec, {
|
|
8
|
-
name: 'Manage Roles',
|
|
9
|
-
key: 'manage_roles',
|
|
10
|
-
description: `Manage PostgreSQL roles and their privileges. Supports creating and dropping roles, as well as granting and revoking privileges on databases, schemas, tables, and other objects.`,
|
|
11
|
-
instructions: [
|
|
12
|
-
'The connecting user must have sufficient privileges to manage roles.',
|
|
13
|
-
'Use the grant/revoke actions to control access to specific tables or schemas.'
|
|
14
|
-
],
|
|
15
|
-
tags: {
|
|
16
|
-
destructive: true
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
.input(
|
|
20
|
-
z.object({
|
|
21
|
-
action: z
|
|
22
|
-
.enum(['create', 'drop', 'grant', 'revoke', 'list'])
|
|
23
|
-
.describe('Action to perform'),
|
|
24
|
-
|
|
25
|
-
// For create/drop
|
|
26
|
-
roleName: z.string().optional().describe('Name of the role to create or drop'),
|
|
27
|
-
rolePassword: z.string().optional().describe('Password for the new role (create only)'),
|
|
28
|
-
canLogin: z
|
|
29
|
-
.boolean()
|
|
30
|
-
.optional()
|
|
31
|
-
.default(false)
|
|
32
|
-
.describe('Whether the role can log in (create only)'),
|
|
33
|
-
isSuperuser: z
|
|
34
|
-
.boolean()
|
|
35
|
-
.optional()
|
|
36
|
-
.default(false)
|
|
37
|
-
.describe('Whether the role is a superuser (create only)'),
|
|
38
|
-
canCreateDb: z
|
|
39
|
-
.boolean()
|
|
40
|
-
.optional()
|
|
41
|
-
.default(false)
|
|
42
|
-
.describe('Whether the role can create databases (create only)'),
|
|
43
|
-
ifExists: z
|
|
44
|
-
.boolean()
|
|
45
|
-
.optional()
|
|
46
|
-
.default(false)
|
|
47
|
-
.describe('Add IF EXISTS clause (drop only)'),
|
|
48
|
-
|
|
49
|
-
// For grant/revoke
|
|
50
|
-
privileges: z
|
|
51
|
-
.array(
|
|
52
|
-
z.enum([
|
|
53
|
-
'SELECT',
|
|
54
|
-
'INSERT',
|
|
55
|
-
'UPDATE',
|
|
56
|
-
'DELETE',
|
|
57
|
-
'TRUNCATE',
|
|
58
|
-
'REFERENCES',
|
|
59
|
-
'TRIGGER',
|
|
60
|
-
'ALL',
|
|
61
|
-
'USAGE',
|
|
62
|
-
'CREATE',
|
|
63
|
-
'CONNECT',
|
|
64
|
-
'EXECUTE'
|
|
65
|
-
])
|
|
66
|
-
)
|
|
67
|
-
.optional()
|
|
68
|
-
.describe('Privileges to grant or revoke'),
|
|
69
|
-
on: z
|
|
70
|
-
.object({
|
|
71
|
-
objectType: z
|
|
72
|
-
.enum([
|
|
73
|
-
'TABLE',
|
|
74
|
-
'SCHEMA',
|
|
75
|
-
'DATABASE',
|
|
76
|
-
'SEQUENCE',
|
|
77
|
-
'FUNCTION',
|
|
78
|
-
'ALL TABLES IN SCHEMA',
|
|
79
|
-
'ALL SEQUENCES IN SCHEMA'
|
|
80
|
-
])
|
|
81
|
-
.describe('Type of object to grant/revoke privileges on'),
|
|
82
|
-
objectName: z.string().describe('Name of the object'),
|
|
83
|
-
schemaName: z
|
|
84
|
-
.string()
|
|
85
|
-
.optional()
|
|
86
|
-
.describe('Schema containing the object (for tables, sequences)')
|
|
87
|
-
})
|
|
88
|
-
.optional()
|
|
89
|
-
.describe('Object to grant/revoke privileges on'),
|
|
90
|
-
grantee: z.string().optional().describe('Role to grant privileges to or revoke from')
|
|
91
|
-
})
|
|
92
|
-
)
|
|
93
|
-
.output(
|
|
94
|
-
z.object({
|
|
95
|
-
success: z.boolean().describe('Whether the operation completed successfully'),
|
|
96
|
-
executedSql: z.string().describe('The SQL statement(s) that were executed'),
|
|
97
|
-
roles: z
|
|
98
|
-
.array(
|
|
99
|
-
z.object({
|
|
100
|
-
roleName: z.string().describe('Name of the role'),
|
|
101
|
-
canLogin: z.boolean().describe('Whether the role can log in'),
|
|
102
|
-
isSuperuser: z.boolean().describe('Whether the role is a superuser'),
|
|
103
|
-
canCreateDb: z.boolean().describe('Whether the role can create databases'),
|
|
104
|
-
canCreateRole: z.boolean().describe('Whether the role can create other roles'),
|
|
105
|
-
connectionLimit: z
|
|
106
|
-
.number()
|
|
107
|
-
.describe('Maximum concurrent connections (-1 = unlimited)')
|
|
108
|
-
})
|
|
109
|
-
)
|
|
110
|
-
.optional()
|
|
111
|
-
.describe('List of roles (for list action)')
|
|
112
|
-
})
|
|
113
|
-
)
|
|
114
|
-
.handleInvocation(async ctx => {
|
|
115
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
116
|
-
let sql: string;
|
|
117
|
-
|
|
118
|
-
if (ctx.input.action === 'list') {
|
|
119
|
-
sql = `
|
|
120
|
-
SELECT
|
|
121
|
-
rolname AS role_name,
|
|
122
|
-
rolcanlogin AS can_login,
|
|
123
|
-
rolsuper AS is_superuser,
|
|
124
|
-
rolcreatedb AS can_create_db,
|
|
125
|
-
rolcreaterole AS can_create_role,
|
|
126
|
-
rolconnlimit AS connection_limit
|
|
127
|
-
FROM pg_roles
|
|
128
|
-
WHERE rolname NOT LIKE 'pg_%'
|
|
129
|
-
ORDER BY rolname
|
|
130
|
-
`;
|
|
131
|
-
|
|
132
|
-
ctx.info('Listing database roles');
|
|
133
|
-
let result = await client.query(sql, ctx.config.queryTimeout);
|
|
134
|
-
|
|
135
|
-
let roles = result.rows.map((row: any) => ({
|
|
136
|
-
roleName: row.role_name as string,
|
|
137
|
-
canLogin: row.can_login === true,
|
|
138
|
-
isSuperuser: row.is_superuser === true,
|
|
139
|
-
canCreateDb: row.can_create_db === true,
|
|
140
|
-
canCreateRole: row.can_create_role === true,
|
|
141
|
-
connectionLimit: Number(row.connection_limit)
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
output: {
|
|
146
|
-
success: true,
|
|
147
|
-
executedSql: sql.trim(),
|
|
148
|
-
roles
|
|
149
|
-
},
|
|
150
|
-
message: `Found **${roles.length}** role(s).`
|
|
151
|
-
};
|
|
152
|
-
} else if (ctx.input.action === 'create') {
|
|
153
|
-
if (!ctx.input.roleName)
|
|
154
|
-
throw postgresServiceError('roleName is required for create action');
|
|
155
|
-
|
|
156
|
-
let options: string[] = [];
|
|
157
|
-
if (ctx.input.canLogin) options.push('LOGIN');
|
|
158
|
-
else options.push('NOLOGIN');
|
|
159
|
-
if (ctx.input.isSuperuser) options.push('SUPERUSER');
|
|
160
|
-
if (ctx.input.canCreateDb) options.push('CREATEDB');
|
|
161
|
-
if (ctx.input.rolePassword)
|
|
162
|
-
options.push(`PASSWORD '${ctx.input.rolePassword.replace(/'/g, "''")}'`);
|
|
163
|
-
|
|
164
|
-
sql = `CREATE ROLE ${escapeIdentifier(ctx.input.roleName)} ${options.join(' ')}`;
|
|
165
|
-
} else if (ctx.input.action === 'drop') {
|
|
166
|
-
if (!ctx.input.roleName)
|
|
167
|
-
throw postgresServiceError('roleName is required for drop action');
|
|
168
|
-
let ifExists = ctx.input.ifExists ? 'IF EXISTS ' : '';
|
|
169
|
-
sql = `DROP ROLE ${ifExists}${escapeIdentifier(ctx.input.roleName)}`;
|
|
170
|
-
} else if (ctx.input.action === 'grant' || ctx.input.action === 'revoke') {
|
|
171
|
-
if (!ctx.input.privileges?.length)
|
|
172
|
-
throw postgresServiceError('privileges are required for grant/revoke actions');
|
|
173
|
-
if (!ctx.input.on)
|
|
174
|
-
throw postgresServiceError(
|
|
175
|
-
'on (object specification) is required for grant/revoke actions'
|
|
176
|
-
);
|
|
177
|
-
if (!ctx.input.grantee)
|
|
178
|
-
throw postgresServiceError('grantee is required for grant/revoke actions');
|
|
179
|
-
|
|
180
|
-
let privs = ctx.input.privileges.join(', ');
|
|
181
|
-
let objectType = ctx.input.on.objectType;
|
|
182
|
-
let objectName: string;
|
|
183
|
-
|
|
184
|
-
if (objectType === 'TABLE' || objectType === 'SEQUENCE') {
|
|
185
|
-
objectName = qualifiedTableName(ctx.input.on.objectName, ctx.input.on.schemaName);
|
|
186
|
-
} else if (
|
|
187
|
-
objectType === 'ALL TABLES IN SCHEMA' ||
|
|
188
|
-
objectType === 'ALL SEQUENCES IN SCHEMA'
|
|
189
|
-
) {
|
|
190
|
-
objectName = escapeIdentifier(ctx.input.on.objectName);
|
|
191
|
-
} else {
|
|
192
|
-
objectName = escapeIdentifier(ctx.input.on.objectName);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
let verb = ctx.input.action === 'grant' ? 'GRANT' : 'REVOKE';
|
|
196
|
-
let preposition = ctx.input.action === 'grant' ? 'TO' : 'FROM';
|
|
197
|
-
|
|
198
|
-
sql = `${verb} ${privs} ON ${objectType} ${objectName} ${preposition} ${escapeIdentifier(ctx.input.grantee)}`;
|
|
199
|
-
} else {
|
|
200
|
-
throw postgresServiceError(`Unknown action: ${ctx.input.action}`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
ctx.info(`Executing: ${sql}`);
|
|
204
|
-
await client.query(sql, ctx.config.queryTimeout);
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
output: {
|
|
208
|
-
success: true,
|
|
209
|
-
executedSql: sql
|
|
210
|
-
},
|
|
211
|
-
message: `Successfully executed \`${ctx.input.action.toUpperCase()}\` operation.`
|
|
212
|
-
};
|
|
213
|
-
})
|
|
214
|
-
.build();
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient, escapeIdentifier } from '../lib/helpers';
|
|
4
|
-
import { postgresServiceError } from '../lib/errors';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
export let manageSchemas = SlateTool.create(spec, {
|
|
8
|
-
name: 'Manage Schemas',
|
|
9
|
-
key: 'manage_schemas',
|
|
10
|
-
description:
|
|
11
|
-
'Create, rename, or drop PostgreSQL schemas. Schemas are namespaces for tables, views, functions, and other database objects.',
|
|
12
|
-
instructions: [
|
|
13
|
-
'Use create for a suite-owned or application-owned namespace before creating tables or views.',
|
|
14
|
-
'Dropping a schema requires confirmDrop=true to avoid accidental removal of contained objects.'
|
|
15
|
-
],
|
|
16
|
-
tags: {
|
|
17
|
-
destructive: true
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
.input(
|
|
21
|
-
z.object({
|
|
22
|
-
action: z.enum(['create', 'rename', 'drop']).describe('Action to perform'),
|
|
23
|
-
schemaName: z.string().describe('Schema name to create, rename, or drop'),
|
|
24
|
-
authorizationRole: z
|
|
25
|
-
.string()
|
|
26
|
-
.optional()
|
|
27
|
-
.describe('Role that should own the new schema (create only)'),
|
|
28
|
-
newSchemaName: z.string().optional().describe('New schema name (rename only)'),
|
|
29
|
-
ifNotExists: z
|
|
30
|
-
.boolean()
|
|
31
|
-
.optional()
|
|
32
|
-
.default(false)
|
|
33
|
-
.describe('Add IF NOT EXISTS for create action'),
|
|
34
|
-
ifExists: z
|
|
35
|
-
.boolean()
|
|
36
|
-
.optional()
|
|
37
|
-
.default(false)
|
|
38
|
-
.describe('Add IF EXISTS for drop action'),
|
|
39
|
-
cascade: z
|
|
40
|
-
.boolean()
|
|
41
|
-
.optional()
|
|
42
|
-
.default(false)
|
|
43
|
-
.describe('Use CASCADE when dropping a schema'),
|
|
44
|
-
confirmDrop: z
|
|
45
|
-
.boolean()
|
|
46
|
-
.optional()
|
|
47
|
-
.default(false)
|
|
48
|
-
.describe('Must be true for drop action')
|
|
49
|
-
})
|
|
50
|
-
)
|
|
51
|
-
.output(
|
|
52
|
-
z.object({
|
|
53
|
-
success: z.boolean().describe('Whether the operation completed successfully'),
|
|
54
|
-
executedSql: z.string().describe('The SQL statement that was executed'),
|
|
55
|
-
schemaName: z.string().describe('Schema affected by the operation'),
|
|
56
|
-
newSchemaName: z.string().optional().describe('New schema name for rename action')
|
|
57
|
-
})
|
|
58
|
-
)
|
|
59
|
-
.handleInvocation(async ctx => {
|
|
60
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
61
|
-
let sql: string;
|
|
62
|
-
|
|
63
|
-
if (ctx.input.action === 'create') {
|
|
64
|
-
if (/^pg_/i.test(ctx.input.schemaName)) {
|
|
65
|
-
throw postgresServiceError(
|
|
66
|
-
'Schema names beginning with "pg_" are reserved by PostgreSQL.'
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let ifNotExists = ctx.input.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
71
|
-
let authorization = ctx.input.authorizationRole
|
|
72
|
-
? ` AUTHORIZATION ${escapeIdentifier(ctx.input.authorizationRole)}`
|
|
73
|
-
: '';
|
|
74
|
-
sql = `CREATE SCHEMA ${ifNotExists}${escapeIdentifier(ctx.input.schemaName)}${authorization}`;
|
|
75
|
-
} else if (ctx.input.action === 'rename') {
|
|
76
|
-
if (!ctx.input.newSchemaName) {
|
|
77
|
-
throw postgresServiceError('newSchemaName is required for rename action.');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (/^pg_/i.test(ctx.input.newSchemaName)) {
|
|
81
|
-
throw postgresServiceError(
|
|
82
|
-
'Schema names beginning with "pg_" are reserved by PostgreSQL.'
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
sql = `ALTER SCHEMA ${escapeIdentifier(ctx.input.schemaName)} RENAME TO ${escapeIdentifier(
|
|
87
|
-
ctx.input.newSchemaName
|
|
88
|
-
)}`;
|
|
89
|
-
} else if (ctx.input.action === 'drop') {
|
|
90
|
-
if (!ctx.input.confirmDrop) {
|
|
91
|
-
throw postgresServiceError('confirmDrop must be true to drop a schema.');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let ifExists = ctx.input.ifExists ? 'IF EXISTS ' : '';
|
|
95
|
-
let dropBehavior = ctx.input.cascade ? ' CASCADE' : ' RESTRICT';
|
|
96
|
-
sql = `DROP SCHEMA ${ifExists}${escapeIdentifier(ctx.input.schemaName)}${dropBehavior}`;
|
|
97
|
-
} else {
|
|
98
|
-
throw postgresServiceError(`Unknown action: ${ctx.input.action}`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
ctx.info(`Executing: ${sql}`);
|
|
102
|
-
await client.query(sql, ctx.config.queryTimeout);
|
|
103
|
-
|
|
104
|
-
let actionLabel =
|
|
105
|
-
ctx.input.action === 'create'
|
|
106
|
-
? 'Created'
|
|
107
|
-
: ctx.input.action === 'rename'
|
|
108
|
-
? 'Renamed'
|
|
109
|
-
: 'Dropped';
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
output: {
|
|
113
|
-
success: true,
|
|
114
|
-
executedSql: sql,
|
|
115
|
-
schemaName: ctx.input.schemaName,
|
|
116
|
-
newSchemaName: ctx.input.newSchemaName
|
|
117
|
-
},
|
|
118
|
-
message: `${actionLabel} schema \`${ctx.input.schemaName}\`.`
|
|
119
|
-
};
|
|
120
|
-
})
|
|
121
|
-
.build();
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { SlateTool } from '@slates/provider';
|
|
2
|
-
import { spec } from '../spec';
|
|
3
|
-
import { createClient, escapeIdentifier, qualifiedTableName } from '../lib/helpers';
|
|
4
|
-
import { postgresServiceError } from '../lib/errors';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
let columnDefinitionSchema = z.object({
|
|
8
|
-
columnName: z.string().describe('Name of the column'),
|
|
9
|
-
dataType: z
|
|
10
|
-
.string()
|
|
11
|
-
.describe(
|
|
12
|
-
'PostgreSQL data type (e.g., text, integer, bigint, boolean, jsonb, timestamptz, uuid)'
|
|
13
|
-
),
|
|
14
|
-
nullable: z
|
|
15
|
-
.boolean()
|
|
16
|
-
.optional()
|
|
17
|
-
.default(true)
|
|
18
|
-
.describe('Whether the column allows NULL values'),
|
|
19
|
-
defaultValue: z
|
|
20
|
-
.string()
|
|
21
|
-
.optional()
|
|
22
|
-
.describe(
|
|
23
|
-
'SQL expression for the default value (e.g., "now()", "0", "gen_random_uuid()")'
|
|
24
|
-
),
|
|
25
|
-
primaryKey: z
|
|
26
|
-
.boolean()
|
|
27
|
-
.optional()
|
|
28
|
-
.default(false)
|
|
29
|
-
.describe('Whether this column is the primary key'),
|
|
30
|
-
unique: z
|
|
31
|
-
.boolean()
|
|
32
|
-
.optional()
|
|
33
|
-
.default(false)
|
|
34
|
-
.describe('Whether this column has a unique constraint'),
|
|
35
|
-
references: z
|
|
36
|
-
.object({
|
|
37
|
-
tableName: z.string().describe('Referenced table name'),
|
|
38
|
-
columnName: z.string().describe('Referenced column name'),
|
|
39
|
-
schemaName: z.string().optional().describe('Referenced table schema')
|
|
40
|
-
})
|
|
41
|
-
.optional()
|
|
42
|
-
.describe('Foreign key reference')
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
export let manageTable = SlateTool.create(spec, {
|
|
46
|
-
name: 'Manage Table',
|
|
47
|
-
key: 'manage_table',
|
|
48
|
-
description: `Create, alter, or drop a PostgreSQL table. Supports creating tables with columns, constraints, and foreign keys.
|
|
49
|
-
For altering tables, supports adding columns, dropping columns, renaming columns, altering column types, and renaming the table.`,
|
|
50
|
-
instructions: [
|
|
51
|
-
'For creating tables, provide the full column definitions.',
|
|
52
|
-
'For altering tables, specify only the changes you want to make.',
|
|
53
|
-
'Use the "drop" action with caution as it permanently removes the table and all its data.'
|
|
54
|
-
],
|
|
55
|
-
tags: {
|
|
56
|
-
destructive: true
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
.input(
|
|
60
|
-
z.object({
|
|
61
|
-
action: z.enum(['create', 'alter', 'drop']).describe('Action to perform on the table'),
|
|
62
|
-
tableName: z.string().describe('Name of the table'),
|
|
63
|
-
schemaName: z.string().optional().describe('Schema for the table'),
|
|
64
|
-
|
|
65
|
-
// Create options
|
|
66
|
-
columns: z
|
|
67
|
-
.array(columnDefinitionSchema)
|
|
68
|
-
.optional()
|
|
69
|
-
.describe('Column definitions (required for create action)'),
|
|
70
|
-
ifNotExists: z
|
|
71
|
-
.boolean()
|
|
72
|
-
.optional()
|
|
73
|
-
.default(false)
|
|
74
|
-
.describe('Add IF NOT EXISTS clause for create action'),
|
|
75
|
-
|
|
76
|
-
// Alter options
|
|
77
|
-
addColumns: z
|
|
78
|
-
.array(columnDefinitionSchema)
|
|
79
|
-
.optional()
|
|
80
|
-
.describe('Columns to add to the table'),
|
|
81
|
-
dropColumns: z
|
|
82
|
-
.array(z.string())
|
|
83
|
-
.optional()
|
|
84
|
-
.describe('Column names to drop from the table'),
|
|
85
|
-
renameColumn: z
|
|
86
|
-
.object({
|
|
87
|
-
from: z.string().describe('Current column name'),
|
|
88
|
-
to: z.string().describe('New column name')
|
|
89
|
-
})
|
|
90
|
-
.optional()
|
|
91
|
-
.describe('Rename a column'),
|
|
92
|
-
renameTable: z.string().optional().describe('New table name (for rename operation)'),
|
|
93
|
-
alterColumns: z
|
|
94
|
-
.array(
|
|
95
|
-
z.object({
|
|
96
|
-
columnName: z.string().describe('Column to alter'),
|
|
97
|
-
setDataType: z.string().optional().describe('New data type'),
|
|
98
|
-
setDefault: z.string().optional().describe('New default value expression'),
|
|
99
|
-
dropDefault: z.boolean().optional().describe('Remove the default value'),
|
|
100
|
-
setNotNull: z.boolean().optional().describe('Add NOT NULL constraint'),
|
|
101
|
-
dropNotNull: z.boolean().optional().describe('Remove NOT NULL constraint')
|
|
102
|
-
})
|
|
103
|
-
)
|
|
104
|
-
.optional()
|
|
105
|
-
.describe('Columns to alter'),
|
|
106
|
-
|
|
107
|
-
// Drop options
|
|
108
|
-
cascade: z
|
|
109
|
-
.boolean()
|
|
110
|
-
.optional()
|
|
111
|
-
.default(false)
|
|
112
|
-
.describe('Use CASCADE when dropping (also drops dependent objects)'),
|
|
113
|
-
ifExists: z
|
|
114
|
-
.boolean()
|
|
115
|
-
.optional()
|
|
116
|
-
.default(false)
|
|
117
|
-
.describe('Add IF EXISTS clause for drop action')
|
|
118
|
-
})
|
|
119
|
-
)
|
|
120
|
-
.output(
|
|
121
|
-
z.object({
|
|
122
|
-
success: z.boolean().describe('Whether the operation completed successfully'),
|
|
123
|
-
executedSql: z.string().describe('The SQL statement(s) that were executed')
|
|
124
|
-
})
|
|
125
|
-
)
|
|
126
|
-
.handleInvocation(async ctx => {
|
|
127
|
-
let client = createClient(ctx.auth, ctx.config);
|
|
128
|
-
let schema = ctx.input.schemaName || ctx.config.defaultSchema;
|
|
129
|
-
let fullTableName = qualifiedTableName(ctx.input.tableName, schema);
|
|
130
|
-
let statements: string[] = [];
|
|
131
|
-
|
|
132
|
-
if (ctx.input.action === 'create') {
|
|
133
|
-
if (!ctx.input.columns || ctx.input.columns.length === 0) {
|
|
134
|
-
throw postgresServiceError('Column definitions are required for create action');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
let columnDefs: string[] = [];
|
|
138
|
-
let pkColumns: string[] = [];
|
|
139
|
-
|
|
140
|
-
for (let col of ctx.input.columns) {
|
|
141
|
-
let def = `${escapeIdentifier(col.columnName)} ${col.dataType}`;
|
|
142
|
-
if (!col.nullable) def += ' NOT NULL';
|
|
143
|
-
if (col.defaultValue) def += ` DEFAULT ${col.defaultValue}`;
|
|
144
|
-
if (col.unique) def += ' UNIQUE';
|
|
145
|
-
if (col.primaryKey) pkColumns.push(col.columnName);
|
|
146
|
-
if (col.references) {
|
|
147
|
-
let refTable = qualifiedTableName(
|
|
148
|
-
col.references.tableName,
|
|
149
|
-
col.references.schemaName
|
|
150
|
-
);
|
|
151
|
-
def += ` REFERENCES ${refTable}(${escapeIdentifier(col.references.columnName)})`;
|
|
152
|
-
}
|
|
153
|
-
columnDefs.push(def);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (pkColumns.length > 0) {
|
|
157
|
-
columnDefs.push(`PRIMARY KEY (${pkColumns.map(escapeIdentifier).join(', ')})`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
let ifNotExists = ctx.input.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
161
|
-
statements.push(
|
|
162
|
-
`CREATE TABLE ${ifNotExists}${fullTableName} (\n ${columnDefs.join(',\n ')}\n)`
|
|
163
|
-
);
|
|
164
|
-
} else if (ctx.input.action === 'alter') {
|
|
165
|
-
if (ctx.input.addColumns) {
|
|
166
|
-
for (let col of ctx.input.addColumns) {
|
|
167
|
-
let def = `${escapeIdentifier(col.columnName)} ${col.dataType}`;
|
|
168
|
-
if (!col.nullable) def += ' NOT NULL';
|
|
169
|
-
if (col.defaultValue) def += ` DEFAULT ${col.defaultValue}`;
|
|
170
|
-
if (col.unique) def += ' UNIQUE';
|
|
171
|
-
if (col.references) {
|
|
172
|
-
let refTable = qualifiedTableName(
|
|
173
|
-
col.references.tableName,
|
|
174
|
-
col.references.schemaName
|
|
175
|
-
);
|
|
176
|
-
def += ` REFERENCES ${refTable}(${escapeIdentifier(col.references.columnName)})`;
|
|
177
|
-
}
|
|
178
|
-
statements.push(`ALTER TABLE ${fullTableName} ADD COLUMN ${def}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (ctx.input.dropColumns) {
|
|
183
|
-
for (let colName of ctx.input.dropColumns) {
|
|
184
|
-
statements.push(
|
|
185
|
-
`ALTER TABLE ${fullTableName} DROP COLUMN ${escapeIdentifier(colName)}`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (ctx.input.renameColumn) {
|
|
191
|
-
statements.push(
|
|
192
|
-
`ALTER TABLE ${fullTableName} RENAME COLUMN ${escapeIdentifier(ctx.input.renameColumn.from)} TO ${escapeIdentifier(ctx.input.renameColumn.to)}`
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (ctx.input.alterColumns) {
|
|
197
|
-
for (let alter of ctx.input.alterColumns) {
|
|
198
|
-
let colName = escapeIdentifier(alter.columnName);
|
|
199
|
-
if (alter.setDataType) {
|
|
200
|
-
statements.push(
|
|
201
|
-
`ALTER TABLE ${fullTableName} ALTER COLUMN ${colName} TYPE ${alter.setDataType}`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
if (alter.setDefault) {
|
|
205
|
-
statements.push(
|
|
206
|
-
`ALTER TABLE ${fullTableName} ALTER COLUMN ${colName} SET DEFAULT ${alter.setDefault}`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
if (alter.dropDefault) {
|
|
210
|
-
statements.push(
|
|
211
|
-
`ALTER TABLE ${fullTableName} ALTER COLUMN ${colName} DROP DEFAULT`
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
if (alter.setNotNull) {
|
|
215
|
-
statements.push(
|
|
216
|
-
`ALTER TABLE ${fullTableName} ALTER COLUMN ${colName} SET NOT NULL`
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
if (alter.dropNotNull) {
|
|
220
|
-
statements.push(
|
|
221
|
-
`ALTER TABLE ${fullTableName} ALTER COLUMN ${colName} DROP NOT NULL`
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (ctx.input.renameTable) {
|
|
228
|
-
statements.push(
|
|
229
|
-
`ALTER TABLE ${fullTableName} RENAME TO ${escapeIdentifier(ctx.input.renameTable)}`
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (statements.length === 0) {
|
|
234
|
-
throw postgresServiceError(
|
|
235
|
-
'No alter operations specified. Provide addColumns, dropColumns, renameColumn, alterColumns, or renameTable.'
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
} else if (ctx.input.action === 'drop') {
|
|
239
|
-
let ifExists = ctx.input.ifExists ? 'IF EXISTS ' : '';
|
|
240
|
-
let cascade = ctx.input.cascade ? ' CASCADE' : '';
|
|
241
|
-
statements.push(`DROP TABLE ${ifExists}${fullTableName}${cascade}`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
let executedSql = statements.join(';\n');
|
|
245
|
-
ctx.info(`Executing: ${executedSql}`);
|
|
246
|
-
|
|
247
|
-
// Execute all statements
|
|
248
|
-
for (let stmt of statements) {
|
|
249
|
-
await client.query(stmt, ctx.config.queryTimeout);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
let actionLabel =
|
|
253
|
-
ctx.input.action === 'create'
|
|
254
|
-
? 'Created'
|
|
255
|
-
: ctx.input.action === 'alter'
|
|
256
|
-
? 'Altered'
|
|
257
|
-
: 'Dropped';
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
output: {
|
|
261
|
-
success: true,
|
|
262
|
-
executedSql
|
|
263
|
-
},
|
|
264
|
-
message: `${actionLabel} table \`${ctx.input.tableName}\`. Executed **${statements.length}** statement(s).`
|
|
265
|
-
};
|
|
266
|
-
})
|
|
267
|
-
.build();
|