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 strFROM tbl1;| str |
|---|
| NULL |
| two |
| three |
Alternativ können Sie analog zu STRUCTs die Punkt-Syntax verwenden.
SELECT u.strFROM tbl1;| str |
|---|
| NULL |
| two |
| three |
Den aktuell aktiven Tag aus dem UNION als ENUM auswählen.
SELECT union_tag(u) AS tFROM 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
FLOAT→UNION(i INTEGER, v VARCHAR)castet denFLOATbeispielsweise immer zuerst auf dasINTEGER-Member vorVARCHAR. - 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
UNIONmehrere Member desselben Typs enthält, z. B. istFLOAT→UNION(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.