apache solr search integration: cck date fieldでfaceted searchするには

Events happening in the community are now at Drupal community events on www.drupal.org.
Usatosi's picture

始めまして。

apache solr search integrationを利用し、CCKで作成したdateフィールドで
facet searchをしようとしています。

apache solr search integrationの導入は完了し、cckで作成したコンテンツを
検索し、その更新日でfaceted searchが出来るようになりました。cckのdate
フィールドに対しても同じ事をしたいのです。具体的には個人情報をコンテンツ
タイプで集積し、そのフィールドとして生年月日を設けます。人の名前などで検索し、
生年月日でしぼりこみを行いたいと言う事です。

そこでまずはdateフィールド(field_date_of_birth)でindexを作成するように以
下のコードを書きました。

<?php
function example_apachesolr_cck_fields_alter(&$mappings) {

 
$mappings['per-field']['field_date_of_birth'] = array('callback' =>
'example_cck_date_callback', 'index_type' => 'date');
    return
$mappings;
}

function
example_cck_date_callback($node, $fieldname){

 
$fields = array();
  foreach (
$node->$fieldname as $field) {
   
$fields[] = array('value' => $field['value'] . 'Z');
  }
  return
$fields;
}
?>

無事インデックスが出来たようで、solr本体で検索してもfield_date_of_birth
の値が返ってきています。enabled filterに同フィールドのフィルターが出来た
ので有効化し、ブロックも有効化しました。念の為"Include a facet for
missing"もOnにしました。しかし、facetは現れません。

下記のページを見る限りは現状では難しいようで、最後のコメントに"6.x-3.x
is working"とあるので、6.x-3.x-devを試してみましたが、生憎モジュールの有
効化もできませんでした。

date facet for cck field

そこでせめて生まれた年で絞れないか、文字列なら単純にできるのではと考え、
生まれた年を文字列でインデックス化する為、先のコードを以下のように変更し
ました。我ながらいい加減なコードですが、ISOフォーマットだと先頭4文字が西
暦なので、安直にそれをインデックスにしています。結果は同じで、Solrでは
きっちり返ってきますが、facetは現れません。

<?php
function example_apachesolr_cck_fields_alter(&$mappings) {

 
$mappings['per-field']['field_date_of_birth'] = array('callback' =>
'example_cck_date_callback', 'index_type' => 'string');
    return
$mappings;

}

function
example_cck_date_callback($node, $fieldname){

 
$fields = array();
  foreach (
$node->$fieldname as $field) {
   
$fields[] = array('value' => substr($field['value'],0,4));
  }
  return
$fields;
}
?>

facetを表示させる為にはまだ他に必要な作業があるのでしょうか。こちらの
ページのコメント欄ではindexing callbackの他にdisplay callbackも指定する
例もありましたが、その必要はあるのでしょうか。少なくとも私が使っている
バージョンのapache solr search integrationのREADME.txtにはそのような記述はありませんでした。

Exposed Solr Hooks (6.x-1.x) for developers

かなり初歩的なところで躓いている気がします。ご存知の方がいらっしゃれば、
ヒントや情報へのポインタでも良いのでご教示下されば助かります。

最後に環境は以下の通りです。

Drupal core 6.25
Apache Solr Search Integration 6.x-1.6
Content Construction Kit (CCK) 6.x-3.0-alpha3

どうぞ宜しくお願い致します。

Comments

Apachesolr

dokumori's picture

Apachesolr モジュールのバグかもしれません。2.x 以降で解決されたようです。斜め読みした限りでは、1.x では解決されていないようです。メインテイナーとしては、1.x のサポートを打ち切る意向があるのかもしれません
パッチのバックポートは難しそうなので、3.0-alpha1 に移行するのが一番簡単な解決法かもしれませんが、既にサーチが作りこまれている場合はアップグレードによる問題が生じるかもしれません。

http://drupal.org/node/558160#comment-4648288

自己解決

Usatosi's picture

コメントありがとうございます。その後以下のコードで自己解決しました。

cck_date_facet.module

<?php
/<em>
 
apache_solrモジュールのデフォルトではDate型はインデックス化されないの
  で、手順も含めて指定する。
</em>/
function
cck_date_facet_apachesolr_cck_fields_alter(&$mappings) {

    /<
em>
     
Date型のfacetがうまく動作しないので、文字列型でインデックス化する
      よう指定する。
   
*/
   
$mappings['date'] = array('date_select' => array('callback' => 'cck_date_facet_cck_date_callback', 'index_type' => 'string'));
   
$mappings['date'] = array('date_popup' => array('callback' => 'cck_date_facet_cck_date_callback', 'index_type' => 'string'));

   
//$mappings['per-field']['field_date_of_birth'] = array('callback' => 'cck_date_facet_cck_date_callback', 'index_type' => 'string');
   
return $mappings;

}

/</
em>solrへのインデックス化手順を定義する。Date型は動かないので、文字列型
  にて対処。
<em>/
function
cck_date_facet_cck_date_callback($node, $fieldname){

   
$fields = array();
    foreach (
$node->$fieldname as $field) {

        /</
em>apache_solrモジュールについてきたsolrのschemaによれば、Date型は
          TimeZoneとしてZをつけてUTCを指定する事が必須。対してCCKでは
          Date型にはTimeZoneが指定できない。そこで単純にZを付加すれば、イ
          ンデックス化自体はできる。ただしほかんところで動かん。
       
<em>/
       
//$fields[] = array('value' => $field['value'] . 'Z');

       
/</em>Date型はISO形式の書式にて文字列で保存されている。年と月があれば
          絞り込みに足りるので、それだけ取り出す。
          保存されている値
:2010-01-01T00:00:00
          先頭7文字を取り出した
:2010-01
       
*/
       
$fields[] = array('value' => substr($field['value'],0,7));
    }
    return
$fields;
}

/**
* Implementation of hook_views_api().
*/
function cck_date_facet_views_api() {
    return array(
'api' => '3.0-dev');
}
?>

更にApache Solr Views Integrationモジュールで作成しViewでもfacetを利用したかった為、Apache Solr Node Reference Viewsモジュールのコードを参考・借用し、以下のように作りました。

cck_date_facet.views.inc:

<?php
/<strong>
*
Implementation of hook_views_handlers().
*/
function
cck_date_facet_views_handlers() {
  return array(
   
'info' => array(
     
'path' => drupal_get_path('module', 'cck_date_facet') . '/handlers',
    ),
   
'handlers' => array(
     
'cck_date_facet_views_handler_argument' => array(
       
'parent' => 'content_handler_argument_string',
      )
    )
  );
}

/</
strong>
*
Implementation of hook_views_data_alter().
*/
function
cck_date_facet_views_data_alter(&$data) {
  foreach (
module_invoke_all('apachesolr_entities') as $base_table => $definition) {
   
// provide CCK mappings filters
   
foreach (apachesolr_cck_fields() as $name => $field) {
      if (
$field['type'] == 'date') {
       
$data['apachesolr_' . $base_table][apachesolr_index_key($field)] = array(
         
'title' => t($field['widget']['label']),
         
'help' => t('CCK Date Mapping for @fieldname', array('@fieldname' => $field['field_name'])),
         
'argument' => array(
           
'handler' => 'cck_date_facet_views_handler_argument',
           
'cck_field' => $field)
                                                                                 );
                    }
    }
  }
}
?>

cck_date_facet_views_handler_argument.inc:

<?php
class cck_date_facet_views_handler_argument extends content_handler_argument_string {
    var
$value;

    function
construct() {
       
parent::construct();

       
$this->content_field = content_fields($this->definition['cck_field']['field_name']);
    }

    /<
em>Date型のfacetをユーザが選択したらこの関数が呼ばれる。この関数の役
      目は、選択したパラメータを取得し、検証する。正しければクエリーに
      セットする事。
   
*/
    function
query() {
       
//ユーザが選択したfacetの値を得る。
       
$passed_arguments = array($this->get_value());

       
//パラメータが正しいか確認し、正しいもののみ集める。</em>/
       
$valid_arguments = array();
        foreach (
$passed_arguments as $value) {
            if (
$this->is_valid_value($value)) {
               
$valid_arguments[] = $value;
            }
        }
       
//集めた結果、空でなければその値をクエリーにセットする。
       
if (!empty($valid_arguments)) {
            foreach (
$valid_arguments as $facet_value) {
               
$this->query->add_filter($this->real_field, $facet_value);
            }
        }
       
//空であれば有効な値無しという事。
       
else {
           
$this->query->add_filter($this->real_field,apachesolr_views_query::escape_term('-1') );
        }
    }

   
/*値が正しいかどうか検証する関数。正規表現にてYYYY-MMの形の文字列に
      なっていればmatchして1以上(=TRUE)を返す。
    */
   
function is_valid_value($val){
        return
preg_match('/[0-9]{4}-[0-9]{2}/', $val);
    }
}
?>

最後のhandlerでは一応渡されたパラメータが正しいかどうかを検証するように
していますが、我ながらかなりいい加減です。ただ動作はきちんとするし、外部に
公開するサービスではないので、とりあえずこれで良しとしました。

Drupalは大変優れたフレームワークだと感じましたが、ドキュメントやノウハウの
共有がまだまだのようですね。今回も散々ぐぐったのですが、モジュールのソースを
追いかける事が一番の早道でした。READMEが貧弱でも、ソースにきっちり書か
れている事も多い。

こちらのように日本語で活発にやりとりする場があれば、もっと敷居が低くなると
思います。上記のコードも素人仕事で恥ずかしいですが、似たような苦労をされ
ている方に参考にしてもらえればと思い、公開する次第です。

日本 コミュニティ: Drupal Japan User Group

Group organizers

Group categories

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: