=====Oracle Text für die Indizierung binärer Daten verwenden===== Einführung in Oracle Text => [[dba:oracle_text|Oracle Text - Volltext Suche über Text Dokumente]] Für die Verarbeitung von binären Daten, wie Word Dokumenten oder PDF stehen uns zwei Optionen mit Oracle Text zur Verfügung. Wir können die "eingebaute" Filterfunktion **"AUTO_FILTER"** verwenden oder unseren eigenen Filter **"USER_FILTER"** implementieren. Unter Filter verstehen wir hier das Wandeln einer Binären Datei, wie PDF, MS World oder auch ein TIF Bild, in den reinen enthaltenen Text. Dieser Text wird dann in den Suchindex aufgenommen. {{ :dba:oracle_text:oracle_text_binaere_daten_filtern_v01.png?direct | Oracle Text - Binäre Daten filtern}} ==Das Filterprogramm ctxhx == Das Programm **ctxhx** führt den eigentlichen Filtervorgang auch im Betriebssystem durch. Für einen ersten Test wandeln wir eine PowerPoint Datei in eine HMTL Datei (ohne Bilder) um: #Windows Powershell Syntax! #Oracle Home setzen & $env:ORACLE_HOME/bin/ctxhx.exe & $env:ORACLE_HOME/bin/ctxhx.exe d:\doag\apex_connect2016.p d:\temp\output_data.html Nett, da fallen einen gleich ein paar Ideen ein, wie das auch außerhalb von Oracle benützt werden kann. ---- === Der AUTO_FILTER Parameter === Mit dem Update auf die Version Oracle Text 11g Release 1 (Oracle Text 11.1.0.7 ) wurde von den Filtern vom Verity Keyview (Inso) auf die Filter von Outside In Technology (OIT) (ehemals Stellent) umgestellt. Die Firma Stellent wiederum wurde von Oracle schon 2006 gekauft. Der Name der Filter Option wurde von INSO_FILTER auf AUTO_FILTER angepasst. Damit das aber rückwärts kompatibel bleibt, gibt es auch weiterhin die INSO_FILTER Option, allerdings sollte immer mit AUTO_FILTER bei neuen Applikationen gearbeitet werden. In der Version 12c R1 können diese Filter auf diesen [[http://docs.oracle.com/database/121/CCREF/afilsupt.htm#CCREF2240|Plattformen]] und diesen [[http://docs.oracle.com/database/121/CCREF/afilsupt.htm#CCREF2244|Dokumenttypen]] angewandt werden. Damit steht uns die Technologie unter Windows und Linux zur Verfügung und es können die wichtigsten MS Office Formate (offiziell bis zur Version 2010) und PDF gelesen werden. Liegen die zu filternden Daten als Lob oder BFILE im Zugriff der Datenbank, ist es ausreichende den [[https://docs.oracle.com/database/121/CCREF/cdatadic.htm#CCREF9902|AUTO_FILTER]] Parameter zu setzen, der Filter erkennt automatisch das richtige Dateiformat. Beispiel: create index idx_documents_docs on documents(docs) indextype is ctxsys.context parameters ('filter ctxsys.auto_filter'); Der Filter ermittelt den Zeichensatz vom Dokument und wandelt die Zeichen entsprechend dem Datenbank Zeichensatz um. Werden binäre und reine Text Daten gemischt in einer Tabellenspalte abgelegt, kann über eine eigene Spalte der Filter gesteuert werden, in der Spalte muss der Wert "binary" für das Wandeln in Text oder "text" für „Filter nicht ausführen“ stehen. Beispiel inkl. Angabe des Zeichensatzes: CREATE TABLE documents ( id NUMBER(11) , txt_type varchar2(10) , charset_name varchar2(20) , docs BLOB ); CREATE INDEX idx_documents_docs ON documents(docs) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('filter ctxsys.AUTO_FILTER format column txt_type charset column charset_name'); Die Angabe des Zeichensatzes des Dokuments ist optional, der Zeichensatz eines TEXT Dokuments kann in einer Spalte hinterlegt werden, Angabe mit "filter ctxsys.AUTO_FILTER charset column charset_name'. Der Zeichensatzname muss aber eine Oracle Zeichensatz Bezeichnung sein, siehe für den DB Zeichensatz "SELECT value$ FROM sys.props$ WHERE name = 'NLS_CHARACTERSET';" und für alle möglichen Zeichensätze die View "V$NLS_VALID_VALUES"; ===Internals=== Wie ruft aber Oracle in 12c das Auto Filter Programm nun aber auf? Test, ob der xtproc Mechanismus zum Aufruf externer DLL's in der 12c Datenbank für Oracle Text noch verwendet wird * Listener stoppen und Index droppen * Trace auf Autofilter anlegen * Index anlegen – Klappt => Laut Trace im Log File alles ok => Daten sind da * Listener wieder einschalten Sehr viel früher ( min < 11g R1) war das meiner Meinung noch anders, sehen wir mal ob man sieht wie das heute funktioniert. Die aktuellen Filter stammen wohl aus folgenden Produkt => [[http://www.oracle.com/technetwork/middleware/content-management/oit-all-085236.html|Oracle Outside In Technology]]: * http://www.oracle.com/technetwork/middleware/content-management/viewer-096532.html * http://www.oracle.com/us/technologies/embedded/025613.htm * Doku => http://docs.oracle.com/outsidein/852/oit/index.html Die genannten DLL's lassen sich auch im Oracle Datenbank Home finden, wie das die „Identification Module“ sccfi.dll und der Export Filter für HTML Files exihtml.dll. Die einzelnen Filter heißen im Filesystem vs*.dll wie vsword.dll. Über die DDL Eigenschaften z.B. auf der vsword.dll ( den OIT Stream Filter) , kann die Version 8.3.7 von Outside In Technology erkannt werden. Das ganze scheint als LIBRARY eingebunden zu sein: select owner,LIBRARY_NAME from dba_libraries where owner ='CTXSYS'; OWNER LIBRARY_NAME --------- ------------ CTXSYS DR$LIB ---- === Der USER_FILTER Parameter === Über den USER_FILTER Parameter können auch weitere/eigene Programme zum Filtern oder bearbeiten/extrahieren der Daten verwendet werden. Nachteil: Der Aufruf erfolgt aus der Datenbank über ein Script und muss vom Entwickler eigenständig auch vollständig gegen Missbrauch abgesichert werden! Früher war das Problem noch ernster, auf meine Hinweis hin wurde mit dem [[http://www.oracle.com/technetwork/topics/security/cpuapr2009-099563.html|Critical Patch Update Advisory - April 2009]] das Verhalten soweit geändert, dass nun das Scriptt/Programm immer unter "$ORACLE_HOME/ctx/bin" liegen muss. In der Datenbank wird eine Preference hinterlegt, welches Programm bei Aufruf des Filters aufgerufen werden soll. begin ctx_ddl.create_preference ( preference_name => 'USER_FILTER_GPI_PREF', object_name => 'USER_FILTER' ); ctx_ddl.set_attribute ('USER_FILTER_GPI_PREF', 'COMMAND', 'readDocument.cmd'); end; / Beim Aufruf des Programme werden zwei Parameter übergeben, der Input Dateiname und das Output Verzeichnis. Test mit folgender CMD Datei: echo PARAM 1: %1 >> d:\temp\ctx_test.log echo PARAM 2: %2 >> d:\temp\ctx_test.log echo PARAM 3: %3 >> d:\temp\ctx_test.log Index anlegen: CREATE INDEX idx_documents_docs ON documents(docs) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('filter USER_FILTER_GPI_PREF format column txt_type charset column charset_name'); Auch hier kann optional der Typ und der Zeichensatz angeben werden, um beim Typ "text" den Filter Prozess zu überspringen. Nun können wir testen was übergeben wurde. Ausgabe unseres Test Programms, wenn Typ auf "binary" PARAM 1: "C:\Users\ORACLE~1\AppData\Local\Temp\drgub19" PARAM 2: "C:\Users\ORACLE~1\AppData\Local\Temp\drgut20" PARAM 3: Wenn Typ auf "text" dann wird das Programm nicht aufgerufen! Wenn alles richtig läuft bleiben die Dateien nicht im Temp Bereich liegen. ---- ==== Beispiel 1 - Datei wird in der Datenbank in einem Secure File in einer Tabelle gespeichert und mit AUTO_FILTER indiziert === Bzgl. Secure File siehe [[dba:oracle_secure_file_lob|LOB und Oracle Secure Files]] Zuvor die Rolle "grant CTXAPP to " vergeben. Im einfachsten Fall wird der Filter als "FILTER CTXSYS.AUTO_FILTER" definiert, ohne weitere Parameter, dann erfolgt alles automatisch mit dem Default Werten. Beispiel: #Tabelle anlegen, Secure File Lob Optionen angeben create table documents ( id number(11) , docs blob ) lob(docs) STORE AS SECUREFILE (CHUNK 4096 CACHE DISABLE STORAGE IN ROW COMPRESS HIGH TABLESPACE USERS KEEP_DUPLICATES ) / -- -- Daten einfügen z.B. mit dem SQL Loader -- Oder einfach mit dem SQL*Developer für einen ersten Test -- --Index anlegen mit dem Parameter FILTER CTXSYS.AUTO_FILTER CREATE INDEX idx_documents_docs ON documents(docs) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('FILTER CTXSYS.AUTO_FILTER') / -- --Auf Fehler prüfen select * from ctx_user_index_errors; -- --Index tabellen anzeigen lassen select table_name from user_tables where table_name like '%IDX_DO%'; Tablename ------------------------------ DR$IDX_DOCUMENTS_DOCS$I DR$IDX_DOCUMENTS_DOCS$K DR$IDX_DOCUMENTS_DOCS$N DR$IDX_DOCUMENTS_DOCS$R -- --Index Tabellen analysieren ob Daten vorhanden sind select token_text from DR$IDX_DOCUMENTS_DOCS$I; TOKEN_TEXT ---------------------------------------------------------------- pfile pipperr pool post processes ... --Daten wurden geparst und in den Index aufgenommen --Abfrage mit contains und score für die Relevanz select id,score(10) from documents where contains(docs ,'pfile',10) > 0; ID SCORE(10) ------------ ------------ 1 12 ---- ---- ====Beispiel 2 - Nur eine Referenz auf die Datei als BFILE Datei Pointer wird in der Datenbank referenziert und mit dann mit mit dem USER_FILTER indiziert ==== Zuvor die Rolle "grant CTXAPP to " vergeben, falls noch nicht durchgeführt. Sollen die Dateien im Dateisystem verbleiben, bietet es sich an nur eine Referenz auf die Datei vom Datentyp BFILE in der Datenbank zu speichern und auf diese Referenz den Index aufzubauen. === Directory Objekt anlegen und Rechte vergeben === Über ein Directory Object wird der Einstieg in die Verzeichnisstruktur auf der Festplatte definiert. In unseren Test Fall liegen die Daten unter "d:\data\info-archiv" in einer Verzeichnisstruktur. Anlegen als SYS und Rechte vergeben: sqlplus / as sysdba #Directory anlegen create directory INFO_ARCHIVE as 'D:\data\info-archiv'; #Rechte vergeben grant read,write on directory INFO_ARCHIVE to GPI; === Tabelle mit der Medien Information ablegen ==== Tabelle anlegen und mit Hilfe der BFILENAME Funktion den File Pointer anlegen CREATE TABLE documents ( id NUMBER(11) , docs BFILE ) / INSERT INTO documents VALUES (1, BFILENAME('INFO_ARCHIVE', 'Oracle8i._salzburg.pdf')); commit; === Oracle Context Index auf den auf die BFile Spalte anlegen=== Im ersten Schritt verwenden wir die Oracle Filter um das Script zu testen: Script readDocument.cmd: echo PARAM 1: %1 >> d:\temp\ctx_test.log echo PARAM 2: %2 >> d:\temp\ctx_test.log set ORACLE_HOME=D:\oracle\products\12.1.0.2\dbhome_1 rem Oracle defaut Filter aufrufen %ORACLE_HOME%/bin/ctxhx.exe %1 %2 >> d:\temp\ctx_test.log USER_FILTER testen: CREATE INDEX idx_documents_docs ON documents(docs) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('FILTER USER_FILTER_GPI_PREF') / --prüfen ob ein Fehler bei Indizieren aufgetreten ist: select * from CTX_USER_INDEX_ERRORS; --Tritt ein Fehler auf wird hier auch auf die Datei mit dem Fehler verwiesen --Testen ob Daten vorhanden sind SELECT token_text FROM DR$IDX_DOCUMENTS_DOCS$I; SELECT id,score(10) FROM documents WHERE contains(docs ,'Oracle',10) > 0; ID SCORE(10) ------------ ------------ 1 100 == Im nächsten Schritt eigene Filter in USER_FILTER == Was können wir dann mehr mit dem USER_FILTER erreichen? Welche Filter könnten bei den >500 Formaten des AUTO_FILTERS fehlen? Zum Beispiel, Filter die Text erzeugen, die aus eigenen Regeln Text erzeugen sollen, oder alternative PDF Filter. PDF Filter / PDF to TEXT Converter: * https://www.foxitsoftware.com/products/pdf-ifilter/ * Adobe PDF iFilter 64 11.0.01 https://www.adobe.com/support/downloads/detail.jsp?ftpID=5542 * http://www.softinterface.com/Convert-Doc/Convert-PDF-Target.htm?gclid=CJCvi_HBosoCFRFmGwodrjwLHw * http://www.pdflib.com/products/tet/ In dem Filterscript wird PDF dann mit einem "eigene" Filter gewandelt, alles anderes mit dem **ctxhx** Filter von Oracle als Alternative. Vorteil: Fehlerbehandlung gleich beim Indizieren des Dokuments. ===Problem beim Aufbau des Index ==== Beim ersten Test schlägt der Aufbau fehl, ein ORA-07445 beendet die Session, Ursache ist noch unklar. Nun läßt sich der Index aber nicht mehr löschen, **ORA-29868: cannot issue DDL on a domain index marked as LOADING** Lösung: drop index idx_doc_files * ERROR at line 1: ORA-29868: cannot issue DDL on a domain index marked as LOADING -- als sys anmelden -- Index alter index GPI.idx_doc_files unusable; -- mark as failed exec ctxsys.ctx_adm.mark_failed( 'GPI', 'IDX_DOC_FILES'); drop index GPI.idx_doc_files; Index dropped. Nach dem Absturz bleibt Auch ein erneuter Neuaufbau schlägt fehl, interessanterweise beim gleichen Dokument! Im Log File vom USER_FILTER Script läßt sich der Speicherort und die letzte Datei ermitteln, ein Test in der Console funktioniert allerdings. BUG? Das ist der Fehler: Exception [type: ACCESS_VIOLATION, UNABLE_TO_READ] [ADDR:0x17AC0000] [PC:0x7FF79E737A80, __intel_memcpy()+5376] ORA-07445: exception encountered: core dump [_intel_memcpy()+5376] [ACCESS_VIOLATION] [ADDR:0x17AC0000] [PC:0x7FF79E737A80] [UNABLE_TO_READ] [] Im nächsten Versuch das Tracing auf Oracle Text (siehe Ende des Artikels) aktivieren um den Fehler einzugrenzen. Kein Erfolg .... Dann mal ohne einen eigentlichen Filter testen, um das Problem einzugrenzen d.h. wir schreiben in die Out Datei einfach immer das gleiche. Keine Besserung! Allerdings taucht der Text nun im Trace vom Fehler auf, es hat wohl etwas mit der Rückgabe der Werte zu tun. Das ist dann wohl ein Fall für den Oracle Support ...... ---- ---- ==== Oracle 12c New STORAGE Feature - Plain Text in $D speichern - Forward Indexing in $O ablegen==== Um später besser/schneller mit den Higlighting, Snippet und Markup Funktionen arbeiten zu können (siehe [[prog:oracle_text_highlight|Oracle Text - Die Treffer einer Oracle Volltext Abfrage in der Ergebnismenge optisch hervorheben]]) kann der Text der Binären Dokument auch als PlainText in einer optionalen Tabelle des Oracle Text Domain Indexes gespeichert werden. Zusätzlich kann dann noch für die Optimierung der Vorschau das Forward Indexing aktiviert werden, es wird eine zusätzliche Tabelle $O angelegt. Gerade bei Binären Daten sollten beide Features eingesetzt werden, falls eine Vorschau auf die Dokumente eingesetzt werden soll. Parameter für die STORAGE Eigenschaft des Index setzen: begin ctx_ddl.create_preference ( preference_name => 'GPI_BASIC_STORAGE', object_name => 'BASIC_STORAGE' ); end; / -- Forward Indexing exec ctx_ddl.set_attribute('GPI_BASIC_STORAGE','forward_index','TRUE'); -- Plain Text exec ctx_ddl.set_attribute('GPI_BASIC_STORAGE','save_copy','PLAINTEXT'); -- Index anlegen drop index idx_doc_files; CREATE INDEX idx_doc_files ON documents(FilePointer) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('FILTER CTXSYS.AUTO_FILTER LEXER gpi_lexer STORAGE GPI_BASIC_STORAGE') / Leider gleich mal auf eine Bug gelaufen Cursor von CTX_DOC.SNIPPET werden nicht geschlossen! ORA-01000: maximum open cursors exceeded Bug 20892798 : MANY CURSORS OPENED WHEN USING FORWARD INDEXING LEADING TO ORA-01000 ERROR => Product Version 12.1.0.2 Kein FIX öffentlich – Falls gleicher Fehler Auftritt erneuten Bug mit Prio eröffnen und Druck machen! ---- ==== Oracle Text Log und Trace anlegen==== Um besser zu verstehen wie das Ganze funktioniert einen Trace anlegen mit [[https://docs.oracle.com/database/121/CCREF/coutpkg.htm#CCREF2125|CTX_OUTPUT]]. Dazu: * Log einschalten mit CTX_OUTPUT.START_LOG(logfile in varchar2, overwrite in default true) * Logfile liegt unter %ORACLE_HOME%ctx\log\ * Trace für Auto Filter aktivieren mit CTX_OUTPUT.ADD_TRACE(trace_id BINARY_INTEGER) * Operationen durchführen * Trace wieder ausschalten * Log wieder ausschalten mit ctx_output.end_log Beispiel: sqlplus / as sysdba grant CTXAPP to gpi; connect gpi/gpi #Log einschalten begin CTX_OUTPUT.START_LOG('gpi_log_07_01_2016.log'); end; / #2=TRACE_IDX_AUTO_FILTER begin ctxsys.CTX_OUTPUT.ADD_TRACE(trace_id => CTX_OUTPUT.TRACE_IDX_AUTO_FILTER); end; / #Was ist gesetzt überprüfen mit: select * from ctx_trace_values; # Was tun drop index idx_documents_docs; #neu anlegen create index .... #Trace Event wieder entfernen begin ctxsys.CTX_OUTPUT.REMOVE_TRACE(trace_id => CTX_OUTPUT.TRACE_IDX_AUTO_FILTER); end; / #Log wieder ausschalten exec ctx_output.end_log Das Logfile liegt dann unter %ORACLE_HOME%ctx\log\ Siehe auch für die Fehlersuche: * MOS Node: Oracle Text Product Info Center Diagnostics Master Note (Doc ID 1087143.1) ==== Quellen ==== * http://www.oracle.com/technetwork/testcontent/altfilters-091485.html