Firebird: Volltextindex

2 - Texttabellen

Die Generierung des Volltextindex der Datenbank wird durch Trigger in den Texttabellen angestoßen und in einigen STORED PROCEDURES erstellt.

Beispieltexttabelle

CREATE TABLE "texts_test" (
    "PK_txt"               INTEGER NOT NULL,
    "CL_txt_text"          BLOB SUB_TYPE 1 SEGMENT SIZE 80,
    "CL_txt_length"        INTEGER NOT NULL,
    "CL_txt_bytes"         INTEGER NOT NULL,
    "CL_txt_hash"          BIGINT NOT NULL,
    "CL_txt_update_index"  SMALLINT DEFAULT 0 NOT NULL,
    "CL_txt_update_delay"  SMALLINT DEFAULT 0 NOT NULL,
    "CL_txt_inserted"      TIMESTAMP DEFAULT 'NOW' NOT NULL,
    "CL_txt_updated"       TIMESTAMP
);

ALTER TABLE "texts" ADD CONSTRAINT "PK_texts"
    PRIMARY KEY ("PK_txt");

Trigger der Texttabellen

In den Texttabellen gibt es typischerweise zwei Trigger für den Volltextindex. Der erste Trigger wird vor einem Update oder Insert gefeuert und prüft Änderungen am Textfeld. Wurde der Datensatz neu angelegt oder hat sich der Text geändert, wird ein Flag gesetzt (im Beispiel "CL_txt_update_index"), das im zweiten Trigger ausgewertet wird.

Erster Trigger

CREATE OR ALTER TRIGGER "texts_BIU0" FOR "texts"
ACTIVE BEFORE INSERT OR UPDATE POSITION 0
AS
DECLARE VARIABLE TEXT_BUF BLOB SUB_TYPE 1 SEGMENT SIZE 80;
BEGIN

  /* PK */
  IF (NEW."PK_txt" IS NULL)
  THEN
    NEW."PK_txt" = GEN_ID("gen_PK", 1);

  /* Zeitpunkt */
  IF (INSERTING)
  THEN
  BEGIN
    NEW."CL_txt_inserted" = 'NOW';
  END
  ELSE
  BEGIN
    IF (UPDATING)
    THEN
    BEGIN
      NEW."CL_txt_inserted" = OLD."CL_txt_inserted";
      NEW."CL_txt_updated"  = 'NOW';
    END
  END

  /* Texteigenschaften */
  TEXT_BUF = COALESCE(NEW."CL_txt_text", '');
  NEW."CL_txt_length" = CHAR_LENGTH (TEXT_BUF);
  NEW."CL_txt_bytes"  = OCTET_LENGTH(TEXT_BUF);
  NEW."CL_txt_hash"   = HASH        (TEXT_BUF);

  /* Flag zur Berechnung des Volltextindex */
  IF (COALESCE(NEW."CL_txt_update_index", 0) = 0)
  THEN
  BEGIN
    IF (
         (NEW."CL_txt_length" <> COALESCE(OLD."CL_txt_length", 0))
         OR
         (NEW."CL_txt_bytes"  <> COALESCE(OLD."CL_txt_bytes" , 0))
         OR
         (NEW."CL_txt_hash"   <> COALESCE(OLD."CL_txt_hash"  , 0))
       )
    THEN
      NEW."CL_txt_update_index" = 1;
    ELSE
      NEW."CL_txt_update_index" = 0;
  END

END

Zweiter Trigger

Der zweite Trigger wird gefeuert, nachdem der Datensatz geschrieben wurde. Hier wird das Flag aus dem ersten Trigger ausgewertet, und gegebenenfalls der Volltextindex für das geänderte Textfeld erzeugt. Das hat den Vorteil, daß das Flag auch manuell gesetzt werden kann, um den Volltextindex manuell zu erzeugen.

Es gibt noch ein zweites Flag (im Beispiel "CL_txt_update_delay"), das dazu führt, daß der Trigger den Volltextindex nicht sofort erzeugt. Das kann z.B. dazu verwendet werden, die Wartezeit bei der Indizierung in einen eigenen Programm-Thread auszulagern. Dazu wird beim ändern des Textes das Flag auf 1 gesetzt. Dadurch wird die Indizierung nicht ausgeführt und die Speicherung des Datensatzes läuft normal schnell. In einem eigenen Thread wird dann das Flag mit einem UPDATE auf 0 gesetzt und die Indizierung wird gestartet.

CREATE OR ALTER TRIGGER "texts_AIU0" FOR "texts"
ACTIVE AFTER INSERT OR UPDATE POSITION 0
AS

  DECLARE VARIABLE BLOCK_SIZE INTEGER;
  DECLARE VARIABLE ID         INTEGER;
  DECLARE VARIABLE WORD_INDEX INTEGER;

BEGIN

  IF (  
       (NEW."CL_txt_update_index" = 1)
       AND
       (NEW."CL_txt_update_delay" = 0)
     )
  THEN 
  BEGIN

    /* Text locken, damit kein anderer Prozess daran arbeitet. */
         SELECT "PK_txt"
           FROM "texts"
          WHERE "PK_txt" = NEW."PK_txt"
     FOR UPDATE OF "CL_txt_text"
      WITH LOCK
           INTO :ID;

    /* Blockgröße bestimmen */
    EXECUTE PROCEDURE "prc_domain_text_length"('dom_TextCache')
      RETURNING_VALUES BLOCK_SIZE;

    /* Aufruf je nach Textlänge */
    IF (CHAR_LENGTH(NEW."CL_txt_text") <= BLOCK_SIZE)
    THEN
      EXECUTE PROCEDURE "prc_ftx_index_char"(NEW."CL_txt_text", 1, 1, 1, 1, 4)
        RETURNING_VALUES WORD_INDEX;
    ELSE
      EXECUTE PROCEDURE "prc_ftx_index_text"(NEW."CL_txt_text", 1, 1, 1, 1, 4)
        RETURNING_VALUES WORD_INDEX;

    /* alte Verknüpfungen löschen */
    IF (UPDATING)
    THEN
      DELETE FROM
        "text_link_words"
       WHERE
         "FK_tlw_txt" = :ID;

    /* temporäre Daten kopieren */
    INSERT INTO
      "text_link_words"
      ("FK_tlw_txt", "FK_tlw_wrd", "CL_tlw_index", "CL_tlw_position", "FK_tlw_wty")
    SELECT
      :ID, "FK_ttw_wrd", "CL_ttw_index", "CL_ttw_position", "FK_ttw_wty"
    FROM
      "_temp_text_link_words";

    /* temporäre Daten löschen */
    DELETE FROM
      "_temp_text_link_words";

    /* Update-Flag zurücksetzen */
    UPDATE
      "texts"
    SET
      "CL_txt_update_index" = 0
    WHERE
      "PK_txt" = :ID;

  END

  WHEN ANY
  DO
  BEGIN
    /* temporäre Daten löschen */
    DELETE FROM
      "_temp_text_link_words";
  END

END