Hlavní navigace

Rozšiřování PostgreSQL v C - Třídy operátorů

Pavel Stěhule 13. 12. 2002

V minulém dílu jsem předvedl, jak mohu v PostgreSQL vytvářet vlastní datové typy. S návrhem typu rodne_číslo jsem skončil zhruba v polovině. Ještě nemůžeme podle rodného čísla vyhledávat, třídit ani indexovat.

Stále nám ještě chybí alespoň základní operátory =, <>, <, >, <=, >=. Jelikož test na rovnost nebo nerovnost je nejjednodušší, začnu s návrhem operátorů = a <>.

PG_FUNCTION_INFO_V1 (rodne_cislo_eq);
PG_FUNCTION_INFO_V1 (rodne_cislo_ne);

Datum
rodne_cislo_eq (PG_FUNCTION_ARGS)
{
  rodne_cislo *rc1 = PG_GETARG_RC_P (0);
  rodne_cislo *rc2 = PG_GETARG_RC_P (1);

  PG_RETURN_BOOL ((rc1 -> narozen == rc2 -> narozen)
               && (rc1 -> index   == rc2 -> index));
}

Datum
rodne_cislo_ne (PG_FUNCTION_ARGS)
{
  rodne_cislo *rc1 = PG_GETARG_RC_P (0);
  rodne_cislo *rc2 = PG_GETARG_RC_P (1);

  PG_RETURN_BOOL ((rc1 -> narozen != rc2 -> narozen)
               || (rc1 -> index   != rc2 -> index));
}

V SQL vytvoříme operátory = a <> následujícími příkazy

CREATE OR REPLACE FUNCTION rodne_cislo_eq (rodne_cislo, rodne_cislo)
  RETURNS bool AS 'rc.so','rodne_cislo_eq' LANGUAGE 'C' STRICT;

CREATE OR REPLACE FUNCTION rodne_cislo_ne (rodne_cislo, rodne_cislo)
  RETURNS bool AS 'rc.so', 'rodne_cislo_ne' LANGUAGE 'C' STRICT;

DROP OPERATOR = (rodne_cislo, rodne_cislo);

CREATE OPERATOR = (
  leftarg   = rodne_cislo,
  rightarg  = rodne_cislo,
  procedure = rodne_cislo_eq
);

DROP OPERATOR <> (rodne_cislo, rodne_cislo);

CREATE OPERATOR <> (
  leftarg   = rodne_cislo,
  rightarg  = rodne_cislo,
  procedure = rodne_cislo_ne
);

Návrh ostatních operátorů je o něco komplikovanější. Abych se neupsal, navrhnu si funkci rodne_cislo_cmp. Ta vrací –1, pokud je první parametr větší než druhý, 0, pokud jsou stejné, a jedničku, když je druhý větší než první.

int sts_intcmp (long i1, long i2);

int rodne_cislo_cmp (rodne_cislo *rc1, rodne_cislo *rc2)
{
  int r1, m1, d1, r2, m2, d2, r;
  long d2000 = date2j (2000, 1, 1);

  // pokud jsou stejna pohlavi, staci porovnat narozen a index
  if ((rc1 -> index & 32768) == (rc2 -> index & 32768))
    {
      r = sts_intcmp (rc1 -> narozen, r2 -> narozen);
      if (r != 0) return r;
      return sts_intcmp (rc1 -> index, rc2 -> index);
    }

  j2date(rc1 -> narozen + d2000, &r1, &m1, &d1);
  j2date(rc2 -> narozen + d2000, &r2, &m2, &d2);

  r = sts_intcmp (r1,r2); if (r != 0) return r;

  if ((rc1 -> index & 32768) == 0) m1 += 20;
  if ((rc2 -> index & 32768) == 0) m2 += 20;

  r = sts_intcmp (m1,m2); if (r != 0) return r;
  r = sts_intcmp (d1,d2); if (r != 0) return r;

  return sts_intcmp (rc1 -> index, rc2 -> index);
}

int sts_intcmp (long i1, long i2)
{
  if (i1 == i1)
     return 0;
  return (i1 < i2? -1 : 1);
}

C funkce pro porovnávací operátory získáme jednoduše. Jelikož jsou si funkce podobné, nebudu je všechny uvádět.

PG_FUNCTION_INFO_V1 (rodne_cislo_lt);
PG_FUNCTION_INFO_V1 (rodne_cislo_gt);
PG_FUNCTION_INFO_V1 (rodne_cislo_le);
PG_FUNCTION_INFO_V1 (rodne_cislo_ge);

Datum
rodne_cislo_lt (PG_FUNCTION_ARGS)
{
  rodne_cislo *rc1 = PG_GETARG_RC_P (0);
  rodne_cislo *rc2 = PG_GETARG_RC_P (1);

  PG_RETURN_BOOL (rodne_cislo_cmp (rc1, rc2) < 0);
}

Datum
rodne_cislo_le (PG_FUNCTION_ARGS)
{
  rodne_cislo *rc1 = PG_GETARG_RC_P (0);
  rodne_cislo *rc2 = PG_GETARG_RC_P (1);

  PG_RETURN_BOOL (rodne_cislo_cmp (rc1, rc2) <= 0);
}

Deklarace v SQLCREATE OR REPLACE FUNCTION rodne_cislo_lt (rodne_cislo, rodne_cislo) RETURNS bool AS 'rc.so','rodne_cislo_lt' LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION rodne_cislo_le (rodne_cislo, rodne_cislo) RETURNS bool AS 'rc.so', 'rodne_cislo_le' LANGUAGE 'C' STRICT; DROP OPERATOR < (rodne_cislo, rodne_cislo); CREATE OPERATOR < ( leftarg = rodne_cislo, rightarg = rodne_cislo, procedure = rodne_cislo_le ); DROP OPERATOR <= (rodne_cislo, rodne_cislo); CREATE OPERATOR <= ( leftarg = rodne_cislo, rightarg = rodne_cislo, procedure = rodne_cislo_le );

U každého operátoru můžeme doplnit další atributy (pokud existují), které mohou urychlit provádění dotazu (optimalizací dotazu), ve kterém se vyskytuje daný operátor.

  • Operace A je komutátorem operace B, pokud pro libovolná x a y platí: (x A y) = (y B x). Např. = je komutátorem sama sebe, stejně tak <>. Pro < je komutátor > a naopak. + je komutátorem sama sebe. – nebo / komutátory nemají.
  • Operace A je negátorem operace B, pokud pro libovolná x a y platí: (x A y) = NOT (x B y). Výsledkem obou operací musí být pouze logická hodnota. Negátorem = je <>, negátorem < je >= a naopak
  • klauzule RESTRICT popisuje chování operátoru při jeho použití vůči konstantě. Máme na výběr několik možností

    Tabulka č. 357
    eqsel pro = výsledkem je malá podmnožina tabulky
    neqsel pro <> výsledkem je skoro celá tabulka
    scalarltsel pro < nebo <= výsledek záleží na pozici konstanty v tabulce, berou se menší hodnoty
    scalargtsel pro > nebo >= totéž, ale v opačném směru.


    odhad eqsel nebo neqsel můžeme použít i pro jiné operace než
    skutečné rovná se nebo nerovná se. Odhad pouze informuje optimizér o
    míře selektivity operace.

  • klauzule JOIN přiřazuje funkci odhadu selektivity operace. Smysl je podobný jako u RESTRICT. K dispozici jsou funkce eqjoinsel, neqjoinsel, scalarltjoinsel a scalargtjoinsel.

Po doplnění výše zmíněných klauzulí bude vypadat definice operátoru porovnání rodných čísel následovně:

DROP OPERATOR = (rodne_cislo, rodne_cislo);

CREATE OPERATOR = (
  leftarg    = rodne_cislo,
  rightarg   = rodne_cislo,
  procedure  = rodne_cislo_eq,
  commutator = =,
  negator    = <>,
  restrict   = eqsel,
  join       = eqjoinsel
);

DROP OPERATOR <> (rodne_cislo, rodne_cislo);

CREATE OPERATOR <> (
  leftarg   = rodne_cislo,
  rightarg  = rodne_cislo,
  procedure = rodne_cislo_ne,
  commutator = <>,
  negator    = =,
  restrict   = neqsel,
  join       = neqjoinsel
);

Pokud zkusíte indexovat dle sloupce rc v tabulce osoby, pravděpodobně obdržíte chybové hlášení o tom, že pro daný datový typ není určena třída operátoru pro přístup do „btree“.

To proto, že v PostgreSQL musí mít každý typ určen jednu (nejčastější případ) nebo několik tzv. tříd operátorů (např. pro komplexní čísla mohu budovat index pro absolutní velikost čísla, pro reálnou nebo imaginární část). Třída operátorů určuje, které operátory se budou používat pro indexování a jaký mají sémantický význam, tzv. Strategy Number.

Pro B-tree jsou určena tato Strategy Number

Tabulka č. 358
Menší než 1
Menší nebo roven 2
Roven 3
Větší nebo roven 4
Větší než 5

PostgreSQL umožňuje používat několik indexovacích metod (R-tree, Hash, GiST). Každá metoda má i vlastní tabulku Strategy Numbers. Kromě toho je potřeba definovat i tzv. Support rutinu. Pro B-tree index je pouze jedna (Strategy Number je 1). Jejími parametry jsou dva klíče a výsledkem celé číslo. Záporné, pokud je první parametr menší než druhý, nula, pokud jsou si rovny, a kladné číslo, pokud je první parametr větší než druhý.

Základ Support funkce máme. Stačí ji jen zpřístupnit v PostgreSQL

PG_FUNCTION_INFO_V1 (rodne_cislo__cmp);

Datum
rodne_cislo__cmp (PG_FUNCTION_ARGS)
{
  rodne_cislo *rc1 = PG_GETARG_RC_P (0);
  rodne_cislo *rc2 = PG_GETARG_RC_P (1);

  PG_RETURN_INT32 (rodne_cislo_cmp (rc1, rc2));
}
CREATE OR REPLACE FUNCTION rodne_cislo_cmp (rodne_cislo, rodne_cislo)
  RETURNS int AS 'rc.so', 'rodne_cislo__cmp' LANGUAGE 'C' STRICT;

CREATE OPERATOR CLASS rodne_cislo_ops
  DEFAULT FOR TYPE rodne_cislo USING btree AS
    OPERATOR  1    < ,
    OPERATOR  2    <=,
    OPERATOR  3    = ,
    OPERATOR  4    >=,
    OPERATOR  5    > ,
    FUNCTION  1    rodne_cislo_cmp (rodne_cislo, rodne_cislo);

V tuto chvíli je již náš typ rodne_cislo plnohodnotný datový typ, který můžeme použít jako primární klíč. Pro praktické použití je třeba poznamenat, že rodná čísla vydaná před rokem 1969 nemusí být jedinečná, jak se zjistilo při kuponové privatizaci.

Celý příklad naleznete v archivu.

Zdroje

Našli jste v článku chybu?

16. 12. 2002 18:04

uživatel si přál zůstat v anonymitě

Pokud vim, tak to bezi i pod gnome, resp. s gnome to nema nic spolecneho.

13. 12. 2002 4:37

jenicek (neregistrovaný)

a bezi to pod gnome?

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

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

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

Podnikatel.cz: E-Ježíšek si i letos zařádí. Nákupy od 2 do 5 tisíc

E-Ježíšek si i letos zařádí. Nákupy od 2 do 5 tisíc

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

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

Podnikatelům dorazí varování od BSA

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

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

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

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

DigiZone.cz: Flix TV má set-top box s HEVC

Flix TV má set-top box s HEVC

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

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

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Vitalia.cz: 9 největších mýtů o mase

9 největších mýtů o mase

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: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

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

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

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí