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