use one multivalue table per schema

This commit is contained in:
Andreas Gohr
2016-02-11 10:28:40 +01:00
parent 74461852bb
commit 0fe33e720e
8 changed files with 90 additions and 46 deletions

View File

@ -7,6 +7,7 @@ spl_autoload_register(array('action_plugin_struct_autoloader', 'autoloader'));
use plugin\struct\meta\SchemaBuilder;
use plugin\struct\meta\Schema;
use plugin\struct\meta;
/**
* Class SchemaData for testing
@ -72,40 +73,33 @@ class schemaDataDB_struct_test extends \DokuWikiTest {
$builder->build();
// revision 1
/** @noinspection SqlResolve */
$this->sqlite->query("INSERT INTO data_testtable (pid, rev, col1) VALUES (?,?,?)", array('testpage', 123, 'value1',));
$this->sqlite->query("INSERT INTO multivals (tbl, colref, pid, rev, row, value) VALUES (?,?,?,?,?,?)",
array('testtable',2,'testpage',123,1,'value2.1',));
$this->sqlite->query("INSERT INTO multivals (tbl, colref, pid, rev, row, value) VALUES (?,?,?,?,?,?)",
array('testtable',2,'testpage',123,2,'value2.2',));
/** @noinspection SqlResolve */
$this->sqlite->query("INSERT INTO multi_testtable (colref, pid, rev, row, value) VALUES (?,?,?,?,?)",
array(2,'testpage',123,1,'value2.1',));
/** @noinspection SqlResolve */
$this->sqlite->query("INSERT INTO multi_testtable (colref, pid, rev, row, value) VALUES (?,?,?,?,?)",
array(2,'testpage',123,2,'value2.2',));
// revision 2
/** @noinspection SqlResolve */
$this->sqlite->query("INSERT INTO data_testtable (pid, rev, col1) VALUES (?,?,?)", array('testpage', 789, 'value1a',));
$this->sqlite->query("INSERT INTO multivals (tbl, colref, pid, rev, row, value) VALUES (?,?,?,?,?,?)",
array('testtable',2,'testpage',789,1,'value2.1a',));
$this->sqlite->query("INSERT INTO multivals (tbl, colref, pid, rev, row, value) VALUES (?,?,?,?,?,?)",
array('testtable',2,'testpage',789,2,'value2.2a',));
/** @noinspection SqlResolve */
$this->sqlite->query("INSERT INTO multi_testtable (colref, pid, rev, row, value) VALUES (?,?,?,?,?)",
array(2,'testpage',789,1,'value2.1a',));
/** @noinspection SqlResolve */
$this->sqlite->query("INSERT INTO multi_testtable (colref, pid, rev, row, value) VALUES (?,?,?,?,?)",
array(2,'testpage',789,2,'value2.2a',));
}
public function tearDown() {
parent::tearDown();
$res = $this->sqlite->query("SELECT name FROM sqlite_master WHERE type='table'");
$tableNames = $this->sqlite->res2arr($res);
$tableNames = array_map(function ($value) { return $value['name'];},$tableNames);
$this->sqlite->res_close($res);
foreach ($tableNames as $tableName) {
if ($tableName == 'opts') continue;
$this->sqlite->query('DROP TABLE ?;', $tableName);
}
$this->sqlite->query("CREATE TABLE schema_assignments ( assign NOT NULL, tbl NOT NULL, PRIMARY KEY(assign, tbl) );");
$this->sqlite->query("CREATE TABLE schema_cols ( sid INTEGER REFERENCES schemas (id), colref INTEGER NOT NULL, enabled BOOLEAN DEFAULT 1, tid INTEGER REFERENCES types (id), sort INTEGER NOT NULL, PRIMARY KEY ( sid, colref) )");
$this->sqlite->query("CREATE TABLE schemas ( id INTEGER PRIMARY KEY AUTOINCREMENT, tbl NOT NULL, ts INT NOT NULL, chksum DEFAULT '' )");
$this->sqlite->query("CREATE TABLE sqlite_sequence(name,seq)");
$this->sqlite->query("CREATE TABLE types ( id INTEGER PRIMARY KEY AUTOINCREMENT, class NOT NULL, ismulti BOOLEAN DEFAULT 0, label DEFAULT '', config DEFAULT '' )");
$this->sqlite->query("CREATE TABLE multivals ( tbl NOT NULL, colref INTEGER NOT NULL, pid NOT NULL, rev INTEGER NOT NULL, row INTEGER NOT NULL, value, PRIMARY KEY(tbl, colref, pid, rev, row) )");
/** @var \helper_plugin_struct_db $sqlite */
$sqlite = plugin_load('helper', 'struct_db');
$sqlite->resetDB();
}
public function test_getDataFromDB_currentRev() {
@ -195,11 +189,12 @@ class schemaDataDB_struct_test extends \DokuWikiTest {
);
// act
$schemaData = new \plugin\struct\meta\SchemaData('testtable','testpage', "");
$schemaData = new meta\SchemaData('testtable','testpage', "");
$result = $schemaData->saveData($testdata);
// assert
$res = $this->sqlite->query("SELECT pid, col1, col2 FROM data_testtable WHERE pid = ? ORDER BY rev DESC LIMIT 1",array('testpage'));
/** @noinspection SqlResolve */
$res = $this->sqlite->query("SELECT pid, col1, col2 FROM data_testtable WHERE pid = ? ORDER BY rev DESC LIMIT 1", array('testpage'));
$actual_saved_single = $this->sqlite->res2row($res);
$expected_saved_single = array(
'pid' => 'testpage',
@ -207,7 +202,8 @@ class schemaDataDB_struct_test extends \DokuWikiTest {
'col2' => ''
);
$res = $this->sqlite->query("SELECT colref, row, value FROM multivals WHERE pid = ? AND tbl = ? ORDER BY rev DESC LIMIT 3",array('testpage', 'testtable'));
/** @noinspection SqlResolve */
$res = $this->sqlite->query("SELECT colref, row, value FROM multi_testtable WHERE pid = ? ORDER BY rev DESC LIMIT 3", array('testpage'));
$actual_saved_multi = $this->sqlite->res2arr($res);
$expected_saved_multi = array(
array(

View File

@ -74,10 +74,9 @@ class schemaDataSQL_struct_test extends \DokuWikiTest {
),
"SELECT col1,col2,M3.value AS col3
FROM data_testtable DATA
LEFT OUTER JOIN multivals M3
LEFT OUTER JOIN multi_testtable M3
ON DATA.pid = M3.pid
AND DATA.rev = M3.rev
AND M3.tbl = 'testtable'
AND M3.colref = 3
WHERE DATA.pid = ?
AND DATA.rev = ?",

View File

@ -1 +1 @@
3
4

1
db/update0004.sql Normal file
View File

@ -0,0 +1 @@
DROP TABLE multivals;

View File

@ -13,7 +13,19 @@ class helper_plugin_struct_db extends DokuWiki_Plugin {
/** @var helper_plugin_sqlite */
protected $sqlite;
/**
* helper_plugin_struct_db constructor.
*/
public function __construct() {
$this->init();
}
/**
* Initialize the database
*
* @throws Exception
*/
protected function init() {
/** @var helper_plugin_sqlite $sqlite */
$this->sqlite = plugin_load('helper', 'sqlite');
if(!$this->sqlite) {
@ -47,6 +59,19 @@ class helper_plugin_struct_db extends DokuWiki_Plugin {
return $this->sqlite;
}
/**
* Completely remove the database and reinitialize it
*
* You do not want to call this except for testing!
*/
public function resetDB() {
if(!$this->sqlite) return;
$file = $this->sqlite->getAdapter()->getDbFile();
if(!$file) return;
unlink($file);
clearstatcache(true, $file);
$this->init();
}
}
// vim:ts=4:sw=4:et:

View File

@ -200,22 +200,36 @@ class SchemaBuilder {
}
/**
* Create a completely new data table with columns yet
* Create a completely new data table with no columns yet also create the appropriate
* multi value table for the schema
*
* @todo how do we want to handle indexes?
* @return bool
*/
protected function newDataTable() {
$tbl = 'data_' . $this->table;
$ok = true;
$tbl = 'data_' . $this->table;
$sql = "CREATE TABLE $tbl (
pid NOT NULL,
rev INTEGER NOT NULL,
latest BOOLEAN NOT NULL DEFAULT 0,
PRIMARY KEY(pid, rev)
)";
$ok = $ok && (bool) $this->sqlite->query($sql);
return (bool) $this->sqlite->query($sql);
$tbl = 'multi_' . $this->table;
$sql = "CREATE TABLE $tbl (
colref INTEGER NOT NULL,
pid NOT NULL,
rev INTEGER NOT NULL,
row INTEGER NOT NULL,
value,
PRIMARY KEY(colref, pid, rev, row)
);";
$ok = $ok && (bool) $this->sqlite->query($sql);
return $ok;
}
/**

View File

@ -40,7 +40,8 @@ class SchemaData extends Schema {
* @return bool success of saving the data to the database
*/
public function saveData($data) {
$table = 'data_' . $this->table;
$stable = 'data_' . $this->table;
$mtable = 'multi_' . $this->table;
$colrefs = array_flip($this->labels);
$now = $this->ts;
@ -48,6 +49,10 @@ class SchemaData extends Schema {
$multiopts = array();
$singlecols = 'pid, rev, latest';
foreach ($data as $colname => $value) {
if(!isset($colrefs[$colname])) {
throw new StructException("Unknown column %s in schema.", hsc($colname));
}
if (is_array($value)) {
foreach ($value as $index => $multivalue) {
$multiopts[] = array($colrefs[$colname], $index+1, $multivalue,);
@ -57,20 +62,23 @@ class SchemaData extends Schema {
$opt[] = $value;
}
}
$singlesql = "INSERT INTO $table ($singlecols) VALUES (" . trim(str_repeat('?,',count($opt)),',') . ")";
$multisql = "INSERT INTO multivals (tbl, rev, pid, colref, row, value) VALUES (?,?,?,?,?,?)";
$singlesql = "INSERT INTO $stable ($singlecols) VALUES (" . trim(str_repeat('?,',count($opt)),',') . ")";
/** @noinspection SqlResolve */
$multisql = "INSERT INTO $mtable (rev, pid, colref, row, value) VALUES (?,?,?,?,?)";
$this->sqlite->query('BEGIN TRANSACTION');
// remove latest status from previous data
$ok = $this->sqlite->query( "UPDATE $table SET latest = 0 WHERE latest = 1 AND pid = ?",array($this->page));
/** @noinspection SqlResolve */
$ok = $this->sqlite->query( "UPDATE $stable SET latest = 0 WHERE latest = 1 AND pid = ?",array($this->page));
// insert single values
$ok = $ok && $this->sqlite->query($singlesql, $opt);
// insert multi values
foreach ($multiopts as $multiopt) {
$multiopt = array_merge(array($this->table, $now, $this->page,), $multiopt);
$multiopt = array_merge(array($now, $this->page,), $multiopt);
$ok = $ok && $this->sqlite->query($multisql, $multiopt);
}
@ -161,7 +169,8 @@ class SchemaData extends Schema {
* @return array Two fields: the SQL string and the parameters array
*/
protected function buildGetDataSQL($singles, $multis) {
$table = 'data_' . $this->table;
$stable = 'data_' . $this->table;
$mtable = 'multi_' . $this->table;
$colsel = join(',', preg_filter('/^/', 'col', $singles));
@ -170,15 +179,15 @@ class SchemaData extends Schema {
foreach($multis as $col) {
$tn = 'M' . $col;
$select .= ",$tn.value AS col$col";
$join .= "LEFT OUTER JOIN multivals $tn";
$join .= "LEFT OUTER JOIN $mtable $tn";
$join .= " ON DATA.pid = $tn.pid AND DATA.rev = $tn.rev";
$join .= " AND $tn.tbl = '{$this->table}' AND $tn.colref = $col\n";
$join .= " AND $tn.colref = $col\n";
}
$where = "WHERE DATA.pid = ? AND DATA.rev = ?";
$opt = array($this->page, $this->ts);
$sql = "$select FROM $table DATA\n$join $where";
$sql = "$select FROM $stable DATA\n$join $where";
return array($sql, $opt);
}

View File

@ -144,9 +144,9 @@ class Search {
if($col->isMulti()) {
$tn = 'M' . $col->getColref();
$select .= "GROUP_CONCAT($tn.value, '$sep') AS $CN, ";
$from .= "\nLEFT OUTER JOIN multivals AS $tn";
$from .= "\nLEFT OUTER JOIN multi_{$col->getTable()} AS $tn";
$from .= " ON data_{$col->getTable()}.pid = $tn.pid AND data_{$col->getTable()}.rev = $tn.rev";
$from .= " AND $tn.tbl = '{$col->getTable()}' AND $tn.colref = {$col->getColref()}\n";
$from .= " AND $tn.colref = {$col->getColref()}\n";
} else {
$select .= 'data_' . $col->getTable() . ' . col' . $col->getColref() . " AS $CN, ";
$grouping[] = $CN;
@ -161,9 +161,9 @@ class Search {
/** @var $col Column */
if($col->isMulti()) {
$tn = 'MN' . $col->getColref(); // FIXME this joins a second time if the column was selected before
$from .= "\nLEFT OUTER JOIN multivals AS $tn";
$from .= "\nLEFT OUTER JOIN multi_{$col->getTable()} AS $tn";
$from .= " ON data_{$col->getTable()}.pid = $tn.pid AND data_{$col->getTable()}.rev = $tn.rev";
$from .= " AND $tn.tbl = '{$col->getTable()}' AND $tn.colref = {$col->getColref()}\n";
$from .= " AND $tn.colref = {$col->getColref()}\n";
$column = "$tn.value";
} else {