Functions based on views

Creating a function based on a view definition is a very powerful and flexible way of providing information to users. As an example of this, I will tell you a story of how I started a simple utility view to answer the question, "What queries are running now, and which queries have been running for the longest time?" This evolved into a function based on this view, plus a few more views based on the function.

The way to get all the data to answer this question in PostgreSQL is by using the following query. Please note that the output is using an expanded mode of psql. You can turn it on using the x meta-command:

hannu=# SELECT * FROM pg_stat_activity WHERE state='active';

-[ RECORD 1 ]----+--------------------------------
datid            | 17557
datname          | hannu
pid              | 8933
usesysid         | 10
usename          | postgres
application_name | psql
client_addr      | 
client_hostname  | 
client_port      | -1
backend_start    | 2013-03-19 13:47:45.920902-04
xact_start       | 2013-03-19 14:05:47.91225-04
query_start      | 2013-03-19 14:05:47.91225-04
state_change     | 2013-03-19 14:05:47.912253-04
waiting          | f
state            | active
query            | select * from pg_stat_activity |   where state='active';

The usual process is to use a variant of the following query, which is already wrapped into a view here:

CREATE VIEW running_queries AS
SELECT
   (CURRENT_TIMESTAMP - query_start) as runtime,
   pid,
   usename,
   waiting,
   query
FROM pg_stat_activity
WHERE state='active'
ORDER BY 1 DESC
LIMIT 10;

Soon, you will notice that putting this query into a view is not enough. Sometimes, you may want to vary the number of lowest queries, while sometimes you may not want to have the full query text, but just the beginning, and so on.

If you want to vary some parameters, the logical thing to do is to use a function instead of a view, as otherwise you will need to create different views of each new requirement:

CREATE OR REPLACE FUNCTION running_queries(rows int, qlen int)
  RETURNS SETOF running_queries AS
$$
BEGIN
   RETURN QUERY SELECT
      runtime,
      pid,
      usename,
      waiting,
      substring(query,1,qlen) as query
   FROM running_queries
   ORDER BY 1 DESC
   LIMIT rows;
END;
$$ LANGUAGE plpgsql;

As a security precaution, the default behavior of the pg_stat_activity view is that only superusers can see what other users are running. Sometimes, it may be necessary to allow the non-superusers to at least see the type of query (SELECT, INSERT, DELETE, or UPDATE) that other users are running, but hide the exact contents. To do this, you have to make two changes to the previous function.

First, replace the row to get current_query with the following code snippet:

(CASE WHEN ( usename = session_user )
        OR (SELECT usesuper
              FROM pg_user
              WHERE usename = session_user)
      THEN
        substring(query,1,qlen)
      ELSE
        substring(ltrim(query), 1, 6) || ' ***'
      END  ) as query

This code snippet checks each row to see whether the user running the function has permission to see the full query. If the user is a superuser, then he she has permission to see the full query. If the user is a regular user, he/she will only see the full query for his/her queries. All other rows will only show the first six characters followed by *** to mark it as a shortened query string.

The other key point to allowing ordinary users to run the function, is to grant them the appropriate rights to do so. When a function is created, the default behavior is to run with the SECURITY INVOKER rights, which means that the function will be called with the rights of the user who called it. To easily grant the correct rights to call the function, the function needs to be created with the SECURITY DEFINER attribute. This causes the function to execute with the privileges of the user who created the function; therefore, creating the function as a superuser will allow it to execute as a superuser, regardless of the rights of the user who called it. This, however, should be done with caution because a superuser can be dangerous, and if you end up executing a string that is passed in, you will have all the issues of SQL injection attacks.

Now, you have a function, which you can use to get the start of the five longest-running queries using the following query:

SELECT * FROM running_queries(5,25);

In order to get a complete query, you can use the following:

SELECT * FROM running_queries(1000,1024);

You may want to define a few convenience views for the variants you use most, as follows:

CREATE OR REPLACE VIEW running_queries_tiny AS
SELECT * FROM running_queries(5,25);
CREATE VIEW running_queries_full AS
SELECT * FROM running_queries(1000,1024);

You can even redefine the original view to use the first version of the function:

CREATE OR REPLACE VIEW running_queries AS
SELECT * FROM running_queries(5,25);

This is usually not recommended, but it demonstrates the following three important things:

  • Views and functions can have exactly the same name
  • You can get a circular reference by basing a function on a view and then basing a view on that function
  • If you get a circular reference in this way, you can't easily change either definition

To resolve this, simply avoid circular references.

Even without circular references, there is still a dependency on the view called from the function. If, for instance, you need to add a column to show the application name to the running_queries view, the function needs to change as well:

CREATE OR REPLACE VIEW running_queries AS
SELECT
   CURRENT_TIMESTAMP - query_start as runtime,
   pid,
   usename,
   waiting,
   query,
   application_name as appname
FROM pg_stat_activity
ORDER BY 1 DESC
LIMIT 10;

The view definition can be changed without an error, but the next time you try to run the running_queries(int, int) function, you will get an error:

hannu=# SELECT * FROM running_queries(5,25);
ERROR:  structure of query does not match function result type
DETAIL:  Number of returned columns (5) does not match expected column count (6).
CONTEXT:  PL/pgSQL function "running_queries" line 3 at RETURN QUERY

To fix this, you need to add an additional column to the function. This is one of the dangers of reusing types in this way: you might end up breaking functions unintentionally. PostgreSQL won't tell you, when you change a type in this way, whether any functions were using this type and will only fail when you try to run the query:

CREATE OR REPLACE FUNCTION running_queries(rows int, qlen int)
  RETURNS SETOF running_queries AS
$$
BEGIN
   RETURN QUERY SELECT
      runtime,
      pid,
      usename,
      waiting,
     (CASE WHEN ( usename= session_user )
        OR (select usesuper
              from pg_user
             where usename = session_user)
      THEN
        substring(query,1,qlen)
      ELSE
        substring(ltrim(query), 1, 6) || ' ***'
      END) as query,
      appname   
    FROM running_queries
   ORDER BY 1 DESC
   LIMIT rows;
END;
$$
LANGUAGE plpgsql
SECURITY DEFINER;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.187.207