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