xref: /plugin/struct/meta/SearchSQLBuilder.php (revision 7923cdacab81de55732b2931695bdc6734571b7e)
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