current_link->query("EXPLAIN {$string}");
$this->explain .= <<
#{$this->query_count} - Select Query
|
{$queryText}
|
Info
|
HTML;
while ($table = $query->fetch(PDO::FETCH_ASSOC)) {
$this->explain .= <<
{$table['QUERY PLAN']} |
HTML;
}
$this->explain .= <<
Query Time: {$duration}
|
HTML;
} else {
$this->explain .= <<
#{$this->query_count} - Write Query
|
{$queryText}
|
Query Time: {$duration}
|
HTML;
}
$this->querylist[$this->query_count]['query'] = $string;
$this->querylist[$this->query_count]['time'] = $qtime;
}
public function list_tables($database, $prefix = '')
{
if ($prefix) {
$query = $this->write_query("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_name LIKE '{$this->escape_string($prefix)}%'");
} else {
$query = $this->write_query("SELECT table_name FROM information_schema.tables WHERE table_schema='public'");
}
return $query->fetchAll(PDO::FETCH_COLUMN);
}
public function table_exists($table)
{
$query = $this->write_query("SELECT COUNT(table_name) as table_names FROM information_schema.tables WHERE table_schema = 'public' AND table_name='{$this->table_prefix}{$table}'");
$exists = $this->fetch_field($query, 'table_names');
return $exists > 0;
}
public function field_exists($field, $table)
{
$query = $this->write_query("SELECT COUNT(column_name) as column_names FROM information_schema.columns WHERE table_name='{$this->table_prefix}{$table}' AND column_name='{$field}'");
$exists = $this->fetch_field($query, 'column_names');
return $exists > 0;
}
public function simple_select($table, $fields = "*", $conditions = "", $options = array())
{
$query = "SELECT {$fields} FROM {$this->table_prefix}{$table}";
if ($conditions != "") {
$query .= " WHERE {$conditions}";
}
if (isset($options['group_by'])) {
$query .= " GROUP BY {$options['group_by']}";
}
if (isset($options['order_by'])) {
$query .= " ORDER BY {$options['order_by']}";
if (isset($options['order_dir'])) {
$query .= " {$options['order_dir']}";
}
}
if (isset($options['limit_start']) && isset($options['limit'])) {
$query .= " LIMIT {$options['limit']} OFFSET {$options['limit_start']}";
} else if (isset($options['limit'])) {
$query .= " LIMIT {$options['limit']}";
}
return $this->query($query);
}
public function insert_query($table, $array)
{
if (!is_array($array)) {
return false;
}
$values = $this->build_value_string($table, $array);
$fields = implode(",", array_keys($array));
$this->write_query("
INSERT
INTO {$this->table_prefix}{$table} ({$fields})
VALUES ({$values})
");
return $this->insert_id();
}
private function quote_val($value, $quote = "'")
{
if (is_int($value)) {
return $value;
}
return "{$quote}{$value}{$quote}";
}
public function insert_query_multiple($table, $array)
{
if (!is_array($array)){
return;
}
// Field names
$fields = array_keys($array[0]);
$fields = implode(",", $fields);
$insert_rows = array();
foreach ($array as $values) {
$insert_rows[] = "(".$this->build_value_string($table, $values).")";
}
$insert_rows = implode(", ", $insert_rows);
$this->write_query("
INSERT
INTO {$this->table_prefix}{$table} ({$fields})
VALUES {$insert_rows}
");
}
public function update_query($table, $array, $where = "", $limit = "", $no_quote = false)
{
global $mybb;
if (!is_array($array)) {
return false;
}
$query = $this->build_field_value_string($table, $array, $no_quote);
if(!empty($where)) {
$query .= " WHERE {$where}";
}
return $this->write_query("
UPDATE {$this->table_prefix}$table
SET $query
");
}
public function delete_query($table, $where = "", $limit = "")
{
$query = "";
if (!empty($where)) {
$query .= " WHERE {$where}";
}
return $this->write_query("
DELETE
FROM {$this->table_prefix}$table
$query
");
}
public function optimize_table($table)
{
$this->write_query("VACUUM {$this->table_prefix}{$table};");
}
public function analyze_table($table)
{
$this->write_query("ANALYZE {$this->table_prefix}{$table};");
}
public function show_create_table($table)
{
$query = $this->write_query("
SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull
FROM pg_class c
LEFT JOIN pg_attribute a ON (a.attrelid = c.oid)
LEFT JOIN pg_type t ON (a.atttypid = t.oid)
WHERE c.relname = '{$this->table_prefix}{$table}' AND a.attnum > 0
ORDER BY a.attnum
");
$lines = array();
$table_lines = "CREATE TABLE {$this->table_prefix}{$table} (\n";
while ($row = $this->fetch_array($query)) {
// Get the data from the table
$query2 = $this->write_query("
SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault
FROM pg_attrdef d
LEFT JOIN pg_class c ON (c.oid = d.adrelid)
WHERE c.relname = '{$this->table_prefix}{$table}' AND d.adnum = '{$row['attnum']}'
");
if (!$query2) {
unset($row['rowdefault']);
} else {
$row['rowdefault'] = $this->fetch_field($query2, 'rowdefault');
}
if ($row['type'] == 'bpchar') {
// Stored in the engine as bpchar, but in the CREATE TABLE statement it's char
$row['type'] = 'char';
}
$line = " {$row['field']} {$row['type']}";
if (strpos($row['type'], 'char') !== false) {
if ($row['lengthvar'] > 0) {
$line .= '('.($row['lengthvar'] - 4).')';
}
}
if (strpos($row['type'], 'numeric') !== false) {
$line .= '('.sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)).')';
}
if (!empty($row['rowdefault'])) {
$line .= " DEFAULT {$row['rowdefault']}";
}
if ($row['notnull'] == 't') {
$line .= ' NOT NULL';
}
$lines[] = $line;
}
// Get the listing of primary keys.
$query = $this->write_query("
SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key
FROM pg_class bc
LEFT JOIN pg_index i ON (bc.oid = i.indrelid)
LEFT JOIN pg_class ic ON (ic.oid = i.indexrelid)
LEFT JOIN pg_attribute ia ON (ia.attrelid = i.indexrelid)
LEFT JOIN pg_attribute ta ON (ta.attrelid = bc.oid AND ta.attrelid = i.indrelid AND ta.attnum = i.indkey[ia.attnum-1])
WHERE bc.relname = '{$this->table_prefix}{$table}'
ORDER BY index_name, tab_name, column_name
");
$primary_key = array();
$primary_key_name = '';
$unique_keys = array();
// We do this in two steps. It makes placing the comma easier
while ($row = $this->fetch_array($query)) {
if ($row['primary_key'] == 't') {
$primary_key[] = $row['column_name'];
$primary_key_name = $row['index_name'];
}
if ($row['unique_key'] == 't') {
$unique_keys[$row['index_name']][] = $row['column_name'];
}
}
if (!empty($primary_key)) {
$lines[] = " CONSTRAINT $primary_key_name PRIMARY KEY (".implode(', ', $primary_key).")";
}
foreach ($unique_keys as $key_name => $key_columns) {
$lines[] = " CONSTRAINT $key_name UNIQUE (".implode(', ', $key_columns).")";
}
$table_lines .= implode(", \n", $lines);
$table_lines .= "\n)\n";
return $table_lines;
}
public function show_fields_from($table)
{
$query = $this->write_query("SELECT column_name FROM information_schema.constraint_column_usage WHERE table_name = '{$this->table_prefix}{$table}' and constraint_name = '{$this->table_prefix}{$table}_pkey' LIMIT 1");
$primary_key = $this->fetch_field($query, 'column_name');
$query = $this->write_query("
SELECT column_name, data_type, is_nullable, column_default, character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale
FROM information_schema.columns
WHERE table_name = '{$this->table_prefix}{$table}'
");
$field_info = array();
while ($field = $this->fetch_array($query)) {
if ($field['column_name'] == $primary_key) {
$field['_key'] = 'PRI';
} else {
$field['_key'] = '';
}
if (!is_null($field['column_default']) && stripos($field['column_default'], 'nextval') !== false) {
$field['_extra'] = 'auto_increment';
} else {
$field['_extra'] = '';
}
// bit, character, text fields.
if (!is_null($field['character_maximum_length'])) {
$field['data_type'] .= '('.(int)$field['character_maximum_length'].')';
}
// numeric/decimal fields.
else if ($field['numeric_precision_radix'] == 10 && !is_null($field['numeric_precision']) && !is_null($field['numeric_scale'])) {
$field['data_type'] .= '('.(int)$field['numeric_precision'].','.(int)$field['numeric_scale'].')';
}
$field_info[] = array(
'Field' => $field['column_name'],
'Type' => $field['data_type'],
'Null' => $field['is_nullable'],
'Key' => $field['_key'],
'Default' => $field['column_default'],
'Extra' => $field['_extra'],
);
}
return $field_info;
}
function is_fulltext($table, $index = "")
{
return false;
}
public function supports_fulltext($table)
{
return false;
}
public function index_exists($table, $index)
{
$err = $this->error_reporting;
$this->error_reporting = 0;
$tableName = $this->escape_string("{$this->table_prefix}{$table}");
$query = $this->write_query("SELECT * FROM pg_indexes WHERE tablename = '{$tableName}'");
$exists = $this->fetch_field($query, $index);
$this->error_reporting = $err;
return (bool)$exists;
}
public function supports_fulltext_boolean($table)
{
return false;
}
public function create_fulltext_index($table, $column, $name = "")
{
return false;
}
public function drop_index($table, $name)
{
$this->write_query("
ALTER TABLE {$this->table_prefix}{$table}
DROP INDEX {$name}
");
}
public function drop_table($table, $hard = false, $table_prefix = true)
{
if ($table_prefix == false) {
$table_prefix = "";
} else {
$table_prefix = $this->table_prefix;
}
$table_prefix_bak = $this->table_prefix;
$this->table_prefix = '';
$fields = array_column($this->show_fields_from($table_prefix.$table), 'Field');
if ($hard == false) {
if($this->table_exists($table_prefix.$table))
{
$this->write_query("DROP TABLE {$table_prefix}{$table}");
}
} else {
$this->write_query("DROP TABLE {$table_prefix}{$table}");
}
$this->table_prefix = $table_prefix_bak;
if(!empty($fields)) {
foreach ($fields as &$field) {
$field = "{$table_prefix}{$table}_{$field}_seq";
}
unset($field);
if (version_compare($this->get_version(), '8.2.0', '>=')) {
$fields = implode(', ', $fields);
$this->write_query("DROP SEQUENCE IF EXISTS {$fields}");
} else {
$fields = "'" . implode("', '", $fields) . "'";
$query = $this->query("SELECT sequence_name as field FROM information_schema.sequences WHERE sequence_name in ({$fields}) AND sequence_schema = 'public'");
while ($row = $this->fetch_array($query)) {
$this->write_query("DROP SEQUENCE {$row['field']}");
}
}
}
}
public function rename_table($old_table, $new_table, $table_prefix = true)
{
if ($table_prefix == false) {
$table_prefix = "";
} else {
$table_prefix = $this->table_prefix;
}
return $this->write_query("ALTER TABLE {$table_prefix}{$old_table} RENAME TO {$table_prefix}{$new_table}");
}
public function replace_query($table, $replacements = array(), $default_field = "", $insert_id = true)
{
global $mybb;
if ($default_field == "") {
$query = $this->write_query("SELECT column_name FROM information_schema.constraint_column_usage WHERE table_name = '{$this->table_prefix}{$table}' and constraint_name = '{$this->table_prefix}{$table}_pkey' LIMIT 1");
$main_field = $this->fetch_field($query, 'column_name');
} else {
$main_field = $default_field;
}
if (!is_array($main_field)) {
$main_field = array($main_field);
}
if(version_compare($this->get_version(), '9.5.0', '>='))
{
// ON CONFLICT clause supported
$main_field_csv = implode(',', $main_field);
// INSERT-like list of fields and values
$fields = implode(",", array_keys($replacements));
$values = $this->build_value_string($table, $replacements);
// UPDATE-like SET list, using special EXCLUDED table to avoid passing values twice
$reassignment_values = array();
$true_replacement_keys = array_diff(
array_keys($replacements),
array_flip($main_field)
);
foreach($true_replacement_keys as $key)
{
$reassignment_values[$key] = 'EXCLUDED.' . $key;
}
$reassignments = $this->build_field_value_string($table, $reassignment_values, true);
$this->write_query("
INSERT
INTO {$this->table_prefix}{$table} ({$fields})
VALUES ({$values})
ON CONFLICT ($main_field_csv) DO UPDATE SET {$reassignments}
");
}
else
{
// manual SELECT and UPDATE/INSERT (prone to TOCTOU issues)
$update = false;
$search_bit = array();
if (!is_array($main_field)) {
$main_field = array($main_field);
}
foreach ($main_field as $field) {
if (isset($mybb->binary_fields[$table][$field]) && $mybb->binary_fields[$table][$field]) {
$search_bit[] = "{$field} = ".$replacements[$field];
} else {
$search_bit[] = "{$field} = ".$this->quote_val($replacements[$field]);
}
}
$search_bit = implode(" AND ", $search_bit);
$query = $this->write_query("SELECT COUNT(".$main_field[0].") as count FROM {$this->table_prefix}{$table} WHERE {$search_bit} LIMIT 1");
if ($this->fetch_field($query, "count") == 1) {
$update = true;
}
if ($update === true) {
return $this->update_query($table, $replacements, $search_bit);
} else {
return $this->insert_query($table, $replacements);
}
}
}
public function drop_column($table, $column)
{
return $this->write_query("ALTER TABLE {$this->table_prefix}{$table} DROP {$column}");
}
public function add_column($table, $column, $definition)
{
return $this->write_query("ALTER TABLE {$this->table_prefix}{$table} ADD {$column} {$definition}");
}
public function modify_column($table, $column, $new_definition, $new_not_null = false, $new_default_value = false)
{
$result1 = $result2 = $result3 = true;
if ($new_definition !== false) {
$result1 = $this->write_query("ALTER TABLE {$this->table_prefix}{$table} ALTER COLUMN {$column} TYPE {$new_definition}");
}
if ($new_not_null !== false) {
$set_drop = "DROP";
if (strtolower($new_not_null) == "set") {
$set_drop = "SET";
}
$result2 = $this->write_query("ALTER TABLE {$this->table_prefix}{$table} ALTER COLUMN {$column} {$set_drop} NOT NULL");
}
if ($new_default_value !== null) {
if($new_default_value !== false) {
$result3 = $this->write_query("ALTER TABLE {$this->table_prefix}{$table} ALTER COLUMN {$column} SET DEFAULT {$new_default_value}");
} else {
$result3 = $this->write_query("ALTER TABLE {$this->table_prefix}{$table} ALTER COLUMN {$column} DROP DEFAULT");
}
}
return $result1 && $result2 && $result3;
}
public function rename_column($table, $old_column, $new_column, $new_definition, $new_not_null = false, $new_default_value = false)
{
$result1 = $this->write_query("ALTER TABLE {$this->table_prefix}{$table} RENAME COLUMN {$old_column} TO {$new_column}");
$result2 = $this->modify_column($table, $new_column, $new_definition, $new_not_null, $new_default_value);
return $result1 && $result2;
}
public function fetch_size($table = '')
{
if (!empty($table)) {
$query = $this->query("SELECT SUM(reltuples), SUM(relpages) FROM pg_class WHERE relname = '{$this->table_prefix}{$table}'");
} else {
$query = $this->query("SELECT SUM(reltuples), SUM(relpages) FROM pg_class");
}
if (null === $query) {
return 0;
}
$result = $this->fetch_array($query, PDO::FETCH_NUM);
if (false === $result) {
return 0;
}
return $result[0] + $result[1];
}
public function fetch_db_charsets()
{
return false;
}
public function fetch_charset_collation($charset)
{
return false;
}
public function build_create_table_collation()
{
return '';
}
public function insert_id()
{
try {
return $this->write_link->lastInsertId();
} catch (PDOException $e) {
// in order to behave the same way as the MySQL driver, we return false if there is no last insert ID
return false;
}
}
public function escape_binary($string)
{
$hex = bin2hex($string);
return "decode('{$hex}', 'hex')";
}
public function unescape_binary($string)
{
// binary fields are treated as streams
/** @var resource $string */
return fgets($string);
}
/**
* @param string $table
* @param string $append
*
* @return string
*/
public function build_fields_string($table, $append="")
{
$fields = $this->show_fields_from($table);
$comma = $fieldstring = '';
foreach($fields as $key => $field)
{
$fieldstring .= "{$comma}{$append}{$field['Field']}";
$comma = ',';
}
return $fieldstring;
}
/**
* @param string $table
* @param array $array
* @param bool $no_quote
*
* @return string
*/
protected function build_field_value_string($table, $array, $no_quote = false)
{
global $mybb;
$strings = array();
if ($no_quote == true)
{
$quote = "";
}
else
{
$quote = "'";
}
foreach($array as $field => $value)
{
if(!isset($mybb->binary_fields[$table][$field]) || !$mybb->binary_fields[$table][$field])
{
$value = $this->quote_val($value, $quote);
}
$strings[] = "{$field}={$value}";
}
$string = implode(', ', $strings);
return $string;
}
/**
* @param string $table
* @param array $array
*
* @return string
*/
protected function build_value_string($table, $array)
{
global $mybb;
$values = array();
foreach($array as $field => $value)
{
if(!isset($mybb->binary_fields[$table][$field]) || !$mybb->binary_fields[$table][$field])
{
$value = $this->quote_val($value);
}
$values[$field] = $value;
}
$string = implode(",", $values);
return $string;
}
public function __set($name, $value)
{
if ($name === 'type') {
// NOTE: This is to prevent the type being set - this type should appear as `pgsql` to ensure compatibility
return;
}
}
public function __get($name)
{
if ($name === 'type') {
// NOTE: this is to ensure compatibility checks on the DB type will work
return 'pgsql';
}
return null;
}
}