1

I'm new to PostgreSQL (currently on PostgreSQL 13) and may be confusing things from what other SQL encounters I've had (Microsoft SQL).

The goal is to assert there are no values in a table column which would get truncated, then reduce the column length and do the same for the return type of a related function

An example of the code giving the error, so it can be reproduced:

/* 
 CREATE TABLE test_table (id uuid, col_a varchar(100), col_b int); 
 INSERT INTO test_table VALUES (gen_random_uuid(), 'asdf', 1);
 **/

DO $$
BEGIN
    IF NOT EXISTS (SELECT * FROM test_table WHERE character_length(col_a) > 100) THEN
        ALTER TABLE test_table ALTER COLUMN col_a TYPE varchar(100);
        
        DROP FUNCTION IF EXISTS test_function(varchar(100));
    
        CREATE OR REPLACE FUNCTION test_function(test_param varchar(100))
        RETURNS TABLE (
            id uuid,
            col_a varchar(100),
            col_b int
        )
        LANGUAGE plpgsql
        AS $$
        BEGIN
            RETURN QUERY
            SELECT test_table.id AS id, test_table.col_a AS col_a, test_table.col_b AS col_b
            FROM test_table
            WHERE test_table.col_a = test_param;
        END;
        $$;
    ELSE
        RAISE NOTICE 'Cannot Revert. Rows exist that would be truncated!';
    END IF;
END $$

SELECT id, col_a, col_b FROM test_function ('asdf');

/* DROP TABLE test_table */

The error I get is:

SQL Error [42601]: ERROR: syntax error at or near "BEGIN" Position: 400

I have tried an attempt at dynamic code, I've run the function statement on its own - sanity check that works; in fact without the IF block, running each statement either together or separately works fine too.

What's wrong with my approach and how to fix it?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228

1 Answers1

2

The immediate cause of the error is improper dollar-quoting. This would work:

DO
$do$
BEGIN
   IF NOT EXISTS (SELECT * FROM test_table WHERE character_length(col_a) > 100) THEN
      ALTER TABLE test_table ALTER COLUMN col_a TYPE varchar(100);

      DROP FUNCTION IF EXISTS test_function(varchar(100));

      CREATE OR REPLACE FUNCTION test_function(test_param varchar(100))
        RETURNS TABLE (
         id uuid,
         col_a varchar(100),
         col_b int
        )
      LANGUAGE plpgsql AS
      $func$
      BEGIN
         RETURN QUERY
         SELECT test_table.id, test_table.col_a, test_table.col_b
         FROM   test_table
         WHERE  test_table.col_a = test_param;
      END
      $func$;
   ELSE
      RAISE NOTICE 'Cannot Revert. Rows exist that would be truncated!';
   END IF;
END
$do$;

See:

But I wouldn't do most of what you are doing there to begin with. Use the data type text in table and function and be done with it. See:

If you positively need a restriction to a maximum number of characters, still consider text and add a CHECK constraint.

ALTER TABLE test_table ADD CONSTRAINT test_table_col_a_maxlen_200 CHECK (length(col_a) < 201);

Then, if you want to change that constraint later, all you do is:

ALTER TABLE test_table
  DROP CONSTRAINT test_table_col_a_maxlen_200  -- or whatever it was
, ADD  CONSTRAINT test_table_col_a_maxlen_100 CHECK (length(col_a) < 101);

Postgres will verify the CHECK constraint for you automatically, and fail with an error if any row violates it:

ERROR: check constraint "test_table_col_a_maxlen_100" of relation "test_table" is violated by some row

In fairness, you can also just apply the change to varchar(n) in modern Postgres. It will check and fail if any existing row is too long:

ERROR: value too long for type character varying(100)

So you can simplify things even if you stick with varchar(n).

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Thank you so much for those links especially @Erwin Brandstetter. So, because I didn't add a token between the dollar signs, the code was interpreting this incorrectly, but by adding them I've made each section, between such dollar quotes, unique? I really appreciate the working example, thanks again, but I'm really keen to make sure I understand too =) – mp_in_training May 26 '23 at 23:36
  • @mp_in_training: The links provide ample information on dollar-quoting. But there is more. Consider the added advice. – Erwin Brandstetter May 26 '23 at 23:38
  • Thanks for the update @Erwin Brandstetter, in truth I omitted details to avoid complicating the question. I'm inheriting this: Right now the table column is varchar(100), and the request is to solve some insert errors on long values AND how a rollback of altering the table could be performed (like a migration script). I'm planning to change it to TEXT and this was to be my restore approach, so it is in exactly the same state, varchar(100), so here I am. I love the idea of the constraint use case though, I'll keep that in mind as I do more in the future. Cheers again, I appreciate your time! – mp_in_training May 26 '23 at 23:59