この付録では、独自のコードを記述するか、またはOracle Textウィザードを使用して、CONTEXT
索引タイプを使用した簡単なWeb検索アプリケーションを作成する方法を説明します。次の項目について説明します。
Oracle Textの一般的な使用目的は、Webサイト上のHTMLファイルを索引付けし、ユーザーに検索機能を提供することです。この付録のサンプル・アプリケーションでは、データベースに格納されているHTMLファイルのセットを索引付けし、Oracle Databaseに接続しているWebサーバーを使用して検索サービスを提供します。
この付録では、Web問合せアプリケーションの次の2つのバージョンについて説明します。
PL/SQL Server Pages(PSP)を使用するバージョン
Java Server Pages(JSP)を使用するバージョン
これらのアプリケーションのバージョンは両方とも、必要なコードを自動的に作成する問合せアプリケーション・ウィザードを使用して作成できます。
PSP、JSP両方のアプリケーション・コードおよびテキスト問合せアプリケーション・ウィザードは、次のOracle Technology NetworkのWebサイトで表示およびダウンロードできます。
http://www.oracle.com/technology/products/text
テキスト問合せアプリケーション・ウィザードのWebページには、このウィザードの使用方法の詳細な説明もあります。
図A-1に、テキスト問合せアプリケーションのJSPバージョンはどのようなものかを示します。このアプリケーションは、Oracle Textアプリケーション・ウィザードを使用して作成されています。
図A-2に、テキスト問合せの結果を示します。
このアプリケーションでは、検索用語を含むドキュメントへのリンクを戻します。各ドキュメントには、次の4つのリンクがあります。
「HTML」リンクによりドキュメントが表示されます。
フィルタ処理されたドキュメントでは、図版は表示されません。(図5-1「ハイライト表示、要旨作成およびテーマ抽出のサンプル・ドキュメント」に、最初にヒットしたドキュメントのソースを示しています。)
「ハイライト」リンクにより、検索用語をハイライト表示したドキュメントが表示されます。図5-2「petがハイライト表示されたPet Magnetドキュメント」に、ハイライトの例を示しています。
「テーマ」リンクにより、ドキュメントに関連する上位50位までのテーマが表示されます。図5-3「ドキュメントのテーマを表示する問合せアプリケーション」に、テーマの抽出例を示しています。
「要旨」リンクにより、ドキュメントの簡単な要約が表示されます。図5-4「ドキュメントの要旨を表示する問合せアプリケーション」に、この要旨表示機能の例を示しています。
このアプリケーションは、PL/SQL Server Pagesに基づいています。図A-3「PSP Webアプリケーション」は、ブラウザが、Webサーバーを経由してOracle DatabaseのPSPストアド・プロシージャをコールする方法を示しています。
このアプリケーションには、次の要件があります。
Oracle Database(リリース8.1.6以上)が起動され、実行中であること。
Oracle PL/SQL Gatewayが実行中であること。
ApacheなどのWebサーバーが起動され、実行中であり、Oracle Databaseに要求を送信するように適切に構成されていること。
この項では、PSP Webアプリケーションの作成方法を説明します。
HTMLファイルを格納するテキスト表を作成する必要があります。この例では、次のようにsearch_table
という表を作成します。
create table search_table (tk numeric primary key, title varchar2(2000), text clob);
テキスト表は、HTMLファイルを使用してロードする必要があります。この例では、制御ファイルloader.ctlを使用して、loader.datで指定したファイルをロードします。SQL*Loader文は次のとおりです。
% sqlldr userid=scott/tiger control=loader.ctl
テキスト問合せウィザードを使用している場合: このウィザードにより、索引を作成するスクリプトが作成されます。(ダウンロードしたWebページの、ウィザードについての説明を参照してください。)そのスクリプトを実行します。
ウィザードを使用していない場合: HTMLファイルを索引付けするには、次のようにCONTEXT
索引をテキスト列に作成します。HTMLの索引付けであるため、この例では、フィルタ処理が不要なNULL_FILTER
プリファレンス型とHTML_SECTION_GROUP
型を使用します。
create index idx_search_table on search_table(text) indextype is ctxsys.context parameters ('filter ctxsys.null_filter section group CTXSYS.HTML_SECTION_GROUP');
アプリケーションには、選択したドキュメントが表示される必要があります。このために、Oracle Databaseではsearch_table
のCLOB
からドキュメントを読み取り、その結果を出力して表示する必要があります。これは、search_htmlservicesパッケージ内のプロシージャをコールして実行します。ファイルsearch_htmlservices.sqlをコンパイルする必要があります。この操作は、SQL*Plusプロンプトで実行できます。
SQL> @search_htmlservices.sql Package created.
検索ページを呼び出すには、ブラウザからsearch_html.pspをコールします。search_htmlは、Oracle Databaseのloadpsp
コマンドライン・プログラムを使用して、次のようにコンパイルします。
% loadpsp -replace -user scott/tiger search_html.psp "search_html.psp": procedure "search_html" created.
関連項目 PSPの使用方法の詳細は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。 |
クライアントのPSP要求をURLとして受け入れるように、Webサーバーを構成する必要があります。Webサーバーは、クライアントの要求をOracle Databaseに転送し、サーバー出力をブラウザに戻します。図A-3を参照してください。
Oracle WebDB Webリスナー、またはApache Webサーバーが組み込まれているOracle Application Serverを使用できます。詳細は、使用しているWebサーバーのマニュアルを参照してください。
URLを使用して、ブラウザから問合せアプリケーションにアクセスできます。URLをWebサーバーで構成します。URLの例は、次のようになります。
http://server.example.com:7777/mypath/search_html
図A-1および図A-2で示すように、アプリケーションでは、ブラウザに問合せのエントリ・ボックスを表示し、問合せ結果をHTMLリンクのリストとして戻します。
この項では、Webアプリケーションのサンプルの作成に使用するコードを示します。次のファイルを記載しています。
この例では、loader.ctl
ファイルのサンプルを示します。sqlldr
では、このファイルを使用してデータファイルloader.dat
がロードされます。
LOAD DATA INFILE 'loader.dat' INTO TABLE search_table REPLACE FIELDS TERMINATED BY ';' (tk INTEGER, title CHAR, text_file FILLER CHAR, text LOBFILE(text_file) TERMINATED BY EOF)
この例では、loader.dat
ファイルのサンプルを示します。各行は、3つのフィールド(ドキュメントの参照番号、ラベル(すなわちタイトル)、およびsearch_table
のテキスト列にロードされるHTMLドキュメント名)で構成されます。この例では、ファイルは切り捨てられています。
1; Pizza Shredder;Pizza.html 2; Refrigerator w/ Front-Door Auto Cantaloupe Dispenser;Cantaloupe.html 3; Self-Tipping Couch;Couch.html 4; Home Air Dirtier;Mess.html 5; Set of Pet Magnets;Pet.html 6; Esteem-Building Talking Pillow;Snooze.html . . . 28; Shaggy Found Inspiration For Success In Jamaica ;shaggy_found.html 29; Solar Flare Eruptions Likely ;solar_flare.html 30; Supersonic Plane Breaks Food Barrier ;food_barrier.html 31; SOUNDSCAN REPORT: Recipe for An Aspiring Top Ten;urban_groove_1.html . . .
set define off create or replace package search_htmlServices as procedure showHTMLDoc (p_id in numeric); procedure showDoc (p_id in varchar2, p_query in varchar2); end; / show errors; create or replace package body search_htmlServices as procedure showHTMLDoc (p_id in numeric) is v_clob_selected CLOB; v_read_amount integer; v_read_offset integer; v_buffer varchar2(32767); begin select text into v_clob_selected from search_table where tk = p_id; v_read_amount := 32767; v_read_offset := 1; begin loop dbms_lob.read(v_clob_selected,v_read_amount,v_read_offset,v_buffer); htp.print(v_buffer); v_read_offset := v_read_offset + v_read_amount; v_read_amount := 32767; end loop; exception when no_data_found then null; end; end showHTMLDoc; procedure showDoc (p_id in varchar2, p_query in varchar2) is v_clob_selected CLOB; v_read_amount integer; v_read_offset integer; v_buffer varchar2(32767); v_query varchar(2000); v_cursor integer; begin htp.p('<html><title>HTML version with highlighted terms</title>'); htp.p('<body bgcolor="#ffffff">'); htp.p('<b>HTML version with highlighted terms</b>'); begin ctx_doc.markup (index_name => 'idx_search_table', textkey => p_id, text_query => p_query, restab => v_clob_selected, starttag => '<i><font color=red>', endtag => '</font></i>'); v_read_amount := 32767; v_read_offset := 1; begin loop dbms_lob.read(v_clob_selected,v_read_amount,v_read_offset,v_buffer); htp.print(v_buffer); v_read_offset := v_read_offset + v_read_amount; v_read_amount := 32767; end loop; exception when no_data_found then null; end; exception when others then null; --showHTMLdoc(p_id); end; end showDoc; end; / show errors set define on
<%@ plsql procedure="search_html" %> <%@ plsql parameter="query" default="null" %> <%! v_results numeric := 0; %> <html> <head> <title>search_html Search </title> </head> <body> <% If query is null Then %> <center> <form method=post action="search_html"> <b>Search for: </b> <input type=text name="query" size=30> <input type=submit value=Search> </center> <hr> <% Else %> <p> <%! color varchar2(6) := 'ffffff'; %> <center> <form method=post action="search_html"> <b>Search for:</b> <input type=text name="query" size=30 value="<%= query %>"> <input type=submit value=Search> </form> </center> <hr> <p> <% -- select statement for doc in ( select /*+ DOMAIN_INDEX_SORT */ rowid, tk, title, score(1) scr from search_table where contains(text, query,1) >0 order by score(1) desc ) loop v_results := v_results + 1; if v_results = 1 then %> <center> <table border="0"> <tr bgcolor="#6699CC"> <th>Score</th> <th>Title</th> </tr> <% end if; %> <tr bgcolor="#<%= color %>"> <td> <%= doc.scr %>% </td> <td> <%= doc.title %> [<a href="search_htmlServices.showHTMLDoc?p_id= <%= doc.tk %>">HTML</a>] [<a href="search_htmlServices.showDoc?p_id= <%= doc.tk %>&p_query=<%= query %>">Highlight</a>] </td> </tr> <% if (color = 'ffffff') then color := 'eeeeee'; else color := 'ffffff'; end if; end loop; %> </table> </center> <% end if; %> </body></html>
JSPベースのWebアプリケーションの作成は、PSPベースのアプリケーションの作成とほぼ同じ手順で行われます(「Webアプリケーションの作成」を参照してください)。同じloader.dat
ファイルおよびloader.ctl
ファイルを使用できます。ただし、JSPベースのアプリケーションでは、次のことは不要です。
search_htmlservices
パッケージのコンパイル
search_html
のPSPページのコンパイル(loadpsp
を使用)
このアプリケーションには、次の要件があります。
Oracle Database(リリース8.1.6以上)が起動され、実行中であること。
ApacheなどのWebサーバーが起動され、実行中であり、Oracle Databaseに要求を送信するように適切に構成されていること。
この項では、Webアプリケーションのサンプルの作成に使用するJavaコードを示します。次のファイルを記載しています。
このファイルのコードは、テキスト問合せアプリケーション・ウィザードで生成されました。(長い行の一部は、コードが読み取りやすいように分割されました。)
<%@ page import="java.sql.*, java.util.*, java.net.*, oracle.jdbc.*, oracle.jsp.dbutil.*" %> <%@ page contentType="text/html;charset=UTF-8" %> <% oracle.jsp.util.PublicUtil.setReqCharacterEncoding(request, "UTF-8"); %> <jsp:useBean id="name" class="oracle.jsp.jml.JmlString" scope ="request" > <jsp:setProperty name="name" property="value" param="query" /> </jsp:useBean> <% String connStr="jdbc:oracle:thin:@jsmith-pc.us.oracle.com:1521:zippy922"; java.util.Properties info=new java.util.Properties(); Connection conn = null; ResultSet rset = null; OracleCallableStatement callStmt = null; Statement stmt = null; String userQuery = null; String myQuery = null; URLEncoder myEncoder; int count=0; int loopNum=0; int startNum=0; if (name.isEmpty()) { %> <html> <title>Text Search</title> <body> <table width="100%"> <tr bgcolor="#336699"> <td><font face="arial, helvetica" align="left" color="#CCCC99" size=+2>Text Search</td> </tr> </table> <center> <form method = post> Search for: <input type=text name=query size = 30> <input type=submit value="Search"> </form> </center> </body> </html> <%} else { %> <html> <title>Text Search</title> <body text="#000000" bgcolor="#FFFFFF" link="#663300" vlink="#996633" alink="#ff6600"> <table width="100%"> <tr bgcolor="#336699"> <td><font face="arial, helvetica" align="left" color="#CCCC99" size=+2>Text Search</td> </tr> </table> <center> <form method = post action="TextSearchApp.jsp"> Search for: <input type=text name="query" value="<%=name.getValue() %>" size = 30> <input type=submit value="Search"> </form> </center> <% try { DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver() ); info.put ("user", "jsmith"); info.put ("password","hello"); conn = DriverManager.getConnection(connStr,info); stmt = conn.createStatement(); userQuery = request.getParameter("query"); myQuery = URLEncoder.encode(userQuery); String numStr = request.getParameter("sn"); if(numStr!=null) startNum=Integer.parseInt(numStr); String theQuery = translate(userQuery); callStmt =(OracleCallableStatement)conn.prepareCall("begin "+ "?:=ctx_query.count_hits(index_name=>'ULTRA_IDX1', "+ "text_query=>?"+ "); " + "end; "); callStmt.setString(2,theQuery); callStmt.registerOutParameter(1, OracleTypes.NUMBER); callStmt.execute(); count=((OracleCallableStatement)callStmt).getNUMBER(1).intValue(); if(count>=(startNum+20)){ %> <font color="#336699" FACE="Arial,Helvetica" SIZE=+1>Results <%=startNum+1%> - <%=startNum+20%> of <%=count%> matches <% } else if(count>0){ %> <font color="#336699" FACE="Arial,Helvetica" SIZE=+1>Results <%=startNum+1%> - <%=count%> of <%=count%> matches <% } else { %> <font color="#336699" FACE="Arial,Helvetica" SIZE=+1>No match found <% } %> <table width="100%"> <TR ALIGN="RIGHT"> <% if((startNum>0)&(count<=startNum+20)) { %> <TD ALIGN="RIGHT"> <a href="TextSearchApp.jsp?sn=<%=startNum-20 %>&query= <%=myQuery %>">previous20</a> </TD> <% } else if((count>startNum+20)&(startNum==0)) { %> <TD ALIGN="RIGHT"> <a href="TextSearchApp.jsp?sn=<%=startNum+20 %>&query=<%=myQuery %>">next20</a> </TD> <% } else if((count>startNum+20)&(startNum>0)) { %> <TD ALIGN="RIGHT"> <a href="TextSearchApp.jsp?sn=<%=startNum-20 %>&query= <%=myQuery %>">previous20</a> <a href="TextSearchApp.jsp?sn=<%=startNum+20 %>&query= <%=myQuery %>">next20</a> </TD> <% } %> </TR> </table> <% String ctxQuery = "select /*+ DOMAIN_INDEX_SORT */ rowid, 'TITLE', score(1) scr from 'ULTRA_TAB1' where contains('TEXT', '"+theQuery+"',1 ) > 0 order by score(1) desc"; rset = stmt.executeQuery(ctxQuery); String color = "ffffff"; String rowid = null; String fakeRowid = null; String[] colToDisplay = new String[1]; int myScore = 0; int items = 0; while (rset.next()&&items< 20) { if(loopNum>=startNum) { rowid = rset.getString(1); fakeRowid = URLEncoder.encode(rowid); colToDisplay[0] = rset.getString(2); myScore = (int)rset.getInt(3); items++; if (items == 1) { %> <center> <table BORDER=1 CELLSPACING=0 CELLPADDING=0 width="100%" <tr bgcolor="#CCCC99"> <th><font face="arial, helvetica" color="#336699">Score</th> <th><font face="arial, helvetica" color="#336699">TITLE</th> <th> <font face="arial, helvetica" color="#336699">Document Services</th> </tr> <% } %> <tr bgcolor="#FFFFE0"> <td ALIGN="CENTER"> <%= myScore %>%</td> <td> <%= colToDisplay[0] %> <td> </td> </tr> <% if (color.compareTo("ffffff") == 0) color = "eeeeee"; else color = "ffffff"; } loopNum++; } } catch (SQLException e) { %> <b>Error: </b> <%= e %><p> <% } finally { if (conn != null) conn.close(); if (stmt != null) stmt.close(); if (rset != null) rset.close(); } %> </table> </center> <table width="100%"> <TR ALIGN="RIGHT"> <% if((startNum>0)&(count<=startNum+20)) { %> <TD ALIGN="RIGHT"> <a href="TextSearchApp.jsp?sn=<%=startNum-20 %>&query= <%=myQuery %>">previous20</a> </TD> <% } else if((count>startNum+20)&(startNum==0)) { %> <TD ALIGN="RIGHT"> <a href="TextSearchApp.jsp?sn=<%=startNum+20 %>&query= <%=myQuery %>">next20</a> </TD> <% } else if((count>startNum+20)&(startNum>0)) { %> <TD ALIGN="RIGHT"> <a href="TextSearchApp.jsp?sn=<%=startNum-20 %>&query= <%=myQuery %>">previous20</a> <a href="TextSearchApp.jsp?sn=<%=startNum+20 %>&query= <%=myQuery %>">next20</a> </TD> <% } %> </TR> </table> </body></html> <%} %> <%! public String translate (String input) { Vector reqWords = new Vector(); StringTokenizer st = new StringTokenizer(input, " '", true); while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.equals("'")) { String phrase = getQuotedPhrase(st); if (phrase != null) { reqWords.addElement(phrase); } } else if (!token.equals(" ")) { reqWords.addElement(token); } } return getQueryString(reqWords); } private String getQuotedPhrase(StringTokenizer st) { StringBuffer phrase = new StringBuffer(); String token = null; while (st.hasMoreTokens() && (!(token = st.nextToken()).equals("'"))) { phrase.append(token); } return phrase.toString(); } private String getQueryString(Vector reqWords) { StringBuffer query = new StringBuffer(""); int length = (reqWords == null) ? 0 : reqWords.size(); for (int ii=0; ii < length; ii++) { if (ii != 0) { query.append(" & "); } query.append("{"); query.append(reqWords.elementAt(ii)); query.append("}"); } return query.toString(); } %>