Montag, 8. Juni 2009

USER_DATASTORE ... indiziert wirklich alles

Eine der interessanten Eigenschaften des Oracle TEXT-Index ist, dass die zu indizierenden Dokumente nicht nur einfach aus einer Tabellenspalte, sondern aus unterschiedlichen Data Stores kommen können.
  • DIRECT_DATASTORE: der "Normalfall"
  • MULTICOLUMN_DATASTORE: Mehrere Tabellenspalten (dazu hatten wir schon Blog-Postings
  • FILE_DATASTORE: Die Dokumente liegen im Dateisysten; die Tabelle enthält für jedes Dokument einen vollständigen, absoluten Pfad
  • URL_DATASTORE: Die Dokumente liegen im "Netzwerk"; die Tabelle enthält für jedes Dokument einen vollständigen, absoluten URL (FTP oder HTTP)
  • DETAIL_DATASTORE: Die Dokumente liegen in einer anderen Tabelle, welche mit der zu indizierenden in einer Master-Detail-Beziehung steht
  • NESTED_DATASTORE: Die Dokumente liegen in nicht direkt in der zu indizierenden Tabelle, sondern in einer Nested Table (dürfte selten vorkommen).
Und schließlich gibt es den USER_DATA_STORE, der quasi "alles kann". Und um den soll es in diesem Posting auch gehen. Als Ausgangspunkt haben wir eine Tabelle mit Dokumenten und diese sollen nicht direkt indiziert werden, sondern über einen USER_DATA_STORE. Warum? Weil wir über eine zusätzliche Spalte (INDEX_DOCUMENT) kontrollieren möchten, ob die Dokumente indiziert werden sollen oder nicht. Beginnen wir mit dem Tabellenaufbau ...
SQL> desc dokumente
 Name                                      Null?    Typ
 ----------------------------------------- -------- ---------------------

 ID                                                 NUMBER
 FILENAME                                           VARCHAR2(2000)
 DOCUMENT                                           BLOB
 INDEX_DOCUMENT                                     CHAR(1)
Nun geht es ans Erstellen des Textindex mit dem User Datastore. Ein User Datastore bedeutet nichts weiter als dass der Index seine Dokumente von einer PL/SQL-Prozedur zugewiesen bekommt und diese eben nicht direkt aus der Tabelle holt. Da man in der PL/SQL-Prozedur programmieren kann, was man möchte, ist das Verhalten des User Datastore auch sehr individuell. Dieses Beispiel soll (wie gesagt), das Dokument normal indizieren, wenn die Spalte INDEX_DOCUMENT auf "Y" steht und gar nicht indizieren, wenn die Spalte auf "N" steht. Beginnen wir mit der PL/SQL-Prozedur:
create or replace procedure dokument_uds_proc (
  rid  in              rowid,
  tlob in out NOCOPY   blob    
) is
begin
  begin
    select document into tlob
    from dokumente where rowid = rid and index_document = 'Y';
  exception
    when NO_DATA_FOUND then tlob := null;
  end;
end;
/
Wichtig an dieser Prozedur ist die Signatur, diese muss genau so aussehen, wie in diesem Beispiel vorgegeben. Die zu indizierende ROWID wird in die Prozedur hineingegeben, das zu indizierende Dokument kommt als OUT-Parameter wieder zurück. Man sieht, dass man das Dokument selbst nun beliebig zusammensetzen kann. Die Inhalte können sehr wohl aus verschiedensten Tabellen oder anderen Datenquellen kommen.
Aber bis jetzt ist die Prozedur nur eine normale PL/SQL-Prozedur. Damit sie für etwas ORACLE Text etwas bewirken kann, muss sie zunächst in Oracle TEXT bekannt gemacht werden; die folgenden PL/SQL-Blöcke richten den User Datastore im Dictionary von Oracle TEXT ein.
begin
  ctx_ddl.drop_preference('DOKUMENT_UDS');
end;
/
sho err

begin
  ctx_ddl.create_preference('DOKUMENT_UDS','user_datastore');
  ctx_ddl.set_attribute('DOKUMENT_UDS','procedure','dokument_uds_proc');
  ctx_ddl.set_attribute('DOKUMENT_UDS','output_type','blob_loc');
end;
/
sho err
Bevor der Index nun erzeugt wird, fehlt noch eine Kleinigkeit: Der Index muss in zwei Fällen aktualisiert werden: Erstens wenn sich das Dokument ändert und zweitens wenn die Spale INDEX_DOCUMENT verändert wird. Wenn der Index also auf die Spalte INDEX_DOCUMENT gelegt wird, benötigen wir noch einen Trigger, der UPDATE-Operationen in DOCUMENT auf die Spalte INDEX_DOCUMENT überträgt:
create or replace trigger trg_uds_document
before update on dokumente
for each row
begin
  :new.index_document := :new.index_document;
end;
/
Nun kann der Index erstellt werden. Wie schon beschrieben, wird der Index auf die Spalte INDEX_DOCUMENT gelegt:
create index idx_text_dokumente
on dokumente (index_document)
indextype is ctxsys.context
parameters ('filter ctxsys.auto_filter
             datastore dokument_uds
             memory 200M
             transactional')
/
Wenn alle Einträge ein "N" in der Spalte INDEX_DOCUMENT haben, ist der Index nach Erstellung immer noch leer. Setzt man eine oder mehrere Zeilen auf "Y", so können diese anschließend dank des TRANSACTIONAL-Parameters (Posting!) gefunden werden, das dauert aber sehr lange. Kein Wunder, denn der Index ist noch nicht synchronisiert; die Dokumente in der Pending-Tabelle werden also on-the-fly durchsucht und dabei muss natürlich auch die PL/SQL-Prozedur des User Datastore durchlaufen werden. Nach einem CTX_DDL.SYNC_INDEX('IDX_TEXT_DOKUMENTE') befinden sich die Einträge denn auch im Textindex.
Dieses einfache Beispiel zeigt, wie man einen User Datastore nutzen kann. Wichtig ist, dass hier keine Grenzen existieren; die PL/SQL-Prozedur des UDS ist für Oracle TEXT eine "Black Box"; man kann dort hineinprogrammieren, was man möchte ...

Beliebte Postings