{"id":4903,"date":"2016-08-11T20:12:36","date_gmt":"2016-08-11T20:12:36","guid":{"rendered":"http:\/\/www.garysieling.com\/blog\/?p=4903"},"modified":"2016-08-11T20:12:36","modified_gmt":"2016-08-11T20:12:36","slug":"get-postgres-format-index-expression","status":"publish","type":"post","link":"https:\/\/www.garysieling.com\/blog\/get-postgres-format-index-expression\/","title":{"rendered":"Get the Postgres format for an index expression"},"content":{"rendered":"<p>Let&#8217;s say you&#8217;ve built an automated system that builds out your database (common with ORM tools), which includes building indexes with &#8216;where&#8217; clauses, like so:<\/p>\n<pre lang=\"sql\">\ncreate index index_test \non test (col1, col2, col3) \nwhere col4 = 'test';\n<\/pre>\n<p>If you were to build an automated migration for this database, it&#8217;s helpful to know if this index will differ in the next version. Assuming a &#8216;duck type&#8217; style equality, you&#8217;d want to verify the columns and their types are the same, as well as the where clause &#8211; if any of these differ, you need to rebuild the index. The problem is that a developer who writes the where clause may not format it exactly as the database does (they are only writing the &#8216;where&#8217; bit), so it would be helpful to be able to parse the SQL, the re-constitute it through Postgres to check if there are material differences.<\/p>\n<p>If you have created an index with a WHERE clause, you can query it back like so:<\/p>\n<pre lang=\"Sql\">\nselect pg_get_expr(indpred, indrelid), *\nFROM pg_index AS idx\nJOIN pg_class AS idx_cls ON idx_cls.oid = idx.indexrelid\nWHERE pg_class.relname = 'index_test'\n<\/pre>\n<p>Internally, there are several tables that expose data of type pg_node_tree (a parse tree), such as index where clauses, and rewrite rules. The pg_get_expr function turns these back into SQL, given the OID of a type, which works great for reconstructing indexes. There is a third argument to this function to &#8220;pretty print&#8221; the output, but it seems to just reduce the amount of parentheses included.<\/p>\n<p>The problem is that there doesn&#8217;t seem to be an exposed API for plpgsql or SQL to reverse this (build a parse tree). There are examples that wrap C APIs<sup><a href=\"#footnote_0_4903\" id=\"identifier_0_4903\" class=\"footnote-link footnote-identifier-link\" title=\"https:\/\/github.com\/lfittl\/pg_query\">1<\/a><\/sup>, but if you search &#8220;pg_node_tree site:postgresql.org\/docs&#8221; you will not find any functions that return pg_node_tree type. For this exercise, I wasn&#8217;t inclined to use one of these extension types, although clearly they could work.<\/p>\n<p>Rather, I decided to use the output of &#8220;explain&#8221;, since it parses the query in the process of determining a plan. One of the nice feature of explain in Postgres is that you can control the format of the output, and now that Postgres has JSON processing, this is fairly concise. If you do something like this, psql and pgadmin both truncate the output with &#8230;,\u00a0so you may need to use some of the JSON enumeration functions (e.g. json_object_keys) to find the correct path to where you want to be:<\/p>\n<pre>\n\"[\n  {\n    \"Plan\": {\n      \"Node Type\": \"Index Scan\",\n      \"Scan Direction\": \"Forward\",\n      \"Index Name\": \"tmf_document_s_pk0\",\n      \"Relation Name\": \"tmf_document_s\",\n      \"Alias\": \"tmf_document_s\",\n      \"Startup Cost\": 0.14,\n      \"Total Cost\": 8.16 (...)\"\n<\/pre>\n<p>The solution is to simply take this output, cast to JSON, and find the where clause:<\/p>\n<pre lang=\"sql\">\nCREATE OR REPLACE FUNCTION format_expr(query text) returns text AS\n$BODY$\nDECLARE\n    query_explain  text;\n    explanation    text;\nBEGIN\n  query_explain := e'EXPLAIN(FORMAT json) ' || query;\n  execute query_explain INTO explanation;\n  return explanation::json->0->'Plan'->'Index Cond';\nEND;\n$BODY$\nLANGUAGE plpgsql;\n<\/pre>\n<p>When you call this function, you need to pass it a query that works off a table as you apparently can&#8217;t &#8220;explain&#8221; a &#8220;create index&#8221; statement.<\/p>\n<pre lang=\"sql\">\nselect format_expr('select * from test where id = ''123''');\n\n\"\"((id)::text = 'test'::text)\"\"\n<\/pre>\n<p>For my use case (testing during a database migration) this is fine, because it&#8217;s relatively simple for the application that generates code to generate &#8220;select * from table where &#8230;&#8221; instead of &#8220;create index where &#8230;&#8221;. <\/p>\n<ol class=\"footnotes\"><li id=\"footnote_0_4903\" class=\"footnote\">https:\/\/github.com\/lfittl\/pg_query<span class=\"footnote-back-link-wrapper\"> [<a href=\"#identifier_0_4903\" class=\"footnote-link footnote-back-link\">&#8617;<\/a>]<\/span><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>How to parse and re-constitute SQL where clauses in Postgres<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[12],"tags":[437,523],"aioseo_notices":[],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts\/4903"}],"collection":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/comments?post=4903"}],"version-history":[{"count":0,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts\/4903\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/media?parent=4903"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/categories?post=4903"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/tags?post=4903"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}