Hlavní navigace

Rozšiřování PostgreSQL v C - Datové typy

Pavel Stěhule

Z minulého dílu máme k dispozici funkce pro určení validity, datumu narození a pohlaví z rodného čísla. Zatím jsem předpokládal, že rodné číslo ukládám jako CHAR(11). Zřejmou nevýhodou je, že vždy, když chci určit některý atribut rodného čísla, dochází k parsování řetězce. Navíc z důvodu efektivnosti nemohu použít rodné číslo jako index a ani nemohu pořádně třídit podle něj, jelikož v rodném čísle může, ale nemusí být použit znak lomítko.

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

Příklady z contribu
  • isbn_isnn
Ostatní
Našli jste v článku chybu?
Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Vitalia.cz: Proč vás každý zubař posílá na dentální hygienu

Proč vás každý zubař posílá na dentální hygienu

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Měšec.cz: Komu musí od ledna zvýšit mzdu?

Komu musí od ledna zvýšit mzdu?

Vitalia.cz: Vychytané vály a válečky na vánoční cukroví

Vychytané vály a válečky na vánoční cukroví

Vitalia.cz: Pamlsková vyhláška bude platit jen na základkách

Pamlsková vyhláška bude platit jen na základkách

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla

120na80.cz: Co všechno ovlivňuje ženskou plodnost?

Co všechno ovlivňuje ženskou plodnost?

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Potvrzeno: Pobyt v lese je skvělý na imunitu

Potvrzeno: Pobyt v lese je skvělý na imunitu

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

DigiZone.cz: Flix TV: dva set-top boxy za korunu

Flix TV: dva set-top boxy za korunu

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Měšec.cz: mBank cenzuruje, zrušila mFórum

mBank cenzuruje, zrušila mFórum