From 36584092e54f805d4e4cc67d342d3c29deadf0f4 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 13 Nov 2019 18:03:37 +0100 Subject: [PATCH] (Experimental) Export a DBSearch as an array/JSON structure. --- core/dbobjectsearch.class.php | 74 +++++++++++++ core/dbsearch.class.php | 17 ++- core/dbunionsearch.class.php | 14 +++ core/oql/expression.class.inc.php | 176 ++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+), 2 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 968fb71ab..ee3f75f20 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1524,6 +1524,80 @@ class DBObjectSearch extends DBSearch } } + /** + * {@inheritDoc} + * @see DBSearch::ToJSON() + */ + public function ToJSON() + { + $aRet = array('selects' => array(), 'joins' => array(), 'where' => array()); + + $aParams = array_merge($this->m_aParams); + $aParams = MetaModel::PrepareQueryArguments($aParams); + + foreach ($this->m_aSelectedClasses as $sAlias => $sClass) + { + $aRet['selects'][] = array('class' => $sClass, 'alias' => $sAlias); + } + $this->JoinsToJSON($aRet); + + $aRet['condition'] = $this->m_oSearchCondition->ToJSON($aParams, true); + return $aRet; + } + + /** + * Export the JOIN operations to a structure (array of arrays) suitable for JSON export + * + * @internal + * + * @param mixed[string] $aRet + * @return void + */ + protected function JoinsToJSON(&$aRet) + { + foreach($this->m_aPointingTo as $sExtKey => $aPointingTo) + { + foreach($aPointingTo as $iOperatorCode => $aFilter) + { + $sOperator = $this->OperatorCodeToOQL($iOperatorCode); + foreach($aFilter as $oFilter) + { + $aRet['joins'][] = array( + 'src' => $this->GetFirstJoinedClass(), + 'src_alias' => $this->GetFirstJoinedClassAlias(), + 'target' => $oFilter->GetFirstJoinedClass(), + 'target_alias' => $oFilter->GetFirstJoinedClassAlias(), + 'foreign_key' => $sExtKey, + 'operator' => $sOperator, + ); + $oFilter->JoinsToJSON($aRet); + } + } + } + foreach($this->m_aReferencedBy as $aReferences) + { + foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) + { + foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) + { + $sOperator = $this->OperatorCodeToOQL($iOperatorCode); + foreach ($aFilters as $oForeignFilter) + { + $aRet['joins'][] = array( + 'src' => $oForeignFilter->GetFirstJoinedClass(), + 'src_alias' => $oForeignFilter->GetFirstJoinedClassAlias(), + 'target' => $this->GetFirstJoinedClass(), + 'target_alias' => $this->GetFirstJoinedClassAlias(), + 'foreign_key' => $sForeignExtKeyAttCode, + 'operator' => $sOperator, + ); + $oForeignFilter->JoinsToJSON($aRet); + } + } + } + } + } + public function InitFromOqlQuery(OqlQuery $oOqlQuery, $sQuery) { $oModelReflection = new ModelReflectionRuntime(); diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php index 22a28c24b..45353308e 100644 --- a/core/dbsearch.class.php +++ b/core/dbsearch.class.php @@ -699,6 +699,15 @@ abstract class DBSearch */ abstract public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false); + /** + * Export the DBSearch as a structure (array of arrays...) suitable for a conversion to JSON + * + * @internal + * + * @return mixed[string] + */ + abstract public function ToJSON(); + static protected $m_aOQLQueries = array(); /** @@ -734,12 +743,13 @@ abstract class DBSearch * * @param string $sQuery The OQL to convert to a DBSearch * @param mixed[string] $aParams array of params index by name + * @param ModelReflection|null $oMetaModel The MetaModel to use when checking the consistency of the OQL * * @return DBObjectSearch|DBUnionSearch * * @throws OQLException */ - static public function FromOQL($sQuery, $aParams = null) + static public function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null) { if (empty($sQuery)) { @@ -781,7 +791,10 @@ abstract class DBSearch $oOql = new OqlInterpreter($sQuery); $oOqlQuery = $oOql->ParseQuery(); - $oMetaModel = new ModelReflectionRuntime(); + if ($oMetaModel === null) + { + $oMetaModel = new ModelReflectionRuntime(); + } $oOqlQuery->Check($oMetaModel, $sQuery); // Exceptions thrown in case of issue $oResultFilter = $oOqlQuery->ToDBSearch($sQuery); diff --git a/core/dbunionsearch.class.php b/core/dbunionsearch.class.php index 427d8745b..4e5a54744 100644 --- a/core/dbunionsearch.class.php +++ b/core/dbunionsearch.class.php @@ -463,6 +463,20 @@ class DBUnionSearch extends DBSearch return $sRet; } + /** + * {@inheritDoc} + * @see DBSearch::ToJSON() + */ + public function ToJSON() + { + $sRet = array('unions' => array()); + foreach ($this->aSearches as $oSearch) + { + $sRet['unions'][] = $oSearch->ToJSON(); + } + return $sRet; + } + /** * Returns a new DBUnionSearch object where duplicates queries have been removed based on their OQLs * diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 10687ff48..3e9e4237e 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -27,6 +27,17 @@ class MissingQueryArgument extends CoreException */ abstract class Expression { + public const OPERATOR_BINARY = 'binary'; + public const OPERATOR_BOOLEAN = 'boolean_binary'; + public const OPERATOR_FIELD = 'field'; + public const OPERATOR_FUNCTION = 'function'; + public const OPERATOR_INTERVAL = 'interval'; + public const OPERATOR_LIST = 'list'; + public const OPERATOR_SCALAR = 'scalar'; + public const OPERATOR_UNARY = 'unary'; + public const OPERATOR_VARIABLE = 'variable'; + + /** * Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects) **/ @@ -98,6 +109,15 @@ abstract class Expression */ abstract public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false); + /** + * Recursively renders the expression as a structure (array) suitable for a JSON export + * + * @param mixed[string] $aArgs + * @param boolean $bRetrofitParams + * @return mixed[string] + */ + abstract public function ToJSON(&$aArgs = null, $bRetrofitParams = false); + /** * @param DBObjectSearch $oSearch * @param array $aArgs @@ -277,6 +297,12 @@ class SQLExpression extends Expression return $this->m_sSQL; } + // recursive rendering + public function toJSON(&$aArgs = null, $bRetrofitParams = false) + { + return null; // TODO should we throw an Exception ?? + } + public function Browse(Closure $callback) { $callback($this); @@ -413,6 +439,31 @@ class BinaryExpression extends Expression return "($sLeft $sOperator $sRight)"; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + if (($this->GetOperator() == 'AND') || ($this->GetOperator() == 'OR')) + { + return array( + 'type' => static::OPERATOR_BOOLEAN, + 'operator' => $this->GetOperator(), + 'left' => $this->GetLeftExpr()->ToJSON($aArgs, $bRetrofitParams), + 'right' => $this->GetRightExpr()->ToJSON($aArgs, $bRetrofitParams), + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + return array( + 'type' => static::OPERATOR_BINARY, + 'operator' => $this->GetOperator(), + 'left' => $this->GetLeftExpr()->ToJSON($aArgs, $bRetrofitParams), + 'right' => $this->GetRightExpr()->ToJSON($aArgs, $bRetrofitParams), + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function Browse(Closure $callback) { $callback($this); @@ -789,6 +840,19 @@ class UnaryExpression extends Expression return CMDBSource::Quote($this->m_value); } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + return array( + 'type' => static::OPERATOR_UNARY, + 'value' => $this->m_value, + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function Browse(Closure $callback) { $callback($this); @@ -909,6 +973,19 @@ class ScalarExpression extends UnaryExpression return $sRet; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + return array( + 'type' => static::OPERATOR_SCALAR, + 'value' => $this->m_value, + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function GetAsScalar($aArgs) { return clone $this; @@ -1157,6 +1234,21 @@ class FieldExpression extends UnaryExpression return "`{$this->m_sParent}`.`{$this->m_sName}`"; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + return array( + 'type' => static::OPERATOR_FIELD, + 'value' => $this->m_value, + 'alias' => $this->m_sParent, + 'field' => $this->m_sName, + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function GetAttDef($aClasses = array()) { if (!empty($this->m_sParent)) @@ -1546,6 +1638,19 @@ class VariableExpression extends UnaryExpression } } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + return array( + 'type' => static::OPERATOR_VARIABLE, + 'value' => $this->m_value, + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function RenameParam($sOldName, $sNewName) { if ($this->m_sName == $sOldName) @@ -1645,6 +1750,24 @@ class ListExpression extends Expression return '('.implode(', ', $aRes).')'; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + $aFields = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aFields[] = $oExpr->ToJSON($aArgs, $bRetrofitParams); + } + return array( + 'type' => static::OPERATOR_LIST, + 'fields' => $aFields, + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function Browse(Closure $callback) { $callback($this); @@ -1798,6 +1921,26 @@ class FunctionExpression extends Expression return $this->m_sVerb.'('.implode(', ', $aRes).')'; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + $aFields = array(); + foreach ($this->m_aArgs as $oExpr) + { + $aFields[] = $oExpr->ToJSON($aArgs, $bRetrofitParams); + } + + return array( + 'type' => static::OPERATOR_FUNCTION, + 'operator' => $this->m_sVerb, + 'fields' => $aFields, + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function Browse(Closure $callback) { $callback($this); @@ -2093,6 +2236,20 @@ class IntervalExpression extends Expression return 'INTERVAL '.$this->m_oValue->RenderExpression($bForSQL, $aArgs, $bRetrofitParams).' '.$this->m_sUnit; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + return array( + 'type' => static::OPERATOR_INTERVAL, + 'unit' => $this->m_sUnit, + 'expression' => $this->m_oValue->ToJSON($aArgs, $bRetrofitParams), + 'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams), + ); + } + public function Browse(Closure $callback) { $callback($this); @@ -2192,6 +2349,25 @@ class CharConcatExpression extends Expression return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)"; } + /** + * {@inheritDoc} + * @see Expression::ToJSON() + */ + public function ToJSON(&$aArgs = null, $bRetrofitParams = false) + { + $aFields = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aFields[] = $oExpr->RenderExpression($aArgs, $bRetrofitParams); + } + + return array( + 'type' => static::OPERATOR_FUNCTION, + 'operator' => 'concat', + 'fields' => $aFields, + ); + } + public function Browse(Closure $callback) { $callback($this);