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