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