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