@tejasanik/postgres-mcp-server 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +63 -10
  2. package/dist/__tests__/analysis-tools.test.d.ts +2 -0
  3. package/dist/__tests__/analysis-tools.test.d.ts.map +1 -0
  4. package/dist/__tests__/analysis-tools.test.js +294 -0
  5. package/dist/__tests__/analysis-tools.test.js.map +1 -0
  6. package/dist/__tests__/db-manager.test.d.ts +2 -0
  7. package/dist/__tests__/db-manager.test.d.ts.map +1 -0
  8. package/dist/__tests__/db-manager.test.js +243 -0
  9. package/dist/__tests__/db-manager.test.js.map +1 -0
  10. package/dist/__tests__/mcp-server.test.d.ts +13 -0
  11. package/dist/__tests__/mcp-server.test.d.ts.map +1 -0
  12. package/dist/__tests__/mcp-server.test.js +131 -0
  13. package/dist/__tests__/mcp-server.test.js.map +1 -0
  14. package/dist/__tests__/schema-tools.test.d.ts +2 -0
  15. package/dist/__tests__/schema-tools.test.d.ts.map +1 -0
  16. package/dist/__tests__/schema-tools.test.js +171 -0
  17. package/dist/__tests__/schema-tools.test.js.map +1 -0
  18. package/dist/__tests__/server-tools.test.d.ts +2 -0
  19. package/dist/__tests__/server-tools.test.d.ts.map +1 -0
  20. package/dist/__tests__/server-tools.test.js +94 -0
  21. package/dist/__tests__/server-tools.test.js.map +1 -0
  22. package/dist/__tests__/sql-tools.test.d.ts +2 -0
  23. package/dist/__tests__/sql-tools.test.d.ts.map +1 -0
  24. package/dist/__tests__/sql-tools.test.js +235 -0
  25. package/dist/__tests__/sql-tools.test.js.map +1 -0
  26. package/dist/__tests__/validation.test.d.ts +2 -0
  27. package/dist/__tests__/validation.test.d.ts.map +1 -0
  28. package/dist/__tests__/validation.test.js +203 -0
  29. package/dist/__tests__/validation.test.js.map +1 -0
  30. package/dist/db-manager.d.ts +17 -4
  31. package/dist/db-manager.d.ts.map +1 -1
  32. package/dist/db-manager.js +143 -26
  33. package/dist/db-manager.js.map +1 -1
  34. package/dist/index.js +62 -26
  35. package/dist/index.js.map +1 -1
  36. package/dist/tools/analysis-tools.d.ts +1 -0
  37. package/dist/tools/analysis-tools.d.ts.map +1 -1
  38. package/dist/tools/analysis-tools.js +158 -81
  39. package/dist/tools/analysis-tools.js.map +1 -1
  40. package/dist/tools/index.js +4 -20
  41. package/dist/tools/index.js.map +1 -1
  42. package/dist/tools/schema-tools.d.ts.map +1 -1
  43. package/dist/tools/schema-tools.js +71 -40
  44. package/dist/tools/schema-tools.js.map +1 -1
  45. package/dist/tools/server-tools.d.ts +11 -1
  46. package/dist/tools/server-tools.d.ts.map +1 -1
  47. package/dist/tools/server-tools.js +23 -14
  48. package/dist/tools/server-tools.js.map +1 -1
  49. package/dist/tools/sql-tools.d.ts.map +1 -1
  50. package/dist/tools/sql-tools.js +88 -61
  51. package/dist/tools/sql-tools.js.map +1 -1
  52. package/dist/types.d.ts +13 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +1 -2
  55. package/dist/utils/index.d.ts +2 -0
  56. package/dist/utils/index.d.ts.map +1 -0
  57. package/dist/utils/index.js +2 -0
  58. package/dist/utils/index.js.map +1 -0
  59. package/dist/utils/validation.d.ts +27 -0
  60. package/dist/utils/validation.d.ts.map +1 -0
  61. package/dist/utils/validation.js +133 -0
  62. package/dist/utils/validation.js.map +1 -0
  63. package/package.json +8 -2
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2
+ import { listServersAndDbs, switchServerDb } from '../tools/server-tools.js';
3
+ import { resetDbManager } from '../db-manager.js';
4
+ describe('Server Tools', () => {
5
+ beforeEach(() => {
6
+ resetDbManager();
7
+ delete process.env.POSTGRES_SERVERS;
8
+ delete process.env.POSTGRES_ACCESS_MODE;
9
+ });
10
+ afterEach(() => {
11
+ resetDbManager();
12
+ });
13
+ describe('listServersAndDbs', () => {
14
+ it('should return empty list when no servers configured', async () => {
15
+ const result = await listServersAndDbs({});
16
+ expect(result.servers).toEqual([]);
17
+ expect(result.currentServer).toBeNull();
18
+ expect(result.currentDatabase).toBeNull();
19
+ });
20
+ it('should list all configured servers', async () => {
21
+ process.env.POSTGRES_SERVERS = JSON.stringify({
22
+ dev: { host: 'dev.example.com', port: '5432', username: 'user', password: 'pass' },
23
+ staging: { host: 'staging.example.com', port: '5432', username: 'user', password: 'pass' },
24
+ prod: { host: 'prod.example.com', port: '5432', username: 'user', password: 'pass' }
25
+ });
26
+ const result = await listServersAndDbs({});
27
+ expect(result.servers).toHaveLength(3);
28
+ expect(result.servers.map(s => s.name)).toContain('dev');
29
+ expect(result.servers.map(s => s.name)).toContain('staging');
30
+ expect(result.servers.map(s => s.name)).toContain('prod');
31
+ });
32
+ it('should filter servers by name', async () => {
33
+ process.env.POSTGRES_SERVERS = JSON.stringify({
34
+ dev: { host: 'dev.example.com', port: '5432', username: 'user', password: 'pass' },
35
+ dev_backup: { host: 'dev-backup.example.com', port: '5432', username: 'user', password: 'pass' },
36
+ prod: { host: 'prod.example.com', port: '5432', username: 'user', password: 'pass' }
37
+ });
38
+ const result = await listServersAndDbs({ filter: 'dev' });
39
+ expect(result.servers).toHaveLength(2);
40
+ expect(result.servers.map(s => s.name)).toContain('dev');
41
+ expect(result.servers.map(s => s.name)).toContain('dev_backup');
42
+ expect(result.servers.map(s => s.name)).not.toContain('prod');
43
+ });
44
+ it('should filter servers by host', async () => {
45
+ process.env.POSTGRES_SERVERS = JSON.stringify({
46
+ server1: { host: 'us-east.example.com', port: '5432', username: 'user', password: 'pass' },
47
+ server2: { host: 'us-west.example.com', port: '5432', username: 'user', password: 'pass' },
48
+ server3: { host: 'eu.example.com', port: '5432', username: 'user', password: 'pass' }
49
+ });
50
+ const result = await listServersAndDbs({ filter: 'us-' });
51
+ expect(result.servers).toHaveLength(2);
52
+ });
53
+ it('should show server connection status', async () => {
54
+ process.env.POSTGRES_SERVERS = JSON.stringify({
55
+ dev: { host: 'localhost', port: '5432', username: 'user', password: 'pass' }
56
+ });
57
+ const result = await listServersAndDbs({});
58
+ expect(result.servers[0].isConnected).toBe(false);
59
+ });
60
+ it('should use default port when not specified', async () => {
61
+ process.env.POSTGRES_SERVERS = JSON.stringify({
62
+ dev: { host: 'localhost', username: 'user', password: 'pass' }
63
+ });
64
+ const result = await listServersAndDbs({});
65
+ expect(result.servers[0].port).toBe('5432');
66
+ });
67
+ });
68
+ describe('switchServerDb', () => {
69
+ it('should throw when server not found', async () => {
70
+ process.env.POSTGRES_SERVERS = JSON.stringify({
71
+ dev: { host: 'localhost', port: '5432', username: 'user', password: 'pass' }
72
+ });
73
+ await expect(switchServerDb({ server: 'nonexistent' }))
74
+ .rejects.toThrow("Server 'nonexistent' not found");
75
+ });
76
+ it('should validate server parameter is required', async () => {
77
+ process.env.POSTGRES_SERVERS = JSON.stringify({
78
+ dev: { host: 'localhost', port: '5432', username: 'user', password: 'pass' }
79
+ });
80
+ await expect(switchServerDb({ server: '' }))
81
+ .rejects.toThrow();
82
+ });
83
+ it('should validate database name format', async () => {
84
+ process.env.POSTGRES_SERVERS = JSON.stringify({
85
+ dev: { host: 'localhost', port: '5432', username: 'user', password: 'pass' }
86
+ });
87
+ await expect(switchServerDb({ server: 'dev', database: 'invalid;db' }))
88
+ .rejects.toThrow('Invalid database name');
89
+ });
90
+ // Note: Actual connection tests would require a real database
91
+ // These tests focus on input validation
92
+ });
93
+ });
94
+ //# sourceMappingURL=server-tools.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-tools.test.js","sourceRoot":"","sources":["../../src/__tests__/server-tools.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sql-tools.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-tools.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sql-tools.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,235 @@
1
+ import { jest, describe, it, expect, beforeEach, beforeAll } from '@jest/globals';
2
+ import * as fs from 'fs';
3
+ // Use jest.unstable_mockModule for ESM
4
+ const mockQuery = jest.fn();
5
+ const mockGetClient = jest.fn();
6
+ const mockIsConnected = jest.fn();
7
+ jest.unstable_mockModule('../db-manager.js', () => ({
8
+ getDbManager: jest.fn(() => ({
9
+ query: mockQuery,
10
+ getClient: mockGetClient,
11
+ isConnected: mockIsConnected.mockReturnValue(true),
12
+ })),
13
+ resetDbManager: jest.fn(),
14
+ }));
15
+ // Dynamic import after mock
16
+ let executeSql;
17
+ let explainQuery;
18
+ beforeAll(async () => {
19
+ const module = await import('../tools/sql-tools.js');
20
+ executeSql = module.executeSql;
21
+ explainQuery = module.explainQuery;
22
+ });
23
+ describe('SQL Tools', () => {
24
+ beforeEach(() => {
25
+ jest.clearAllMocks();
26
+ mockIsConnected.mockReturnValue(true);
27
+ });
28
+ describe('executeSql', () => {
29
+ it('should require sql parameter', async () => {
30
+ await expect(executeSql({ sql: '' }))
31
+ .rejects.toThrow('sql parameter is required');
32
+ await expect(executeSql({ sql: null }))
33
+ .rejects.toThrow('sql parameter is required');
34
+ });
35
+ it('should reject SQL that is too long', async () => {
36
+ const longSql = 'SELECT ' + 'a'.repeat(100001);
37
+ await expect(executeSql({ sql: longSql }))
38
+ .rejects.toThrow('exceeds maximum length');
39
+ });
40
+ it('should return results for small result sets', async () => {
41
+ mockQuery.mockResolvedValue({
42
+ rows: [{ id: 1, name: 'Test' }],
43
+ fields: [{ name: 'id' }, { name: 'name' }]
44
+ });
45
+ const result = await executeSql({ sql: 'SELECT * FROM users' });
46
+ expect(result.rows).toHaveLength(1);
47
+ expect(result.rowCount).toBe(1);
48
+ expect(result.fields).toEqual(['id', 'name']);
49
+ expect(result.outputFile).toBeUndefined();
50
+ expect(result.truncated).toBeUndefined();
51
+ });
52
+ it('should write large results to file', async () => {
53
+ const largeRows = Array.from({ length: 2000 }, (_, i) => ({ id: i, name: `User ${i}` }));
54
+ mockQuery.mockResolvedValue({
55
+ rows: largeRows,
56
+ fields: [{ name: 'id' }, { name: 'name' }]
57
+ });
58
+ const result = await executeSql({ sql: 'SELECT * FROM users' });
59
+ expect(result.rows).toEqual([]);
60
+ expect(result.rowCount).toBe(2000);
61
+ expect(result.outputFile).toBeDefined();
62
+ expect(result.truncated).toBe(true);
63
+ // Verify file was created with correct permissions
64
+ if (result.outputFile) {
65
+ expect(fs.existsSync(result.outputFile)).toBe(true);
66
+ // Read and verify content
67
+ const content = JSON.parse(fs.readFileSync(result.outputFile, 'utf-8'));
68
+ expect(content.totalRows).toBe(2000);
69
+ expect(content.rows).toHaveLength(2000);
70
+ // Clean up
71
+ fs.unlinkSync(result.outputFile);
72
+ }
73
+ });
74
+ it('should respect maxRows parameter', async () => {
75
+ const rows = Array.from({ length: 50 }, (_, i) => ({ id: i }));
76
+ mockQuery.mockResolvedValue({
77
+ rows,
78
+ fields: [{ name: 'id' }]
79
+ });
80
+ const result = await executeSql({ sql: 'SELECT * FROM users', maxRows: 10 });
81
+ // Should write to file because rows > maxRows
82
+ expect(result.truncated).toBe(true);
83
+ expect(result.outputFile).toBeDefined();
84
+ // Clean up
85
+ if (result.outputFile) {
86
+ fs.unlinkSync(result.outputFile);
87
+ }
88
+ });
89
+ it('should validate maxRows parameter', async () => {
90
+ // Invalid maxRows should throw
91
+ await expect(executeSql({ sql: 'SELECT 1', maxRows: -1 }))
92
+ .rejects.toThrow('maxRows must be an integer between');
93
+ });
94
+ it('should handle empty result sets', async () => {
95
+ mockQuery.mockResolvedValue({
96
+ rows: [],
97
+ fields: [{ name: 'id' }]
98
+ });
99
+ const result = await executeSql({ sql: 'SELECT * FROM users WHERE 1=0' });
100
+ expect(result.rows).toEqual([]);
101
+ expect(result.rowCount).toBe(0);
102
+ expect(result.outputFile).toBeUndefined();
103
+ });
104
+ });
105
+ describe('explainQuery', () => {
106
+ let mockClient;
107
+ beforeEach(() => {
108
+ mockClient = {
109
+ query: jest.fn(),
110
+ release: jest.fn()
111
+ };
112
+ mockGetClient.mockResolvedValue(mockClient);
113
+ });
114
+ it('should require sql parameter', async () => {
115
+ await expect(explainQuery({ sql: '' }))
116
+ .rejects.toThrow('sql parameter is required');
117
+ });
118
+ it('should reject SQL that is too long', async () => {
119
+ const longSql = 'SELECT ' + 'a'.repeat(100001);
120
+ await expect(explainQuery({ sql: longSql }))
121
+ .rejects.toThrow('exceeds maximum length');
122
+ });
123
+ it('should return execution plan in JSON format', async () => {
124
+ mockClient.query.mockResolvedValue({
125
+ rows: [{ 'QUERY PLAN': [{ Plan: { 'Node Type': 'Seq Scan' } }] }]
126
+ });
127
+ const result = await explainQuery({ sql: 'SELECT * FROM users' });
128
+ expect(result.plan).toEqual({ Plan: { 'Node Type': 'Seq Scan' } });
129
+ expect(mockClient.release).toHaveBeenCalled();
130
+ });
131
+ it('should return execution plan in text format', async () => {
132
+ mockClient.query.mockResolvedValue({
133
+ rows: [
134
+ { 'QUERY PLAN': 'Seq Scan on users' },
135
+ { 'QUERY PLAN': ' Filter: (id = 1)' }
136
+ ]
137
+ });
138
+ const result = await explainQuery({ sql: 'SELECT * FROM users', format: 'text' });
139
+ expect(result.plan).toContain('Seq Scan');
140
+ expect(mockClient.release).toHaveBeenCalled();
141
+ });
142
+ it('should block EXPLAIN ANALYZE on write queries', async () => {
143
+ await expect(explainQuery({ sql: 'DELETE FROM users', analyze: true }))
144
+ .rejects.toThrow('EXPLAIN ANALYZE is not allowed for write queries');
145
+ await expect(explainQuery({ sql: 'INSERT INTO users VALUES (1)', analyze: true }))
146
+ .rejects.toThrow('EXPLAIN ANALYZE is not allowed for write queries');
147
+ await expect(explainQuery({ sql: 'UPDATE users SET name = \'test\'', analyze: true }))
148
+ .rejects.toThrow('EXPLAIN ANALYZE is not allowed for write queries');
149
+ });
150
+ it('should allow EXPLAIN ANALYZE on SELECT queries', async () => {
151
+ mockClient.query.mockResolvedValue({
152
+ rows: [{ 'QUERY PLAN': [{ Plan: { 'Node Type': 'Seq Scan' } }] }]
153
+ });
154
+ const result = await explainQuery({ sql: 'SELECT * FROM users', analyze: true });
155
+ expect(result).toBeDefined();
156
+ const queryCall = mockClient.query.mock.calls.find((call) => typeof call[0] === 'string' && call[0].includes('EXPLAIN') && call[0].includes('ANALYZE'));
157
+ expect(queryCall).toBeDefined();
158
+ });
159
+ it('should release client even on error', async () => {
160
+ mockClient.query.mockRejectedValue(new Error('Query failed'));
161
+ await expect(explainQuery({ sql: 'SELECT * FROM users' }))
162
+ .rejects.toThrow('Query failed');
163
+ expect(mockClient.release).toHaveBeenCalled();
164
+ });
165
+ describe('hypothetical indexes', () => {
166
+ it('should validate table name in hypothetical indexes', async () => {
167
+ // Mock hypopg check to return true
168
+ mockClient.query.mockResolvedValueOnce({ rows: [{ has_hypopg: true }] });
169
+ await expect(explainQuery({
170
+ sql: 'SELECT * FROM users',
171
+ hypotheticalIndexes: [
172
+ { table: 'users; DROP TABLE', columns: ['id'] }
173
+ ]
174
+ })).rejects.toThrow('invalid characters');
175
+ });
176
+ it('should validate column names in hypothetical indexes', async () => {
177
+ // Mock hypopg check to return true
178
+ mockClient.query.mockResolvedValueOnce({ rows: [{ has_hypopg: true }] });
179
+ await expect(explainQuery({
180
+ sql: 'SELECT * FROM users',
181
+ hypotheticalIndexes: [
182
+ { table: 'users', columns: ['id; DROP'] }
183
+ ]
184
+ })).rejects.toThrow('invalid characters');
185
+ });
186
+ it('should validate index type', async () => {
187
+ // Mock hypopg check to return true
188
+ mockClient.query.mockResolvedValueOnce({ rows: [{ has_hypopg: true }] });
189
+ await expect(explainQuery({
190
+ sql: 'SELECT * FROM users',
191
+ hypotheticalIndexes: [
192
+ { table: 'users', columns: ['id'], indexType: 'invalid' }
193
+ ]
194
+ })).rejects.toThrow('Invalid index type');
195
+ });
196
+ it('should limit number of hypothetical indexes', async () => {
197
+ const manyIndexes = Array.from({ length: 11 }, (_, i) => ({
198
+ table: 'users',
199
+ columns: [`col${i}`]
200
+ }));
201
+ await expect(explainQuery({
202
+ sql: 'SELECT * FROM users',
203
+ hypotheticalIndexes: manyIndexes
204
+ })).rejects.toThrow('Maximum 10 hypothetical indexes');
205
+ });
206
+ it('should require columns in hypothetical indexes', async () => {
207
+ // Mock hypopg check to return true
208
+ mockClient.query.mockResolvedValueOnce({ rows: [{ has_hypopg: true }] });
209
+ await expect(explainQuery({
210
+ sql: 'SELECT * FROM users',
211
+ hypotheticalIndexes: [
212
+ { table: 'users', columns: [] }
213
+ ]
214
+ })).rejects.toThrow('columns array is required and must not be empty');
215
+ });
216
+ it('should handle hypothetical indexes with hypopg', async () => {
217
+ mockClient.query
218
+ .mockResolvedValueOnce({ rows: [{ has_hypopg: true }] })
219
+ .mockResolvedValueOnce({}) // hypopg_create_index
220
+ .mockResolvedValueOnce({ rows: [{ 'QUERY PLAN': [{ Plan: {} }] }] })
221
+ .mockResolvedValueOnce({}); // hypopg_reset
222
+ await explainQuery({
223
+ sql: 'SELECT * FROM users',
224
+ hypotheticalIndexes: [
225
+ { table: 'users', columns: ['id'] }
226
+ ]
227
+ });
228
+ // Verify parameterized call to hypopg
229
+ const hypopgCall = mockClient.query.mock.calls.find((call) => typeof call[0] === 'string' && call[0].includes('hypopg_create_index'));
230
+ expect(hypopgCall).toBeDefined();
231
+ });
232
+ });
233
+ });
234
+ });
235
+ //# sourceMappingURL=sql-tools.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-tools.test.js","sourceRoot":"","sources":["../../src/__tests__/sql-tools.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/validation.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,203 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { validateIdentifier, escapeIdentifier, isReadOnlySql, validatePositiveInteger, validateIndexType, } from '../utils/validation.js';
3
+ describe('Validation Utilities', () => {
4
+ describe('validateIdentifier', () => {
5
+ it('should accept valid identifiers', () => {
6
+ expect(validateIdentifier('users', 'table')).toBe('users');
7
+ expect(validateIdentifier('user_accounts', 'table')).toBe('user_accounts');
8
+ expect(validateIdentifier('_private', 'table')).toBe('_private');
9
+ expect(validateIdentifier('Table123', 'table')).toBe('Table123');
10
+ expect(validateIdentifier('column$1', 'column')).toBe('column$1');
11
+ });
12
+ it('should reject empty or null identifiers', () => {
13
+ expect(() => validateIdentifier('', 'table')).toThrow('table is required');
14
+ expect(() => validateIdentifier(null, 'table')).toThrow('table is required');
15
+ expect(() => validateIdentifier(undefined, 'table')).toThrow('table is required');
16
+ });
17
+ it('should reject identifiers starting with numbers', () => {
18
+ expect(() => validateIdentifier('123table', 'table')).toThrow('invalid characters');
19
+ });
20
+ it('should reject identifiers with special characters', () => {
21
+ expect(() => validateIdentifier('table;drop', 'table')).toThrow('invalid characters');
22
+ expect(() => validateIdentifier("table'", 'table')).toThrow('invalid characters');
23
+ expect(() => validateIdentifier('table"', 'table')).toThrow('invalid characters');
24
+ expect(() => validateIdentifier('table--', 'table')).toThrow('invalid characters');
25
+ expect(() => validateIdentifier('table/*', 'table')).toThrow('invalid characters');
26
+ });
27
+ it('should reject identifiers that are too long', () => {
28
+ const longName = 'a'.repeat(64);
29
+ expect(() => validateIdentifier(longName, 'table')).toThrow('63 characters or less');
30
+ });
31
+ it('should reject SQL injection attempts', () => {
32
+ expect(() => validateIdentifier('users; DROP TABLE users;--', 'table')).toThrow('invalid characters');
33
+ expect(() => validateIdentifier("users' OR '1'='1", 'table')).toThrow('invalid characters');
34
+ expect(() => validateIdentifier('users UNION SELECT', 'table')).toThrow('invalid characters');
35
+ });
36
+ });
37
+ describe('escapeIdentifier', () => {
38
+ it('should wrap identifiers in double quotes', () => {
39
+ expect(escapeIdentifier('users')).toBe('"users"');
40
+ expect(escapeIdentifier('public')).toBe('"public"');
41
+ });
42
+ it('should double internal double quotes', () => {
43
+ expect(escapeIdentifier('table"name')).toBe('"table""name"');
44
+ expect(escapeIdentifier('a"b"c')).toBe('"a""b""c"');
45
+ });
46
+ it('should reject empty identifiers', () => {
47
+ expect(() => escapeIdentifier('')).toThrow('Identifier is required');
48
+ expect(() => escapeIdentifier(null)).toThrow('Identifier is required');
49
+ });
50
+ });
51
+ describe('isReadOnlySql', () => {
52
+ describe('should allow read-only queries', () => {
53
+ it('SELECT statements', () => {
54
+ expect(isReadOnlySql('SELECT * FROM users').isReadOnly).toBe(true);
55
+ expect(isReadOnlySql('SELECT id, name FROM users WHERE id = 1').isReadOnly).toBe(true);
56
+ expect(isReadOnlySql(' SELECT * FROM users ').isReadOnly).toBe(true);
57
+ });
58
+ it('EXPLAIN statements', () => {
59
+ expect(isReadOnlySql('EXPLAIN SELECT * FROM users').isReadOnly).toBe(true);
60
+ expect(isReadOnlySql('EXPLAIN ANALYZE SELECT * FROM users').isReadOnly).toBe(true);
61
+ });
62
+ it('WITH (CTE) SELECT statements', () => {
63
+ expect(isReadOnlySql('WITH cte AS (SELECT * FROM users) SELECT * FROM cte').isReadOnly).toBe(true);
64
+ });
65
+ it('SHOW statements', () => {
66
+ expect(isReadOnlySql('SHOW search_path').isReadOnly).toBe(true);
67
+ });
68
+ });
69
+ describe('should block write operations', () => {
70
+ it('INSERT statements', () => {
71
+ const result = isReadOnlySql('INSERT INTO users (name) VALUES (\'test\')');
72
+ expect(result.isReadOnly).toBe(false);
73
+ expect(result.reason).toContain('INSERT');
74
+ });
75
+ it('UPDATE statements', () => {
76
+ const result = isReadOnlySql('UPDATE users SET name = \'test\'');
77
+ expect(result.isReadOnly).toBe(false);
78
+ expect(result.reason).toContain('UPDATE');
79
+ });
80
+ it('DELETE statements', () => {
81
+ const result = isReadOnlySql('DELETE FROM users WHERE id = 1');
82
+ expect(result.isReadOnly).toBe(false);
83
+ expect(result.reason).toContain('DELETE');
84
+ });
85
+ it('DROP statements', () => {
86
+ const result = isReadOnlySql('DROP TABLE users');
87
+ expect(result.isReadOnly).toBe(false);
88
+ expect(result.reason).toContain('DROP');
89
+ });
90
+ it('CREATE statements', () => {
91
+ const result = isReadOnlySql('CREATE TABLE test (id INT)');
92
+ expect(result.isReadOnly).toBe(false);
93
+ expect(result.reason).toContain('CREATE');
94
+ });
95
+ it('ALTER statements', () => {
96
+ const result = isReadOnlySql('ALTER TABLE users ADD COLUMN email TEXT');
97
+ expect(result.isReadOnly).toBe(false);
98
+ expect(result.reason).toContain('ALTER');
99
+ });
100
+ it('TRUNCATE statements', () => {
101
+ const result = isReadOnlySql('TRUNCATE TABLE users');
102
+ expect(result.isReadOnly).toBe(false);
103
+ expect(result.reason).toContain('TRUNCATE');
104
+ });
105
+ it('GRANT/REVOKE statements', () => {
106
+ expect(isReadOnlySql('GRANT SELECT ON users TO public').isReadOnly).toBe(false);
107
+ expect(isReadOnlySql('REVOKE SELECT ON users FROM public').isReadOnly).toBe(false);
108
+ });
109
+ });
110
+ describe('should detect SQL injection via CTEs', () => {
111
+ it('CTE with INSERT', () => {
112
+ const result = isReadOnlySql('WITH x AS (INSERT INTO users VALUES (1) RETURNING *) SELECT * FROM x');
113
+ expect(result.isReadOnly).toBe(false);
114
+ });
115
+ it('CTE with DELETE', () => {
116
+ const result = isReadOnlySql('WITH deleted AS (DELETE FROM users RETURNING *) SELECT * FROM deleted');
117
+ expect(result.isReadOnly).toBe(false);
118
+ });
119
+ it('CTE with UPDATE', () => {
120
+ const result = isReadOnlySql('WITH updated AS (UPDATE users SET x=1 RETURNING *) SELECT * FROM updated');
121
+ expect(result.isReadOnly).toBe(false);
122
+ });
123
+ });
124
+ describe('should detect dangerous functions', () => {
125
+ it('file read functions', () => {
126
+ expect(isReadOnlySql('SELECT pg_read_file(\'/etc/passwd\')').isReadOnly).toBe(false);
127
+ expect(isReadOnlySql('SELECT pg_read_binary_file(\'/etc/passwd\')').isReadOnly).toBe(false);
128
+ });
129
+ it('large object functions', () => {
130
+ expect(isReadOnlySql('SELECT lo_import(\'/etc/passwd\')').isReadOnly).toBe(false);
131
+ expect(isReadOnlySql('SELECT lo_export(12345, \'/tmp/file\')').isReadOnly).toBe(false);
132
+ });
133
+ it('dblink functions', () => {
134
+ expect(isReadOnlySql('SELECT dblink_exec(\'host=evil\', \'DROP TABLE users\')').isReadOnly).toBe(false);
135
+ });
136
+ });
137
+ describe('should handle comments and whitespace', () => {
138
+ it('single-line comments', () => {
139
+ const result = isReadOnlySql('-- comment\nDELETE FROM users');
140
+ expect(result.isReadOnly).toBe(false);
141
+ });
142
+ it('multi-line comments', () => {
143
+ const result = isReadOnlySql('/* comment */ DELETE FROM users');
144
+ expect(result.isReadOnly).toBe(false);
145
+ });
146
+ it('comments hiding write operations', () => {
147
+ const result = isReadOnlySql('SELECT * FROM users; -- \nDELETE FROM users');
148
+ expect(result.isReadOnly).toBe(false);
149
+ });
150
+ });
151
+ describe('should handle edge cases', () => {
152
+ it('empty or null SQL', () => {
153
+ expect(isReadOnlySql('').isReadOnly).toBe(false);
154
+ expect(isReadOnlySql(null).isReadOnly).toBe(false);
155
+ });
156
+ it('case insensitivity', () => {
157
+ expect(isReadOnlySql('delete FROM users').isReadOnly).toBe(false);
158
+ expect(isReadOnlySql('DELETE from users').isReadOnly).toBe(false);
159
+ expect(isReadOnlySql('DeLeTe FrOm users').isReadOnly).toBe(false);
160
+ });
161
+ });
162
+ });
163
+ describe('validatePositiveInteger', () => {
164
+ it('should return value for valid integers', () => {
165
+ expect(validatePositiveInteger(5, 'limit')).toBe(5);
166
+ expect(validatePositiveInteger(100, 'limit', 1, 1000)).toBe(100);
167
+ expect(validatePositiveInteger('10', 'limit')).toBe(10);
168
+ });
169
+ it('should return min for undefined/null', () => {
170
+ expect(validatePositiveInteger(undefined, 'limit', 1, 100)).toBe(1);
171
+ expect(validatePositiveInteger(null, 'limit', 5, 100)).toBe(5);
172
+ });
173
+ it('should throw for out of range values', () => {
174
+ expect(() => validatePositiveInteger(0, 'limit', 1, 100)).toThrow('between 1 and 100');
175
+ expect(() => validatePositiveInteger(101, 'limit', 1, 100)).toThrow('between 1 and 100');
176
+ expect(() => validatePositiveInteger(-5, 'limit', 1, 100)).toThrow('between 1 and 100');
177
+ });
178
+ it('should throw for non-numeric values', () => {
179
+ expect(() => validatePositiveInteger('abc', 'limit')).toThrow('must be an integer');
180
+ expect(() => validatePositiveInteger(NaN, 'limit')).toThrow('must be an integer');
181
+ });
182
+ });
183
+ describe('validateIndexType', () => {
184
+ it('should accept valid index types', () => {
185
+ expect(validateIndexType('btree')).toBe('btree');
186
+ expect(validateIndexType('BTREE')).toBe('btree');
187
+ expect(validateIndexType('hash')).toBe('hash');
188
+ expect(validateIndexType('gist')).toBe('gist');
189
+ expect(validateIndexType('gin')).toBe('gin');
190
+ expect(validateIndexType('brin')).toBe('brin');
191
+ expect(validateIndexType('spgist')).toBe('spgist');
192
+ });
193
+ it('should default to btree for empty/undefined', () => {
194
+ expect(validateIndexType('')).toBe('btree');
195
+ expect(validateIndexType(undefined)).toBe('btree');
196
+ });
197
+ it('should throw for invalid index types', () => {
198
+ expect(() => validateIndexType('invalid')).toThrow('Invalid index type');
199
+ expect(() => validateIndexType('index')).toThrow('Invalid index type');
200
+ });
201
+ });
202
+ });
203
+ //# sourceMappingURL=validation.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.test.js","sourceRoot":"","sources":["../../src/__tests__/validation.test.ts"],"names":[],"mappings":"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"}