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