Postgres (postgres)¶
The postgres connector indexes table rows as searchable records. Each row
becomes an object you can search by meaning, and each table exposes a
schema.json file for structure. When [summary].enabled is on, that schema also
gets a searchable schema_summary chunk so you can search questions like "which
table has an email column?".
How MFS sees it¶
Tables live under their schema. Every table exposes its rows and its schema:
postgres://prod-db/
└── public/
├── tickets/
│ ├── rows.jsonl table_rows → one searchable chunk per row
│ └── schema.json table_schema → browsable schema; searchable with summary enabled
└── users/
├── rows.jsonl
└── schema.json
Rows are chunked per-row. To make a table searchable you must tell the connector
which columns carry text — see [[objects]] below. Without that, rows still
enumerate and you can grep/cat them, but they produce no semantic chunks.
Credentials¶
You already have a database; what you need is a DSN and a read-only role.
- Cloud Postgres (RDS/Aurora, Cloud SQL, Azure): copy the connection string from the console; substitute the real password.
- Self-hosted: run
\conninfoinpsqlto read host/port/db/user.
A read-only role is enough — USAGE on each in-scope schema plus SELECT on its
tables. If you are creating a role for MFS, start with the narrowest database and
schema grants you can:
CREATE USER mfs_reader WITH PASSWORD '<password>';
GRANT CONNECT ON DATABASE prod TO mfs_reader;
GRANT USAGE ON SCHEMA public TO mfs_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mfs_reader;
GRANT SELECT ON FUTURE TABLES IN SCHEMA public TO mfs_reader;
For managed Postgres, also allow the server's egress IP in the database firewall or security group. Confirm connectivity from the machine that runs the server before handing the DSN to MFS:
Configuration¶
dsn = "env:PG_DSN"
schemas = ["public"]
cursor_column = "updated_at" # strengthens the object fingerprint
max_read_rows = 100000
[[objects]]
match = "/public/tickets"
text_fields = ["title", "description"]
locator_fields = ["id"]
metadata_fields = ["status", "updated_at"]
match targets the connector-relative object path (/public/tickets), which
covers its rows.jsonl. text_fields become the embedded text; locator_fields
let you reopen an exact row with cat --locator; metadata_fields are returned
alongside hits for filtering and display.
Keep the DSN in the server environment, then probe and index:
export PG_DSN='postgresql://mfs_reader:<password>@db.example.com:5432/prod'
mfs connector probe postgres://prod-db --config ./postgres.toml
mfs add postgres://prod-db --config ./postgres.toml
Sync and freshness¶
When you set cursor_column (typically updated_at), the connector includes
max(cursor_column) in the table object's fingerprint. If the row count or cursor
maximum changes, MFS re-reads and re-indexes that table's rows.jsonl object.
Deletions are caught by full_scan. grep runs as a pushdown — it queries
Postgres directly rather than the index.
Search and browse¶
mfs search "SSO migration" postgres://prod-db/public/tickets/rows.jsonl
mfs search "email column" postgres://prod-db --kind schema_summary
mfs cat postgres://prod-db/public/tickets/schema.json
mfs cat postgres://prod-db/public/tickets/rows.jsonl --locator '{"id":12345}'
Pitfalls¶
- No
text_fields→ rows enumerate but produce no searchable chunks. - Use read-only credentials; the connector only needs
SELECT. - If the DSN works locally but probe fails, check the server host, container, or pod network path rather than the client shell.
max_read_rowscaps large tables and can mark recall partial.schema_summarysearch requires[summary].enabled;schema.jsonis still browsable without it.