Zum Inhalt springen

Union-Typ

Ein UNION-Typ (nicht zu verwechseln mit dem SQL-UNION-Operator) ist ein verschachtelter Typ, der einen von mehreren „alternativen“ Werten aufnehmen kann, ähnlich dem union in C. Der wesentliche Unterschied ist, dass diese UNION-Typen tagged Unions sind und daher immer einen Diskriminator-„Tag“ mitführen, der angibt, welche Alternative aktuell gehalten wird, selbst wenn der innere Wert selbst null ist. UNION-Typen ähneln daher eher std::variant aus C++17, Enum in Rust oder dem „Summentyp“, der in den meisten funktionalen Sprachen vorkommt.

UNION-Typen müssen mindestens ein Member haben, und obwohl sie mehrere Member desselben Typs enthalten können, müssen die Tag-Namen eindeutig sein. UNION-Typen können höchstens 256 Member haben.

Unter der Haube sind UNION-Typen auf STRUCT-Typen aufgebaut und halten den „Tag“ einfach als ersten Eintrag.

UNION-Werte können mit der Funktion union_value(tag := expr) oder durch Casten von einem Member-Typ erzeugt werden.

Beispiel

Eine Tabelle mit einer UNION-Spalte anlegen:

CREATE TABLE tbl1 (u UNION(num INTEGER, str VARCHAR));
INSERT INTO tbl1 VALUES (1), ('two'), (union_value(str := 'three'));

Jeder Typ kann implizit in ein UNION gecastet werden, das den Typ enthält. Jedes UNION kann auch implizit in ein anderes UNION gecastet werden, wenn die Member des Quell-UNION eine Teilmenge der Ziel-Member sind (sofern der Cast eindeutig ist).

UNION verwendet die VARCHAR-Cast-Funktionen der Member-Typen beim Casten nach VARCHAR:

SELECT u FROM tbl1;
u
1
two
three

Alle str-Member auswählen:

SELECT union_extract(u, 'str') AS str
FROM tbl1;
str
NULL
two
three

Alternativ können Sie analog zu STRUCTs die Punkt-Syntax verwenden.

SELECT u.str
FROM tbl1;
str
NULL
two
three

Den aktuell aktiven Tag aus dem UNION als ENUM auswählen.

SELECT union_tag(u) AS t
FROM tbl1;
t
num
str
str

Union-Casts

Im Vergleich zu anderen verschachtelten Typen erlauben UNIONs eine Menge impliziter Casts, um die unaufdringliche und natürliche Verwendung ihrer Member als „Subtypen“ zu erleichtern. Diese Casts wurden jedoch nach zwei Prinzipien entworfen: Mehrdeutigkeit zu vermeiden und Casts zu vermeiden, die zu Informationsverlust führen könnten. Dadurch sind UNIONs nicht vollständig „transparent“, können aber dennoch eine „Supertyp“-Beziehung zu ihren Membern haben.

Daher können UNION-Typen im Allgemeinen nicht implizit in einen ihrer Member-Typen gecastet werden, weil die Information in den anderen Membern, die nicht dem Zieltyp entsprechen, „verloren“ ginge. Wenn Sie ein UNION in eines seiner Member zwingen möchten, sollten Sie stattdessen explizit die Funktion union_extract verwenden.

Die einzige Ausnahme ist das Casten eines UNION nach VARCHAR; in diesem Fall verwenden alle Member ihre entsprechenden VARCHAR-Casts. Da alles nach VARCHAR gecastet werden kann, ist das in gewissem Sinne „sicher“.

Casten auf Unions

Ein Typ kann immer implizit in ein UNION gecastet werden, wenn er implizit in einen der UNION-Member-Typen gecastet werden kann.

  • Gibt es mehrere Kandidaten, bestimmen die eingebauten Prioritätsregeln für implizite Casts den Zieltyp. Ein Cast FLOATUNION(i INTEGER, v VARCHAR) castet den FLOAT beispielsweise immer zuerst auf das INTEGER-Member vor VARCHAR.
  • Bleibt der Cast mehrdeutig, d. h. es gibt mehrere Kandidaten mit derselben Priorität für implizite Casts, wird ein Fehler ausgelöst. Das geschieht üblicherweise, wenn das UNION mehrere Member desselben Typs enthält, z. B. ist FLOATUNION(i INTEGER, num INTEGER) immer mehrdeutig.

Wie unterscheiden wir also, wenn wir ein UNION mit mehreren Membern desselben Typs erzeugen wollen? Mit der Funktion union_value, die ein Keyword-Argument für den Tag entgegennimmt. union_value(num := 2::INTEGER) erzeugt beispielsweise ein UNION mit einem einzelnen Member vom Typ INTEGER mit dem Tag num. Das kann dann verwendet werden, um in einem expliziten (oder impliziten, siehe unten!) UNION-nach-UNION-Cast zu unterscheiden, etwa CAST(union_value(b := 2) AS UNION(a INTEGER, b INTEGER)).

Casten zwischen Unions

UNION-Typen können ineinander gecastet werden, wenn der Quelltyp eine „Teilmenge“ des Zieltyps ist. Mit anderen Worten: Alle Tags im Quell-UNION müssen im Ziel-UNION vorhanden sein, und alle Typen der übereinstimmenden Tags müssen implizit zwischen Quelle und Ziel castbar sein. Im Wesentlichen bedeutet das, dass UNION-Typen bezüglich ihrer Member kovariant sind.

Ok Quelle Ziel Kommentar
UNION(a A, b B) UNION(a A, b B, c C)
UNION(a A, b B) UNION(a A, b C) wenn B implizit nach C gecastet werden kann
UNION(a A, b B, c C) UNION(a A, b B)
UNION(a A, b B) UNION(a A, b C) wenn B nicht implizit nach C gecastet werden kann
UNION(A, B, D) UNION(A, B, C)

Vergleich und Sortierung

Da UNION-Typen intern auf STRUCT-Typen aufgebaut sind, können sie mit allen Vergleichsoperatoren sowie in WHERE- und HAVING-Klauseln mit derselben Semantik wie STRUCTs verwendet werden. Der „Tag“ wird immer als erster Struct-Eintrag gespeichert, sodass UNION-Typen zuerst nach „Tag“ verglichen und geordnet werden.

Funktionen

Siehe Union-Funktionen.