1Pietsje LLeeuwarden1983
2Klaasje LLeeuwarden1982
3Groningen1983
4Thomas 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');
}
}
?>