matimo-postgres 0.1.0__tar.gz
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.
- matimo_postgres-0.1.0/.gitignore +63 -0
- matimo_postgres-0.1.0/PKG-INFO +122 -0
- matimo_postgres-0.1.0/README.md +107 -0
- matimo_postgres-0.1.0/pyproject.toml +28 -0
- matimo_postgres-0.1.0/src/matimo_postgres/__init__.py +17 -0
- matimo_postgres-0.1.0/src/matimo_postgres/tools/execute-sql/definition.yaml +59 -0
- matimo_postgres-0.1.0/src/matimo_postgres/tools/execute-sql/execute-sql.py +93 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
**/node_modules/
|
|
3
|
+
package-lock.json
|
|
4
|
+
yarn.lock
|
|
5
|
+
|
|
6
|
+
# Build output
|
|
7
|
+
**/dist/
|
|
8
|
+
*.tsbuildinfo
|
|
9
|
+
|
|
10
|
+
# Python compiled / build
|
|
11
|
+
**/__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
*$py.class
|
|
14
|
+
*.egg-info/
|
|
15
|
+
*.egg
|
|
16
|
+
**/build/
|
|
17
|
+
**/.eggs/
|
|
18
|
+
**/.venv/
|
|
19
|
+
**/.mypy_cache/
|
|
20
|
+
**/.ruff_cache/
|
|
21
|
+
**/.pytest_cache/
|
|
22
|
+
|
|
23
|
+
# Test coverage
|
|
24
|
+
**/coverage/
|
|
25
|
+
**/.nyc_output/
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
*.swp
|
|
31
|
+
*.swo
|
|
32
|
+
*~
|
|
33
|
+
.DS_Store
|
|
34
|
+
|
|
35
|
+
# Environment
|
|
36
|
+
.env
|
|
37
|
+
.env.local
|
|
38
|
+
.env.*.local
|
|
39
|
+
|
|
40
|
+
# Logs
|
|
41
|
+
*.log
|
|
42
|
+
npm-debug.log*
|
|
43
|
+
yarn-debug.log*
|
|
44
|
+
yarn-error.log*
|
|
45
|
+
|
|
46
|
+
# OS
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
|
49
|
+
|
|
50
|
+
# Temporary files
|
|
51
|
+
tmp/
|
|
52
|
+
temp/
|
|
53
|
+
*.tmp
|
|
54
|
+
typescript/examples/mcp/matimo-tools/.matimo-approvals.json
|
|
55
|
+
typescript/examples/mcp/matimo-tools/fetch-weather/definition.yaml
|
|
56
|
+
typescript/examples/mcp/matimo-tools/npm_downloads/definition.yaml
|
|
57
|
+
typescript/examples/mcp/matimo-tools/skills/ecosystem-health/SKILL.md
|
|
58
|
+
typescript/examples/mcp/matimo-tools/skills/matimo-health-check/SKILL.md
|
|
59
|
+
typescript/examples/mcp/matimo-tools/skills/moltbook-identity/SKILL.md
|
|
60
|
+
typescript/packages/cli/.matimo/certs/server.crt
|
|
61
|
+
typescript/packages/cli/.matimo/certs/server.key
|
|
62
|
+
typescript/examples/mcp/.matimo/certs/server.crt
|
|
63
|
+
typescript/examples/mcp/.matimo/certs/server.key
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matimo-postgres
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Matimo provider — PostgreSQL tools (query, insert, update, delete, execute SQL)
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: agents,ai,matimo,postgres,tools
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: asyncpg>=0.29
|
|
13
|
+
Requires-Dist: matimo-core<0.2.0,>=0.1.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# matimo-postgres
|
|
17
|
+
|
|
18
|
+
> PostgreSQL tools for [Matimo](https://matimo.dev) — execute SQL queries safely with policy-gated approval.
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/matimo-postgres/)
|
|
21
|
+
[](https://matimo.dev/docs)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install matimo matimo-postgres
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Available Tools (1 Tool)
|
|
34
|
+
|
|
35
|
+
| Tool | Description |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| `execute-sql` | Execute a SQL query against a PostgreSQL database |
|
|
38
|
+
|
|
39
|
+
The `execute-sql` tool is marked `requires_approval: true` — destructive operations (INSERT, UPDATE, DELETE, DROP) trigger HITL approval by default.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import asyncio
|
|
47
|
+
from matimo import Matimo, InitOptions
|
|
48
|
+
from matimo_postgres import get_tools_path
|
|
49
|
+
|
|
50
|
+
async def main():
|
|
51
|
+
# Auto-approve for read-only usage (CI/CD)
|
|
52
|
+
matimo = await Matimo.init(
|
|
53
|
+
get_tools_path(),
|
|
54
|
+
InitOptions(on_hitl=lambda req: {'approved': True, 'reason': 'auto'}),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Run a SELECT query
|
|
58
|
+
result = await matimo.execute('execute-sql', {
|
|
59
|
+
'query': 'SELECT id, name FROM users LIMIT 10',
|
|
60
|
+
})
|
|
61
|
+
print(result)
|
|
62
|
+
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With Interactive Approval (Recommended for Writes)
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
async def ask_user(request) -> dict:
|
|
70
|
+
print(f"\nSQL requires approval:\n{request.params.get('query')}")
|
|
71
|
+
answer = input("Run this query? [y/n]: ").strip()
|
|
72
|
+
return {'approved': answer == 'y', 'reason': 'user reviewed'}
|
|
73
|
+
|
|
74
|
+
matimo = await Matimo.init(
|
|
75
|
+
get_tools_path(),
|
|
76
|
+
InitOptions(on_hitl=ask_user),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# This will prompt before executing
|
|
80
|
+
await matimo.execute('execute-sql', {
|
|
81
|
+
'query': 'DELETE FROM sessions WHERE expired_at < NOW()',
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Authentication
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
export DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
|
|
91
|
+
# or individual params
|
|
92
|
+
export POSTGRES_HOST="localhost"
|
|
93
|
+
export POSTGRES_PORT="5432"
|
|
94
|
+
export POSTGRES_DB="mydb"
|
|
95
|
+
export POSTGRES_USER="myuser"
|
|
96
|
+
export POSTGRES_PASSWORD="mypassword"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Security Notes
|
|
102
|
+
|
|
103
|
+
- All SQL queries go through Matimo's **content validator** — SSRF and injection patterns are detected
|
|
104
|
+
- The tool has `requires_approval: true` — writes trigger approval by default
|
|
105
|
+
- Use a **read-only database user** for agent workloads when possible
|
|
106
|
+
- Consider a [policy file](https://matimo.dev/docs/api-reference/POLICY_AND_LIFECYCLE) to restrict allowed SQL patterns
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
- [Approval System](https://matimo.dev/docs/api-reference/APPROVAL-SYSTEM)
|
|
113
|
+
- [Policy & Lifecycle](https://matimo.dev/docs/api-reference/POLICY_AND_LIFECYCLE)
|
|
114
|
+
- [Python Examples](https://github.com/tallclub/matimo/tree/main/python/examples/langchain/postgres)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Links
|
|
119
|
+
|
|
120
|
+
- **PyPI:** https://pypi.org/project/matimo-postgres/
|
|
121
|
+
- **GitHub:** https://github.com/tallclub/matimo
|
|
122
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# matimo-postgres
|
|
2
|
+
|
|
3
|
+
> PostgreSQL tools for [Matimo](https://matimo.dev) — execute SQL queries safely with policy-gated approval.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/matimo-postgres/)
|
|
6
|
+
[](https://matimo.dev/docs)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install matimo matimo-postgres
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Available Tools (1 Tool)
|
|
19
|
+
|
|
20
|
+
| Tool | Description |
|
|
21
|
+
|------|-------------|
|
|
22
|
+
| `execute-sql` | Execute a SQL query against a PostgreSQL database |
|
|
23
|
+
|
|
24
|
+
The `execute-sql` tool is marked `requires_approval: true` — destructive operations (INSERT, UPDATE, DELETE, DROP) trigger HITL approval by default.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import asyncio
|
|
32
|
+
from matimo import Matimo, InitOptions
|
|
33
|
+
from matimo_postgres import get_tools_path
|
|
34
|
+
|
|
35
|
+
async def main():
|
|
36
|
+
# Auto-approve for read-only usage (CI/CD)
|
|
37
|
+
matimo = await Matimo.init(
|
|
38
|
+
get_tools_path(),
|
|
39
|
+
InitOptions(on_hitl=lambda req: {'approved': True, 'reason': 'auto'}),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Run a SELECT query
|
|
43
|
+
result = await matimo.execute('execute-sql', {
|
|
44
|
+
'query': 'SELECT id, name FROM users LIMIT 10',
|
|
45
|
+
})
|
|
46
|
+
print(result)
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### With Interactive Approval (Recommended for Writes)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
async def ask_user(request) -> dict:
|
|
55
|
+
print(f"\nSQL requires approval:\n{request.params.get('query')}")
|
|
56
|
+
answer = input("Run this query? [y/n]: ").strip()
|
|
57
|
+
return {'approved': answer == 'y', 'reason': 'user reviewed'}
|
|
58
|
+
|
|
59
|
+
matimo = await Matimo.init(
|
|
60
|
+
get_tools_path(),
|
|
61
|
+
InitOptions(on_hitl=ask_user),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# This will prompt before executing
|
|
65
|
+
await matimo.execute('execute-sql', {
|
|
66
|
+
'query': 'DELETE FROM sessions WHERE expired_at < NOW()',
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Authentication
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
|
|
76
|
+
# or individual params
|
|
77
|
+
export POSTGRES_HOST="localhost"
|
|
78
|
+
export POSTGRES_PORT="5432"
|
|
79
|
+
export POSTGRES_DB="mydb"
|
|
80
|
+
export POSTGRES_USER="myuser"
|
|
81
|
+
export POSTGRES_PASSWORD="mypassword"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Security Notes
|
|
87
|
+
|
|
88
|
+
- All SQL queries go through Matimo's **content validator** — SSRF and injection patterns are detected
|
|
89
|
+
- The tool has `requires_approval: true` — writes trigger approval by default
|
|
90
|
+
- Use a **read-only database user** for agent workloads when possible
|
|
91
|
+
- Consider a [policy file](https://matimo.dev/docs/api-reference/POLICY_AND_LIFECYCLE) to restrict allowed SQL patterns
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Documentation
|
|
96
|
+
|
|
97
|
+
- [Approval System](https://matimo.dev/docs/api-reference/APPROVAL-SYSTEM)
|
|
98
|
+
- [Policy & Lifecycle](https://matimo.dev/docs/api-reference/POLICY_AND_LIFECYCLE)
|
|
99
|
+
- [Python Examples](https://github.com/tallclub/matimo/tree/main/python/examples/langchain/postgres)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Links
|
|
104
|
+
|
|
105
|
+
- **PyPI:** https://pypi.org/project/matimo-postgres/
|
|
106
|
+
- **GitHub:** https://github.com/tallclub/matimo
|
|
107
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "matimo-postgres"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Matimo provider — PostgreSQL tools (query, insert, update, delete, execute SQL)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
keywords = ["ai", "tools", "agents", "matimo", "postgres"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"matimo-core>=0.1.0,<0.2.0",
|
|
21
|
+
"asyncpg>=0.29",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.entry-points."matimo.providers"]
|
|
25
|
+
postgres = "matimo_postgres:get_tools_path"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/matimo_postgres"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Matimo postgres provider — exposes the path to YAML tool definitions."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib.resources
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_tools_path() -> str:
|
|
9
|
+
"""Return the absolute path to the bundled postgres tool definitions."""
|
|
10
|
+
try:
|
|
11
|
+
ref = importlib.resources.files("matimo_postgres") / "tools"
|
|
12
|
+
return str(ref)
|
|
13
|
+
except Exception:
|
|
14
|
+
return str(Path(__file__).parent / "tools")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["get_tools_path"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: postgres-execute-sql
|
|
2
|
+
version: '1.0.0'
|
|
3
|
+
description: Execute arbitrary SQL against a Postgres database. Supports both connection string and explicit env var configuration.
|
|
4
|
+
|
|
5
|
+
parameters:
|
|
6
|
+
sql:
|
|
7
|
+
type: string
|
|
8
|
+
description: "SQL statement to execute. Use parameterized queries for safety (e.g. $1, $2)."
|
|
9
|
+
required: true
|
|
10
|
+
params:
|
|
11
|
+
type: array
|
|
12
|
+
description: "Optional array of parameters to pass to the query."
|
|
13
|
+
required: false
|
|
14
|
+
schema:
|
|
15
|
+
type: string
|
|
16
|
+
description: "Optional schema name to search for tables or qualify queries."
|
|
17
|
+
required: false
|
|
18
|
+
|
|
19
|
+
execution:
|
|
20
|
+
type: function
|
|
21
|
+
# The function file path is resolved relative to this definition.yaml
|
|
22
|
+
code: ./execute-sql.py
|
|
23
|
+
timeout: 30000
|
|
24
|
+
|
|
25
|
+
authentication:
|
|
26
|
+
type: custom
|
|
27
|
+
notes: |
|
|
28
|
+
The tool supports two authentication modes (choose either):
|
|
29
|
+
1. Connection string - set `MATIMO_POSTGRES_URL` to a full Postgres connection string (recommended).
|
|
30
|
+
2. Separate env vars - set `MATIMO_POSTGRES_HOST`, `MATIMO_POSTGRES_PORT`, `MATIMO_POSTGRES_USER`, `MATIMO_POSTGRES_PASSWORD`, `MATIMO_POSTGRES_DB`.
|
|
31
|
+
# Consumers should treat secrets as env variables. This custom auth type
|
|
32
|
+
# documents expected environment variables but does not enforce a single
|
|
33
|
+
# auth scheme - the executor reads either `MATIMO_POSTGRES_URL` or the
|
|
34
|
+
# individual env vars at runtime.
|
|
35
|
+
|
|
36
|
+
error_handling:
|
|
37
|
+
retry: 1
|
|
38
|
+
backoff_type: exponential
|
|
39
|
+
initial_delay_ms: 500
|
|
40
|
+
|
|
41
|
+
output_schema:
|
|
42
|
+
type: object
|
|
43
|
+
properties:
|
|
44
|
+
rows:
|
|
45
|
+
type: array
|
|
46
|
+
items:
|
|
47
|
+
type: object
|
|
48
|
+
rowCount:
|
|
49
|
+
type: number
|
|
50
|
+
|
|
51
|
+
examples:
|
|
52
|
+
- name: Simple select
|
|
53
|
+
params:
|
|
54
|
+
sql: "SELECT id, name FROM users WHERE id = $1"
|
|
55
|
+
params: [1]
|
|
56
|
+
|
|
57
|
+
- name: Parameterless query
|
|
58
|
+
params:
|
|
59
|
+
sql: "SELECT count(*) as cnt FROM users"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
execute-sql — Execute arbitrary SQL against a PostgreSQL database.
|
|
3
|
+
Mirrors: typescript/packages/postgres/tools/execute-sql/execute-sql.ts
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import quote_plus
|
|
10
|
+
|
|
11
|
+
from matimo.errors import ErrorCode, MatimoError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def run(params: dict[str, Any]) -> dict[str, Any]:
|
|
15
|
+
sql: str = params.get("sql", "").strip()
|
|
16
|
+
query_params: list | None = params.get("params")
|
|
17
|
+
schema: str | None = params.get("schema")
|
|
18
|
+
|
|
19
|
+
if not sql:
|
|
20
|
+
raise MatimoError(
|
|
21
|
+
"Missing SQL statement",
|
|
22
|
+
ErrorCode.EXECUTION_FAILED,
|
|
23
|
+
{"tool_name": "postgres-execute-sql"},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Build connection string from MATIMO_POSTGRES_URL or separate env vars
|
|
27
|
+
connection_string = os.environ.get("MATIMO_POSTGRES_URL")
|
|
28
|
+
|
|
29
|
+
if not connection_string:
|
|
30
|
+
host = os.environ.get("MATIMO_POSTGRES_HOST")
|
|
31
|
+
port = os.environ.get("MATIMO_POSTGRES_PORT", "5432")
|
|
32
|
+
user = os.environ.get("MATIMO_POSTGRES_USER")
|
|
33
|
+
password = os.environ.get("MATIMO_POSTGRES_PASSWORD")
|
|
34
|
+
database = os.environ.get("MATIMO_POSTGRES_DB")
|
|
35
|
+
|
|
36
|
+
if host and user and password and database:
|
|
37
|
+
connection_string = (
|
|
38
|
+
f"postgresql://{quote_plus(user)}:{quote_plus(password)}@{host}:{port}/{database}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if not connection_string:
|
|
42
|
+
raise MatimoError(
|
|
43
|
+
"Postgres connection information not provided. "
|
|
44
|
+
"Set MATIMO_POSTGRES_URL or MATIMO_POSTGRES_HOST/PORT/USER/PASSWORD/DB",
|
|
45
|
+
ErrorCode.EXECUTION_FAILED,
|
|
46
|
+
{"tool_name": "postgres-execute-sql"},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
import asyncpg # type: ignore[import]
|
|
51
|
+
except ImportError:
|
|
52
|
+
raise MatimoError(
|
|
53
|
+
"asyncpg is required for the postgres tool. Install it with: pip install asyncpg",
|
|
54
|
+
ErrorCode.EXECUTION_FAILED,
|
|
55
|
+
{"tool_name": "postgres-execute-sql"},
|
|
56
|
+
) from None
|
|
57
|
+
|
|
58
|
+
conn = None
|
|
59
|
+
try:
|
|
60
|
+
conn = await asyncpg.connect(connection_string)
|
|
61
|
+
|
|
62
|
+
# Set search_path if schema is specified
|
|
63
|
+
if schema:
|
|
64
|
+
await conn.execute(f"SET search_path TO {schema}")
|
|
65
|
+
|
|
66
|
+
rows = await conn.fetch(sql, *(query_params or []))
|
|
67
|
+
return {
|
|
68
|
+
"rows": [dict(row) for row in rows],
|
|
69
|
+
"row_count": len(rows),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
except MatimoError:
|
|
73
|
+
raise
|
|
74
|
+
except Exception as exc:
|
|
75
|
+
msg = str(exc)
|
|
76
|
+
details: dict[str, Any] = {"original_message": msg}
|
|
77
|
+
|
|
78
|
+
if "Connection refused" in msg or "ECONNREFUSED" in msg:
|
|
79
|
+
details["hint"] = "Connection refused — is Postgres running at the configured host/port?"
|
|
80
|
+
elif "role" in msg and "does not exist" in msg:
|
|
81
|
+
details["hint"] = "Database user does not exist — check MATIMO_POSTGRES_USER env var"
|
|
82
|
+
elif "database" in msg and "does not exist" in msg:
|
|
83
|
+
details["hint"] = "Database does not exist — check MATIMO_POSTGRES_DB env var"
|
|
84
|
+
|
|
85
|
+
raise MatimoError(
|
|
86
|
+
f"Postgres query failed: {msg}",
|
|
87
|
+
ErrorCode.EXECUTION_FAILED,
|
|
88
|
+
{"tool_name": "postgres-execute-sql", "details": details},
|
|
89
|
+
) from exc
|
|
90
|
+
|
|
91
|
+
finally:
|
|
92
|
+
if conn:
|
|
93
|
+
await conn.close()
|