cleanup
[xendri.git] / lib / Rql.php
blobda69c6fa3a91be8f7ee36d47fe4926c843cdafde
1 <?php
3 /**
4 * Класс преобразует RQL-запросы в язык SQL
5 * @author chedim
6 * @category DAL
7 * @package XDAL
8 * @version 1
12 if (!function_exists('__autoload')) {
13 include dirname(__FILE__).'/cercea/XMLConvertable.php';
14 include dirname(__FILE__).'/cercea/JSONConvertable.php';
15 include dirname(__FILE__).'/cercea/Set.php';
16 include dirname(__FILE__).'/csda/RecordType.php';
17 include dirname(__FILE__).'/csda/Record.php';
18 include dirname(__FILE__).'/csda/RecordSet.php';
19 include dirname(__FILE__).'/csda/MixedRecord.php';
20 include dirname(__FILE__).'/RQLAutomato.php';
21 include dirname(__FILE__).'/RQLAutomatoState.php';
22 include dirname(__FILE__).'/RQLDescription.php';
23 include dirname(__FILE__).'/RQLNode.php';
24 include dirname(__FILE__).'/RQLFork.php';
25 include dirname(__FILE__).'/RQLPath.php';
28 class Rql {
30 protected $source;
31 protected $sql;
32 protected $checksum;
33 protected static $cache = false;
34 protected static $stype = 'cercea';
35 protected static $sid = 1;
36 protected static $slock = false;
38 const ACL_SELECT = 1;
39 const ACL_UPDATE = 2;
40 const ACL_DELETE = 4;
41 const ACL_CHILD_APPEND = 16;
42 const ACL_CHILD_OPERATION = 32;
43 const ACL_RELATION_OPERATION = 64;
45 /**
47 * @var mysqli
49 protected static $connection;
50 static function destroy_nodes($type, $ids) {
51 $ids = array_unique($ids);
52 $query = "DELETE FROM {$type} WHERE `id` IN (".implode(',',$ids).")";
53 self::query($query);
54 $query = "DELETE FROM `relations` WHERE child = '{$type}' and `child_id` IN (".implode(',',$ids).")";
55 self::query($query);
58 static protected function query($q) {
59 if (!self::$connection->query($q)) {
60 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$q<br/><br/>\r\n\r\n".self::$connection->error);
65 static function exec_delete($query, $order = null, $limit = null) {
66 if (!$query) return;
67 if ($order !== null) {
68 $query .= ' ORDER BY '.$order;
70 if ($limit !== null) {
71 $query .= ' LIMIt '.$limit;
73 $delete = array();
74 if ($res = self::$connection->query($query)) {
75 while($row = $res->fetch_array(MYSQLI_NUM)) {
76 for($i=0;$i<count($row);$i=$i+2) {
77 $delete[$row[$i]][] = $row[$i+1];
80 foreach($delete as $table => $ids) {
81 self::drop_nodes($table, $ids);
83 } else {
84 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
88 static function exec_select($query, $types, $order = null, $limit = null) {
89 if ($order !== null) {
90 $query .= ' ORDER BY '.$order;
92 if ($limit !== null) {
93 $query .= ' LIMIT '.$limit;
95 $res = self::$connection->query($query);
96 if (!$res) return false;
97 try {
98 $ret = new RecordSet($res, $types, $res->field_count);
99 } catch (exception $e) {
100 if ($e->getCode() == 1011) return false;
101 else throw $e;
103 return $ret;
108 * @param <type> $query
109 * @param <type> $order
110 * @param none $limit ignored
111 * @return <type>
113 static function exec_update($query, $order = null, $limit = null) {
114 if (!$query) return;
115 if ($order !== null) {
116 $query .= ' ORDER BY '.$order;
118 if (!self::$connection->query($query)) {
119 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
124 function __construct($host = null, $user = null, $password = null, $database = null, $names = null) {
126 // initializing connection parameters
127 if (class_exists('X') && ($host === null || $names === null)) {
128 // Config is a Xendri Framework module from a base package
129 if ($host === null) {
130 $host = X::cfg('sql:host');
131 $user = X::cfg('sql:user');
132 $password = X::cfg('sql:password');
133 $database = X::cfg('sql:database');
136 if ($names === null) {
137 $names = X::cfg('sql:names');
142 self::$connection = new mysqli($host, $user, $password, $database);
143 if (mysqli_connect_errno()) {
144 throw new Exception('Connection to database failed!');
148 public function get_sql() {
149 return $this->mk_sql($this->sql);
152 protected function translate($rql) {
153 $automato = new RqlAutomato($rql);
154 $automato->start();
155 $res = $automato->result();
156 return $arr['sql'];
159 public static function from($rql) {
160 if (self::$connection == null) throw new Exception('You MUST create at least one object of class RQL!');
161 $chk7 = $this->checksum = md5($rql);
162 if (!self::$cache) self::$cache = array();
164 if (!key_exists($chk, self::$cache)) {
165 $automato = new RqlAutomato($rql);
166 $automato->start();
167 $res = $automato->result();
168 $this->sql = $res;
169 self::$cache[$chk] = $this->sql;
172 return $arr['sql'];
177 * @param string $rql RQL-query to database
178 * @param string $order affected rows sorting options
179 * @param string $limit affected rows limit options
180 * @return RecordSet
182 public static function exec($rql, $order = null, $limit = null) {
183 if (self::$connection == null) throw new Exception('You MUST create at least one object of class RQL!');
185 if (strpos(trim($rql), 'self') != 0) {
186 $rql = 'self / '.$rql;
189 $rql = str_replace('self', (self::$stype).'[id='.self::$sid.']', $rql);
191 $automato = new RqlAutomato($rql);
192 $automato->start();
193 $res = $automato->result();
194 var_dump($res);
195 echo "<hr/>\r\n";
196 echo "inserts: ".print_r($res->get_insert_queries(), 1)."\r\n";
198 $insertion_result = self::exec_inserts($res->get_insert_queries());
200 for($i=0;$i<count($insertion_result); $i++) {
201 $res->update_description_by_real_name($insertion_result[$i]['name'], 'id', '=', $insertion_result[$i]['id']);
202 // $res->drop_insertion_by_real_name($insertion_result[$i]['name']);
205 echo "update: ".$res->get_update_query()."\r\n";
206 self::exec_update($res->get_update_query(), $order, $limit);
208 echo "select: ".$res->get_select_query()."\r\n";
209 $sel = self::exec_select($res->get_select_query(), $res->get_selected_real_names(), $order, $limit);
210 self::exec_delete($res->get_delete_query(), $order, $limit);
212 return $sel;
215 protected static function exec_inserts($inserts) {
216 $holder = '##**RQL_NEW_NODE_ID_PLACEHOLDER**##';
217 $return = array();
218 for($i=0;$i<count($inserts);$i++) {
219 $insert = $inserts[$i];
220 $query = $insert['check'];
221 $names = $insert['insert']['parent_type'];
222 if (!is_array($names)) {
223 $names = array($names);
224 } else {
225 $names = array_unique($names);
227 $ids = array();
228 $values = array();
229 if ($res = self::$connection->query($query)) {
230 while(true == ($row = $res->fetch_array())) {
231 $ids[$row[0]][] = $row[1];
232 $values[] = "( 255, '{$row[0]}', '{$row[1]}', '{$insert['insert']}', '{$holder}')";
234 } else {
235 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
237 if (count($values) == 0) throw new Exception('There was no parents available for new node `'.$insert['insert'].'` (Yoda says: privileges insufficient maybe?)!');
238 $query = "INSERT INTO `{$insert['insert']}` (`id`) VALUES ('')";
239 if($res = self::$connection->query($query)) {
240 $relations = "INSERT INTO `relations` (`acl`, `parent`, `parent_id`, `child`, `child_id`) VALUES ".implode(',', $values);
241 $relations = str_replace($holder, self::$connection->insert_id, $relations);
242 $return[] = array(
243 'name' => $insert['insert'],
244 'id' => self::$connection->insert_id
246 if (!self::$connection->query($relations)) {
247 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
249 } else {
250 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
254 return $return;
258 protected function drop_nodes($type, $ids) {
259 $ids = array_unique($ids);
260 $query = "UPDATE `relations` SET `parent` = '---deleted---', `parent_id` = -1 WHERE (`child` = '{$type}' AND `child_id` IN (".implode(',',$ids).")) OR (`parent` = '{$type}' AND `parent_id` IN (".implode(',',$ids)."))";
261 if (!self::$connection->query($query)) {
262 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
264 self::clear_database();
268 * clears deleted nodes from database
270 protected static function clear_database() {
271 $query = 'select distinct deleted.child, deleted.child_id '.
272 'from relations as deleted '.
273 'left join relations as remains ON '.
274 'remains.child_id = deleted.child_id AND '.
275 'remains.child = deleted.child AND '.
276 'remains.parent_id != -1 '.
277 'where deleted.parent_id = -1 and coalesce(remains.parent_id, 0) = 0 '.
278 'group by deleted.child, deleted.child_id';
279 $result = self::$connection->query($query);
280 if (!$result) {
281 throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
284 $drops = array();
285 while($row = $result->fetch_array(MYSQLI_NUM)) {
286 $drops[$row[0]][] = $row[1];
289 var_dump($drops);
291 foreach ($drops as $type=>$ids) {
292 self::destroy_nodes($type, $ids);
295 if(count($drops) > 0) self::clear_database();
298 static function get_connection() {
299 return self::$connection;
302 static function set_self(Record $rec, $lock = true) {
303 if (self::$slock) return false;
304 self::$slock = $lock;
305 self::$stype = $rec->get_type();
306 self::$sid = $rec->id();
307 return true;