This is a small but very, very useful tip. Often there's situations where you need to know what happened in the database, for a particular JIRA operation (or any database-using webapp). This is how you do it.
It's pretty straightforward: generate a SQL dump of the JIRA database before and after, and then use diff
to see the changes.
The only trick to know is that for the diff to be legible, you need to generate SQL dumps with full INSERT statements on every line. By default this is not true: Postgres emits a giant COPY
block for each table, for instance. So:
- When using PostgreSQL, use
pg_dump --inserts
- When using MySQL, use
mysqldump --skip-extended-insert
To spell it out:
PostgreSQL
Generate a pre-change dump of the JIRA database:
pg_dump --inserts > ~/pre.sql
- Make the JIRA change
Generate a post-change dump of the JIRA database:
pg_dump --inserts > ~/post.sql
Generate a diff of the changes:
diff ~/pre.sql ~/post.sql
MySQL
Generate a pre-change dump of the JIRA database:
mysqldump --skip-extended-insert > ~/pre.sql
- Make the JIRA change
Generate a post-change dump of the JIRA database:
mysqldump --skip-extended-insert > ~/post.sql
Generate a diff of the changes:
diff ~/pre.sql ~/post.sql
Example: finding private filters
Have you ever see this in Confluence?
or been redirected to a JIRA search, only to see:
This is a common situation: someone has created a filter in JIRA, but forgotten to 'share' it (filters being private by default) before using it in a Confluence page or email link. The page looks fine to the sender, but not to anyone else.
How can we, as JIRA administrators, identify all private filters?
JIRA database changes
What is the database-level difference between a private filter, and the same filter shared with a group? Let's take a database diff, and make a sample filter visible to the ac
group:
Here's the database diff:
root@coastserver [jira]~ # diff /tmp/{pre,post}.sql 52476c52476 < INSERT INTO propertytext VALUES (12996, 'project in (TESTAC, AC) AND resolution is EMPTY AND Reminders = "1 week before" AND due >= 7d AND due < 8d'); --- > INSERT INTO propertytext VALUES (12996, NULL); 53568d53567 < INSERT INTO searchrequest VALUES (10600, 'due_in_oneweek', 'jturner', NULL, 'jturner', NULL, NULL, 'project in (TESTAC, AC) AND resolution is empty and Reminders = "1 week before" AND due >= 7d AND due < 8d', 1, 'due_in_oneweek'); 53607a53607 > INSERT INTO searchrequest VALUES (10600, 'due_in_oneweek', 'jturner', '', 'jturner', NULL, NULL, 'project in (TESTAC, AC) AND resolution is empty and Reminders = "1 week before" AND due >= 7d AND due < 8d', 1, 'due_in_oneweek'); 53664d53663 < INSERT INTO sequence_value_item VALUES ('SharePermissions', 11100); 53712a53712 > INSERT INTO sequence_value_item VALUES ('SharePermissions', 11200); 53763a53764 > INSERT INTO sharepermissions VALUES (11100, 10600, 'SearchRequest', 'group', 'ac', NULL); root@coastserver [jira]~ #
What does this tell us? The propertytext and searchrequest changes are minor and irrelevant. What's important is the sharepermissions
table, which has gained a new value. The table looks like:
jira=> \d sharepermissions Table "public.sharepermissions" Column | Type | Modifiers ------------+------------------------+----------- id | numeric(18,0) | not null entityid | numeric(18,0) | entitytype | character varying(60) | sharetype | character varying(10) | param1 | character varying(255) | param2 | character varying(60) | Indexes: "pk_sharepermissions" PRIMARY KEY, btree (id) "share_index" btree (entityid, entitytype)
The second value, 10600
, refers to the entityid
, or ID of the searchrequest
table entry.
So to find all unshared filters, simply search for all searchrequest
rows without a sharepermission
row:
jira=> SELECT username, id, filtername FROM searchrequest WHERE NOT EXISTS (SELECT * FROM sharepermissions WHERE entityid=searchrequest.id AND entitytype='SearchRequest'); username | id | filtername ---------------+-------+------------------------------------------------- jturner | 10101 | All Keys jsmith | 10500 | Stuff due in next 6 months jturner | 10601 | due_in_onemonth jturner | 10602 | due_in_threedays jturner | 10603 | due_in_oneday jturner | 10604 | due_today
Then, just to close off the example, we could find Confluence usages of these private filters by searching the Confluence database for the jira
macro's XHTML:
with some bash-plus-SQL scripting:
echo "SELECT string_agg(''||id, '|') FROM searchrequest WHERE NOT EXISTS (SELECT * FROM sharepermissions WHERE entityid=searchrequest.id AND entitytype='SearchRequest');" \ | psql -tAq jira \ | while read ids; do \ echo "SELECT distinct content.contentid, content.title \ FROM bodycontent JOIN content ON bodycontent.contentid=content.contentid \ WHERE content.prevver IS NULL AND bodycontent.body ~ 'jqlQuery\">filter=(${ids})' \ GROUP BY 1"; done \ | psql -tAq confluence \ | while read id title; do echo "https://confluence.example.com/pages/viewpage.action?pageId=$id $title"; done https://confluence.example.com/pages/viewpage.action?pageId=21004542|AC Meeting 2016-1 Agenda https://confluence.example.com/pages/viewpage.action?pageId=17793222|AC Meeting 2015-4 Agenda