Skip to content
This repository has been archived by the owner on Dec 11, 2022. It is now read-only.

Commit

Permalink
Don't save over data in the database when the entity in it is newer t…
Browse files Browse the repository at this point in the history
…han the entity being saved. Fixes #12.
  • Loading branch information
hperrin committed Dec 23, 2020
1 parent 4e2264a commit 6152150
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 86 deletions.
39 changes: 28 additions & 11 deletions src/Drivers/DriverTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ private function importFromFile(
$guid = null;
$line = '';
$data = [];
$tags = [];
$etype = '__undefined';
if ($startTransactionCallback) {
$startTransactionCallback();
}
Expand Down Expand Up @@ -460,7 +462,7 @@ public function deleteEntity(&$entity) {
* @return bool True if the reference is found, false otherwise.
*/
protected function entityReferenceSearch($value, $entity) {
if (!is_array($value) && !($value instanceof Traversable)) {
if (!is_array($value) && !($value instanceof \Traversable)) {
return false;
}
if (!isset($entity)) {
Expand Down Expand Up @@ -772,12 +774,13 @@ private function saveEntityRowLike(
$startTransactionCallback = null,
$commitTransactionCallback = null
) {
// Save the created date.
// Get a modified date.
$mdate = microtime(true);
// Get a created date.
if (!isset($entity->guid)) {
$entity->cdate = microtime(true);
$cdate = $mdate;
}
// Save the modified date.
$entity->mdate = microtime(true);
$tags = array_diff($entity->tags, ['']);
$data = $entity->getData();
$sdata = $entity->getSData();
$varlist = array_merge(array_keys($data), array_keys($sdata));
Expand All @@ -791,6 +794,7 @@ private function saveEntityRowLike(
if ($startTransactionCallback) {
$startTransactionCallback();
}
$success = false;
if (!isset($entity->guid)) {
while (true) {
// 2^53 is the maximum number in JavaScript
Expand All @@ -804,32 +808,45 @@ private function saveEntityRowLike(
break;
}
}
$entity->guid = $newId;
$saveNewEntityCallback(
$success = $saveNewEntityCallback(
$entity,
$newId,
$tags,
$data,
$sdata,
$cdate,
$etype,
$etypeDirty
);
if ($success) {
$entity->guid = $newId;
$entity->cdate = $cdate;
$entity->mdate = $mdate;
}
} else {
// Removed any cached versions of this entity.
if ($this->config['cache']) {
$this->cleanCache($entity->guid);
}
$saveExistingEntityCallback(
$success = $saveExistingEntityCallback(
$entity,
$entity->guid,
$tags,
$data,
$sdata,
$mdate,
$etype,
$etypeDirty
);
if ($success) {
$entity->mdate = $mdate;
}
}
if ($commitTransactionCallback) {
$commitTransactionCallback();
$commitTransactionCallback($success);
}
// Cache the entity.
if ($this->config['cache']) {
if ($success && $this->config['cache']) {
$this->pushCache(
$entity->guid,
$entity->cdate,
Expand All @@ -839,7 +856,7 @@ private function saveEntityRowLike(
$sdata
);
}
return true;
return $success;
}

/**
Expand Down
53 changes: 32 additions & 21 deletions src/Drivers/MySQLDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1135,15 +1135,15 @@ public function renameUID($oldName, $newName) {
}

public function saveEntity(&$entity) {
$insertData = function ($entity, $data, $sdata, $etype, $etypeDirty) {
$runInsertQuery = function ($name, $value, $svalue) use ($entity, $etype, $etypeDirty) {
$this->query("INSERT INTO `{$this->prefix}data{$etype}` (`guid`, `name`, `value`) VALUES (".((int) $entity->guid).", '".mysqli_real_escape_string($this->link, $name)."', '".mysqli_real_escape_string($this->link, $svalue)."');", $etypeDirty);
$insertData = function ($guid, $data, $sdata, $etype, $etypeDirty) {
$runInsertQuery = function ($name, $value, $svalue) use ($guid, $etype, $etypeDirty) {
$this->query("INSERT INTO `{$this->prefix}data{$etype}` (`guid`, `name`, `value`) VALUES (".((int) $guid).", '".mysqli_real_escape_string($this->link, $name)."', '".mysqli_real_escape_string($this->link, $svalue)."');", $etypeDirty);
$this->query(
"INSERT INTO `{$this->prefix}comparisons{$etype}` (`guid`, `name`, `eq_true`, `eq_one`, `eq_zero`, `eq_negone`, `eq_emptyarray`, `string`, `int`, `float`, `is_int`) VALUES ".
$this->makeInsertValuesComparisons($entity->guid, $name, $value).';',
$this->makeInsertValuesComparisons($guid, $name, $value).';',
$etypeDirty
);
$referenceValues = $this->makeInsertValuesReferences($entity->guid, $name, $svalue);
$referenceValues = $this->makeInsertValuesReferences($guid, $name, $svalue);
if ($referenceValues) {
$this->query(
"INSERT INTO `{$this->prefix}references{$etype}` (`guid`, `name`, `reference`) VALUES {$referenceValues};",
Expand All @@ -1169,35 +1169,46 @@ function ($guid) {
mysqli_free_result($result);
return !isset($row[0]);
},
function ($entity, $data, $sdata, $etype, $etypeDirty) use ($insertData) {
$this->query("INSERT INTO `{$this->prefix}guids` (`guid`) VALUES ({$entity->guid});");
$this->query("INSERT INTO `{$this->prefix}entities{$etype}` (`guid`, `tags`, `cdate`, `mdate`) VALUES ({$entity->guid}, ' ".mysqli_real_escape_string($this->link, implode(' ', array_diff($entity->tags, [''])))." ', ".((float) $entity->cdate).", ".((float) $entity->mdate).");", $etypeDirty);
$insertData($entity, $data, $sdata, $etype, $etypeDirty);
function ($entity, $guid, $tags, $data, $sdata, $cdate, $etype, $etypeDirty) use ($insertData) {
$this->query("INSERT INTO `{$this->prefix}guids` (`guid`) VALUES ({$guid});");
$this->query("INSERT INTO `{$this->prefix}entities{$etype}` (`guid`, `tags`, `cdate`, `mdate`) VALUES ({$guid}, ' ".mysqli_real_escape_string($this->link, implode(' ', $tags))." ', ".((float) $cdate).", ".((float) $cdate).");", $etypeDirty);
$insertData($guid, $data, $sdata, $etype, $etypeDirty);
return true;
},
function ($entity, $data, $sdata, $etype, $etypeDirty) use ($insertData) {
function ($entity, $guid, $tags, $data, $sdata, $mdate, $etype, $etypeDirty) use ($insertData) {
if ($this->config['MySQL']['row_locking']) {
$this->query("SELECT 1 FROM `{$this->prefix}entities{$etype}` WHERE `guid`='".((int) $entity->guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}data{$etype}` WHERE `guid`='".((int) $entity->guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}comparisons{$etype}` WHERE `guid`='".((int) $entity->guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}references{$etype}` WHERE `guid`='".((int) $entity->guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}entities{$etype}` WHERE `guid`='".((int) $guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}data{$etype}` WHERE `guid`='".((int) $guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}comparisons{$etype}` WHERE `guid`='".((int) $guid)."' GROUP BY 1 FOR UPDATE;");
$this->query("SELECT 1 FROM `{$this->prefix}references{$etype}` WHERE `guid`='".((int) $guid)."' GROUP BY 1 FOR UPDATE;");
}
if ($this->config['MySQL']['table_locking']) {
$this->query("LOCK TABLES `{$this->prefix}entities{$etype}` WRITE, `{$this->prefix}data{$etype}` WRITE, `{$this->prefix}comparisons{$etype}` WRITE, `{$this->prefix}references{$etype}` WRITE;");
}
$this->query("UPDATE `{$this->prefix}entities{$etype}` SET `tags`=' ".mysqli_real_escape_string($this->link, implode(' ', array_diff($entity->tags, [''])))." ', `mdate`=".((float) $entity->mdate)." WHERE `guid`='".((int) $entity->guid)."';", $etypeDirty);
$this->query("DELETE FROM `{$this->prefix}data{$etype}` WHERE `guid`='".((int) $entity->guid)."';");
$this->query("DELETE FROM `{$this->prefix}comparisons{$etype}` WHERE `guid`='".((int) $entity->guid)."';");
$this->query("DELETE FROM `{$this->prefix}references{$etype}` WHERE `guid`='".((int) $entity->guid)."';");
$insertData($entity, $data, $sdata, $etype, $etypeDirty);
$this->query("UPDATE `{$this->prefix}entities{$etype}` SET `tags`=' ".mysqli_real_escape_string($this->link, implode(' ', $tags))." ', `mdate`=".((float) $mdate)." WHERE `guid`='".((int) $guid)."' AND abs(`mdate` - ".((float) $entity->mdate).") < 0.001;", $etypeDirty);
$changed = mysqli_affected_rows($this->link);
$success = false;
if ($changed === 1) {
$this->query("DELETE FROM `{$this->prefix}data{$etype}` WHERE `guid`='".((int) $guid)."';");
$this->query("DELETE FROM `{$this->prefix}comparisons{$etype}` WHERE `guid`='".((int) $guid)."';");
$this->query("DELETE FROM `{$this->prefix}references{$etype}` WHERE `guid`='".((int) $guid)."';");
$insertData($guid, $data, $sdata, $etype, $etypeDirty);
$success = true;
}
if ($this->config['MySQL']['table_locking']) {
$this->query("UNLOCK TABLES;");
}
return $success;
},
$this->config['MySQL']['transactions'] ? function () {
$this->query("BEGIN;");
} : null,
$this->config['MySQL']['transactions'] ? function () {
$this->query("COMMIT;");
$this->config['MySQL']['transactions'] ? function ($success) {
if ($success) {
$this->query("COMMIT;");
} else {
$this->query("ROLLBACK;");
}
} : null
);
}
Expand Down
43 changes: 27 additions & 16 deletions src/Drivers/PostgreSQLDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ public function renameUID($oldName, $newName) {
}

public function saveEntity(&$entity) {
$insertData = function ($entity, $data, $sdata, $etype, $etypeDirty) {
$insertData = function ($guid, $data, $sdata, $etype, $etypeDirty) {
$fullData = [];
foreach ($data as $name => $value) {
$fullData[$name] = [$value, serialize($value)];
Expand All @@ -1134,13 +1134,13 @@ public function saveEntity(&$entity) {
list($value, $svalue) = $values;
$queries[] = (
"INSERT INTO \"{$this->prefix}data{$etype}\" (\"guid\", \"name\", \"value\") VALUES ".
$this->makeInsertValuesData($entity->guid, $name, $svalue).';'
$this->makeInsertValuesData($guid, $name, $svalue).';'
);
$queries[] = (
"INSERT INTO \"{$this->prefix}comparisons{$etype}\" (\"guid\", \"name\", \"eq_true\", \"eq_one\", \"eq_zero\", \"eq_negone\", \"eq_emptyarray\", \"string\", \"int\", \"float\", \"is_int\") VALUES ".
$this->makeInsertValuesComparisons($entity->guid, $name, $value).';'
$this->makeInsertValuesComparisons($guid, $name, $value).';'
);
$referenceValues = $this->makeInsertValuesReferences($entity->guid, $name, $svalue);
$referenceValues = $this->makeInsertValuesReferences($guid, $name, $svalue);
if ($referenceValues) {
$queries[] = "INSERT INTO \"{$this->prefix}references{$etype}\" (\"guid\", \"name\", \"reference\") VALUES {$referenceValues};";
}
Expand All @@ -1158,24 +1158,35 @@ function ($guid) {
pg_free_result($result);
return !isset($row[0]);
},
function ($entity, $data, $sdata, $etype, $etypeDirty) use ($insertData) {
$this->query("INSERT INTO \"{$this->prefix}guids\" (\"guid\") VALUES ({$entity->guid});");
$this->query("INSERT INTO \"{$this->prefix}entities{$etype}\" (\"guid\", \"tags\", \"cdate\", \"mdate\") VALUES ({$entity->guid}, '".pg_escape_string($this->link, '{'.implode(',', array_diff($entity->tags, [''])).'}')."', ".((float) $entity->cdate).", ".((float) $entity->mdate).");", $etypeDirty);
$insertData($entity, $data, $sdata, $etype, $etypeDirty);
function ($entity, $guid, $tags, $data, $sdata, $cdate, $etype, $etypeDirty) use ($insertData) {
$this->query("INSERT INTO \"{$this->prefix}guids\" (\"guid\") VALUES ({$guid});");
$this->query("INSERT INTO \"{$this->prefix}entities{$etype}\" (\"guid\", \"tags\", \"cdate\", \"mdate\") VALUES ({$guid}, '".pg_escape_string($this->link, '{'.implode(',', $tags).'}')."', ".((float) $cdate).", ".((float) $cdate).");", $etypeDirty);
$insertData($guid, $data, $sdata, $etype, $etypeDirty);
return true;
},
function ($entity, $data, $sdata, $etype, $etypeDirty) use ($insertData) {
$this->query("UPDATE \"{$this->prefix}entities{$etype}\" SET \"tags\"='".pg_escape_string($this->link, '{'.implode(',', array_diff($entity->tags, [''])).'}')."', \"cdate\"=".((float) $entity->cdate).", \"mdate\"=".((float) $entity->mdate)." WHERE \"guid\"={$entity->guid};", $etypeDirty);
$this->query("DELETE FROM \"{$this->prefix}data{$etype}\" WHERE \"guid\"={$entity->guid};");
$this->query("DELETE FROM \"{$this->prefix}comparisons{$etype}\" WHERE \"guid\"={$entity->guid};");
$this->query("DELETE FROM \"{$this->prefix}references{$etype}\" WHERE \"guid\"={$entity->guid};");
$insertData($entity, $data, $sdata, $etype, $etypeDirty);
function ($entity, $guid, $tags, $data, $sdata, $mdate, $etype, $etypeDirty) use ($insertData) {
$result = $this->query("UPDATE \"{$this->prefix}entities{$etype}\" SET \"tags\"='".pg_escape_string($this->link, '{'.implode(',', $tags).'}')."', \"mdate\"=".((float) $mdate)." WHERE \"guid\"={$guid} AND abs(\"mdate\" - ".((float) $entity->mdate).") < 0.001;", $etypeDirty);
$changed = pg_affected_rows($result);
$success = false;
if ($changed === 1) {
$this->query("DELETE FROM \"{$this->prefix}data{$etype}\" WHERE \"guid\"={$guid};");
$this->query("DELETE FROM \"{$this->prefix}comparisons{$etype}\" WHERE \"guid\"={$guid};");
$this->query("DELETE FROM \"{$this->prefix}references{$etype}\" WHERE \"guid\"={$guid};");
$insertData($guid, $data, $sdata, $etype, $etypeDirty);
$success = true;
}
return $success;
},
function () {
$this->query("BEGIN;");
},
function () {
function ($success) {
pg_get_result($this->link); // Clear any pending result.
$this->query("COMMIT;");
if ($success) {
$this->query("COMMIT;");
} else {
$this->query("ROLLBACK;");
}
}
);
}
Expand Down
49 changes: 30 additions & 19 deletions src/Drivers/SQLite3Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ public function connect() {
'preg_match',
'preg_match',
2,
defined('SQLITE3_DETERMINISTIC') ? SQLITE3_DETERMINISTIC : 0
defined('SQLITE3_DETERMINISTIC') ? \SQLITE3_DETERMINISTIC : 0
);
$this->link->createFunction(
'regexp',
function ($pattern, $subject) {
return !!$this->posixRegexMatch($pattern, $subject);
},
2,
defined('SQLITE3_DETERMINISTIC') ? SQLITE3_DETERMINISTIC : 0
defined('SQLITE3_DETERMINISTIC') ? \SQLITE3_DETERMINISTIC : 0
);
} else {
$this->connected = false;
Expand Down Expand Up @@ -1051,19 +1051,19 @@ public function renameUID($oldName, $newName) {

public function saveEntity(&$entity) {
$this->checkReadOnlyMode();
$insertData = function ($entity, $data, $sdata, $etype, $etypeDirty) {
$runInsertQuery = function ($name, $value, $svalue) use ($entity, $etype, $etypeDirty) {
$insertData = function ($guid, $data, $sdata, $etype, $etypeDirty) {
$runInsertQuery = function ($name, $value, $svalue) use ($guid, $etype, $etypeDirty) {
$this->query(
"INSERT INTO \"{$this->prefix}data{$etype}\" (\"guid\", \"name\", \"value\") VALUES ".
$this->makeInsertValuesData($entity->guid, $name, serialize($value)).';',
$this->makeInsertValuesData($guid, $name, serialize($value)).';',
$etypeDirty
);
$this->query(
"INSERT INTO \"{$this->prefix}comparisons{$etype}\" (\"guid\", \"name\", \"eq_true\", \"eq_one\", \"eq_zero\", \"eq_negone\", \"eq_emptyarray\", \"string\", \"int\", \"float\", \"is_int\") VALUES ".
$this->makeInsertValuesComparisons($entity->guid, $name, $value).';',
$this->makeInsertValuesComparisons($guid, $name, $value).';',
$etypeDirty
);
$referenceValues = $this->makeInsertValuesReferences($entity->guid, $name, serialize($value));
$referenceValues = $this->makeInsertValuesReferences($guid, $name, serialize($value));
if ($referenceValues) {
$this->query(
"INSERT INTO \"{$this->prefix}references{$etype}\" (\"guid\", \"name\", \"reference\") VALUES {$referenceValues};",
Expand All @@ -1089,23 +1089,34 @@ function ($guid) {
$result->finalize();
return !isset($row[0]);
},
function ($entity, $data, $sdata, $etype, $etypeDirty) use ($insertData) {
$this->query("INSERT INTO \"{$this->prefix}guids\" (\"guid\") VALUES ({$entity->guid});");
$this->query("INSERT INTO \"{$this->prefix}entities{$etype}\" (\"guid\", \"tags\", \"cdate\", \"mdate\") VALUES ({$entity->guid}, '".SQLite3::escapeString(','.implode(',', array_diff($entity->tags, [''])).',')."', ".((float) $entity->cdate).", ".((float) $entity->mdate).");", $etypeDirty);
$insertData($entity, $data, $sdata, $etype, $etypeDirty);
function ($entity, $guid, $tags, $data, $sdata, $cdate, $etype, $etypeDirty) use ($insertData) {
$this->query("INSERT INTO \"{$this->prefix}guids\" (\"guid\") VALUES ({$guid});");
$this->query("INSERT INTO \"{$this->prefix}entities{$etype}\" (\"guid\", \"tags\", \"cdate\", \"mdate\") VALUES ({$guid}, '".SQLite3::escapeString(','.implode(',', $tags).',')."', ".((float) $cdate).", ".((float) $cdate).");", $etypeDirty);
$insertData($guid, $data, $sdata, $etype, $etypeDirty);
return true;
},
function ($entity, $data, $sdata, $etype, $etypeDirty) use ($insertData) {
$this->query("UPDATE \"{$this->prefix}entities{$etype}\" SET \"tags\"='".SQLite3::escapeString(','.implode(',', array_diff($entity->tags, [''])).',')."', \"cdate\"=".((float) $entity->cdate).", \"mdate\"=".((float) $entity->mdate)." WHERE \"guid\"={$entity->guid};", $etypeDirty);
$this->query("DELETE FROM \"{$this->prefix}data{$etype}\" WHERE \"guid\"={$entity->guid};");
$this->query("DELETE FROM \"{$this->prefix}comparisons{$etype}\" WHERE \"guid\"={$entity->guid};");
$this->query("DELETE FROM \"{$this->prefix}references{$etype}\" WHERE \"guid\"={$entity->guid};");
$insertData($entity, $data, $sdata, $etype, $etypeDirty);
function ($entity, $guid, $tags, $data, $sdata, $mdate, $etype, $etypeDirty) use ($insertData) {
$this->query("UPDATE \"{$this->prefix}entities{$etype}\" SET \"tags\"='".SQLite3::escapeString(','.implode(',', $tags).',')."', \"mdate\"=".((float) $mdate)." WHERE \"guid\"={$guid} AND abs(\"mdate\" - ".((float) $entity->mdate).") < 0.001;", $etypeDirty);
$changed = $this->link->changes();
$success = false;
if ($changed === 1) {
$this->query("DELETE FROM \"{$this->prefix}data{$etype}\" WHERE \"guid\"={$guid};");
$this->query("DELETE FROM \"{$this->prefix}comparisons{$etype}\" WHERE \"guid\"={$guid};");
$this->query("DELETE FROM \"{$this->prefix}references{$etype}\" WHERE \"guid\"={$guid};");
$insertData($guid, $data, $sdata, $etype, $etypeDirty);
$success = true;
}
return $success;
},
function () {
$this->query("SAVEPOINT 'save';");
},
function () {
$this->query("RELEASE 'save';");
function ($success) {
if ($success) {
$this->query("RELEASE 'save';");
} else {
$this->query("ROLLBACK TO 'save';");
}
}
);
}
Expand Down
Loading

0 comments on commit 6152150

Please sign in to comment.