1fd9c77d3SAndreas Gohr<?php 2fd9c77d3SAndreas Gohr 3fd9c77d3SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4fd9c77d3SAndreas Gohr 5fd9c77d3SAndreas Gohr/** 6fd9c77d3SAndreas Gohr * Creates the SQL query using the QueryBuilder 7fd9c77d3SAndreas Gohr * 8fd9c77d3SAndreas Gohr * @internal This class is used by the Search class and should probably never be used directly 9fd9c77d3SAndreas Gohr */ 10fd9c77d3SAndreas Gohrclass SearchSQLBuilder 11fd9c77d3SAndreas Gohr{ 12fd9c77d3SAndreas Gohr /** @var QueryBuilder */ 13fd9c77d3SAndreas Gohr protected $qb; 14fd9c77d3SAndreas Gohr 15b37d1c8bSAnna Dabrowska /** @var bool Include latest = 1 in select query */ 16b37d1c8bSAnna Dabrowska protected $selectLatest = true; 17b37d1c8bSAnna Dabrowska 18fd9c77d3SAndreas Gohr /** 19fd9c77d3SAndreas Gohr * SearchSQLBuilder constructor. 20fd9c77d3SAndreas Gohr */ 21fd9c77d3SAndreas Gohr public function __construct() 22fd9c77d3SAndreas Gohr { 23fd9c77d3SAndreas Gohr $this->qb = new QueryBuilder(); 24fd9c77d3SAndreas Gohr } 25fd9c77d3SAndreas Gohr 26fd9c77d3SAndreas Gohr /** 27fd9c77d3SAndreas Gohr * Add the schemas to the query 28fd9c77d3SAndreas Gohr * 29fd9c77d3SAndreas Gohr * @param Schema[] $schemas Schema names to query 30fd9c77d3SAndreas Gohr */ 31fd9c77d3SAndreas Gohr public function addSchemas($schemas) 32fd9c77d3SAndreas Gohr { 33fd9c77d3SAndreas Gohr // basic tables 34fd9c77d3SAndreas Gohr $first_table = ''; 35fd9c77d3SAndreas Gohr foreach ($schemas as $schema) { 36fd9c77d3SAndreas Gohr $datatable = 'data_' . $schema->getTable(); 37fd9c77d3SAndreas Gohr if ($first_table) { 38fd9c77d3SAndreas Gohr // follow up tables 39fd9c77d3SAndreas Gohr $this->qb->addLeftJoin($first_table, $datatable, $datatable, "$first_table.pid = $datatable.pid"); 40fd9c77d3SAndreas Gohr } else { 41fd9c77d3SAndreas Gohr // first table 42fd9c77d3SAndreas Gohr $this->qb->addTable($datatable); 43fd9c77d3SAndreas Gohr 44fd9c77d3SAndreas Gohr // add conditional page clauses if pid has a value 45fd9c77d3SAndreas Gohr $subAnd = $this->qb->filters()->whereSubAnd(); 46fd9c77d3SAndreas Gohr $subAnd->whereAnd("$datatable.pid = ''"); 47fd9c77d3SAndreas Gohr $subOr = $subAnd->whereSubOr(); 48fd9c77d3SAndreas Gohr $subOr->whereAnd("GETACCESSLEVEL($datatable.pid) > 0"); 49fd9c77d3SAndreas Gohr $subOr->whereAnd("PAGEEXISTS($datatable.pid) = 1"); 50*6636674bSAnna Dabrowska // make sure to check assignment for page data only 51*6636674bSAnna Dabrowska $subOr->whereAnd("($datatable.rid != 0 OR (ASSIGNED = 1 OR ASSIGNED IS NULL))"); 52fd9c77d3SAndreas Gohr 53fd9c77d3SAndreas Gohr // add conditional schema assignment check 54fd9c77d3SAndreas Gohr $this->qb->addLeftJoin( 55fd9c77d3SAndreas Gohr $datatable, 56fd9c77d3SAndreas Gohr 'schema_assignments', 57fd9c77d3SAndreas Gohr '', 58fd9c77d3SAndreas Gohr "$datatable.pid != '' 59fd9c77d3SAndreas Gohr AND $datatable.pid = schema_assignments.pid 60fd9c77d3SAndreas Gohr AND schema_assignments.tbl = '{$schema->getTable()}'" 61fd9c77d3SAndreas Gohr ); 62fd9c77d3SAndreas Gohr 63fd9c77d3SAndreas Gohr $this->qb->addSelectColumn($datatable, 'rid'); 64fd9c77d3SAndreas Gohr $this->qb->addSelectColumn($datatable, 'pid', 'PID'); 65fd9c77d3SAndreas Gohr $this->qb->addSelectColumn($datatable, 'rev'); 66fd9c77d3SAndreas Gohr $this->qb->addSelectColumn('schema_assignments', 'assigned', 'ASSIGNED'); 67fd9c77d3SAndreas Gohr $this->qb->addGroupByColumn($datatable, 'pid'); 68fd9c77d3SAndreas Gohr $this->qb->addGroupByColumn($datatable, 'rid'); 69fd9c77d3SAndreas Gohr 70fd9c77d3SAndreas Gohr $first_table = $datatable; 71fd9c77d3SAndreas Gohr } 72b37d1c8bSAnna Dabrowska $this->qb->filters()->whereAnd($this->addPublishClauses($datatable)); 73fd9c77d3SAndreas Gohr } 74fd9c77d3SAndreas Gohr } 75fd9c77d3SAndreas Gohr 76fd9c77d3SAndreas Gohr /** 77fd9c77d3SAndreas Gohr * Add the columns to select, handling multis 78fd9c77d3SAndreas Gohr * 79fd9c77d3SAndreas Gohr * @param Column[] $columns 80fd9c77d3SAndreas Gohr */ 81fd9c77d3SAndreas Gohr public function addColumns($columns) 82fd9c77d3SAndreas Gohr { 83fd9c77d3SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 84fd9c77d3SAndreas Gohr $n = 0; 85fd9c77d3SAndreas Gohr foreach ($columns as $col) { 86fd9c77d3SAndreas Gohr $CN = 'C' . $n++; 87fd9c77d3SAndreas Gohr 88fd9c77d3SAndreas Gohr if ($col->isMulti()) { 89fd9c77d3SAndreas Gohr $datatable = "data_{$col->getTable()}"; 90fd9c77d3SAndreas Gohr $multitable = "multi_{$col->getTable()}"; 91fd9c77d3SAndreas Gohr $MN = $this->qb->generateTableAlias('M'); 92fd9c77d3SAndreas Gohr 93fd9c77d3SAndreas Gohr $this->qb->addLeftJoin( 94fd9c77d3SAndreas Gohr $datatable, 95fd9c77d3SAndreas Gohr $multitable, 96fd9c77d3SAndreas Gohr $MN, 97fd9c77d3SAndreas Gohr "$datatable.pid = $MN.pid AND $datatable.rid = $MN.rid AND 98fd9c77d3SAndreas Gohr $datatable.rev = $MN.rev AND 99fd9c77d3SAndreas Gohr $MN.colref = {$col->getColref()}" 100fd9c77d3SAndreas Gohr ); 101fd9c77d3SAndreas Gohr 102fd9c77d3SAndreas Gohr $col->getType()->select($this->qb, $MN, 'value', $CN); 103fd9c77d3SAndreas Gohr $sel = $this->qb->getSelectStatement($CN); 104fd9c77d3SAndreas Gohr $this->qb->addSelectStatement("GROUP_CONCAT_DISTINCT($sel, '$sep')", $CN); 105fd9c77d3SAndreas Gohr } else { 106fd9c77d3SAndreas Gohr $col->getType()->select($this->qb, 'data_' . $col->getTable(), $col->getColName(), $CN); 107fd9c77d3SAndreas Gohr $this->qb->addGroupByStatement($CN); 108fd9c77d3SAndreas Gohr } 109fd9c77d3SAndreas Gohr } 110fd9c77d3SAndreas Gohr } 111fd9c77d3SAndreas Gohr 112fd9c77d3SAndreas Gohr /** 113fd9c77d3SAndreas Gohr * Add the filters to the query 114fd9c77d3SAndreas Gohr * 115fd9c77d3SAndreas Gohr * All given filters are added to their own subclause. 116fd9c77d3SAndreas Gohr * 117fd9c77d3SAndreas Gohr * @param array[] $filters 118fd9c77d3SAndreas Gohr */ 119fd9c77d3SAndreas Gohr public function addFilters($filters) 120fd9c77d3SAndreas Gohr { 121fd9c77d3SAndreas Gohr if (!$filters) return; // ignore empty filters 122fd9c77d3SAndreas Gohr 123fd9c77d3SAndreas Gohr $subClause = $this->qb->filters()->where('AND'); 124fd9c77d3SAndreas Gohr 125fd9c77d3SAndreas Gohr foreach ($filters as $filter) { 126fd9c77d3SAndreas Gohr /** @var Column $col */ 1277234bfb1Ssplitbrain [$col, $value, $comp, $op] = $filter; 128fd9c77d3SAndreas Gohr 129fd9c77d3SAndreas Gohr $datatable = "data_{$col->getTable()}"; 130fd9c77d3SAndreas Gohr $multitable = "multi_{$col->getTable()}"; 131fd9c77d3SAndreas Gohr 132fd9c77d3SAndreas Gohr /** @var $col Column */ 133fd9c77d3SAndreas Gohr if ($col->isMulti()) { 134fd9c77d3SAndreas Gohr $MN = $this->qb->generateTableAlias('MN'); 135fd9c77d3SAndreas Gohr 136fd9c77d3SAndreas Gohr $this->qb->addLeftJoin( 137fd9c77d3SAndreas Gohr $datatable, 138fd9c77d3SAndreas Gohr $multitable, 139fd9c77d3SAndreas Gohr $MN, 140fd9c77d3SAndreas Gohr "$datatable.pid = $MN.pid AND $datatable.rid = $MN.rid AND 141fd9c77d3SAndreas Gohr $datatable.rev = $MN.rev AND 142fd9c77d3SAndreas Gohr $MN.colref = {$col->getColref()}" 143fd9c77d3SAndreas Gohr ); 144fd9c77d3SAndreas Gohr $coltbl = $MN; 145fd9c77d3SAndreas Gohr $colnam = 'value'; 146fd9c77d3SAndreas Gohr } else { 147fd9c77d3SAndreas Gohr $coltbl = $datatable; 148fd9c77d3SAndreas Gohr $colnam = $col->getColName(); 149fd9c77d3SAndreas Gohr } 150fd9c77d3SAndreas Gohr 151fd9c77d3SAndreas Gohr $col->getType()->filter($subClause, $coltbl, $colnam, $comp, $value, $op); // type based filter 152fd9c77d3SAndreas Gohr } 153fd9c77d3SAndreas Gohr } 154fd9c77d3SAndreas Gohr 155fd9c77d3SAndreas Gohr /** 156fd9c77d3SAndreas Gohr * Add the sort by clauses to the query 157fd9c77d3SAndreas Gohr * 158fd9c77d3SAndreas Gohr * We always sort by the single val column which contains a copy of the first value of the multi column 159fd9c77d3SAndreas Gohr * 160fd9c77d3SAndreas Gohr * @param array[] $sorts 161fd9c77d3SAndreas Gohr */ 162fd9c77d3SAndreas Gohr public function addSorts($sorts) 163fd9c77d3SAndreas Gohr { 164fd9c77d3SAndreas Gohr foreach ($sorts as $sort) { 1657234bfb1Ssplitbrain [$col, $asc, $nc] = $sort; 166fd9c77d3SAndreas Gohr /** @var $col Column */ 167fd9c77d3SAndreas Gohr $colname = $col->getColName(false); 168fd9c77d3SAndreas Gohr if ($nc) $colname .= ' COLLATE NOCASE'; 169fd9c77d3SAndreas Gohr $col->getType()->sort($this->qb, 'data_' . $col->getTable(), $colname, $asc ? 'ASC' : 'DESC'); 170fd9c77d3SAndreas Gohr } 171fd9c77d3SAndreas Gohr } 172fd9c77d3SAndreas Gohr 173fd9c77d3SAndreas Gohr /** 174b37d1c8bSAnna Dabrowska * @param string $datatable 175b37d1c8bSAnna Dabrowska * @return string 176b37d1c8bSAnna Dabrowska */ 177b37d1c8bSAnna Dabrowska public function addPublishClauses($datatable) 178b37d1c8bSAnna Dabrowska { 179b37d1c8bSAnna Dabrowska $latestClause = "IS_PUBLISHER($datatable.pid)"; 180b37d1c8bSAnna Dabrowska if ($this->selectLatest) { 181b37d1c8bSAnna Dabrowska $latestClause .= " AND $datatable.latest = 1"; 182b37d1c8bSAnna Dabrowska } 183b37d1c8bSAnna Dabrowska $publishedClause = "IS_PUBLISHER($datatable.pid) !=1 AND $datatable.published = 1"; 184b37d1c8bSAnna Dabrowska 185b37d1c8bSAnna Dabrowska return "( ($latestClause) OR ($publishedClause) )"; 186b37d1c8bSAnna Dabrowska } 187b37d1c8bSAnna Dabrowska 188b37d1c8bSAnna Dabrowska /** 189299ca8ccSAndreas Gohr * Access to the underlying QueryBuilder 190299ca8ccSAndreas Gohr * 191299ca8ccSAndreas Gohr * @return QueryBuilder 192299ca8ccSAndreas Gohr */ 193299ca8ccSAndreas Gohr public function getQueryBuilder() 194299ca8ccSAndreas Gohr { 195299ca8ccSAndreas Gohr return $this->qb; 196299ca8ccSAndreas Gohr } 197299ca8ccSAndreas Gohr 198299ca8ccSAndreas Gohr /** 199fd9c77d3SAndreas Gohr * Get the SQL query and parameters 200fd9c77d3SAndreas Gohr * 201299ca8ccSAndreas Gohr * Shortcut for $this->getQueryBuilder()->getSQL() 202299ca8ccSAndreas Gohr * 203fd9c77d3SAndreas Gohr * @return array ($sql, $params) 204fd9c77d3SAndreas Gohr */ 205fd9c77d3SAndreas Gohr public function getSQL() 206fd9c77d3SAndreas Gohr { 207fd9c77d3SAndreas Gohr return $this->qb->getSQL(); 208fd9c77d3SAndreas Gohr } 209b37d1c8bSAnna Dabrowska 210b37d1c8bSAnna Dabrowska /** 211b37d1c8bSAnna Dabrowska * Allows disabling default 'latest = 1' clause in select statement 212b37d1c8bSAnna Dabrowska * 213b37d1c8bSAnna Dabrowska * @param bool $selectLatest 214b37d1c8bSAnna Dabrowska */ 215b37d1c8bSAnna Dabrowska public function setSelectLatest(bool $selectLatest) 216b37d1c8bSAnna Dabrowska { 217b37d1c8bSAnna Dabrowska $this->selectLatest = $selectLatest; 218b37d1c8bSAnna Dabrowska } 219fd9c77d3SAndreas Gohr} 220