952 days continuous production uptime, 40k+ tp/s single node. Original corpo Bitbucket history not included — clean archive commit.
498 lines
19 KiB
PHP
498 lines
19 KiB
PHP
<?php
|
|
|
|
/**
|
|
* some assumptions to make:
|
|
*
|
|
* -- that this class will be invoked via the gacFactory class
|
|
* -- that the gacFactory class will pre-validate the data template as being for the Ddb schema
|
|
*
|
|
*/
|
|
class gacDdb extends gaaNamasteCore
|
|
{
|
|
private $globalIndexes = null;
|
|
private $localIndexes = null;
|
|
protected $service; // defines the end-point service
|
|
|
|
|
|
/**
|
|
* gacDdb constructor.
|
|
*
|
|
* the constructor for this class requires two input parameters:
|
|
*
|
|
* $_meta -- the meta data payload (has been vetted by the factory class)
|
|
* $_guid -- an optional string containing a guid value -- if this is specified, then we're telling the class
|
|
* to instantiate the class with the designated record pre-loaded.
|
|
*
|
|
* We're going to initialize with some housekeeping chores - like loading up the configuration, initializing
|
|
* a connection to the DDB resource via the resource manager, and then loading the template.
|
|
*
|
|
* if we passed a guid into the method, validate the guid
|
|
* if cache is enabled, check to see if the record is cached and, if so, load it
|
|
* if the $data property is still null, call the method to load the record
|
|
* note that the state/status of the class will be set here
|
|
*
|
|
* return control to the calling client
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_meta
|
|
* @param string $_guid
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-21-17 mks original coding
|
|
*
|
|
*/
|
|
public function __construct(array $_meta, string $_guid = '')
|
|
{
|
|
// set-up
|
|
register_shutdown_function([$this, STRING_DESTRUCTOR]);
|
|
$this->status = false;
|
|
|
|
parent::__construct();
|
|
|
|
// load config
|
|
$this->config = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_DDB];
|
|
if (empty($this->config)) {
|
|
$this->logger->fatal(ERROR_CONFIG_RESOURCE_404 . CONFIG_DATABASE . COLON . CONFIG_DATABASE_DDB);
|
|
$this->state = STATE_FRAMEWORK_FAIL;
|
|
return;
|
|
}
|
|
|
|
// set the class properties based off the template settings for the data
|
|
$this->templateName = STRING_CLASS_GAT . $_meta[META_TEMPLATE];
|
|
if (!$this->loadTemplate()) {
|
|
$this->logger->warn(ERROR_TEMPLATE_INSTANTIATE . $_meta[META_TEMPLATE]);
|
|
$this->state = STATE_TEMPLATE_ERROR;
|
|
return;
|
|
}
|
|
$this->class = $_meta[META_TEMPLATE]; // set the class to the name of the requested data class
|
|
|
|
// get the http resource for connecting to DynamoDB instance
|
|
if (gasResourceManager::$ddbAvailable and is_null($this->connection)) {
|
|
$this->connection = gasResourceManager::fetchResource(RESOURCE_DDB);
|
|
if (!is_object($this->connection)) {
|
|
$this->logger->fatal(ERROR_RESOURCE_DDB_404);
|
|
$this->setState(STATE_DB_ERROR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// store meta data and client identifier
|
|
$this->client = STRING_UNDEFINED; // todo: why?
|
|
if (!empty($_meta)) {
|
|
$this->metaPayload = $_meta;
|
|
if (isset($this->metaPayload[META_CLIENT])) {
|
|
$this->client = $this->metaPayload[META_CLIENT];
|
|
}
|
|
}
|
|
|
|
// store the event GUID
|
|
if (isset($this->metaPayload[META_EVENT_GUID])) {
|
|
$this->eventGUID = $this->metaPayload[META_EVENT_GUID];
|
|
} else {
|
|
$this->logger->warn(ERROR_EVENT_GUID_404);
|
|
}
|
|
|
|
// if a GUID/key was passed to the constructor, we need to fetch that record from the db
|
|
// NOTE: mBEDS code has connecting-to-remote-service (vault) code in this block
|
|
$this->data = null; // reset the data container
|
|
if (!empty($_guid) and validateGUID($_guid)) { // if we have a valid guid...
|
|
if ($this->useCache and gasResourceManager::$cacheAvailable) { // if cache is on and available
|
|
$this->data = gasCache::get($_guid); // search cache for the key
|
|
if (!is_null($this->data)) {
|
|
$this->data = json_decode(gzuncompress($this->data), true);
|
|
}
|
|
}
|
|
if (is_null($this->data)) {
|
|
$this->guidFetch($_guid);
|
|
}
|
|
} else {
|
|
$this->state = STATE_SUCCESS;
|
|
$this->status = true;
|
|
}
|
|
}
|
|
|
|
|
|
protected function _createRecord($_data)
|
|
{
|
|
|
|
}
|
|
|
|
protected function _fetchRecords($_dd, $_rd = null, $_co = true, $_skip = 0, $_limit = 0, $_sort = null)
|
|
{
|
|
|
|
}
|
|
|
|
protected function _updateRecord($_data){
|
|
|
|
}
|
|
|
|
protected function _deleteRecord($_data)
|
|
{
|
|
|
|
}
|
|
|
|
protected function _lockRecord()
|
|
{
|
|
|
|
}
|
|
|
|
protected function _releaseLock()
|
|
{
|
|
|
|
}
|
|
|
|
protected function _isLocked()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* guidFetch() -- private method
|
|
*
|
|
* this method is (should only be) called from the class constructor and only then if the calling client passes
|
|
* a guid into the constructor - which the framework interprets as a request to instantiate and populate with the
|
|
* record designated by the guid - which is the primary key for the ddb table.
|
|
*
|
|
* The required input parameter is the guid for the record. Validation (that this is a guid) happens in the
|
|
* constructor or the calling client.
|
|
*
|
|
* First, we build the table name from the current environment and then build the query. The query is just a
|
|
* key-value-pair fetch but converts into something overly-complicated once we phrase it in ddb syntax. Just
|
|
* for logging and general readability, we sql-ize the ddb-query and store the string in the designated property.
|
|
*
|
|
* If the class is using timers, start the timer
|
|
* Execute the query - which returns an iterator and not a data set
|
|
* Log the timer results to the metrics table
|
|
* parse the return data set and assign it to the $data property
|
|
*
|
|
* We exception-wrap the query so we'll return the error as to why the query failed and will log accordingly.
|
|
*
|
|
* Next, if cache is enabled for the current class, cache the item after converting the data set to a json
|
|
* string.
|
|
*
|
|
* At all levels, set the state-status values accordingly - these are what should be evaluated by the calling
|
|
* client on return from the method.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_guid
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-21-17 mks original coding
|
|
*
|
|
*/
|
|
private function guidFetch(string $_guid)
|
|
{
|
|
if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__);
|
|
$this->status = false;
|
|
$startTime = floatval(0);
|
|
$tableName = gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $this->collectionName . $this->ext;
|
|
$query = [
|
|
DDB_STRING_CONSISTENT_READ => true,
|
|
DDB_TABLE_NAME => $tableName,
|
|
DDB_STRING_EXPR_ATTR_VALS => [
|
|
':q1' => [
|
|
$this->fieldTypes[$this->pKey . $this->ext] => $_guid
|
|
]
|
|
],
|
|
DDB_STRING_KEY_COND_EXPR => "$this->pKey$this->ext = :q1"
|
|
];
|
|
/** @noinspection SqlNoDataSourceInspection */
|
|
$this->strQuery = 'DDB:SELECT * FROM ' . $tableName . ' WHERE id' . $this->ext . ' = "' . $_guid . '"';
|
|
|
|
// todo - this was coded to see if it worked - next, build a query request array and pass to queryBuilder() and _fetchRecords()
|
|
// exec the DDB query
|
|
try {
|
|
if ($this->useTimers) $startTime = gasStatic::doingTime();
|
|
/** @var AWS\Result $result */
|
|
/** @noinspection PhpUndefinedMethodInspection */
|
|
$result = $this->connection->query($query);
|
|
if ($this->useTimers) $this->logger->metrics($this->strQuery, gasStatic::doingTime($startTime));
|
|
if ($result[DDB_STRING_COUNT] != 1) {
|
|
$msg = sprintf(ERROR_DDB_RECORD_COUNT, count($result), 1);
|
|
$this->eventMessages[] = $msg;
|
|
$this->logger->error($msg);
|
|
return;
|
|
}
|
|
// parse the return data set
|
|
/** @var array $resultData */
|
|
$resultData = $result[DDB_STRING_ITEMS];
|
|
foreach ($resultData as $item) {
|
|
$record = null;
|
|
foreach ($item as $key => $value)
|
|
foreach ($value as $type => $column)
|
|
$record[$key] = $column;
|
|
$this->data[] = $record;
|
|
}
|
|
} catch (\Aws\Exception\AwsException $e) {
|
|
$this->eventMessages[] = $e->getMessage();
|
|
$this->logger->error($e->getMessage());
|
|
$this->state = STATE_DB_ERROR;
|
|
return;
|
|
}
|
|
|
|
// cache the item if cache is enabled for the current class
|
|
if ($this->useCache and !empty($this->data)) {
|
|
$cacheData = json_encode($this->data);
|
|
if (is_null(gasCache::add($this->getColumn($this->pKey), $cacheData))) {
|
|
$msg = ERROR_CACHE_ADD_FAIL . $this->getColumn($this->pKey);
|
|
$this->eventMessages[] = $msg;
|
|
$this->logger->error($msg);
|
|
$this->state = STATE_CACHE_ERROR;
|
|
return;
|
|
}
|
|
}
|
|
$this->event = DB_EVENT_FETCH;
|
|
$this->status = true;
|
|
$this->state = STATE_SUCCESS;
|
|
}
|
|
|
|
|
|
private function queryBuilder(array $_query)
|
|
{
|
|
if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__);
|
|
$this->status = false;
|
|
$foundIndex = false;
|
|
$attribute = '';
|
|
|
|
if (!is_array($_query) or empty($_query)) { // '[]' tests true for is_array
|
|
$msg = ERROR_PARAM_404 . DDB_STRING_QUERY;
|
|
$this->eventMessages[] = $msg;
|
|
$this->logger->error($msg);
|
|
$this->state = STATE_DATA_ERROR;
|
|
return;
|
|
}
|
|
// deal with a single-key query
|
|
if (count($_query) == 1) {
|
|
// extract the key value pair
|
|
foreach ($_query as $k1 => $v1) { // attribute level
|
|
$attribute = $k1;
|
|
foreach ($v1 as $k2 => $v2) { // operand level
|
|
foreach ($v2 as $k3 => $v3) { // operator level
|
|
// check that the operand is '='
|
|
if ($k3 != OPERATOR_DDB_EQ) {
|
|
$this->eventMessages[] = ERROR_DDB_EXP_EQ_Q1;
|
|
$this->logger->data(ERROR_DDB_EXP_EQ_Q1);
|
|
$this->state = STATE_DATA_ERROR;
|
|
return;
|
|
}
|
|
if (count($v3) != 1) {
|
|
$this->eventMessages[] = ERROR_DDB_EXP_VAL_Q1;
|
|
$this->logger->data(ERROR_DDB_EXP_VAL_Q1);
|
|
$this->state = STATE_DATA_ERROR;
|
|
return;
|
|
}
|
|
$value = $v3[0]; // grab the search value
|
|
}
|
|
}
|
|
}
|
|
// we have extracted the search key attribute and the search value...need to find it in one of
|
|
// the three possible index declarations for the current table (VALIDATE THE INDEX ATTRIBUTE)
|
|
if (array_key_exists($attribute, $this->indexes) and $this->indexes[$attribute] == DDB_INDEX_HASH) {
|
|
$foundIndex = 'base';
|
|
} else {
|
|
$ca = ['globalIndexes', 'localIndexes'];
|
|
foreach ($ca as $index) {
|
|
if (is_array($this->$index)) {
|
|
foreach ($this->$index as $subIndex) {
|
|
if (array_key_exists($attribute, $subIndex) and $subIndex[$attribute] == DDB_INDEX_HASH) {
|
|
$foundIndex = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($foundIndex) break;
|
|
}
|
|
}
|
|
// if (is_array($this->globalIndexes)) {
|
|
// foreach ($this->globalIndexes as $gIndex) {
|
|
// if (array_key_exists($attribute, $gIndex) and $gIndex[$attribute] == DDB_INDEX_HASH) {
|
|
// $foundIndex = true;
|
|
// }
|
|
// }
|
|
// }
|
|
// if (!$foundIndex and is_array($this->localIndexes)) {
|
|
// foreach ($this->localIndexes as $lIndex) {
|
|
// if (array_key_exists($attribute, $lIndex) and $lIndex[$attribute] == DDB_INDEX_HASH) {
|
|
// $foundIndex = true;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
if (!$foundIndex) {
|
|
$msg = sprintf(ERROR_DDB_NO_HASH_IDX, $attribute);
|
|
$this->eventMessages[] = $msg;
|
|
$this->logger->data($msg);
|
|
$this->state = STATE_DATA_ERROR;
|
|
return;
|
|
}
|
|
} elseif (count($_query) == 1) {
|
|
// todo: query uses the hash and the range
|
|
|
|
} else {
|
|
// todo -- malformed request
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* loadTemplate() -- private method
|
|
*
|
|
* this method is invoked by the constructor and serves to load the class template file, assimilating it into
|
|
* the current instantiation.
|
|
*
|
|
* template loads are done on the schema-instantiation level, instead of the core, because of the changes in
|
|
* the template file(s) across various schemas.
|
|
*
|
|
* the method will load the class template and set the class member variables controlled/referenced by the
|
|
* template.
|
|
*
|
|
* successful loading of the template is determined by the return (boolean) value -- on error, a log message
|
|
* will be generated so it's up to the developer to check logs on fail-returns to see why their template
|
|
* file was not correctly assimilated.
|
|
*
|
|
* The template to be loaded is first derived in the constructor (post validation that the template file
|
|
* exists) and is pulled from the member variable (also set in the constructor) within this method.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-20-17 mks original coding
|
|
*
|
|
*/
|
|
private function loadTemplate():bool
|
|
{
|
|
if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__);
|
|
|
|
try {
|
|
$this->template = new $this->templateName();
|
|
} catch (Exception $e) {
|
|
$this->logger->warn($e->getMessage());
|
|
$this->state = STATE_FRAMEWORK_FAIL;
|
|
return (false);
|
|
}
|
|
|
|
if (!is_object($this->template)) {
|
|
$this->logger->warn(ERROR_FILE_404 . $this->templateName);
|
|
$this->setState(ERROR_FILE_404 . $this->templateName);
|
|
return (false);
|
|
}
|
|
|
|
if ($this->template->schema != TEMPLATE_DB_DDB) {
|
|
$this->logger->warn(ERROR_SCHEMA_MISMATCH . $this->template->schema . ERROR_STUB_EXPECTING . TEMPLATE_DB_DDB);
|
|
$this->setState(ERROR_SCHEMA_MISMATCH . $this->templateName);
|
|
return (false);
|
|
}
|
|
|
|
// transfer meta data info to current instantiation
|
|
$this->service = $this->template->service;
|
|
$this->schema = $this->template->schema;
|
|
$this->collectionName = $this->template->collection;
|
|
$this->ext = $this->template->extension;
|
|
$this->useCache = $this->template->setCache;
|
|
$this->useDeletes = $this->template->setDeletes;
|
|
$this->useAuditing = $this->template->setAuditing;
|
|
$this->useJournaling = $this->template->setJournaling;
|
|
$this->allowUpdates = $this->template->setUpdates;
|
|
$this->useDetailedHistory = $this->template->setHistory;
|
|
$this->defaultStatus = $this->template->setDefaultStatus;
|
|
$this->searchStatus = $this->template->setSearchStatus;
|
|
$this->useLocking = $this->template->setLocking;
|
|
$this->useTimers = ($this->template->setTimers and gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMERS]);
|
|
$this->pKey = $this->template->setPKey;
|
|
$this->useToken = $this->template->setTokens;
|
|
$this->cacheExpiry = $this->template->cacheTimer;
|
|
|
|
if (isset($this->template->fields) and is_array($this->template->fields)) {
|
|
foreach ($this->template->fields as $key => $value) {
|
|
if ($key == DB_HISTORY) {
|
|
$this->fieldList[] = $key;
|
|
$this->fieldTypes[$key] = $value;
|
|
} else {
|
|
$this->fieldList[] = ($key . $this->ext);
|
|
$this->fieldTypes[($key . $this->ext)] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($this->template->indexes) and is_array($this->template->indexes)) {
|
|
foreach ($this->template->indexes as $key => $value) {
|
|
$this->indexes[] = ($key . $this->ext);
|
|
}
|
|
}
|
|
|
|
// todo: validate the global index data
|
|
if (isset($this->template->globalIndexes) and is_array($this->template->globalIndexes)) {
|
|
foreach ($this->template->globalIndexes as $key ) {
|
|
$this->globalIndexes[] = $key;
|
|
}
|
|
}
|
|
|
|
// todo: validate the local index data
|
|
if (isset($this->template->localIndexes) and is_array($this->template->localIndexes)) {
|
|
foreach ($this->template->localIndexes as $key) {
|
|
$this->localIndexes[] = $key;
|
|
}
|
|
}
|
|
|
|
if (!is_null($this->template->cacheMap)) {
|
|
foreach ($this->template->cacheMap as $key => $value) {
|
|
$this->cacheMap[($key . $this->ext)] = $value;
|
|
}
|
|
} else {
|
|
$this->cacheMap = null;
|
|
}
|
|
|
|
if (!is_null($this->template->binFields)) {
|
|
foreach ($this->template->binFields as $key) {
|
|
$this->binaryFields[] = ($key . $this->ext);
|
|
}
|
|
}
|
|
|
|
if ($this->template->selfDestruct) {
|
|
unset($this->template);
|
|
}
|
|
return (true);
|
|
}
|
|
|
|
|
|
/**
|
|
* __destruct() -- public function
|
|
*
|
|
* class destructor
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-20-17 mks original coding
|
|
*
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
// As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.
|
|
//
|
|
// destructor is registered shut-down function in constructor -- so any recovery
|
|
// efforts should go in this method.
|
|
|
|
// there is no destructor method defined in the core abstraction class, hence
|
|
// there is no call to that parent destructor in this class.
|
|
parent::__destruct();
|
|
}
|
|
|
|
} |