xref: /plugin/struct/_test/SearchTest.php (revision dc2a4c3797f5bf070182ba559e664d9df1325275)
1<?php
2
3namespace dokuwiki\plugin\struct\test;
4
5use dokuwiki\plugin\struct\meta;
6
7/**
8 * Tests for the building of SQL-Queries for the struct plugin
9 *
10 * @group plugin_struct
11 * @group plugins
12 *
13 */
14class SearchTest extends StructTest
15{
16
17    public function setUp(): void
18    {
19        // workaround for recent GitHub disk I/O errors
20        parent::setUpBeforeClass();
21
22        parent::setUp();
23
24        $this->loadSchemaJSON('schema1');
25        $this->loadSchemaJSON('schema2');
26        $_SERVER['REMOTE_USER'] = 'testuser';
27
28        $as = mock\Assignments::getInstance();
29        $page = 'page01';
30        $as->assignPageSchema($page, 'schema1');
31        $as->assignPageSchema($page, 'schema2');
32        saveWikiText($page, "===== TestTitle =====\nabc", "Summary");
33        p_get_metadata($page);
34        $now = time();
35        $this->saveData(
36            $page,
37            'schema1',
38            [
39                'first' => 'first data',
40                'second' => ['second data', 'more data', 'even more'],
41                'third' => 'third data',
42                'fourth' => 'fourth data'
43            ],
44            $now
45        );
46        $this->saveData(
47            $page,
48            'schema2',
49            [
50                'afirst' => 'first data',
51                'asecond' => ['second data', 'more data', 'even more'],
52                'athird' => 'third data',
53                'afourth' => 'fourth data'
54            ],
55            $now
56        );
57
58        $as->assignPageSchema('test:document', 'schema1');
59        $as->assignPageSchema('test:document', 'schema2');
60        $this->saveData(
61            'test:document',
62            'schema1',
63            [
64                'first' => 'document first data',
65                'second' => ['second', 'more'],
66                'third' => '',
67                'fourth' => 'fourth data'
68            ],
69            $now
70        );
71        $this->saveData(
72            'test:document',
73            'schema2',
74            [
75                'afirst' => 'first data',
76                'asecond' => ['second data', 'more data', 'even more'],
77                'athird' => 'third data',
78                'afourth' => 'fourth data'
79            ],
80            $now
81        );
82
83        for ($i = 10; $i <= 20; $i++) {
84            $this->saveData(
85                "page$i",
86                'schema2',
87                [
88                    'afirst' => "page$i first data",
89                    'asecond' => ["page$i second data"],
90                    'athird' => "page$i third data",
91                    'afourth' => "page$i fourth data"
92                ],
93                $now
94            );
95            $as->assignPageSchema("page$i", 'schema2');
96        }
97    }
98
99    public function test_simple()
100    {
101        $search = new mock\Search();
102
103        $search->addSchema('schema1');
104        $search->addColumn('%pageid%');
105        $search->addColumn('first');
106        $search->addColumn('second');
107
108        /** @var meta\Value[][] $result */
109        $result = $search->getRows();
110
111        $this->assertCount(2, $result, 'result rows');
112        $this->assertCount(3, $result[0], 'result columns');
113        $this->assertEquals('page01', $result[0][0]->getValue());
114        $this->assertEquals('first data', $result[0][1]->getValue());
115        $this->assertEquals(['second data', 'more data', 'even more'], $result[0][2]->getValue());
116    }
117
118    public function test_simple_title()
119    {
120        $search = new mock\Search();
121
122        $search->addSchema('schema1');
123        $search->addColumn('%title%');
124        $search->addColumn('first');
125        $search->addColumn('second');
126
127        /** @var meta\Value[][] $result */
128        $result = $search->getRows();
129
130        $this->assertCount(2, $result, 'result rows');
131        $this->assertCount(3, $result[0], 'result columns');
132        $this->assertEquals('["page01","TestTitle"]', $result[0][0]->getValue());
133        $this->assertEquals('first data', $result[0][1]->getValue());
134        $this->assertEquals(['second data', 'more data', 'even more'], $result[0][2]->getValue());
135    }
136
137    public function test_search_published()
138    {
139        $search = new mock\Search();
140        $search->isNotPublisher();
141
142        $search->addSchema('schema1');
143        $search->addColumn('%pageid%');
144        $search->addColumn('first');
145        $search->addColumn('second');
146
147        /** @var meta\Value[][] $result */
148        $result = $search->getRows();
149
150        $this->assertCount(0, $result, 'result rows');
151    }
152
153    public function test_search_lasteditor()
154    {
155        $search = new mock\Search();
156
157        $search->addSchema('schema1');
158        $search->addColumn('%title%');
159        $search->addColumn('%lasteditor%');
160        $search->addColumn('first');
161        $search->addColumn('second');
162
163        /** @var meta\Value[][] $result */
164        $result = $search->getRows();
165
166        $this->assertCount(2, $result, 'result rows');
167        $this->assertCount(4, $result[0], 'result columns');
168        $this->assertEquals('testuser', $result[0][1]->getValue());
169        $this->assertEquals(['second data', 'more data', 'even more'], $result[0][3]->getValue());
170    }
171
172
173    /**
174     * @group slow
175     */
176    public function test_search_lastupdate()
177    {
178        sleep(1);
179        saveWikiText('page01', "===== TestTitle =====\nabcd", "Summary");
180        p_get_metadata('page01');
181
182        $search = new mock\Search();
183
184        $search->addSchema('schema1');
185        $search->addColumn('%pageid%');
186        $search->addColumn('%lastupdate%');
187        $search->addColumn('first');
188        $search->addColumn('second');
189
190        /** @var meta\Value[][] $result */
191        $result = $search->getRows();
192
193        $expected_time = dformat(filemtime(wikiFN('page01')), '%Y-%m-%d %H:%M:%S');
194
195        $this->assertCount(2, $result, 'result rows');
196        $this->assertCount(4, $result[0], 'result columns');
197        $this->assertEquals($expected_time, $result[0][1]->getValue(), "Is your date.timezone set up in php.ini?");
198        $this->assertEquals(['second data', 'more data', 'even more'], $result[0][3]->getValue());
199    }
200
201    /**
202     * @group slow
203     */
204    public function test_search_lastsummary()
205    {
206        sleep(1);
207        $summary = 'Summary';
208        saveWikiText('page01', "===== TestTitle =====\nabcd", $summary);
209        p_get_metadata('page01');
210
211        $search = new mock\Search();
212
213        $search->addSchema('schema1');
214        $search->addColumn('%pageid%');
215        $search->addColumn('%lastsummary%');
216        $search->addColumn('first');
217        $search->addColumn('second');
218
219        /** @var meta\Value[][] $result */
220        $result = $search->getRows();
221
222        $this->assertCount(2, $result, 'result rows');
223        $this->assertCount(4, $result[0], 'result columns');
224        $this->assertEquals($summary, $result[0][1]->getValue());
225        $this->assertEquals(array('second data', 'more data', 'even more'), $result[0][3]->getValue());
226    }
227
228    public function test_search()
229    {
230        $search = new mock\Search();
231
232        $search->addSchema('schema1');
233        $search->addSchema('schema2', 'foo');
234        $this->assertCount(2, $search->schemas);
235
236        $search->addColumn('first');
237        $this->assertEquals('schema1', $search->columns[0]->getTable());
238        $this->assertEquals(1, $search->columns[0]->getColref());
239
240        $search->addColumn('afirst');
241        $this->assertEquals('schema2', $search->columns[1]->getTable());
242        $this->assertEquals(1, $search->columns[1]->getColref());
243
244        $search->addColumn('schema1.third');
245        $this->assertEquals('schema1', $search->columns[2]->getTable());
246        $this->assertEquals(3, $search->columns[2]->getColref());
247
248        $search->addColumn('foo.athird');
249        $this->assertEquals('schema2', $search->columns[3]->getTable());
250        $this->assertEquals(3, $search->columns[3]->getColref());
251
252        $search->addColumn('asecond');
253        $this->assertEquals('schema2', $search->columns[4]->getTable());
254        $this->assertEquals(2, $search->columns[4]->getColref());
255
256        $search->addColumn('doesntexist');
257        $this->assertEquals(5, count($search->columns));
258
259        $search->addColumn('%pageid%');
260        $this->assertEquals('schema1', $search->columns[5]->getTable());
261        $exception = false;
262        try {
263            $search->columns[5]->getColref();
264        } catch (meta\StructException $e) {
265            $exception = true;
266        }
267        $this->assertTrue($exception, "Struct exception expected for accesing colref of PageColumn");
268
269        $search->addSort('first', false);
270        $this->assertCount(1, $search->sortby);
271
272        $search->addFilter('%pageid%', '%ag%', '~', 'AND');
273        $search->addFilter('second', '%sec%', '~', 'AND');
274        $search->addFilter('first', '%rst%', '~', 'AND');
275
276        $result = $search->getRows();
277        $count = $search->getCount();
278
279        $this->assertEquals(1, $count, 'result count');
280        $this->assertCount(1, $result, 'result rows');
281        $this->assertCount(6, $result[0], 'result columns');
282
283        // sort by multi-column
284        $search->addSort('second');
285        $this->assertCount(2, $search->sortby);
286        $result = $search->getRows();
287        $count = $search->getCount();
288        $this->assertEquals(1, $count, 'result count');
289        $this->assertCount(1, $result, 'result rows');
290        $this->assertCount(6, $result[0], 'result columns');
291    }
292
293    public function test_ranges()
294    {
295        $search = new mock\Search();
296        $search->addSchema('schema2');
297        $search->addColumn('%pageid%');
298        $search->addColumn('afirst');
299        $search->addColumn('asecond');
300        $search->addFilter('%pageid%', '%ag%', '~', 'AND');
301        $search->addSort('%pageid%', false);
302
303        /** @var meta\Value[][] $result */
304        $result = $search->getRows();
305        $count = $search->getCount();
306
307        // check result dimensions
308        $this->assertEquals(12, $count, 'result count');
309        $this->assertCount(12, $result, 'result rows');
310        $this->assertCount(3, $result[0], 'result columns');
311
312        // check sorting
313        $this->assertEquals('page20', $result[0][0]->getValue());
314        $this->assertEquals('page19', $result[1][0]->getValue());
315        $this->assertEquals('page18', $result[2][0]->getValue());
316
317        // now with limit
318        // new search object because result is fetched only once
319        $search = new mock\Search();
320        $search->addSchema('schema2');
321        $search->addColumn('%pageid%');
322        $search->addColumn('afirst');
323        $search->addColumn('asecond');
324        $search->addFilter('%pageid%', '%ag%', '~', 'AND');
325        $search->addSort('%pageid%', false);
326        $search->setLimit(5);
327
328        /** @var meta\Value[][] $result */
329        $result = $search->getRows();
330        $count = $search->getCount();
331
332        // check result dimensions
333        $this->assertEquals(12, $count, 'result count'); // full result set
334        $this->assertCount(5, $result, 'result rows'); // wanted result set
335
336        // check the values
337        $this->assertEquals('page20', $result[0][0]->getValue());
338        $this->assertEquals('page16', $result[4][0]->getValue());
339
340        // now add offset
341        // again a new object
342        $search = new mock\Search();
343        $search->addSchema('schema2');
344        $search->addColumn('%pageid%');
345        $search->addColumn('afirst');
346        $search->addColumn('asecond');
347        $search->addFilter('%pageid%', '%ag%', '~', 'AND');
348        $search->addSort('%pageid%', false);
349        $search->setLimit(5);
350        $search->setOffset(5);
351        $result = $search->getRows();
352        $count = $search->getCount();
353
354        // check result dimensions
355        $this->assertEquals(12, $count, 'result count'); // full result set
356        $this->assertCount(5, $result, 'result rows'); // wanted result set
357
358        // check the values
359        $this->assertEquals('page15', $result[0][0]->getValue());
360        $this->assertEquals('page11', $result[4][0]->getValue());
361    }
362
363    public static function addFilter_testdata()
364    {
365        return [
366            ['%pageid%', 'val', '<>', 'OR', [['%pageid%', 'val', '!=', 'OR']], false, 'replace <> comp'],
367            ['%pageid%', 'val', '*~', 'OR', [['%pageid%', '%val%', 'LIKE', 'OR']], false, 'replace *~ comp'],
368            ['%pageid%', 'val*', '~', 'OR', [['%pageid%', 'val%', 'LIKE', 'OR']], false, 'replace * in value'],
369            ['%pageid%', 'val.*', '=*', 'OR', [['%pageid%', 'val.*', 'REGEXP', 'OR']], false, 'replace * in value'],
370            ['nonexisting', 'val', '~', 'OR', [], false, 'ignore missing columns'],
371            ['%pageid%', 'val', '?', 'OR', [], '\dokuwiki\plugin\struct\meta\StructException', 'wrong comperator'],
372            ['%pageid%', 'val', '=', 'NOT', [], '\dokuwiki\plugin\struct\meta\StructException', 'wrong type']
373        ];
374    }
375
376    /**
377     * @dataProvider addFilter_testdata
378     *
379     */
380    public function test_addFilter($colname, $value, $comp, $type, $expected_filter, $expectException, $msg)
381    {
382        $search = new mock\Search();
383        $search->addSchema('schema2');
384        $search->addColumn('%pageid%');
385        if ($expectException !== false) $this->setExpectedException($expectException);
386
387        $search->addFilter($colname, $value, $comp, $type);
388
389        if (count($expected_filter) === 0) {
390            $this->assertCount(0, $search->filter, $msg);
391            return;
392        }
393        $this->assertEquals($expected_filter[0][0], $search->filter[0][0]->getLabel(), $msg);
394        $this->assertEquals($expected_filter[0][1], $search->filter[0][1], $msg);
395        $this->assertEquals($expected_filter[0][2], $search->filter[0][2], $msg);
396        $this->assertEquals($expected_filter[0][3], $search->filter[0][3], $msg);
397    }
398
399    public function test_wildcard()
400    {
401        $search = new mock\Search();
402        $search->addSchema('schema2', 'alias');
403        $search->addColumn('*');
404        $this->assertCount(4, $search->getColumns());
405
406        $search = new mock\Search();
407        $search->addSchema('schema2', 'alias');
408        $search->addColumn('schema2.*');
409        $this->assertCount(4, $search->getColumns());
410
411        $search = new mock\Search();
412        $search->addSchema('schema2', 'alias');
413        $search->addColumn('alias.*');
414        $this->assertCount(4, $search->getColumns());
415
416        $search = new mock\Search();
417        $search->addSchema('schema2', 'alias');
418        $search->addColumn('nope.*');
419        $this->assertCount(0, $search->getColumns());
420    }
421
422    public function test_filterValueList()
423    {
424        $search = new mock\Search();
425
426        //simple - single quote
427        $this->assertEquals(array('test'),
428            $this->callInaccessibleMethod($search, 'parseFilterValueList', array('("test")')));
429
430        //simple - double quote
431        $this->assertEquals(array('test'),
432            $this->callInaccessibleMethod($search, 'parseFilterValueList', array("('test')")));
433
434        //many elements
435        $this->assertEquals(array('test', 'test2', '18'),
436            $this->callInaccessibleMethod($search, 'parseFilterValueList', array('("test", \'test2\', 18)')));
437
438        $str = <<<'EOD'
439("t\"est", 't\'est2', 18)
440EOD;
441        //escape sequences
442        $this->assertEquals(array('t"est', "t'est2", '18'),
443            $this->callInaccessibleMethod($search, 'parseFilterValueList', array($str)));
444
445        //numbers
446        $this->assertEquals(array('18.7', '10e5', '-100'),
447            $this->callInaccessibleMethod($search, 'parseFilterValueList', array('(18.7, 10e5, -100)')));
448
449    }
450}
451