Sphinx 在WINDOWS和LINUX下安装入门与测试实例
从Sphin x官网上http://www.sphinxsearch.com/downloads.html 下载mysql-5.0.45-sphinxse-r871-win32.zip和sphinx-0.9.8-svn-r985-win32.zip
如果您的MySQL服务已启动请先停止掉
解压mysql-5.0.45-sphinxse-r871-win32.zip,将里面bin与share目录覆盖掉你的mysql安装目录下的相应目录
解压sphinx-0.9.8-svn-r985-win32.zip ,将里面的文件解压到D:\sphinx
sphinx的配置与实际应用是相关的,因此以下我以例子进行说明,至此sphinx安装部分结束
2.2. Linux下安装 下载mysql-5.1.22-rc.tar.gz解压至/root/mysql-5.1.22
下载sphinx-0.9.8-svn-r985.tar.gz,解压至/root/sphinx-0.9.8-svn-r985
将/root/sphinx-0.9.8-svn-r985/mysqlse下的文件复制至/root/mysql-5.1.22/storage/sphinx
在/root/mysql-5.1.22目录下运行
sh BUILD/autorun.sh
./configure --prefix=/usr/local/mysql --with-charset=utf8 --with-extra-charsets=all??\
--enable-thread-safe-client --enable-assembler --with-readline --with-big-tables --with-plugins=sphinx
make && make install
groupadd mysql
useradd g mysql mysql
chown mysql:mysql /usr/local/mysql -R
cd /usr/local/mysql
bin/mysql_install_db user=mysql
(此时系统可能会提示:
[Warning] Storage engine 'SPHINX' has conflicting typecode. Assigning value 42.可忽略,不影响使用)
cp /root/mysql-5.1.22/support-files/mysql.server /etc/init.d/mysqld
chmod 700 /etc/init.d/mysqld
cp /root/mysql-5.1.22/support-files/my-medium.cnf /etc/my.cnf
/etc/init.d/mysql start
(至些mysql启动了)然后进入mysql命令行,运行show engines,看是不是有一个叫sphinx的engine,有的话就表示sphinxSE(mysql的sphinx引擎)安装正常了
进入/root/sphinx-0.9.8-svn-r985,运行
ldconfig /usr/local/mysql/lib/mysql
ldconfig /usr/local/mysql/include/mysql
./configure --prefix=/usr/local/sphinx --with-mysql=/usr/local/mysql
make && make install3. 实例说明
为更好说明如何应用Sphin x,现结合实例说明,我们以网站的新闻文章表为例。我们想要对新闻文章表进行全文检索(主要是标题与内容),新闻文章表的相关信息如下:
CREATE TABLE `eht_articles` (
`ARTICLESID` int(11) NOT NULL auto_increment,
`TITLE` varchar(100) NOT NULL default '',
`TITLECOLOR` varchar(20) default NULL,
`AUTHOR` varchar(200) default NULL,
`COMEFROM` varchar(200) default NULL,
`KEYWORD` varchar(200) default NULL,
`HTMLURL` varchar(200) default NULL,
`CATALOGID` int(6) default NULL,
`CONTENTS` mediumtext,
`EDITUSERID` int(6) default NULL,
`ADDTIME` int(10) default NULL,
`UPDATETIME` int(10) default NULL,
`HITS` int(6) default NULL,
??PRIMARY KEY??(`ARTICLESID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;在这个表中,我主要想对标题(TITLE)与内容(CONTENTS)字段进行全文检索,在检索过程中可能我会根据文章的栏目 (CATALOGID),编辑(EDITUSERID),时间段(ADDTIME)进行条件性的全文检索,然后可能会根据主键 ID(ARTICLESID),人气(HITS)进行排序显示,如何配置Sphin x来实现呢?
4. Sphin x配置
sphinx是以sphinx.conf为配置文件,索引与搜索均以这个文件为依据进行,要进行全文检索,首先就要配置好sphinx.conf,告诉sphinx哪些字段需要进行索引,哪些字段需要在where,orderby,groupby中用到。
通过将下载后的ZIP包解压到D盘。安装完Sphin x后,在D:/sphinx目录有一个sphinx.conf.in,这个相当于sphinx的配置例子文件,我们以这个文件为蓝本,重新创建一个空白内容的sphinx.conf,存放在d:/sphinx根目录。
sphinx.conf的内容组成
source 源名称1{
…
}
index 索引名称1{
source=源名称1
…
}
source 源名称2{
…
}
index 索引名称2{
source = 源名称2
…
}
indexer{
…
}
searchd{
…
}提示
从组成我们可以发现sphinx可以定义多个索引与数据源,不同的索引与数据源可以应用到不同表或不同应用的全文检索。
根据前面的实例,我们配置出我们需要的sphinx.conf,如下:
source cgfinal
{
type = mysql
strip_html = 0
index_html_attrs =
sql_host = localhost
sql_user = root
sql_pass = admin
sql_db = test
sql_port= 3306 # optional, default is 3306
sql_query_pre=??SET NAMES utf8
sql_query = SELECT ARTICLESID,TITLE,CONTENTS,AUTHOR,CATALOGID,ADDTIME,EDITUSERID,\
HITS FROM a.eht_news_articles
#sql_query = SELECT * FROM a.eht_news_articles
sql_attr_uint= CATALOGID
sql_attr_uint= EDITUSERID
sql_attr_uint = HITS
sql_attr_timestamp = ADDTIME
sql_query_post??=
sql_ranged_throttle= 0
#sql_query_info = SELECT * FROM a.eht_news_articles WHERE ARTICLESID=$id
}
index cgfinal
{
source? ?= cgfinal
path? ?= d:/sphinx/data/cgfinal
docinfo? ?= extern
mlock? ?= 0
morphology? ?= none
stopwords? ?=
min_word_len??= 1
charset_type??= utf-8
charset_table = U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z,\
A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6,\
U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101,\
U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109,\
U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F,\
U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, \
U+0116->U+0117,U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D,\
U+011D,U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, \
U+0134->U+0135,U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, \
U+013C,U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, \
U+0143->U+0144,U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, \
U+014B,U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, \
U+0152->U+0153,U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159,\
U+0159,U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, \
U+0160->U+0161,U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, \
U+0167,U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, \
U+016E->U+016F,U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175,\
U+0175,U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, \
U+017B->U+017C,U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, \
U+0430..U+044F,U+05D0..U+05EA, U+0531..U+0556->U+0561..U+0586, U+0561..U+0587, \
U+0621..U+063A, U+01B9,U+01BF, U+0640..U+064A, U+0660..U+0669, U+066E, U+066F, \
U+0671..U+06D3, U+06F0..U+06FF,U+0904..U+0939, U+0958..U+095F, U+0960..U+0963, \
U+0966..U+096F, U+097B..U+097F,U+0985..U+09B9, U+09CE, U+09DC..U+09E3, U+09E6..U+09EF, \
U+0A05..U+0A39, U+0A59..U+0A5E,U+0A66..U+0A6F, U+0A85..U+0AB9, U+0AE0..U+0AE3, \
U+0AE6..U+0AEF, U+0B05..U+0B39,U+0B5C..U+0B61, U+0B66..U+0B6F, U+0B71, U+0B85..U+0BB9, \
U+0BE6..U+0BF2, U+0C05..U+0C39,U+0C66..U+0C6F, U+0C85..U+0CB9, U+0CDE..U+0CE3, \
U+0CE6..U+0CEF, U+0D05..U+0D39, U+0D60,U+0D61, U+0D66..U+0D6F, U+0D85..U+0DC6, \
U+1900..U+1938, U+1946..U+194F, U+A800..U+A805,U+A807..U+A822, U+0386->U+03B1, \
U+03AC->U+03B1, U+0388->U+03B5, U+03AD->U+03B5,U+0389->U+03B7, U+03AE->U+03B7, \
U+038A->U+03B9, U+0390->U+03B9, U+03AA->U+03B9,U+03AF->U+03B9, U+03CA->U+03B9, \
U+038C->U+03BF, U+03CC->U+03BF, U+038E->U+03C5,U+03AB->U+03C5, U+03B0->U+03C5, \
U+03CB->U+03C5, U+03CD->U+03C5, U+038F->U+03C9,U+03CE->U+03C9, U+03C2->U+03C3, \
U+0391..U+03A1->U+03B1..U+03C1,U+03A3..U+03A9->U+03C3..U+03C9, U+03B1..U+03C1, \
U+03C3..U+03C9, U+0E01..U+0E2E,U+0E30..U+0E3A, U+0E40..U+0E45, U+0E47, U+0E50..U+0E59, \
U+A000..U+A48F, U+4E00..U+9FBF,U+3400..U+4DBF, U+20000..U+2A6DF, U+F900..U+FAFF, \
U+2F800..U+2FA1F, U+2E80..U+2EFF,U+2F00..U+2FDF, U+3100..U+312F, U+31A0..U+31BF, \
U+3040..U+309F, U+30A0..U+30FF,U+31F0..U+31FF, U+AC00..U+D7AF, U+1100..U+11FF, \
U+3130..U+318F, U+A000..U+A48F,U+A490..U+A4CF
min_prefix_len??= 0
min_infix_len??= 1
ngram_len = 1
ngrams_chars = U+4E00..U+9FBF, U+3400..U+4DBF, U+20000..U+2A6DF, U+F900..U+FAFF,\
U+2F800..U+2FA1F, U+2E80..U+2EFF, U+2F00..U+2FDF, U+3100..U+312F, U+31A0..U+31BF,\
U+3040..U+309F, U+30A0..U+30FF, U+31F0..U+31FF, U+AC00..U+D7AF, U+1100..U+11FF,\
U+3130..U+318F, U+A000..U+A48F, U+A490..U+A4CF
}
indexer
{
mem_limit? ?= 32M
}
searchd
{
# address? ? = 0.0.0.0
port? ? = 3312
log? ???= d:/sphinx/log/searchd.log
query_log? ?= d:/sphinx/log/query.log
read_timeout??= 5
max_children??= 30
pid_file? ?= d:/sphinx/log/searchd.pid
max_matches? ?= 1000
seamless_rotate??= 1
}相关配置项说明:
Source部分配置项说明
#type 数据库类型,目前支持mysql与pgsql
#strip_html 是否去掉html标签
#sql_host 数据库主机地址
#sql_user 数据库用户名
#sql_pass 数据库密码
#sql_db? ?数据库名称
#sql_port 数据库采用的端口
#sql_query_pre 执行sql前要设置的字符集,用utf8必须SET NAMES utf8
#sql_query??全文检索要显示的内容,在这里尽可能不使用where或group by,将where与groupby的内容交给sphinx,由sphinx进行条件过滤与groupby效率会更高
#注意:select 出来的字段必须至少包括一个唯一主键(ARTICLESID)以及要全文检索的字段,你计划原本在where中要用到的字段也要select出来
#这里不用使用orderby
#sql_attr_开头的表示一些属性字段,你原计划要用在where,orderby,groupby中的字段要在这里定义
#根据我们原先的SQL:
#select * from eht_articles where title like ? and catalogid=? And edituserid=???And addtime between ? and ? order by hits desc
#我们需要对catalogid,edituserid,addtime,hits进行属性定义(这四个字段也要在select的字段列表中),定义时不同的字段类型有不同的属性名称,具体可以见sphinx.conf.in中的说明
index部分配置项说明
#source 数据源名
#path? ?索引$docs = array
(
"this is my test text to be highlighted, and for the sake of the testing we need to pump its length somewhat",
"another test text to be highlighted, below limit",
"test number three, without phrase match",
"final test, not only without phrase match, but also above limit and with swapped phrase text test as well",
);
$words = "test";
$index = "cgfinal";
$opts = array
(
"before_match"??=> "<span style='font-weight:bold;color:red'>",
"after_match"??=> "</span>",
"chunk_separator" => " ... ",
"limit"? ? => 60,
"around"? ?=> 3,
);
foreach ( array(0,1) as $exact )
{
$opts["exact_phrase"] = $exact;
print "exact_phrase=$exact\n";
$cl = new Sphin xClient ();
$res = $cl->BuildExcerpts ( $docs, $index, $words, $opts );
if ( !$res )
{
??die ( "ERROR: " . $cl->GetLastError() . ".\n" );
} else
{
??$n = 0;
??foreach ( $res as $entry )
??{
? ?$n++;
? ?print "n=$n, res=$entry<br/>";
??}
??print "\n";
}
}在IE上运行的效果是:
在实际环境中,上面代码的$docs是我们用sphinx搜索出来的结果,这个结果利用BuildExcerpts方法可以实现摘要的功能。
采用Sphin xSE方式调用Sphin x
采用sphinxSE必须要求为mySQL安装sphinxSE Engine驱动,方法在第1节中我已讲到
要创建一张sphinx 专用表,你可以这样建
CREATE TABLE `sphinx` (
`id` int(11) NOT NULL,
`weight` int(11) NOT NULL,
`query` varchar(255) NOT NULL,
`CATALOGID` INT NOT NULL,
`EDITUSERID` INT NOT NULL,
`HITS` INT NULL,
`ADDTIME` INT NOT NULL,
KEY `Query` (`Query`)
) ENGINE=SPHINX DEFAULT CHARSET=utf8 CONNECTION='sphinx://localhost:3312/cgfinal';警告
注:与一般mysql表不同的是ENGINE=SPHINX DEFAULT CHARSET=utf8 CONNECTION='sphinx://localhost:3312/cgfinal';,这里表示这个表采用SPHINXSE引擎,字符集是 utf8,与sphinx的连接串是'sphinx://localhost:3312/cgfinal,cgfinal是索引名称
根据sphinx官方说明,这个表必须至少有三个字段,字段起什么名称无所谓,但类型的顺序必须是integer,integer,varchar,分别表示记录标识document ID,匹配权重weight与查询query,同时document ID与query必须建索引。另外这个表还可以建立几个字段,这几个字段的只能是integer或TIMESTAMP类型,字段是与sphinx的结果集绑定的,因此字段的名称必须与在sphinx.conf中定义的属性名称一致,否则取出来的将是Null值。
比如我在上面有定义了sql_attr_uint= CATALOGID,sql_attr_uint= EDITUSERID,sql_attr_uint = HITS,sql_attr_timestamp = ADDTIME,那么在这个表里头,你就可以再定义CATALOGID,EDITUSERID,HITS,ADDTIME四个字段。
通过sql语句实现查询。通过select * from sphinx where query='sphinx表达式' 的方式可以实现查询,通过让sphinx表与eht_articles或其他表并联查询(条件是 sphinx.id=eht_articles.Articlesid)还可以实现更为复杂的sql,基本上可以符合我们日常的要求。
sphinx表达式在sphinx的 weight desc,hits asc';
offset,结果记录集的起始位置,默认是0
limit,从结果记录集中取出的数量,默认是20条
index,要搜索的索引名称
??... where query='test;index=cgfinal';
??... where query='test;index=test1,test2,test3;';
minid,maxid,匹配最小与最大文档ID
weights,以逗号分割的分配给sphinx全文检索字段的权重列表
? ?... where query='test;weights=1,2,3;';
filter,!filter,以逗号分隔的属性名与一堆要匹配的值
? ?#只包括1,5,19的组
? ?... where query='test;filter=group_id,1,5,19;';
??#不包括3,11的组
? ?... where query='test;!filter=group_id,3,11';
range,!range,逗号分隔的属性名一最小与最大要匹配的值
? ?#从3至7的组
? ?... where query='test;range=group_id,3,7;';
? ?#不包括从5至25的组
? ?... where query='test;!range=group_id,5,25;';
maxmatches,每个查询最大匹配的值
? ?... where query='test;maxmatches=2000;';
groupby,group by 方法与属性
? ?... where query='test;groupby=day:published_ts;';
? ?... where query='test;groupby=attr:group_id;';
groupsort,group by 的排序
? ?... where query='test;gropusort='@count desc';需要注意的重要一点是让sphinx进行排序,过滤,切分结果记录集比用MySQL的where,orderby 和limit将有更好的效率。有两个原因,首先sphinx做了很多优化,在这些任务上它比mySQL做得更出色,其次searchd在打包,sphinxSE在传输与解包上需要的数据量更少。
你可以通过运用join在sphinxSE的搜索表和其他引擎类型的表做并联查询。这有一个从example.sql中documents表的例子:
mysql> SELECT content, date_added FROM test.documents docs
-> JOIN t1 ON (docs.id=t1.id)
-> WHERE query="one document;mode=any";
+-------------------------------------+---------------------+
| content? ?? ?? ?? ?? ?? ?? ?? ?? ???| docdate? ?? ?? ?? ? |
+-------------------------------------+---------------------+
| this IS my test document number two | 2006-06-17 14:04:28 |
| this IS my test document number one | 2006-06-17 14:04:28 |
+-------------------------------------+---------------------+
2 rows IN SET (0.00 sec)
mysql> SHOW ENGINE SPHINX STATUS;
+--------+-------+---------------------------------------------+
| Type? ?| Name??| STATUS? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???|
+--------+-------+---------------------------------------------+
| SPHINX | stats | total: 2, total found: 2, time: 0, words: 2 |
| SPHINX | words | one:1:2 document:2:2? ?? ?? ?? ?? ?? ?? ?? ?|
+--------+-------+---------------------------------------------+
2 rows IN SET (0.00 sec)8. Sphin xSE的SQL查询例子演练
从eht_articles中查询标题含有“动画”关键字的记录。
SELECT c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=t.id AND query='@title 动画;mode=extended'提示
说明:要指定某个字段进行搜索,要用@字段名+空格+关键字+分号+mode=extended 如果不指定字段,则系统会对TITLE,CONTENTS进行搜索,对什么字段进行全文检索取决于在sphinx.conf中sql_query定义的select 中的字段(文本类型)
从eht_articles中查询文章内容或标题含有“CGArt”关键字的记录。
SELECT c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=sphinx.id AND query='动画'若AUTHOR,TITLE,CONTENTS三个字段都全文索引了,但只想搜title,或contents中含有“动画”关键字的文章
SELECT c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=t.id AND query='@title 动画 | @contents 动画;
mode=extended'查询标题含有“动画”关键字,catalogid为7,edituserid为1的记录
SELECT c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=t.id AND query='@title 动画;
filter=edituserid,1;filter=catalogid,7;mode=extended'提示
采用filter=字段名称,值就相当于where中的 字段名=值,filter提到的字段必须在sphinx的source部分的字段属性定义中定义,如
sql_attr_uint = CATALOGID
sql_attr_uint = EDITUSERID
sql_attr_uint = HITS
sql_attr_timestamp = ADDTIME查询标题含有“动画”关键字,按人气Hits从大至小,栏目ID从大至小排序
SELECT c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=t.id AND query='@title 动画;mode=extended;
sort=extended:hits desc,catalogid desc'在sphinx中,select出来的内容是按weight从大至小排序的,weight是根据sphinx内部一定的算法算出来的,越大就表示越匹配,如果想按匹配度从大至小排序,则可以:
SELECT c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=t.id AND query='@title 动画;mode=extended;
sort=@weight desc'搜内容或标题含有优秀或Icon或设计,按catalogid分组,按匹配度从高至低排序
SELECT t.*,c.* FROM eht_articles AS c,sphinx AS t WHERE c.articlesid=t.id AND query='优秀 | Icon | 设计;
mode=extended;groupby=attr:catalogid;groupsort=@weight;'9. 如何自动重建索引
?
?
?
?
?
?
?
?
?
?
?
?
我的开发心得:
首先在mysql数据库建立一个sphinx的数据库,并且建立表documents(建表语句以及测试数据都在附件中)。
?
1.自己新建一个sphinx.conf配置,放置在和
api
bin
contrib
...
相同的目录下。
?
2.在该配置文件中找到
source src1
{
?? ...
sql_query????= \
??SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content \
??FROM documents
#该位置就是告诉sphinx对documents表的字段进行索引
? ...
}
?
?
index test1{
path???= D:/sphinx/data/tuping
#该段表示的含义是,将产生的索引文件位置放在D:/sphinx/data的目录下,后面的tuping 表示产生的所有的索引文件名称为tuping.
例如:tuping.spa,tuping.spd,tuping.sph...
如果不配置,路径只写为
path???= D:/sphinx/data/
那么产生的索引文件就没有名称。
?
?
3.开始建立索引
在WINDOWS下进入到sphinx的BIN目录下,运行:
D:\sphinx\sphinx-0.9.9-win32\bin>indexer? config d:/sphinx/sphinx-0.9.9-win32/sphinx.conf test1
indexer表示开始建立索引,-config 后加上告诉sphinx要建立索引的配置文件地址d:/sphinx/sphinx-0.9.9-win32/sphinx.conf ,然后加上要建立的索引名,也就是配置在sphinx.conf中的index test1的名称。
?
运行后结果:
Sphinx 0.9.9-release (r2117)
Copyright (c) 2001-2009, Andrew Aksyonoff
using config file 'd:/sphinx/sphinx-0.9.9-win32/sphinx.conf'...
indexing index 'test1'...
collected 1003 docs, 0.0 MB
sorted 0.0 Mhits, 100.0% done
total 1003 docs, 13802 bytes
total 1.073 sec, 12860 bytes/sec, 934.56 docs/sec
total 2 reads, 0.000 sec, 13.4 kb/call avg, 0.0 msec/call avg
total 7 writes, 0.001 sec, 8.7 kb/call avg, 0.1 msec/call avg
表和索引建立成功!
?
4.搜索
运用命令行进行搜索,进入到SPHINX的安装目录:
d:/sphinx/sphinx-0.9.9-win32/bin/search -c d:/sphinx/sphinx-0.9.9-win32/sphinx.conf? tuping
?
运行结果:
Sphinx 0.9.9-release (r2117)
Copyright (c) 2001-2009, Andrew Aksyonoff
using config file 'd:/sphinx/sphinx-0.9.9-win32/sphinx.conf'...
index 'test1': query 'tuping ': returned 1 matches of 1 total in 0.-04 sec
displaying matches:
1. document=4, weight=1, group_id=0, date_added=Thu Jan 01 08:00:00 1970
??????? ID=4
??????? Name=??0
??????? Content=tuping
??????? Group_id=
??????? date_added=(NULL)
??????? title=????0
words:
1. 'tuping': 1 documents, 1 hits
?
?
?
?
?
?
?