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?