00001 <?php
00002
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 if (!function_exists('xml_parser_create'))
00031 trigger_error('xml_* functions not defined.', E_USER_ERROR);
00032 require_once(dirname(__FILE__) . '/KjwFakeSql.php');
00033 require_once(dirname(__FILE__) . '/KjwArrayResultSet.php');
00034
00042 class KjwXmlSql extends KjwFakeSql {
00043 var $_dbpath = null;
00044 var $_last_insert_id = null;
00045
00055 function KjwXmlSql($server, $unused_port, $unused_user, $unused_pass, $path) {
00056 assert('$server == "localhost" && $unused_port === 0 && $unused_user == "" && $unused_pass == ""');
00057 if (substr($path, -1) != '/')
00058 $path .= '/';
00059 parent::KjwFakeSql('xmlsql', $server, $unused_port, $unused_user, $unused_pass, $path . 'lock');
00060 $this->_dbpath = $path;
00061 }
00062
00063
00064
00065
00066
00067
00068
00069 function insertId() {
00070 return $this->_last_insert_id;
00071 }
00072
00073 function _simpleSelect($table, $columns, $order, $skip = 0, $max = -1) {
00074 $obj = new KjwXmlSqlTable($table, $this->_dbpath);
00075 $obj->sort($order);
00076 $resultSet = $obj->getResultSet($skip, $max);
00077 $obj->destroy();
00078 return $resultSet;
00079 }
00080
00081 function _simpleInsert($table, $args) {
00082 $this->_last_insert_id = 0;
00083 $obj = new KjwXmlSqlTable($table, $this->_dbpath);
00084
00085
00086 if (array_key_exists(0, $args)) {
00087
00088 if (sizeof($args) != sizeof($obj->_description))
00089 $this->croak('Database Data Transfer Failure', 'Value count mismatches column count');
00090 $values = $args;
00091
00092 } else {
00093 $values = array();
00094 foreach ($args as $key => $value) {
00095 for ($i = 0; $i < sizeof($obj->_description); ++$i) {
00096 if ($obj->_description[$i]['name'] == $key) {
00097 $values[$i] = $value;
00098 break;
00099 }
00100 }
00101 }
00102 if (sizeof($values) != sizeof($args))
00103 $this->croak('Database Data Transfer Failure', 'Trying to write to non-existent columns?');
00104 for ($i = 0; $i < sizeof($obj->_description); ++$i) {
00105 if (!isset($values[$i]))
00106 $values[$i] = null;
00107 if ($obj->_description[$i]['type'] == 'serial' && $values[$i] === null)
00108 $this->_last_insert_id = $values[$i] = $obj->_description[$i]['next']++;
00109 }
00110 ksort($values);
00111 }
00112
00113
00114 for ($i = 0; $i < sizeof($obj->_description); ++$i) {
00115 if ($obj->_description[$i]['type'] == 'serial' && $values[$i] === null)
00116 $this->_last_insert_id = $values[$i] = $obj->_description[$i]['next']++;
00117 }
00118
00119
00120 array_push($obj->_data, $values);
00121 $obj->store($table);
00122 $obj->destroy();
00123 }
00124
00125 }
00126
00127 class KjwXmlSqlTable extends KjwObject {
00128 function KjwXmlSqlTable($tableName, $dbPath) {
00129 parent::KjwObject();
00130 $this->_tableName = $tableName;
00131 $this->_tableFile = $dbPath . $tableName . '.xml';
00132 $this->_state = 'root';
00133
00134 $parser = xml_parser_create();
00135 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
00136 xml_set_object($parser, &$this);
00137 xml_set_element_handler($parser, 'onBeginElement', 'onEndElement');
00138 xml_set_character_data_handler($parser, 'onData');
00139
00140 $fp = fopen($this->_tableFile, "rb");
00141 while (($data = fread($fp, 8192)) !== '') {
00142 if (!xml_parse($parser, $data, feof($fp)))
00143 $this->croak('Database Data Transfer Failure', 'Xml parsing failed!');
00144 }
00145 fclose($fp);
00146 xml_parser_free($parser);
00147 }
00148
00149 function sort($columnOrder) {
00150 $this->_setColumnOrder($columnOrder);
00151 $ret = usort($this->_data, array($this, 'cmp'));
00152 assert('$ret == true');
00153 unset($this->_columnOrder);
00154 }
00155
00156 function onBeginElement($parser, $name, $attributes) {
00157 if ($this->_state == 'root' && $name == 'table') {
00158 if ($attributes['name'] != $this->_tableName)
00159 $this->croak('Database Data Transfer Failure', 'Table name missing or invalid');
00160 $this->_state = 'table';
00161 } elseif ($this->_state == 'table' && $name == 'description') {
00162 if (isset($this->_description))
00163 $this->croak('Database Data Transfer Failure', 'Got description already');
00164 $this->_state = 'description';
00165 $this->_description = array();
00166 } elseif ($this->_state == 'description' && $name == 'column') {
00167 if (!isset($attributes['type']) || !isset($attributes['name']))
00168 $this->croak('Database Data Transfer Failure', 'Description lacks type or name');
00169 array_push($this->_description, $attributes);
00170 } elseif ($this->_state == 'table' && $name == 'data') {
00171 if (!isset($this->_description))
00172 $this->croak('Database Data Transfer Failure', 'Data found before description.');
00173 $this->_state = 'data';
00174 $this->_data = array();
00175 } elseif ($this->_state == 'data' && $name == 'row') {
00176 $this->_state = 'data_row';
00177 $this->_tmp_row = array();
00178 } elseif ($this->_state == 'data_row' && $name == 'col') {
00179 if (@$attributes['null']) {
00180 $this->_state = 'data_column_null';
00181 array_push($this->_tmp_row, null);
00182 } else {
00183 $this->_state = 'data_column';
00184 array_push($this->_tmp_row, '');
00185 }
00186 } else {
00187 $this->croak('Database Data Transfer Failure', 'Unexpected element: ' . $this->_state . '/' . $name);
00188 }
00189 }
00190
00191 function onEndElement($parser, $name) {
00192 if ($this->_state == 'table' && $name == 'table') {
00193 $this->_state = 'root';
00194 } elseif ($this->_state == 'description' && $name == 'description') {
00195 $this->_state = 'table';
00196 } elseif ($this->_state == 'description' && $name == 'column') {
00197 ;
00198 } elseif ($this->_state == 'data' && $name == 'data') {
00199 $this->_state = 'table';
00200 } elseif (($this->_state == 'data_column' || $this->_state == 'data_column_null')
00201 && $name == 'col') {
00202 $this->_state = 'data_row';
00203 } elseif ($this->_state == 'data_row' && $name == 'row') {
00204 $this->_state = 'data';
00205 array_push($this->_data, array_map(array($this, 'cast'), $this->_tmp_row, array_keys($this->_tmp_row)));
00206 unset($this->_tmp_row);
00207 } else {
00208 $this->croak('Database Data Transfer Failure', 'Unexpected element: ' . $this->_state . '/' . $name);
00209 }
00210 }
00211
00212 function onData($parser, $data) {
00213 if ($this->_state == 'data_column') {
00214 $this->_tmp_row[sizeof($this->_tmp_row)-1] .= $data;
00215 } elseif ($this->_state == 'data_column_null') {
00216 ;
00217 } elseif (trim($data) != '') {
00218 $this->croak('Database Data Transfer Failure', $this->_state . ' - ' . $data);
00219 }
00220 }
00221
00222 function cast($data, $column) {
00223 $type = $this->_description[$column]['type'];
00224 switch ($type) {
00225 case 'serial': return (int)$data;
00226 case 'string': return (string)$data;
00227 case 'number': return (float)$data;
00228 default: $this->croak('Database Data Transfer Failure', "Unknown data type $type");
00229 }
00230 }
00231
00232 function getResultSet($skip = 0, $max = -1) {
00233 $arr = array();
00234 foreach ($this->_data as $k => $v) {
00235 if ($skip-- > 0)
00236 continue;
00237 if ($max-- == 0)
00238 break;
00239 $dict = array();
00240 for ($i = 0; $i < sizeof($this->_description); ++$i)
00241 $dict[$this->_description[$i]['name']] = $v[$i];
00242 array_push($arr, $dict);
00243 }
00244 return new KjwArrayResultSet($arr);
00245 }
00246
00247 function store() {
00248 $fp = fopen($this->_tableFile, 'wb');
00249 fwrite($fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
00250 fwrite($fp, "<table name=\"" . htmlentities($this->_tableName, ENT_COMPAT, 'UTF-8') . "\">\n <description>\n");
00251 foreach ($this->_description as $column) {
00252 fwrite($fp, " <column");
00253 foreach ($column as $key => $value)
00254 fwrite($fp, " $key=\"" . htmlentities($value, ENT_COMPAT, 'UTF-8') . "\"");
00255 fwrite($fp, "/>\n");
00256 }
00257 fwrite($fp, " </description>\n <data>\n");
00258 foreach ($this->_data as $row) {
00259 fwrite($fp, " <row>");
00260 foreach ($row as $column) {
00261 if ($column === null)
00262 fwrite($fp, "<col null=\"1\"/>");
00263 else
00264 fwrite($fp, "<col>" . htmlentities($column, ENT_COMPAT, 'UTF-8') . "</col>");
00265 }
00266 fwrite($fp, "</row>\n");
00267 }
00268 fwrite($fp, " </data>\n</table>\n");
00269 }
00270
00271 function cmp($a, $b) {
00272 foreach ($this->_sortOrder as $column => $asc) {
00273 if (($ret = $this->_primitive_compare($a[$column], $b[$column])) != 0)
00274 return $asc * $ret;
00275 }
00276 return 0;
00277 }
00278
00279 function _setColumnOrder($order) {
00280 $column_order = array();
00281 foreach ($order as $column) {
00282 $asc = 1;
00283 if ($column[0] == '+') {
00284 $column = substr($column, 1);
00285 } elseif ($column[0] == '-') {
00286 $column = substr($column, 1);
00287 $asc = -1;
00288 }
00289 $column_pos = -1;
00290 for ($i = 0; $i < sizeof($this->_description); ++$i) {
00291 if ($this->_description[$i]['name'] == $column) {
00292 $column_pos = $i;
00293 break;
00294 }
00295 }
00296 if ($column_pos == -1 || isset($column_order[$column_pos]))
00297 continue;
00298 $column_order[$column_pos] = $asc;
00299 }
00300 $this->_sortOrder = $column_order;
00301 }
00302
00303 function _primitive_compare($a, $b) {
00304 if ($a === $b)
00305 return 0;
00306 if (is_null($a) && !is_null($b))
00307 return 1;
00308 if (is_null($b))
00309 return -1;
00310 if (is_int($a) or is_float($a))
00311 return $a < $b ? -1 : ($a == $b ? 0 : 1);
00312 if (is_string($a))
00313 return strcasecmp($a, $b);
00314 assert('false');
00315 }
00316 }
00317
00318 ?>