2ceb6dea8126f737bd90a18fb3546fdc5bae8ebd
[WebKit.git] / Websites / perf.webkit.org / public / include / db.php
1 <?php
2
3 function ends_with($str, $key) {
4     return strrpos($str, $key) == strlen($str) - strlen($key);
5 }
6
7 function ctype_alnum_underscore($str) {
8     return ctype_alnum(str_replace('_', '', $str));
9 }
10
11 function &array_ensure_item_has_array(&$array, $key) {
12     if (!array_key_exists($key, $array))
13         $array[$key] = array();
14     return $array[$key];
15 }
16
17 function array_get($array, $key, $default = NULL) {
18     if (!array_key_exists($key, $array))
19         return $default;
20     return $array[$key];
21 }
22
23 function array_set_default(&$array, $key, $default) {
24     if (!array_key_exists($key, $array))
25         $array[$key] = $default;
26 }
27
28 $_config = NULL;
29
30 define('CONFIG_DIR', realpath(dirname(__FILE__) . '/../../'));
31
32 function config($key, $default = NULL) {
33     global $_config;
34     if (!$_config) {
35         $file_path = getenv('ORG_WEBKIT_PERF_CONFIG_PATH');
36         if (!$file_path)
37             $file_path = CONFIG_DIR . '/config.json';
38         $_config = json_decode(file_get_contents($file_path), true);
39     }
40     return array_get($_config, $key, $default);
41 }
42
43 function config_path($key, $path) {
44     return CONFIG_DIR . '/' . config($key) . '/' . $path;
45 }
46
47 function generate_data_file($filename, $content) {
48     if (!assert(ctype_alnum(str_replace(array('-', '_', '.'), '', $filename))))
49         return FALSE;
50     return file_put_contents(config_path('dataDirectory', $filename), $content, LOCK_EX);
51 }
52
53 if (config('debug')) {
54     error_reporting(E_ALL | E_STRICT);
55     ini_set('display_errors', 'On');
56 } else
57     error_reporting(E_ERROR);
58
59 date_default_timezone_set('UTC');
60
61 class Database
62 {
63     private $connection = false;
64
65     function __destruct() {
66         if ($this->connection)
67             pg_close($this->connection);
68         $this->connection = false;
69     }
70
71     static function is_true($value) {
72         return $value == 't';
73     }
74
75     static function to_database_boolean($value) {
76         return $value ? 't' : 'f';
77     }
78
79     static function to_js_time($time_str) {
80         $timestamp_in_s = strtotime($time_str);
81         $dot_index = strrpos($time_str, '.');
82         if ($dot_index !== FALSE)
83             $timestamp_in_s += floatval(substr($time_str, $dot_index));
84         return intval($timestamp_in_s * 1000);
85     }
86
87     static function escape_for_like($string) {
88         return str_replace(array('\\', '_', '%'), array('\\\\', '\\_', '\\%'), $string);
89     }
90
91     function connect() {
92         $databaseConfig = config('database');
93         $this->connection = @pg_connect('host=' . $databaseConfig['host'] . ' port=' . $databaseConfig['port']
94             . ' dbname=' . $databaseConfig['name'] . ' user=' . $databaseConfig['username'] . ' password=' . $databaseConfig['password']);
95         return $this->connection ? true : false;
96     }
97
98     private function prefixed_column_names($columns, $prefix = NULL) {
99         if (!$prefix || !$columns)
100             return join(', ', $columns);
101         return $prefix . '_' . join(', ' . $prefix . '_', $columns);
102     }
103
104     private function prefixed_name($column, $prefix = NULL) {
105         return $prefix ? $prefix . '_' . $column : $column;
106     }
107
108     private function prepare_params($params, &$placeholders, &$values, $null_columns = NULL) {
109         $column_names = array();
110
111         $i = count($values) + 1;
112         foreach (array_keys($params) as $name) {
113             $current_value = $params[$name];
114             if ($current_value === NULL && $null_columns !== NULL) {
115                 array_push($null_columns, $name);
116                 continue;
117             }
118             assert(ctype_alnum_underscore($name));
119             array_push($column_names, $name);
120             array_push($placeholders, '$' . $i);
121             array_push($values, $current_value);
122             $i++;
123         }
124
125         return $column_names;
126     }
127
128     private function select_conditions_with_null_columns($prefix, $column_names, $placeholders, $null_columns) {
129         $column_names = $this->prefixed_column_names($column_names, $prefix);
130         $placeholders = join(', ', $placeholders);
131
132         if (!$column_names && !$placeholders)
133             $column_names = $placeholders = '1';
134         $query = "($column_names) = ($placeholders)";
135         foreach ($null_columns as $column_name)
136             $query .= ' AND ' . $this->prefixed_name($column_name, $prefix) . ' IS NULL';
137         return $query;
138     }
139
140     function insert_row($table, $prefix, $params, $returning = 'id') {
141         $placeholders = array();
142         $values = array();
143         $column_names = $this->prepare_params($params, $placeholders, $values);
144
145         assert(!$prefix || ctype_alnum_underscore($prefix));
146         $column_names = $this->prefixed_column_names($column_names, $prefix);
147         $placeholders = join(', ', $placeholders);
148
149         $value_query = $column_names ? "($column_names) VALUES ($placeholders)" : ' VALUES (default)';
150         if ($returning) {
151             $returning_column_name = $this->prefixed_name($returning, $prefix);
152             $rows = $this->query_and_fetch_all("INSERT INTO $table $value_query RETURNING $returning_column_name", $values);
153             return $rows ? $rows[0][$returning_column_name] : NULL;
154         }
155
156         return $this->query_and_get_affected_rows("INSERT INTO $table $value_query", $values) == 1;
157     }
158
159     function select_or_insert_row($table, $prefix, $select_params, $insert_params = NULL, $returning = 'id') {
160         return $this->_select_update_or_insert_row($table, $prefix, $select_params, $insert_params, $returning, FALSE, TRUE);
161     }
162
163     function update_or_insert_row($table, $prefix, $select_params, $insert_params = NULL, $returning = 'id') {
164         return $this->_select_update_or_insert_row($table, $prefix, $select_params, $insert_params, $returning, TRUE, TRUE);
165     }
166
167     function update_row($table, $prefix, $select_params, $update_params, $returning = 'id') {
168         return $this->_select_update_or_insert_row($table, $prefix, $select_params, $update_params, $returning, TRUE, FALSE);
169     }
170
171     private function _select_update_or_insert_row($table, $prefix, $select_params, $insert_params, $returning, $should_update, $should_insert) {
172         $values = array();
173
174         $select_placeholders = array();
175         $select_null_columns = array();
176         $select_column_names = $this->prepare_params($select_params, $select_placeholders, $values, $select_null_columns);
177         $select_values = array_slice($values, 0);
178
179         if ($insert_params === NULL)
180             $insert_params = $select_params;
181         $insert_placeholders = array();
182         $insert_column_names = $this->prepare_params($insert_params, $insert_placeholders, $values);
183
184         assert(!!$returning);
185         assert(!$prefix || ctype_alnum_underscore($prefix));
186         $returning_column_name = $returning == '*' ? '*' : $this->prefixed_name($returning, $prefix);
187
188         $condition = $this->select_conditions_with_null_columns($prefix, $select_column_names, $select_placeholders, $select_null_columns);
189         $query = "SELECT $returning_column_name FROM $table WHERE $condition";
190
191         $insert_column_names = $this->prefixed_column_names($insert_column_names, $prefix);
192         $insert_placeholders = join(', ', $insert_placeholders);
193
194         // http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-in-postgresql
195         $rows = NULL;
196         if ($should_update) {
197             $rows = $this->query_and_fetch_all("UPDATE $table SET ($insert_column_names) = ($insert_placeholders)
198                 WHERE $condition RETURNING $returning_column_name", $values);
199         }
200         if (!$rows && $should_insert) {
201             $rows = $this->query_and_fetch_all("INSERT INTO $table ($insert_column_names) SELECT $insert_placeholders
202                 WHERE NOT EXISTS ($query) RETURNING $returning_column_name", $values);
203         }
204         if (!$should_update && !$rows)
205             $rows = $this->query_and_fetch_all($query, $select_values);
206
207         return $rows ? ($returning == '*' ? $rows[0] : $rows[0][$returning_column_name]) : NULL;
208     }
209
210     // FIXME: Should improve _select_update_or_insert_row to handle the NULL column case.
211     function select_or_insert_repository_row($repository_name, $repository_owner_id)
212     {
213         $result = NULL;
214         if ($repository_owner_id == NULL) {
215             $result = $this->query_and_fetch_all('INSERT INTO repositories (repository_name) SELECT $1
216                 WHERE NOT EXISTS (SELECT repository_id FROM repositories WHERE repository_name = $2 AND repository_owner IS NULL) RETURNING repository_id',
217                 array($repository_name, $repository_name));
218             if (!$result)
219                 $result = $this->query_and_fetch_all('SELECT repository_id FROM repositories WHERE repository_name = $1 AND repository_owner IS NULL', array($repository_name));
220         } else {
221             $result = $this->query_and_fetch_all('INSERT INTO repositories (repository_name, repository_owner) SELECT $1, $2
222                 WHERE NOT EXISTS (SELECT repository_id FROM repositories WHERE (repository_name, repository_owner) = ($3, $4)) RETURNING repository_id',
223                 array($repository_name, $repository_owner_id, $repository_name, $repository_owner_id));
224             if (!$result)
225                 $result = $this->query_and_fetch_all('SELECT repository_id FROM repositories WHERE (repository_name, repository_owner) = ($1, $2)', array($repository_name, $repository_owner_id));
226         }
227         return $result ? $result[0]['repository_id'] : NULL;
228     }
229
230     function select_first_row($table, $prefix, $params, $order_by = NULL) {
231         return $this->select_first_or_last_row($table, $prefix, $params, $order_by, FALSE);
232     }
233
234     function select_last_row($table, $prefix, $params, $order_by = NULL) {
235         return $this->select_first_or_last_row($table, $prefix, $params, $order_by, TRUE);
236     }
237
238     private function select_first_or_last_row($table, $prefix, $params, $order_by, $descending_order) {
239         $rows = $this->select_rows($table, $prefix, $params, $order_by, $descending_order, 0, 1);
240         return $rows ? $rows[0] : NULL;
241     }
242
243     function select_rows($table, $prefix, $params,
244         $order_by = NULL, $descending_order = FALSE, $offset = NULL, $limit = NULL) {
245
246         $placeholders = array();
247         $values = array();
248         $null_columns = array();
249         $column_names = $this->prepare_params($params, $placeholders, $values, $null_columns);
250         $condition = $this->select_conditions_with_null_columns($prefix, $column_names, $placeholders, $null_columns);
251
252         $query = "SELECT * FROM $table WHERE $condition";
253
254         if ($order_by) {
255             if (!is_array($order_by))
256                 $order_by = array($order_by);
257
258             $order_columns = array();
259             foreach ($order_by as $order_key) {
260                 assert(ctype_alnum_underscore($order_key));
261                 $order_column = $this->prefixed_name($order_key, $prefix) . ' ' . ($descending_order? 'DESC' : 'ASC');
262                 array_push($order_columns, $order_column);
263             }
264             $query .= ' ORDER BY ' . join(', ', $order_columns);
265         }
266         if ($offset !== NULL)
267             $query .= ' OFFSET ' . intval($offset);
268         if ($limit !== NULL)
269             $query .= ' LIMIT ' . intval($limit);
270
271         return $this->query_and_fetch_all($query, $values);
272     }
273
274     function query_and_get_affected_rows($query, $params = array()) {
275         if (!$this->connection)
276             return FALSE;
277         $result = pg_query_params($this->connection, $query, $params);
278         if (!$result)
279             return FALSE;
280         return pg_affected_rows($result);
281     }
282
283     function query_and_fetch_all($query, $params = array()) {
284         if (!$this->connection)
285             return NULL;
286         $result = pg_query_params($this->connection, $query, $params);
287         if (!$result)
288             return NULL;
289         if (pg_num_rows($result) == 0)
290             return array();
291         return pg_fetch_all($result);
292     }
293
294     function query($query, $params = array()) {
295         if (!$this->connection)
296             return FALSE;
297         return pg_query_params($this->connection, $query, $params);
298     }
299
300     function fetch_next_row($result) {
301         return pg_fetch_assoc($result);
302     }
303
304     function fetch_table($table_name, $column_to_be_ordered_by = null) {
305         if (!$this->connection || !ctype_alnum_underscore($table_name) || ($column_to_be_ordered_by && !ctype_alnum_underscore($column_to_be_ordered_by)))
306             return false;
307         $clauses = '';
308         if ($column_to_be_ordered_by)
309             $clauses .= 'ORDER BY ' . $column_to_be_ordered_by;
310         return $this->query_and_fetch_all("SELECT * FROM $table_name $clauses");
311     }
312
313     function begin_transaction() {
314         return $this->connection and pg_query($this->connection, "BEGIN");
315     }
316
317     function commit_transaction() {
318         return $this->connection and pg_query($this->connection, 'COMMIT');
319     }
320
321     function rollback_transaction() {
322         return $this->connection and pg_query($this->connection, 'ROLLBACK');
323     }
324
325 }
326
327 ?>