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