variant

variant allows for storing any PostgreSQL data type in a column, as well as remembering what the original data type was.

Usage

For creating storage, you would use variant as any other data type: (Note that by default the default variant shown here is disabled)

CREATE TABLE setting(
    setting_name        text                NOT NULL PRIMARY KEY
    , setting_value     variant.variant     NOT NULL
);

You can then insert whatever data you want:

INSERT INTO setting VALUES( 'foobar', 1::int );
INSERT INTO setting VALUES( 'box', '((0,0),(1,1))'::box );

SELECT * FROM setting;
 setting_name |    setting_value    
--------------+---------------------
 box          | (box,"(1,1),(0,0)")
 foobar       | (integer,1)
(2 rows)

Variant modifier

In order to more sanely support droping data types, variant will eventually allow you to specify what types are actually allowed to be stored in a variant. In order to allow for different settings here, you should register a variant using variant.register():

SELECT variant.register( 'setting' );
 register 
----------
        1
(1 row)

ALTER TABLE setting ALTER setting_value TYPE variant.variant(setting);
ALTER TABLE
\d setting
                Table "public.setting"
    Column     |           Type           | Modifiers 
---------------+--------------------------+-----------
 setting_name  | text                     | not null
 setting_value | variant.variant(setting) | not null
Indexes:
    "setting_pkey" PRIMARY KEY, btree (setting_name)

There is a default variant, but you are encouraged not to use it (it is disabled by default). This is because some parts of PostgreSQL do not inspect (or even store) a type modifier. That means you could accidentally end up with data in the default variant instead of a registered variant.

Support Functions

type_text() / type_type()

This function takes a variant as an input, and returns the original type as a text string (including the original type modifier). If you to strip off the type modifier, cast the output of this function to regtype: SELECT variant.type(v)::regtype

text_in() / text_out()

The main method of changing the value of a variant field is meant to be via casting, ie: 'some data'::varchar(20)::variant. That's a bit awkward when written out, but would work great in something like plpgsql:

DO $$DECLARE v_setting_value     text;
BEGIN
    v_setting_value := 'test';
    INSERT INTO setting SELECT 'test setting', v_setting_value;
END

You can also construct the text representation of a variant: SELECT '(text,test)'::variant.variant; variant
------------- (text,test) (1 row)

What does not work is trying to construct a variant input in a text field and then using that to create a variant:

DO $$
    DECLARE v_in text;
    BEGIN
    v_in := '(text,test)';
    INSERT INTO setting SELECT 'test', v_in;
END$$;

SELECT setting_value FROM setting WHERE setting_name = 'test';
    setting_value     
----------------------
 (text,"(text,test)")
(1 row)

To support this, you can instead use variant.text_in():

DO $$
DECLARE v_in text;
BEGIN
v_in := '(text,test)';
INSERT INTO setting SELECT 'test2', variant.text_in(v_in, 'setting');
END$$;

SELECT setting_value FROM setting WHERE setting_name = 'test2';
 setting_value 
---------------
 (text,test)
(1 row)

The second argument to text_in() is the registered name of the variant. If omitted, the default variant is used. You may also pass in the raw typmod value.

create_casts()

The primary interface for storing and retrieving data from a variant is casting. For that to work, we need to tell Postgres that it's OK to cast from an existing data type to a variant and vice-versa.

To facilitate this, the function variant.create_casts() will create casts to and from variant for all existing types (note that composite types are not supported). When you install variant it will create all these casts for you, but if you add new data types after installation you should SELECT variant.create_casts();.

TODO

  • Better support for dropping types

variant uses the input and output functions for each type. It also stores the type in it's native storage format (as opposed to something like text). This means that if you drop a type that has been used to store data in a variant you won't be able to get your data back. Generally this shouldn't be a danger, because you should have casts defined between variant and all your types. However, variant doesn't actually enforce that, and even if it did you could always drop the cast first or use the CASCADE option to DROP TYPE.

My plan here is to allow specifying exactly what types a registered variant is allowed to use. Because it's easy to see what columns are using a particular registered variant we could do something to verify that no records exist with the type in question before dis-allowing that types use with that registered variant. I hope we could also create pg_depend entries that would explicitly tie the original data types to individual table fields.

  • Dynamic type support

Once we can restrict registered variants to only using particular types, it would be nice to have "dynamic" variants that have all the tracking associated with restricted variants, but allow you to put whatever you want into the variant. In essence, instead of throwing an error if you try and use an unapproved type, the variant would dynamically mark that type as being approved.

Support

You can see the current status of released versions of this extension on PGXN-tester.

Travis-CI status: Build Status

Please report issues at https://github.com/decibel/variant/issues.

Author

Jim Nasby, Blue Treble Consulting

Copyright and License

Copyright (c) 2014 Jim Nasby