Question

  1. Is it possible to do something like the following with SQL, not PL/pgSQL (note if it's only possible with PL/pgSQL, then how)?

    IF password = 'swordfish' THEN
        SELECT a, b, c FROM users;
    ELSE
        SELECT -1; -- unauthorized error code
    END IF;
    
  2. Ideally, could I wrap the above in a function with TRUE being an argument?

  3. Rather, is it possible to set the command status string to -1?

I'm asking this because I want the query to return an error code, like -1, if someone tries to get a list of all the users with the wrong password. This is for a web app with user accounts that each have a password. So, this is not something I want to manage with database roles/permissions.

Was it helpful?

Solution

Algorithm

  1. Select 1 into a (authorized) if we find a user_id_1-session_id match.
  2. Select 0, NULL, NULL into u (unauthorized) if we didn't find a match in step 1.
  3. Select user_id, body, sent into s (select) if we did find a match in step 1.
  4. Union u and s.

Code

-- List messages between two users with `user_id_1`, `session_id`, `user_id_2`
CREATE FUNCTION messages(bigint, uuid, bigint) RETURNS TABLE(i bigint, b text, s double precision) AS
$$
    WITH a AS (
        SELECT 1
        FROM sessions
        WHERE user_id = $1
        AND id = $2
    ), u AS (
        SELECT 0, NULL::text, NULL::double precision
        WHERE NOT EXISTS (SELECT 1 FROM a)
    ), s AS (
        SELECT user_id, body, trunc(EXTRACT(EPOCH FROM sent))
        FROM messages
        WHERE EXISTS (SELECT 1 FROM a)
        AND chat_id = pair($1, $3)
        LIMIT 20
    )
    SELECT * FROM u UNION ALL SELECT * FROM s;
$$
LANGUAGE SQL STABLE;

OTHER TIPS

The PL/pgsql function below returns the messages sent between user_id & with_user_id if the user_id:key pair is authorized, as determined by the user-defined function (UDF) user_auth. Otherwise, it returns one row with from = -1 . The other UDF, pair, is a unique unordered pairing function that, given two user IDs, returns the chat_id to which the messages belong.

--- Arguments: user_id, key, with_user_id
CREATE FUNCTION messages(bigint, uuid, bigint)
RETURNS TABLE(from bigint, body text, sent double precision) AS $$
BEGIN
    IF user_auth($1, $2) THEN
        RETURN QUERY SELECT from, body, trunc(EXTRACT(EPOCH FROM sent))
                     FROM messages WHERE chat_id = pair($1, $3);
    ELSE
        i := -1;
        RETURN NEXT;
    END IF;
END;
$$ LANGUAGE plpgsql STABLE;

I don't know how to translate this to an SQL function or whether that would be better.

This will work, but it's not pretty:

WITH 
  u AS (SELECT * FROM user WHERE mail = '..'),
  code AS (
    SELECT 
      CASE (SELECT count(*) FROM u)
      WHEN 0 THEN
        'not found'
      ELSE
        CASE (SELECT count(*) FROM u WHERE password = '..')
        WHEN 1 THEN
          'right password'
        ELSE
          'wrong password'
        END
      END)
SELECT
  code.*,
  u.*
FROM code NATURAL LEFT OUTER JOIN u

I think you might want to look into creating a result set returning function instead.

There is a CASE expression in addition to the (pl/pgsql only) CASE control structure.

EDIT: CASE expression in sql context:

SELECT CASE
           WHEN my_conditions_are_met THEN a
           ELSE NULL
       END AS a_or_null,
       b,
       c
FROM users;

EDIT 2: given your example that's how you can do it in pure SQL:

WITH params AS (
    SELECT user_auth(:user_id, :key)     AS user_auth,
           pair(:user_id, :with_user_id) AS chat_id
), error_message AS (
    SELECT -1                                    AS "from",
           'auth error'                          AS "body",
           EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) AS "sent"
)
SELECT from, body, trunc(EXTRACT(EPOCH FROM sent))
FROM messages
JOIN params ON messages.chat_id = params.chat_id AND params.user_auth
UNION ALL
SELECT error_message.*
FROM error_message
JOIN params ON NOT params.user_auth
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top