Line # Revision Author
1 599 ahitrov package sphinx::Keeper;
2
3 use strict;
4 use warnings 'all';
5 use base qw(Contenido::Keeper);
6 use Contenido::Globals;
7 774 ahitrov use LWP::UserAgent;
8 use JSON::XS;
9 603 ahitrov use Data::Dumper;
10 599 ahitrov
11 774 ahitrov my $ua = LWP::UserAgent->new;
12 $ua->timeout(1);
13
14
15 599 ahitrov ######################
16 # Отправить объект в поиск:
17 # Вызов: $keeper->set_search( $object );
18 # Объект должен обязательно иметь метод
19 # ->get_search_data,
20 # возвращающий структуру:
21 # {
22 # id => $object->id,
23 # class => $object->class,
24 # name => $object_name,
25 # text => $search_text
26 # }
27 # Кроме того, метод чувствителен к полю status и предполагает,
28 # что status=1 - документ активен; status=0 - документ не активен.
29 ##########################################################################
30 sub send
31 {
32 my $self = shift;
33 my $doc = shift;
34 return undef unless ref $doc && $doc->id;
35
36 603 ahitrov my ($object) = $keeper->get_documents(
37 599 ahitrov class => 'sphinx::Search',
38 object_id => $doc->id,
39 object_class => $doc->class,
40 );
41 if ( $doc->status == 1 ) {
42 my $data = $doc->get_search_data;
43 790 ahitrov if ( ref $object && !$data ) {
44 $object->delete;
45 } else {
46 return undef;
47 }
48 599 ahitrov unless ( ref $object ) {
49 603 ahitrov $object = sphinx::Search->new( $keeper );
50 599 ahitrov $object->status( 1 );
51 $object->is_deleted( 0 );
52 $object->object_id( $doc->id );
53 $object->object_class( $doc->class );
54 $object->name( $data->{name} );
55 $object->search( $data->{text} );
56 $object->store;
57 } else {
58 639 ahitrov if ( $data->{name} ne $object->name || $data->{text} ne $object->search || $object->is_deleted || $object->status <= 0 ) {
59 599 ahitrov $object->status( 1 );
60 $object->is_deleted( 0 );
61 $object->name( $data->{name} );
62 $object->search( $data->{text} );
63 $object->store;
64 }
65 }
66 } else {
67 if ( ref $object ) {
68 789 ahitrov # $object->status( 0 );
69 # $object->is_deleted( 1 );
70 # $object->store;
71 $object->delete;
72 599 ahitrov }
73 }
74 }
75
76
77 603 ahitrov # Методы поиска
78 ####################################################################
79 774 ahitrov sub proxy {
80 my $self = shift;
81 my $text = shift;
82 return unless $text;
83 my $opts = shift // {};
84
85 my $db_table = delete $opts->{db_table} || $self->state->table_name;
86 my $uri = URI->new( 'http://'.$self->state->db_host.':'.$self->state->db_port.'/search/'.$db_table );
87 $uri->query_param( search => $text );
88 if ( exists $opts->{object_class} ) {
89 if ( ref $opts->{object_class} eq 'ARRAY' ) {
90 $uri->query_param( class => $opts->{object_class} );
91 } else {
92 $uri->query_param( class => $opts->{object_class} );
93 }
94 }
95 if ( exists $opts->{order_by} ) {
96 my @Orders;
97 if ( ref $opts->{order_by} eq 'ARRAY' ) {
98 foreach my $order ( @{$opts->{order_by}} ) {
99 if ( ref $order eq 'ARRAY' && scalar @$order > 0 && scalar @$order <= 2 ) {
100 if ( scalar @$order == 2 && lc($order->[1]) eq 'desc' ) {
101 push @Orders, { field => $order->[0], reverse => 'desc' };
102 } else {
103 push @Orders, { field => $order->[0] };
104 }
105 } elsif ( $order && !ref $order ) {
106 my @parts = split /\s*,\s+/, $order;
107 if ( scalar @parts == 2 && lc($parts[1]) eq 'desc' ) {
108 push @Orders, { field => $parts[0], reverse => 'desc' };
109 } else {
110 push @Orders, { field => $parts[0] };
111 }
112 }
113 }
114 } else {
115 my @orders = split /\s*,\s+/, $opts->{order_by};
116 foreach my $order ( @orders ) {
117 if ( $order =~ /^([\w\.]+)\s+(asc|desc)/i ) {
118 push @Orders, { field => $1, reverse => $2 };
119 } else {
120 push @Orders, { field => $order };
121 }
122 }
123 }
124 my $i = 1;
125 foreach my $order ( @Orders ) {
126 $uri->query_param( "order_$i" => $order->{field} );
127 if ( exists $order->{reverse} && lc($order->{reverse}) eq 'desc' ) {
128 $uri->query_param( "sort_$i" => 'desc' );
129 }
130 last if ++$i > 5;
131 }
132 }
133 if ( exists $opts->{limit} ) {
134 $uri->query_param( limit => $opts->{limit} );
135 }
136 if ( exists $opts->{offset} ) {
137 $uri->query_param( offset => $opts->{offset} );
138 }
139 warn "Ask sphinx: $uri\n" if $DEBUG;
140 my $res = $ua->get($uri);
141 if ($res->is_success) {
142 my $body = $res->decoded_content;
143 warn "Response: [$body]\n" if $DEBUG;
144 my $result = decode_json $body;
145 return $result;
146 } else {
147 warn "SPHINX PROXY error! Code: ".$res->code.". Status: ".$res->status_line."\n";
148 }
149 }
150
151 603 ahitrov sub search {
152 my $self = shift;
153 my $text = shift;
154 return unless $text;
155 my (%opts) = @_;
156
157 my $result;
158 my $db_table = delete $opts{db_table} || $self->state->table_name;
159 my @wheres = ("MATCH(?)");
160 my @values = ($text);
161 658 ahitrov my @orders;
162 603 ahitrov my $count = delete $opts{count};
163 my $limit = delete $opts{limit};
164 return if $limit && ($limit =~ /\D/ || $limit < 0);
165 605 ahitrov my $offset = delete $opts{offset};
166 return if $offset && ($offset =~ /\D/ || $offset < 0);
167 603 ahitrov my $no_limit = delete $opts{no_limit};
168 unless ( $no_limit ) {
169 $limit ||= 1000;
170 }
171 605 ahitrov if ( $count ) {
172 $limit = 0;
173 $offset = 0;
174 }
175 793 ahitrov my $weights = delete $opts{weights};
176 603 ahitrov my $return_value = delete $opts{return_value} || 'array_ref';
177 my $hash_by = delete $opts{hash_by} || 'object_id';
178
179 605 ahitrov if ( exists $opts{object_class} ) {
180 push @wheres, "object_class in (".join( ',', map { '?' } ref $opts{object_class} eq 'ARRAY' ? @{$opts{object_class}} : ($opts{object_class}) ).")";
181 push @values, ref $opts{object_class} ? @{$opts{object_class}} : $opts{object_class};
182 603 ahitrov }
183 605 ahitrov
184 658 ahitrov if ( exists $opts{order_by} ) {
185 push @orders, $opts{order_by};
186 } elsif ( !$count ) {
187 push @orders, 'weight desc, last_edited desc';
188 }
189
190 793 ahitrov my $query = "SELECT ".($count ? 'count(*) as cnt' : '*, weight() as weight')." FROM $db_table WHERE ".join( ' AND ', @wheres ).(@orders ? " ORDER BY ".join(', ', @orders) : '');
191 if ( ref $weights eq 'HASH' ) {
192 my @weights;
193 foreach my $field ( keys %$weights ) {
194 push @weights, "$field=".$weights->{$field};
195 }
196 if ( @weights ) {
197 $query .= " OPTION field_weights=(".join(', ', @weights).") ";
198 }
199 }
200 605 ahitrov if ( $limit && $offset ) {
201 793 ahitrov $query .= " LIMIT $offset, $limit ";
202 605 ahitrov } elsif ( $limit ) {
203 793 ahitrov $query .= " LIMIT 0, $limit ";
204 603 ahitrov }
205 warn "SEARCH QUERY: $query\n" if $DEBUG;
206 warn "SEARCH VALUES: ".Dumper( \@values ) if $DEBUG;
207 605 ahitrov my $sth = $self->SQL->prepare( $query );
208 661 ahitrov if ( $sth->execute( @values ) ) {
209 if ( $count ) {
210 $result = $sth->fetchrow_arrayref;
211 warn "COUNT: ". Dumper( $result ) if $DEBUG;
212 $result = $result->[0];
213 } else {
214 $result = [];
215 while ( my $row = $sth->fetchrow_hashref ) {
216 push @$result, $row;
217 }
218 }
219 603 ahitrov } else {
220 661 ahitrov warn "ERROR in statement: ".$sth->errstr."\n";
221 warn "SEARCH QUERY: $query\n";
222 warn "SEARCH VALUES: ".Dumper( \@values );
223 603 ahitrov }
224 765 ahitrov $sth->finish;
225 603 ahitrov return $result;
226 }
227
228 sub stemmed {
229 my $self = shift;
230 my $db_table = $self->state->table_name_stemmed;
231 return $self->search( @_, db_table => $db_table );
232 }
233
234 # МЕТОДЫ ДОСТУПА К СОЕДИНЕНИЯМ С БАЗОЙ УМНЫЕ
235 ####################################################################
236 # получение соединения с базой или установка нового если его не было
237 sub SQL {
238 my $self = shift;
239 return ($self->connect_check() ? $self->{SQL} : undef);
240 }
241
242 # -------------------------------------------------------------------------------------------------
243 # Открываем соединение с базой данных
244 # -------------------------------------------------------------------------------------------------
245 sub connect {
246 my $self = shift;
247 #соединение уже есть
248 if ($self->is_connected) {
249 } else {
250 unless ($self->{SQL} = $self->db_connect) {
251 warn "Не могу соединиться с базой данных";
252 die;
253 }
254 $self->{SQL}->do("SET NAMES '".$self->state->db_client_encoding."'") if ($self->state->db_client_encoding);
255 }
256
257 $self->{_connect_ok} = 1;
258 return 1;
259 }
260
261 #проверка соединения с базой кеширующая состояние соединения
262 sub connect_check {
263 my $self = shift;
264 return 1 if ($self->{_connect_ok});
265 if ($self->is_connected) {
266 $self->{_connect_ok} = 1;
267 return 1;
268 } else {
269 if ($self->connect) {
270 return 1;
271 } else {
272 #сюда по логике попадать не должно так как die вылететь должен
273 warn "Connect failed\n";
274 return 0;
275 }
276 }
277 }
278
279 sub db_connect {
280 my $self = shift;
281 my $dbh = DBI->connect('DBI:mysql:host='.$self->{db_host}.';port='.$self->{db_port}.';mysql_enable_utf8=1')
282 || die "Contenido Error: Не могу соединиться с Sphinx базой данных\n";
283
284 658 ahitrov $dbh->{mysql_auto_reconnect} = 1;
285 603 ahitrov # $dbh->{'AutoCommit'} = 1;
286
287 return $dbh;
288 }
289
290 sub is_connected {
291 my $self = shift;
292 if ( ref $self->{SQL} and $self->{SQL}->can('ping') and $self->{SQL}->ping() ) {
293 $self->{_connect_ok} = 1;
294 return 1;
295 } else {
296 $self->{_connect_ok} = 0;
297 return 0;
298 }
299
300 # warn 'Check if MySQL DB connected: '.(ref $self && exists $self->{SQL} && ref $self->{SQL} ? 1 : 0 ) if $DEBUG;
301 # return ( ref($self) && exists $self->{SQL} && ref $self->{SQL} );
302 }
303 599 ahitrov 1;