Skip to content

Commit 61a2511

Browse files
committed
[pgmoneta#1092] fix mapping & translation error
how to generate mapping file
1 parent 2656cf6 commit 61a2511

3 files changed

Lines changed: 173 additions & 18 deletions

File tree

doc/WALINFO.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,80 @@ The configuration is loaded from either the path specified by the `-c` flag or `
1010
- **Command-line mode**: Raw text or JSON output with filtering options (`-r`, `-s`, `-e`, `-x`, `-l`, etc.)
1111
- **Interactive mode**: Full-screen ncurses UI for browsing, searching, filtering, marking rows, and exporting walfilter YAML
1212
- **Format support**: Plain, compressed (zstd, gz, lz4, bz2), encrypted (aes), and combined compression+encryption
13-
- **OID translation**: Convert OIDs to human-readable object names (`-t` with `-m` or `-u`)
13+
- **OID translation**: Convert OIDs to human-readable object names (`-t` with `-m` or `-u`). Use `-m /path/to/mapping.json` to translate using a local JSON mapping file with `tablespaces`, `databases`, and `relations` sections.
1414
- **Summary statistics**: Analyze WAL record distribution by resource manager (`-S`)
1515

16+
## OID translation
17+
18+
`pgmoneta-walinfo` can translate OIDs to object names using either:
19+
20+
- `-u /path/to/pgmoneta_users.conf` to fetch object names from a live PostgreSQL server
21+
- `-m /path/to/mapping.json` to load object name mappings from a local JSON file
22+
23+
Example `mapping.json` format:
24+
25+
```json
26+
{
27+
"tablespaces": [
28+
{"pg_default": "1663"}
29+
],
30+
"databases": [
31+
{"postgres": "16384"}
32+
],
33+
"relations": [
34+
{"public.test_table": "16734"}
35+
]
36+
}
37+
```
38+
39+
If both a mapping file and a user credentials file are provided, the mapping file takes precedence.
40+
41+
### Creating the mapping file
42+
43+
The mapping file can be created manually or generated from a PostgreSQL database using SQL queries. Each section (`tablespaces`, `databases`, `relations`) is an array of objects where the key is the object name and the value is the OID as a string.
44+
45+
#### Generating from PostgreSQL
46+
47+
Connect to your PostgreSQL database and run these queries to generate the JSON structure:
48+
49+
**Tablespaces:**
50+
```sql
51+
SELECT '{' || string_agg('"' || spcname || '": "' || oid || '"', ', ') || '}' FROM pg_tablespace;
52+
```
53+
54+
**Databases:**
55+
```sql
56+
SELECT '{' || string_agg('"' || datname || '": "' || oid || '"', ', ') || '}' FROM pg_database WHERE datname NOT IN ('template0', 'template1');
57+
```
58+
59+
**Relations:**
60+
```sql
61+
SELECT '{' || string_agg('"' || nspname || '.' || relname || '": "' || c.oid || '"', ', ') || '}'
62+
FROM pg_class c
63+
JOIN pg_namespace n ON c.relnamespace = n.oid
64+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f') AND n.nspname NOT IN ('pg_catalog', 'information_schema');
65+
```
66+
67+
Combine the results into a full JSON file:
68+
69+
```json
70+
{
71+
"tablespaces": {
72+
/* paste tablespaces query result */
73+
},
74+
"databases": {
75+
/* paste databases query result */
76+
},
77+
"relations": {
78+
/* paste relations query result */
79+
}
80+
}
81+
```
82+
83+
#### Manual creation
84+
85+
You can also create the file manually by looking up OIDs in system catalogs or using tools like `pg_dump` output. Ensure OIDs are strings and object names are fully qualified (schema.table for relations).
86+
1687
## Interactive mode
1788

1889
Launch the interactive viewer with the `-I` flag:

doc/man/pgmoneta-walinfo.1.rst

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ With **-I** (**--interactive**), pgmoneta-walinfo runs a full-screen **ncurses**
105105

106106
**Search** (separate from filtering)
107107

108-
- **s** -- Open the search dialog (resource manager, LSN fields, XID, description).
108+
- **s** -- Open the search dialog (resource manager, LSN fields, XID, description). **Tab** cycles through known values for RMGR, Start LSN, and End LSN fields.
109109
- **n** / **p** (or **Right** / **Left** when search is active) -- Next / previous search match; **Right** / **Left** can cycle multiple matches in a field.
110110
- **Esc** -- Clear **search** highlights and results (does not clear **filters**).
111111

112112
**Filtering**
113113

114-
- **f** -- Open the **filter** dialog. Criteria you set restrict which rows remain in the list. The dialog is **pre-filled** with the current rules so you can edit or remove them.
114+
- **f** -- Open the **filter** dialog. Criteria you set restrict which rows remain in the list. The dialog is **pre-filled** with the current rules so you can edit or remove them. **Tab** cycles through known values for RMGR, Start LSN, and End LSN fields.
115115
- **u** -- **Clear all filters** and reload the full record list from the WAL file.
116116
- **Ctrl+U** (in the filter dialog only) -- Clear all filter fields.
117117

@@ -162,6 +162,41 @@ To display information and translate the OIDs to the corresponding object names:
162162
or
163163
pgmoneta-walinfo -c pgmoneta_walinfo.conf -t -u /path/to/pgmoneta_users.conf /path/to/walfile
164164

165+
OID TRANSLATION
166+
===============
167+
168+
The `-t` flag enables translation of WAL record OIDs to object names. Use either:
169+
170+
- `-u /path/to/pgmoneta_users.conf` to resolve names from a live PostgreSQL server
171+
- `-m /path/to/mapping.json` to resolve names from a local JSON mapping file
172+
173+
The mapping JSON file must contain `tablespaces`, `databases`, and `relations` sections with object names mapped to OIDs.
174+
175+
If both mappings and server credentials are supplied, the mapping file takes precedence.
176+
177+
CREATING THE MAPPING FILE
178+
=========================
179+
180+
The mapping file can be created manually or generated from a PostgreSQL database.
181+
182+
Each section is an array of objects where the key is the object name and the value is the OID as a string.
183+
184+
**Generating from PostgreSQL:**
185+
186+
Tablespaces:
187+
SELECT '{' || string_agg('"' || spcname || '": "' || oid || '"', ', ') || '}' FROM pg_tablespace;
188+
189+
Databases:
190+
SELECT '{' || string_agg('"' || datname || '": "' || oid || '"', ', ') || '}' FROM pg_database WHERE datname NOT IN ('template0', 'template1');
191+
192+
Relations:
193+
SELECT '{' || string_agg('"' || nspname || '.' || relname || '": "' || c.oid || '"', ', ') || '}'
194+
FROM pg_class c
195+
JOIN pg_namespace n ON c.relnamespace = n.oid
196+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f') AND n.nspname NOT IN ('pg_catalog', 'information_schema');
197+
198+
Combine into a JSON file with "tablespaces", "databases", and "relations" arrays.
199+
165200
REPORTING BUGS
166201
==============
167202

src/libpgmoneta/wal.c

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,10 +1308,17 @@ pgmoneta_read_mappings_from_json(char* mappings_path)
13081308
for (int i = 0; i < 3; i++)
13091309
{
13101310
section = (struct json*)pgmoneta_json_get(root, sections[i]);
1311-
if (section && section->type == JSONItem)
1311+
if (section)
13121312
{
1313-
struct art* art_tree = (struct art*)section->elements;
1314-
total_entries += art_tree->size;
1313+
if (section->type == JSONItem)
1314+
{
1315+
struct art* art_tree = (struct art*)section->elements;
1316+
total_entries += art_tree->size;
1317+
}
1318+
else if (section->type == JSONArray)
1319+
{
1320+
total_entries += pgmoneta_json_array_length(section);
1321+
}
13151322
}
13161323
}
13171324

@@ -1326,21 +1333,63 @@ pgmoneta_read_mappings_from_json(char* mappings_path)
13261333
{
13271334
current_type = (object_type)i;
13281335
section = (struct json*)pgmoneta_json_get(root, sections[i]);
1329-
if (section && section->type == JSONItem)
1336+
if (section)
13301337
{
1331-
pgmoneta_json_iterator_create(section, &iter);
1332-
while (pgmoneta_json_iterator_next(iter))
1338+
if (section->type == JSONItem)
13331339
{
1334-
char* name = iter->key;
1335-
oid_str = (char*)iter->value->data;
1336-
oid = (int)strtol(oid_str, NULL, 10);
1337-
1338-
oidMappings[index].oid = oid;
1339-
oidMappings[index].type = current_type;
1340-
oidMappings[index].name = strdup(name);
1341-
index++;
1340+
if (pgmoneta_json_iterator_create(section, &iter))
1341+
{
1342+
goto error;
1343+
}
1344+
while (pgmoneta_json_iterator_next(iter))
1345+
{
1346+
char* oid_str = iter->key;
1347+
char* name = (char*)iter->value->data;
1348+
oid = (int)strtol(oid_str, NULL, 10);
1349+
1350+
oidMappings[index].oid = oid;
1351+
oidMappings[index].type = current_type;
1352+
oidMappings[index].name = strdup(name);
1353+
index++;
1354+
}
1355+
pgmoneta_json_iterator_destroy(iter);
1356+
}
1357+
else if (section->type == JSONArray)
1358+
{
1359+
if (pgmoneta_json_iterator_create(section, &iter))
1360+
{
1361+
goto error;
1362+
}
1363+
while (pgmoneta_json_iterator_next(iter))
1364+
{
1365+
struct json* item = (struct json*)iter->value;
1366+
if (item == NULL || item->type != JSONItem)
1367+
{
1368+
pgmoneta_json_iterator_destroy(iter);
1369+
goto error;
1370+
}
1371+
1372+
struct json_iterator* inner_iter = NULL;
1373+
if (pgmoneta_json_iterator_create(item, &inner_iter))
1374+
{
1375+
pgmoneta_json_iterator_destroy(iter);
1376+
goto error;
1377+
}
1378+
while (pgmoneta_json_iterator_next(inner_iter))
1379+
{
1380+
char* name = inner_iter->key;
1381+
oid_str = (char*)inner_iter->value->data;
1382+
oid = (int)strtol(oid_str, NULL, 10);
1383+
1384+
oidMappings[index].oid = oid;
1385+
oidMappings[index].type = current_type;
1386+
oidMappings[index].name = strdup(name);
1387+
index++;
1388+
}
1389+
pgmoneta_json_iterator_destroy(inner_iter);
1390+
}
1391+
pgmoneta_json_iterator_destroy(iter);
13421392
}
1343-
pgmoneta_json_iterator_destroy(iter);
13441393
}
13451394
}
13461395

0 commit comments

Comments
 (0)