1Pietsje LLeeuwarden19832Klaasje LLeeuwarden19823Groningen19834Thomas AAnderlecht1982
*/ /* (example.php) $sql = kjwsql_from_url('xmlsql://localhost/mydb'); print_r($sql->selectAll("* FROM mytable order by destination, id desc limit 1,2")); */ if (!function_exists('xml_parser_create')) trigger_error('xml_* functions not defined.', E_USER_ERROR); require_once(dirname(__FILE__) . '/KjwFakeSql.php'); require_once(dirname(__FILE__) . '/KjwArrayResultSet.php'); /** * KjwXmlSql is a tiny xml implementation of the KjwFakeSql abstract class. It does not * support much SQL at all. * It's supposed to be a temporary solution for tiny database needs. * Don't use this for anything larger than a tiny guestbook or some other * small thing. */ class KjwXmlSql extends KjwFakeSql { var $_dbpath = null; var $_last_insert_id = null; /** * Construct a KjwXmlSql object. * * @param $server Must be 'localhost' as we'll be using local files. * @param $unused_port Unused, must be a zero. * @param $unused_user Username, must be an empty string. * @param $unused_pass Password, must be an empty string. * @param $path Path to the xml/lock files, should be writable. */ function KjwXmlSql($server, $unused_port, $unused_user, $unused_pass, $path) { assert('$server == "localhost" && $unused_port === 0 && $unused_user == "" && $unused_pass == ""'); if (substr($path, -1) != '/') $path .= '/'; parent::KjwFakeSql('xmlsql', $server, $unused_port, $unused_user, $unused_pass, $path . 'lock'); $this->_dbpath = $path; } /* function updateArray($table, $args, $where = null) { $this->croak('Database Data Transfer Failure', 'implement me'); } */ function insertId() { return $this->_last_insert_id; } function _simpleSelect($table, $columns, $order, $skip = 0, $max = -1) { $obj = new KjwXmlSqlTable($table, $this->_dbpath); $obj->sort($order); $resultSet = $obj->getResultSet($skip, $max); $obj->destroy(); return $resultSet; } function _simpleInsert($table, $args) { $this->_last_insert_id = 0; $obj = new KjwXmlSqlTable($table, $this->_dbpath); // Args without keys => columnless insert if (array_key_exists(0, $args)) { if (sizeof($args) != sizeof($obj->_description)) $this->croak('Database Data Transfer Failure', 'Value count mismatches column count'); $values = $args; // Args with keys => rewrite to array } else { $values = array(); foreach ($args as $key => $value) { for ($i = 0; $i < sizeof($obj->_description); ++$i) { if ($obj->_description[$i]['name'] == $key) { $values[$i] = $value; break; } } } if (sizeof($values) != sizeof($args)) $this->croak('Database Data Transfer Failure', 'Trying to write to non-existent columns?'); for ($i = 0; $i < sizeof($obj->_description); ++$i) { if (!isset($values[$i])) $values[$i] = null; if ($obj->_description[$i]['type'] == 'serial' && $values[$i] === null) $this->_last_insert_id = $values[$i] = $obj->_description[$i]['next']++; } ksort($values); // fix our unordered array insert } // Convert nulls in serial columns to 'next' for ($i = 0; $i < sizeof($obj->_description); ++$i) { if ($obj->_description[$i]['type'] == 'serial' && $values[$i] === null) $this->_last_insert_id = $values[$i] = $obj->_description[$i]['next']++; } // Add the values to the end of the data array_push($obj->_data, $values); $obj->store($table); $obj->destroy(); } } class KjwXmlSqlTable extends KjwObject { function KjwXmlSqlTable($tableName, $dbPath) { parent::KjwObject(); $this->_tableName = $tableName; $this->_tableFile = $dbPath . $tableName . '.xml'; $this->_state = 'root'; $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); xml_set_object($parser, &$this); xml_set_element_handler($parser, 'onBeginElement', 'onEndElement'); xml_set_character_data_handler($parser, 'onData'); $fp = fopen($this->_tableFile, "rb"); // XXX mooie croaks als het misgaat while (($data = fread($fp, 8192)) !== '') { if (!xml_parse($parser, $data, feof($fp))) $this->croak('Database Data Transfer Failure', 'Xml parsing failed!'); } fclose($fp); xml_parser_free($parser); } function sort($columnOrder) { $this->_setColumnOrder($columnOrder); $ret = usort($this->_data, array($this, 'cmp')); assert('$ret == true'); unset($this->_columnOrder); } function onBeginElement($parser, $name, $attributes) { if ($this->_state == 'root' && $name == 'table') { if ($attributes['name'] != $this->_tableName) $this->croak('Database Data Transfer Failure', 'Table name missing or invalid'); $this->_state = 'table'; } elseif ($this->_state == 'table' && $name == 'description') { if (isset($this->_description)) $this->croak('Database Data Transfer Failure', 'Got description already'); $this->_state = 'description'; $this->_description = array(); } elseif ($this->_state == 'description' && $name == 'column') { if (!isset($attributes['type']) || !isset($attributes['name'])) $this->croak('Database Data Transfer Failure', 'Description lacks type or name'); array_push($this->_description, $attributes); } elseif ($this->_state == 'table' && $name == 'data') { if (!isset($this->_description)) $this->croak('Database Data Transfer Failure', 'Data found before description.'); $this->_state = 'data'; $this->_data = array(); } elseif ($this->_state == 'data' && $name == 'row') { $this->_state = 'data_row'; $this->_tmp_row = array(); } elseif ($this->_state == 'data_row' && $name == 'col') { if (@$attributes['null']) { $this->_state = 'data_column_null'; array_push($this->_tmp_row, null); } else { $this->_state = 'data_column'; array_push($this->_tmp_row, ''); } } else { $this->croak('Database Data Transfer Failure', 'Unexpected element: ' . $this->_state . '/' . $name); } } function onEndElement($parser, $name) { if ($this->_state == 'table' && $name == 'table') { $this->_state = 'root'; } elseif ($this->_state == 'description' && $name == 'description') { $this->_state = 'table'; } elseif ($this->_state == 'description' && $name == 'column') { ; } elseif ($this->_state == 'data' && $name == 'data') { $this->_state = 'table'; } elseif (($this->_state == 'data_column' || $this->_state == 'data_column_null') && $name == 'col') { $this->_state = 'data_row'; } elseif ($this->_state == 'data_row' && $name == 'row') { $this->_state = 'data'; array_push($this->_data, array_map(array($this, 'cast'), $this->_tmp_row, array_keys($this->_tmp_row))); unset($this->_tmp_row); } else { $this->croak('Database Data Transfer Failure', 'Unexpected element: ' . $this->_state . '/' . $name); } } function onData($parser, $data) { if ($this->_state == 'data_column') { $this->_tmp_row[sizeof($this->_tmp_row)-1] .= $data; } elseif ($this->_state == 'data_column_null') { ; // Nothing, is already null } elseif (trim($data) != '') { $this->croak('Database Data Transfer Failure', $this->_state . ' - ' . $data); } } function cast($data, $column) { $type = $this->_description[$column]['type']; switch ($type) { case 'serial': return (int)$data; case 'string': return (string)$data; case 'number': return (float)$data; default: $this->croak('Database Data Transfer Failure', "Unknown data type $type"); } } function getResultSet($skip = 0, $max = -1) { $arr = array(); foreach ($this->_data as $k => $v) { if ($skip-- > 0) continue; if ($max-- == 0) break; $dict = array(); for ($i = 0; $i < sizeof($this->_description); ++$i) $dict[$this->_description[$i]['name']] = $v[$i]; array_push($arr, $dict); } return new KjwArrayResultSet($arr); } function store() { $fp = fopen($this->_tableFile, 'wb'); fwrite($fp, "\n"); fwrite($fp, "_tableName, ENT_COMPAT, 'UTF-8') . "\">\n \n"); foreach ($this->_description as $column) { fwrite($fp, " $value) fwrite($fp, " $key=\"" . htmlentities($value, ENT_COMPAT, 'UTF-8') . "\""); fwrite($fp, "/>\n"); } fwrite($fp, " \n \n"); foreach ($this->_data as $row) { fwrite($fp, " "); foreach ($row as $column) { if ($column === null) fwrite($fp, ""); else fwrite($fp, "" . htmlentities($column, ENT_COMPAT, 'UTF-8') . ""); } fwrite($fp, "\n"); } fwrite($fp, " \n
\n"); } function cmp($a, $b) { foreach ($this->_sortOrder as $column => $asc) { if (($ret = $this->_primitive_compare($a[$column], $b[$column])) != 0) return $asc * $ret; } return 0; } function _setColumnOrder($order) { $column_order = array(); foreach ($order as $column) { $asc = 1; if ($column[0] == '+') { $column = substr($column, 1); } elseif ($column[0] == '-') { $column = substr($column, 1); $asc = -1; } $column_pos = -1; for ($i = 0; $i < sizeof($this->_description); ++$i) { if ($this->_description[$i]['name'] == $column) { $column_pos = $i; break; } } if ($column_pos == -1 || isset($column_order[$column_pos])) continue; $column_order[$column_pos] = $asc; } $this->_sortOrder = $column_order; } function _primitive_compare($a, $b) { if ($a === $b) return 0; if (is_null($a) && !is_null($b)) return 1; // null sorts later.. this is implementation dependent in sql if (is_null($b)) return -1; if (is_int($a) or is_float($a)) return $a < $b ? -1 : ($a == $b ? 0 : 1); if (is_string($a)) return strcasecmp($a, $b); assert('false'); } } ?>