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