Tyto problémy můžeme vyřešit vytvořením vlastního typu (zvýší se ale režie při zobrazení hodnoty). Samotné vytvoření typu spočívá v:
- definici typu
- vytvoření konverzních funkcí mezi naším typem a typem cstring
- vytvoření funkcí pro operátory
- registraci typu a registraci operátorů
Typ rodné číslo se skládá z datumu narození a indexu. Jelikož index je vždy menší nežli 32767, mohu nejvyšší bit použít pro určení pohlaví.
typedef struct rodne_cislo { DateADT narozen; int16 index; } rodne_cislo;
Velikost typu je 8 bytů, tudíž bude všem funkcím předáván odkazem. Dodefinuji si makra (jde jen o přetypování ukazatele).
#define DatumGetRodneCisloP(X) ((rodne_cislo*) DatumGetPointer(X)) #define RodneCisloPGetDatum(X) PointerGetDatum(X) #define PG_GETARG_RC_P(n) DatumGetRodneCisloP(PG_GETARG_DATUM(n)) #define PG_RETURN_RC_P(X) return RodneCisloPGetDatum(X)
Základem jsou konverzní funkce z a do typu cstring. PostgreSQL je automaticky použije při zobrazení a zadávání konkrétní hodnoty. Doporučuje se tyto funkce pojmenovat typ_out a typ_in. V příkladu je použita funkce parsen_rodné_číslo z předchozího dílu.
PG_FUNCTION_INFO_V1 (rodne_cislo_in); PG_FUNCTION_INFO_V1 (rodne_cislo_out); Datum rodne_cislo_in (PG_FUNCTION_ARGS) { int parts [] = {0, 0, 0, 0}, result, pohlavi; char *rcstr; rodne_cislo *rc; if (PG_ARGISNULL (0)) PG_RETURN_NULL (); rcstr = PG_GETARG_CSTRING (0); result = parsen_rodne_cislo (rcstr, parts, strlen (rcstr)); if (result != 0) elog (ERROR, chyba [result - 1]); rc = (rodne_cislo*) palloc (sizeof (rodne_cislo)); if (parts [1] > 50) { pohlavi = 0; parts [1] -= 50; } else pohlavi = 1; rc -> narozen = date2j (parts [0], parts [1], parts [2]) - date2j (2000, 1, 1); rc -> index = (parts [3] | pohlavi << 15); PG_RETURN_RC_P (rc); } char *sts_ito2d (int i, char *temp); Datum rodne_cislo_out (PG_FUNCTION_ARGS) { int r, m, d, r2, length; char temp [10], *rcstr; rodne_cislo *rc; if (PG_ARGISNULL (0)) PG_RETURN_NULL (); rc = PG_GETARG_RC_P (0); j2date (rc -> narozen + date2j (2000, 1, 1), &r, &m, &d); if ((rc -> index & 32768) == 0) m += 50; if (r > 2000) r2 = r - 2000; else if (r > 1900) r2 = r - 1900; else if (r > 1800) r2 = r - 1800; rcstr = (char*) palloc (12); rcstr [0] = '\0'; strcat (rcstr, sts_ito2d (r2, temp)); strcat (rcstr, sts_ito2d (m, temp)); strcat (rcstr, sts_ito2d (d, temp)); sprintf (temp, "%d", rc -> index & 32767); length = strlen (temp); if (r <= 1953) { strcat (rcstr, "/000"); strcpy (&rcstr [10 - l], temp); } else { strcat (rcstr, "/0000"); strcpy (&rcstr [11 - l], temp); } PG_RETURN_CSTRING (rcstr); } char *sts_ito2d (int i, char *buf) { if (i < 10) sprintf (buf, "0%d", i); else sprintf (buf, "%d", i); return buf; }
Nyní již můžeme deklarovat tyto funkce a typ rodne_cislo v PostgreSQL.
DROP TYPE rodne_cislo CASCADE; CREATE OR REPLACE FUNCTION rodne_cislo_in (cstring) RETURNS rodne_cislo AS 'rc.so','rodne_cislo_in' LANGUAGE 'C'; CREATE OR REPLACE FUNCTION rc_out (rodne_cislo) RETURNS cstring AS 'rc.so', 'rodne_cislo_out' LANGUAGE 'C'; -- poprve se vypise varovani, ze typ rodne_cislo neni CREATE TYPE rodne_cislo ( internallength = 8, input = rc_in, output = rc_out );
Teď už nám nic nebrání navrhnout tabulku osoby a vložit do ní jeden záznam.
CREATE TABLE osoby ( rc rodne_cislo, jmeno VARCHAR(20), prijmeni VARCHAR(20) ); INSERT INTO osoby VALUES ('7307150807','Pavel','Stěhule');
Můžeme si napsat funkce, které jsou obdobou funkcí sex a birth_date z předchozího dílu seriálu. Čistější by bylo (alespoň v případě data narození) napsat si vlastní přetypování, tedy datum narození získat přetypováním rodného čísla do datumu, pohlaví do charu.
Konverze musí být realizovány funkcí, kdy parametrem je hodnota v původním typu a výsledkem hodnota v novém typu.
PG_FUNCTION_INFO_V1 (rodne_cislo_todate); PG_FUNCTION_INFO_V1 (rodne_cislo_dobpchar); Datum rodne_cislo_todate (PG_FUNCTION_ARGS) { rodne_cislo *rc = PG_GETARG_RC_P (0); PG_RETURN_DATEADT (rc -> narozen); } Datum rodne_cislo_tobpchar (PG_FUNCTION_ARGS) { rodne_cislo * rc = PG_GETARG_RC_P (0); BpChar *result = (BpChar *) palloc (VARHDRSZ + 1); VARATT_SIZEP (result) = VARHDRSZ + 1; if (rc -> index & 32768) *(VARDATA (result)) = 'M'; else *(VARDATA (result)) = 'F'; PG_RETURN_BPCHAR_P (result); }
V SQL nadeklarujeme nové funkce a přetypování.
CREATE OR REPLACE FUNCTION rodne_cislo_todate (rodne_cislo) RETURNS date AS 'rc.so', 'rodne_cislo_todate' LANGUAGE 'C' STRICT IMMUTABLE; CREATE OR REPLACE FUNCTION rodne_cislo_tobpchar (rodne_cislo) RETURNS char AS 'rc.so', 'rodne_cislo_tobpchar' LANGUAGE 'C' STRICT IMMUTABLE; DROP CAST (rodne_cislo AS date); CREATE CAST (rodne_cislo AS date) WITH FUNCTION rodne_cislo_todate (rodne_cislo); DROP CAST (rodne_cislo AS char); CREATE CAST (rodne_cislo AS char) WITH FUNCTION rodne_cislo_tobpchar (rodne_cislo);
Funkce, které chceme použít pro přetypování, musí být IMMUTABLE, tj. výsledek funkce záleží pouze na parametrech funkce. STRICT mi zajišťuje, že pokud by byl parametr NULL, pak je automaticky výsledkem funkce NULL. Odpadá tím nutnost kontrolovat vstupní parametry na NULL.
Přetypování můžeme vyzkoušet příkazem
SELECT CAST ('7307150807'::rodne_cislo AS date);
Celý příklad naleznete v archivu.
Zdroje
- V dokumentaci
- isbn_isnn