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 15fd9c77d3SAndreas Gohr /** 16fd9c77d3SAndreas Gohr * SearchSQLBuilder constructor. 17fd9c77d3SAndreas Gohr */ 18fd9c77d3SAndreas Gohr public function __construct() 19fd9c77d3SAndreas Gohr { 20fd9c77d3SAndreas Gohr $this->qb = new QueryBuilder(); 21fd9c77d3SAndreas Gohr } 22fd9c77d3SAndreas Gohr 23fd9c77d3SAndreas Gohr /** 24fd9c77d3SAndreas Gohr * Add the schemas to the query 25fd9c77d3SAndreas Gohr * 26fd9c77d3SAndreas Gohr * @param Schema[] $schemas Schema names to query 27fd9c77d3SAndreas Gohr */ 28fd9c77d3SAndreas Gohr public function addSchemas($schemas) 29fd9c77d3SAndreas Gohr { 30fd9c77d3SAndreas Gohr // basic tables 31fd9c77d3SAndreas Gohr $first_table = ''; 32fd9c77d3SAndreas Gohr foreach ($schemas as $schema) { 33fd9c77d3SAndreas Gohr $datatable = 'data_' . $schema->getTable(); 34fd9c77d3SAndreas Gohr if ($first_table) { 35fd9c77d3SAndreas Gohr // follow up tables 36fd9c77d3SAndreas Gohr $this->qb->addLeftJoin($first_table, $datatable, $datatable, "$first_table.pid = $datatable.pid"); 37fd9c77d3SAndreas Gohr } else { 38fd9c77d3SAndreas Gohr // first table 39fd9c77d3SAndreas Gohr $this->qb->addTable($datatable); 40fd9c77d3SAndreas Gohr 41fd9c77d3SAndreas Gohr // add conditional page clauses if pid has a value 42fd9c77d3SAndreas Gohr $subAnd = $this->qb->filters()->whereSubAnd(); 43fd9c77d3SAndreas Gohr $subAnd->whereAnd("$datatable.pid = ''"); 44fd9c77d3SAndreas Gohr $subOr = $subAnd->whereSubOr(); 45fd9c77d3SAndreas Gohr $subOr->whereAnd("GETACCESSLEVEL($datatable.pid) > 0"); 46fd9c77d3SAndreas Gohr $subOr->whereAnd("PAGEEXISTS($datatable.pid) = 1"); 47fd9c77d3SAndreas Gohr $subOr->whereAnd('(ASSIGNED = 1 OR ASSIGNED IS NULL)'); 48fd9c77d3SAndreas Gohr 49fd9c77d3SAndreas Gohr // add conditional schema assignment check 50fd9c77d3SAndreas Gohr $this->qb->addLeftJoin( 51fd9c77d3SAndreas Gohr $datatable, 52fd9c77d3SAndreas Gohr 'schema_assignments', 53fd9c77d3SAndreas Gohr '', 54fd9c77d3SAndreas Gohr "$datatable.pid != '' 55fd9c77d3SAndreas Gohr AND $datatable.pid = schema_assignments.pid 56fd9c77d3SAndreas Gohr AND schema_assignments.tbl = '{$schema->getTable()}'" 57fd9c77d3SAndreas Gohr ); 58fd9c77d3SAndreas Gohr 59fd9c77d3SAndreas Gohr $this->qb->addSelectColumn($datatable, 'rid'); 60fd9c77d3SAndreas Gohr $this->qb->addSelectColumn($datatable, 'pid', 'PID'); 61fd9c77d3SAndreas Gohr $this->qb->addSelectColumn($datatable, 'rev'); 62fd9c77d3SAndreas Gohr $this->qb->addSelectColumn('schema_assignments', 'assigned', 'ASSIGNED'); 63fd9c77d3SAndreas Gohr $this->qb->addGroupByColumn($datatable, 'pid'); 64fd9c77d3SAndreas Gohr $this->qb->addGroupByColumn($datatable, 'rid'); 65fd9c77d3SAndreas Gohr 66fd9c77d3SAndreas Gohr $first_table = $datatable; 67fd9c77d3SAndreas Gohr } 68fd9c77d3SAndreas Gohr // phpcs:ignore 69fd9c77d3SAndreas Gohr $this->qb->filters()->whereAnd("( (IS_PUBLISHER($datatable.pid) AND $datatable.latest = 1) OR (IS_PUBLISHER($datatable.pid) !=1 AND $datatable.published = 1) )"); 70fd9c77d3SAndreas Gohr } 71fd9c77d3SAndreas Gohr } 72fd9c77d3SAndreas Gohr 73fd9c77d3SAndreas Gohr /** 74fd9c77d3SAndreas Gohr * Add the columns to select, handling multis 75fd9c77d3SAndreas Gohr * 76fd9c77d3SAndreas Gohr * @param Column[] $columns 77fd9c77d3SAndreas Gohr */ 78fd9c77d3SAndreas Gohr public function addColumns($columns) 79fd9c77d3SAndreas Gohr { 80fd9c77d3SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 81fd9c77d3SAndreas Gohr $n = 0; 82fd9c77d3SAndreas Gohr foreach ($columns as $col) { 83fd9c77d3SAndreas Gohr $CN = 'C' . $n++; 84fd9c77d3SAndreas Gohr 85fd9c77d3SAndreas Gohr if ($col->isMulti()) { 86fd9c77d3SAndreas Gohr $datatable = "data_{$col->getTable()}"; 87fd9c77d3SAndreas Gohr $multitable = "multi_{$col->getTable()}"; 88fd9c77d3SAndreas Gohr $MN = $this->qb->generateTableAlias('M'); 89fd9c77d3SAndreas Gohr 90fd9c77d3SAndreas Gohr $this->qb->addLeftJoin( 91fd9c77d3SAndreas Gohr $datatable, 92fd9c77d3SAndreas Gohr $multitable, 93fd9c77d3SAndreas Gohr $MN, 94fd9c77d3SAndreas Gohr "$datatable.pid = $MN.pid AND $datatable.rid = $MN.rid AND 95fd9c77d3SAndreas Gohr $datatable.rev = $MN.rev AND 96fd9c77d3SAndreas Gohr $MN.colref = {$col->getColref()}" 97fd9c77d3SAndreas Gohr ); 98fd9c77d3SAndreas Gohr 99fd9c77d3SAndreas Gohr $col->getType()->select($this->qb, $MN, 'value', $CN); 100fd9c77d3SAndreas Gohr $sel = $this->qb->getSelectStatement($CN); 101fd9c77d3SAndreas Gohr $this->qb->addSelectStatement("GROUP_CONCAT_DISTINCT($sel, '$sep')", $CN); 102fd9c77d3SAndreas Gohr } else { 103fd9c77d3SAndreas Gohr $col->getType()->select($this->qb, 'data_' . $col->getTable(), $col->getColName(), $CN); 104fd9c77d3SAndreas Gohr $this->qb->addGroupByStatement($CN); 105fd9c77d3SAndreas Gohr } 106fd9c77d3SAndreas Gohr } 107fd9c77d3SAndreas Gohr } 108fd9c77d3SAndreas Gohr 109fd9c77d3SAndreas Gohr /** 110fd9c77d3SAndreas Gohr * Add the filters to the query 111fd9c77d3SAndreas Gohr * 112fd9c77d3SAndreas Gohr * All given filters are added to their own subclause. 113fd9c77d3SAndreas Gohr * 114fd9c77d3SAndreas Gohr * @param array[] $filters 115fd9c77d3SAndreas Gohr */ 116fd9c77d3SAndreas Gohr public function addFilters($filters) 117fd9c77d3SAndreas Gohr { 118fd9c77d3SAndreas Gohr if (!$filters) return; // ignore empty filters 119fd9c77d3SAndreas Gohr 120fd9c77d3SAndreas Gohr $subClause = $this->qb->filters()->where('AND'); 121fd9c77d3SAndreas Gohr 122fd9c77d3SAndreas Gohr foreach ($filters as $filter) { 123fd9c77d3SAndreas Gohr /** @var Column $col */ 124fd9c77d3SAndreas Gohr list($col, $value, $comp, $op) = $filter; 125fd9c77d3SAndreas Gohr 126fd9c77d3SAndreas Gohr $datatable = "data_{$col->getTable()}"; 127fd9c77d3SAndreas Gohr $multitable = "multi_{$col->getTable()}"; 128fd9c77d3SAndreas Gohr 129fd9c77d3SAndreas Gohr /** @var $col Column */ 130fd9c77d3SAndreas Gohr if ($col->isMulti()) { 131fd9c77d3SAndreas Gohr $MN = $this->qb->generateTableAlias('MN'); 132fd9c77d3SAndreas Gohr 133fd9c77d3SAndreas Gohr $this->qb->addLeftJoin( 134fd9c77d3SAndreas Gohr $datatable, 135fd9c77d3SAndreas Gohr $multitable, 136fd9c77d3SAndreas Gohr $MN, 137fd9c77d3SAndreas Gohr "$datatable.pid = $MN.pid AND $datatable.rid = $MN.rid AND 138fd9c77d3SAndreas Gohr $datatable.rev = $MN.rev AND 139fd9c77d3SAndreas Gohr $MN.colref = {$col->getColref()}" 140fd9c77d3SAndreas Gohr ); 141fd9c77d3SAndreas Gohr $coltbl = $MN; 142fd9c77d3SAndreas Gohr $colnam = 'value'; 143fd9c77d3SAndreas Gohr } else { 144fd9c77d3SAndreas Gohr $coltbl = $datatable; 145fd9c77d3SAndreas Gohr $colnam = $col->getColName(); 146fd9c77d3SAndreas Gohr } 147fd9c77d3SAndreas Gohr 148fd9c77d3SAndreas Gohr $col->getType()->filter($subClause, $coltbl, $colnam, $comp, $value, $op); // type based filter 149fd9c77d3SAndreas Gohr } 150fd9c77d3SAndreas Gohr } 151fd9c77d3SAndreas Gohr 152fd9c77d3SAndreas Gohr /** 153fd9c77d3SAndreas Gohr * Add the sort by clauses to the query 154fd9c77d3SAndreas Gohr * 155fd9c77d3SAndreas Gohr * We always sort by the single val column which contains a copy of the first value of the multi column 156fd9c77d3SAndreas Gohr * 157fd9c77d3SAndreas Gohr * @param array[] $sorts 158fd9c77d3SAndreas Gohr */ 159fd9c77d3SAndreas Gohr public function addSorts($sorts) 160fd9c77d3SAndreas Gohr { 161fd9c77d3SAndreas Gohr foreach ($sorts as $sort) { 162fd9c77d3SAndreas Gohr list($col, $asc, $nc) = $sort; 163fd9c77d3SAndreas Gohr /** @var $col Column */ 164fd9c77d3SAndreas Gohr $colname = $col->getColName(false); 165fd9c77d3SAndreas Gohr if ($nc) $colname .= ' COLLATE NOCASE'; 166fd9c77d3SAndreas Gohr $col->getType()->sort($this->qb, 'data_' . $col->getTable(), $colname, $asc ? 'ASC' : 'DESC'); 167fd9c77d3SAndreas Gohr } 168fd9c77d3SAndreas Gohr } 169fd9c77d3SAndreas Gohr 170fd9c77d3SAndreas Gohr /** 171*299ca8ccSAndreas Gohr * Access to the underlying QueryBuilder 172*299ca8ccSAndreas Gohr * 173*299ca8ccSAndreas Gohr * @return QueryBuilder 174*299ca8ccSAndreas Gohr */ 175*299ca8ccSAndreas Gohr public function getQueryBuilder() 176*299ca8ccSAndreas Gohr { 177*299ca8ccSAndreas Gohr return $this->qb; 178*299ca8ccSAndreas Gohr } 179*299ca8ccSAndreas Gohr 180*299ca8ccSAndreas Gohr /** 181fd9c77d3SAndreas Gohr * Get the SQL query and parameters 182fd9c77d3SAndreas Gohr * 183*299ca8ccSAndreas Gohr * Shortcut for $this->getQueryBuilder()->getSQL() 184*299ca8ccSAndreas Gohr * 185fd9c77d3SAndreas Gohr * @return array ($sql, $params) 186fd9c77d3SAndreas Gohr */ 187fd9c77d3SAndreas Gohr public function getSQL() 188fd9c77d3SAndreas Gohr { 189fd9c77d3SAndreas Gohr return $this->qb->getSQL(); 190fd9c77d3SAndreas Gohr } 191fd9c77d3SAndreas Gohr} 192