Completed
Pull Request — master (#25)
by Matt
02:48
created

postgres::get_query()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 18
nc 1
nop 4
1
<?php
2
/**
3
 *
4
 * Precise Similar Topics
5
 *
6
 * @copyright (c) 2018 Matt Friedman
7
 * @license GNU General Public License, version 2 (GPL-2.0)
8
 *
9
 */
10
11
namespace vse\similartopics\driver;
12
13
class postgres implements driver_interface
14
{
15
	/** @var \phpbb\db\driver\driver_interface */
16
	protected $db;
17
18
	/** @var \phpbb\config\config */
19
	protected $config;
20
21
	/** @var string */
22
	protected $ts_name;
23
24
	/**
25
	 * Cnstructor
26
	 *
27
	 * @param \phpbb\db\driver\driver_interface $db
28
	 * @param \phpbb\config\config              $config
29
	 */
30
	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config)
31
	{
32
		$this->db = $db;
33
		$this->config = $config;
34
35
		$this->set_ts_name($config['pst_postgres_ts_name']);
36
	}
37
38
	/**
39
	 * {@inheritdoc}
40
	 */
41
	public function get_name()
42
	{
43
		return 'postgres';
44
	}
45
46
	/**
47
	 * {@inheritdoc}
48
	 */
49
	public function get_type()
50
	{
51
		return 'postgres';
52
	}
53
54
	/**
55
	 * {@inheritdoc}
56
	 */
57
	public function get_query($topic_id, $topic_title, $length, $sensitivity)
58
	{
59
		$ts_name = $this->db->sql_escape($this->ts_name);
60
		$ts_query_text = $this->db->sql_escape(preg_replace('/\s+/', '|', $topic_title));
61
		$ts_rank_cd = "ts_rank_cd('{1,1,1,1}', to_tsvector('$ts_name', t.topic_title), to_tsquery('$ts_name', '$ts_query_text'), 32)";
62
63
		return array(
64
			'SELECT'	=> "f.forum_id, f.forum_name, t.*, $ts_rank_cd AS score",
65
66
			'FROM'		=> array(
67
				TOPICS_TABLE	=> 't',
68
			),
69
			'LEFT_JOIN'	=> array(
70
				array(
71
					'FROM'	=>	array(FORUMS_TABLE	=> 'f'),
72
					'ON'	=> 'f.forum_id = t.forum_id',
73
				),
74
			),
75
			'WHERE'		=> "to_tsquery('$ts_name', '$ts_query_text') @@ to_tsvector('$ts_name', t.topic_title) AND $ts_rank_cd >= " . (float) $sensitivity . '
76
				AND t.topic_status <> ' . ITEM_MOVED . '
77
				AND t.topic_visibility = ' . ITEM_APPROVED . '
78
				AND t.topic_time > (extract(epoch from current_timestamp)::integer - ' . (int) $length . ')
79
				AND t.topic_id <> ' . (int) $topic_id,
80
			'ORDER_BY'	=> 'score DESC, t.topic_time DESC',
81
		);
82
	}
83
84
	/**
85
	 * {@inheritdoc}
86
	 */
87
	public function is_supported()
88
	{
89
		return ($this->db->get_sql_layer() === 'postgres');
90
	}
91
92
	/**
93
	 * {@inheritdoc}
94
	 */
95
	public function is_fulltext($column = 'topic_title', $table = TOPICS_TABLE)
96
	{
97
		foreach ($this->get_fulltext_indexes($column, $table) as $index)
98
		{
99
			if ($index === $table . '_' . $this->ts_name . '_' . $column)
100
			{
101
				return true;
102
			}
103
		}
104
105
		return false;
106
	}
107
108
	/**
109
	 * {@inheritdoc}
110
	 */
111
	public function get_fulltext_indexes($column = 'topic_title', $table = TOPICS_TABLE)
112
	{
113
		$indexes = array();
114
115
		if (!$this->is_supported())
116
		{
117
			return $indexes;
118
		}
119
120
		$sql = "SELECT c2.relname
121
			FROM pg_catalog.pg_class c1, pg_catalog.pg_index i, pg_catalog.pg_class c2
122
			WHERE c1.relname = '" . $this->db->sql_escape($table) . "'
123
				AND position('to_tsvector' in pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)) > 0
124
				AND pg_catalog.pg_table_is_visible(c1.oid)
125
				AND c1.oid = i.indrelid
126
				AND i.indexrelid = c2.oid";
127
		$result = $this->db->sql_query($sql);
128
		while ($row = $this->db->sql_fetchrow($result))
129
		{
130
			if (strpos($row['relname'], $column) !== false)
131
			{
132
				$indexes[] = $row['relname'];
133
			}
134
		}
135
		$this->db->sql_freeresult($result);
136
137
		return $indexes;
138
	}
139
140
	/**
141
	 * {@inheritdoc}
142
	 */
143
	public function create_fulltext_index($column = 'topic_title', $table = TOPICS_TABLE)
144
	{
145
		// Make sure ts_name is current
146
		$this->set_ts_name($this->config['pst_postgres_ts_name']);
147
148
		$new_index = $table . '_' . $this->ts_name . '_' . $column;
149
150
		$indexed = false;
151
152
		foreach ($this->get_fulltext_indexes($column, $table) as $index)
153
		{
154
			if ($index === $new_index)
155
			{
156
				$indexed = true;
157
			}
158
			else
159
			{
160
				$sql = 'DROP INDEX ' . $index;
161
				$this->db->sql_query($sql);
162
			}
163
		}
164
165
		if (!$indexed)
166
		{
167
			$sql = 'CREATE INDEX ' . $this->db->sql_escape($new_index) . ' 
168
				ON '  . $this->db->sql_escape($table) . " 
169
				USING gin (to_tsvector ('" . $this->db->sql_escape($this->ts_name) . "', " . $this->db->sql_escape($column) . '))';
170
			$this->db->sql_query($sql);
171
		}
172
	}
173
174
	/**
175
	 * {@inheritdoc}
176
	 */
177
	public function get_engine()
178
	{
179
		return '';
180
	}
181
182
	/**
183
	 * Set the PostgreSQL Text Search name (dictionary)
184
	 *
185
	 * @param string $ts_name Dictionary name
186
	 */
187
	protected function set_ts_name($ts_name)
188
	{
189
		$this->ts_name = $ts_name ?: 'simple';
190
	}
191
}
192