blob: 32c3522345eb7bb6b65b8564c44443808bab59fb [file] [log] [blame]
<?php
function ends_with($str, $key) {
return strrpos($str, $key) == strlen($str) - strlen($key);
}
function ctype_alnum_underscore($str) {
return ctype_alnum(str_replace('_', '', $str));
}
function &array_ensure_item_has_array(&$array, $key) {
if (!array_key_exists($key, $array))
$array[$key] = array();
return $array[$key];
}
function array_get($array, $key, $default = NULL) {
if (!array_key_exists($key, $array))
return $default;
return $array[$key];
}
function array_set_default(&$array, $key, $default) {
if (!array_key_exists($key, $array))
$array[$key] = $default;
}
$_config = NULL;
define('CONFIG_DIR', realpath(dirname(__FILE__) . '/../../'));
function config($key, $default = NULL) {
global $_config;
if (!$_config) {
$file_path = getenv('ORG_WEBKIT_PERF_CONFIG_PATH');
if (!$file_path)
$file_path = CONFIG_DIR . '/config.json';
$_config = json_decode(file_get_contents($file_path), true);
}
return array_get($_config, $key, $default);
}
function config_path($key, $path) {
return CONFIG_DIR . '/' . config($key) . '/' . $path;
}
function same_till_last_occurrence_of_string($main_string, $comparing_string, $compare_to_last_position_string) {
if (!$compare_to_last_position_string)
return !strcmp($main_string, $comparing_string);
$position_in_main_string = strrpos($main_string, $compare_to_last_position_string);
$position_in_comparing_string = strrpos($comparing_string, $compare_to_last_position_string);
if ($position_in_main_string !== $position_in_comparing_string)
return FALSE;
if ($position_in_main_string === FALSE)
return !strcmp($main_string, $comparing_string);
return !substr_compare($main_string, $comparing_string, 0, $position_in_main_string);
}
function generate_json_data_with_elapsed_time_if_needed($filename, $object, $elapsed_time) {
if (!assert(ctype_alnum(str_replace(array('-', '_', '.'), '', $filename))))
return NULL;
$target_file_path = config_path('dataDirectory', $filename);
$object['elapsedTime'] = $elapsed_time;
$content = json_encode($object);
if (file_exists($target_file_path) && same_till_last_occurrence_of_string(file_get_contents($target_file_path), $content, 'elapsedTime'))
return $content;
$temp_file_path = tempnam(config_path('dataDirectory', ''), $filename . '-temp-');
file_put_contents($temp_file_path, $content, LOCK_EX);
return rename($temp_file_path, $target_file_path) ? $content : NULL;
}
if (config('debug')) {
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'On');
} else
error_reporting(E_ERROR);
date_default_timezone_set('UTC');
class Database
{
private $connection = false;
function __destruct() {
if ($this->connection)
pg_close($this->connection);
$this->connection = false;
}
static function is_true($value) {
return $value == 't';
}
static function to_database_boolean($value) {
return $value ? 't' : 'f';
}
static function to_js_time($time_str) {
$timestamp_in_ms = strtotime($time_str) * 1000;
$dot_index = strrpos($time_str, '.');
if ($dot_index !== FALSE) {
// Keep 5 decimal digits as postgres timestamp may only have 5 decimal digits.
// Multiply by 1000 ahead to avoid losing precision. 1538041792.670479 will become 1538041792.6705 on php.
$timestamp_in_ms += round(floatval(substr($time_str, $dot_index)), 5) * 1000;
}
return intval($timestamp_in_ms);
}
static function escape_for_like($string) {
return str_replace(array('\\', '_', '%'), array('\\\\', '\\_', '\\%'), $string);
}
function connect() {
$databaseConfig = config('database');
$host = $databaseConfig['host'];
$port = $databaseConfig['port'];
$dbname = $databaseConfig['name'];
$user = $databaseConfig['username'];
$password = $databaseConfig['password'];
$connectionString = "host=$host port=$port dbname=$dbname user=$user password=$password";
$sslConfigString = '';
if (array_get($databaseConfig, 'ssl')) {
$sslConfig = $databaseConfig['ssl'];
$sslConfigString .= ' sslmode=' . array_get($sslConfig, 'mode', 'require');
foreach (array('rootcert', 'cert', 'key') as $key) {
if (!array_get($sslConfig, $key))
continue;
$path = $sslConfig[$key];
if (strlen($path) && $path[0] !== '/')
$path = CONFIG_DIR . '/' . $path;
$sslConfigString .= " ssl$key=$path";
}
}
$connectionString .= $sslConfigString;
$this->connection = @pg_connect($connectionString);
return $this->connection ? true : false;
}
private function prefixed_column_names($columns, $prefix = NULL) {
if (!$prefix || !$columns)
return join(', ', $columns);
return $prefix . '_' . join(', ' . $prefix . '_', $columns);
}
private function prefixed_name($column, $prefix = NULL) {
return $prefix ? $prefix . '_' . $column : $column;
}
private function prepare_params($params, &$placeholders, &$values, &$null_columns = NULL) {
$column_names = array();
$i = count($values) + 1;
foreach (array_keys($params) as $name) {
$current_value = $params[$name];
if ($current_value === NULL && $null_columns !== NULL) {
array_push($null_columns, $name);
continue;
}
assert(ctype_alnum_underscore($name));
array_push($column_names, $name);
array_push($placeholders, '$' . $i);
if (is_bool($current_value))
$current_value = $this->to_database_boolean($current_value);
array_push($values, $current_value);
$i++;
}
return $column_names;
}
private function select_conditions_with_null_columns($prefix, $column_names, $placeholders, $null_columns) {
$column_names = $this->prefixed_column_names($column_names, $prefix);
$placeholders = join(', ', $placeholders);
if (!$column_names && !$placeholders)
$column_names = $placeholders = '1';
$query = "($column_names) = ($placeholders)";
foreach ($null_columns as $column_name)
$query .= ' AND ' . $this->prefixed_name($column_name, $prefix) . ' IS NULL';
return $query;
}
function insert_row($table, $prefix, $params, $returning = 'id') {
$placeholders = array();
$values = array();
$column_names = $this->prepare_params($params, $placeholders, $values);
assert(!$prefix || ctype_alnum_underscore($prefix));
$column_names = $this->prefixed_column_names($column_names, $prefix);
$placeholders = join(', ', $placeholders);
$value_query = $column_names ? "($column_names) VALUES ($placeholders)" : ' VALUES (default)';
if ($returning) {
$returning_column_name = $this->prefixed_name($returning, $prefix);
$rows = $this->query_and_fetch_all("INSERT INTO $table $value_query RETURNING $returning_column_name", $values);
return $rows ? $rows[0][$returning_column_name] : NULL;
}
return $this->query_and_get_affected_rows("INSERT INTO $table $value_query", $values) == 1;
}
function select_or_insert_row($table, $prefix, $select_params, $insert_params = NULL, $returning = 'id') {
return $this->_select_update_or_insert_row($table, $prefix, $select_params, $insert_params, $returning, FALSE, TRUE);
}
function update_or_insert_row($table, $prefix, $select_params, $insert_params = NULL, $returning = 'id') {
return $this->_select_update_or_insert_row($table, $prefix, $select_params, $insert_params, $returning, TRUE, TRUE);
}
function update_row($table, $prefix, $select_params, $update_params, $returning = 'id') {
return $this->_select_update_or_insert_row($table, $prefix, $select_params, $update_params, $returning, TRUE, FALSE);
}
private function _select_update_or_insert_row($table, $prefix, $select_params, $insert_params, $returning, $should_update, $should_insert) {
$values = array();
$select_placeholders = array();
$select_null_columns = array();
$select_column_names = $this->prepare_params($select_params, $select_placeholders, $values, $select_null_columns);
$select_values = array_slice($values, 0);
if ($insert_params === NULL)
$insert_params = $select_params;
$insert_placeholder_list = array();
$insert_column_name_list = $this->prepare_params($insert_params, $insert_placeholder_list, $values);
assert(!!$returning);
assert(!$prefix || ctype_alnum_underscore($prefix));
$returning_column_name = $returning == '*' ? '*' : $this->prefixed_name($returning, $prefix);
$condition = $this->select_conditions_with_null_columns($prefix, $select_column_names, $select_placeholders, $select_null_columns);
$query = "SELECT $returning_column_name FROM $table WHERE $condition";
$insert_column_names = $this->prefixed_column_names($insert_column_name_list, $prefix);
$insert_placeholders = join(', ', $insert_placeholder_list);
// http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-in-postgresql
$rows = NULL;
if ($should_update) {
// FIXME: Remove this special case and use ROW expression when no instance uses Postgres < 10.
if (count($insert_placeholder_list) == 1 && count($insert_column_name_list) == 1)
$rows = $this->query_and_fetch_all("UPDATE $table SET $insert_column_names = $insert_placeholders
WHERE $condition RETURNING $returning_column_name", $values);
else
$rows = $this->query_and_fetch_all("UPDATE $table SET ($insert_column_names) = ($insert_placeholders)
WHERE $condition RETURNING $returning_column_name", $values);
}
if (!$rows && $should_insert) {
$rows = $this->query_and_fetch_all("INSERT INTO $table ($insert_column_names) SELECT $insert_placeholders
WHERE NOT EXISTS ($query) RETURNING $returning_column_name", $values);
}
if (!$should_update && !$rows)
$rows = $this->query_and_fetch_all($query, $select_values);
return $rows ? ($returning == '*' ? $rows[0] : $rows[0][$returning_column_name]) : NULL;
}
function select_first_row($table, $prefix, $params, $order_by = NULL) {
return $this->select_first_or_last_row($table, $prefix, $params, $order_by, FALSE);
}
function select_last_row($table, $prefix, $params, $order_by = NULL) {
return $this->select_first_or_last_row($table, $prefix, $params, $order_by, TRUE);
}
private function select_first_or_last_row($table, $prefix, $params, $order_by, $descending_order) {
$rows = $this->select_rows($table, $prefix, $params, $order_by, $descending_order, 0, 1);
return $rows ? $rows[0] : NULL;
}
function select_rows($table, $prefix, $params,
$order_by = NULL, $descending_order = FALSE, $offset = NULL, $limit = NULL) {
$placeholders = array();
$values = array();
$null_columns = array();
$column_names = $this->prepare_params($params, $placeholders, $values, $null_columns);
$condition = $this->select_conditions_with_null_columns($prefix, $column_names, $placeholders, $null_columns);
$query = "SELECT * FROM $table WHERE $condition";
if ($order_by) {
if (!is_array($order_by))
$order_by = array($order_by);
$order_columns = array();
foreach ($order_by as $order_key) {
assert(ctype_alnum_underscore($order_key));
$order_column = $this->prefixed_name($order_key, $prefix) . ' ' . ($descending_order? 'DESC' : 'ASC');
array_push($order_columns, $order_column);
}
$query .= ' ORDER BY ' . join(', ', $order_columns);
}
if ($offset !== NULL)
$query .= ' OFFSET ' . intval($offset);
if ($limit !== NULL)
$query .= ' LIMIT ' . intval($limit);
return $this->query_and_fetch_all($query, $values);
}
function query_and_get_affected_rows($query, $params = array()) {
if (!$this->connection)
return FALSE;
$result = pg_query_params($this->connection, $query, $params);
if (!$result)
return FALSE;
return pg_affected_rows($result);
}
function query_and_fetch_all($query, $params = array()) {
if (!$this->connection)
return NULL;
$result = pg_query_params($this->connection, $query, $params);
if (!$result)
return NULL;
if (pg_num_rows($result) == 0)
return array();
return pg_fetch_all($result);
}
function query($query, $params = array()) {
if (!$this->connection)
return FALSE;
return pg_query_params($this->connection, $query, $params);
}
function fetch_next_row($result) {
return pg_fetch_assoc($result);
}
function fetch_table($table_name, $column_to_be_ordered_by = null) {
if (!$this->connection || !ctype_alnum_underscore($table_name) || ($column_to_be_ordered_by && !ctype_alnum_underscore($column_to_be_ordered_by)))
return false;
$clauses = '';
if ($column_to_be_ordered_by)
$clauses .= 'ORDER BY ' . $column_to_be_ordered_by;
return $this->query_and_fetch_all("SELECT * FROM $table_name $clauses");
}
function begin_transaction() {
return $this->connection and pg_query($this->connection, "BEGIN");
}
function commit_transaction() {
return $this->connection and pg_query($this->connection, 'COMMIT');
}
function rollback_transaction() {
return $this->connection and pg_query($this->connection, 'ROLLBACK');
}
}
?>