Commit bc236c59 authored by guilhermeblanco's avatar guilhermeblanco

Finished first parts of SELECT support. Two test cases added and passing....

Finished first parts of SELECT support. Two test cases added and passing. Introduced the concept of DCTRN in queryComponent. Added concept of queryField, which validates for already defined fields in SELECT.
parent 07a16620
......@@ -131,6 +131,18 @@ abstract class Doctrine_Query_AbstractResult
}
/**
* Get the component alias for a given query component
*
* @param array $queryComponent The query component
* @param string Component alias
*/
public function getComponentAlias($queryComponent)
{
return array_search($queryComponent, $this->_queryComponents);;
}
/**
* Whether or not this object has a declaration for given component alias.
*
......
......@@ -114,10 +114,25 @@ class Doctrine_Query_Parser
public function __construct($dql, Doctrine_Connection $connection = null)
{
$this->_scanner = new Doctrine_Query_Scanner($dql);
$this->_parserResult = new Doctrine_Query_ParserResult();
$this->_sqlBuilder = Doctrine_Query_SqlBuilder::fromConnection($connection);
$this->_keywordTable = new Doctrine_Query_Token();
$this->_parserResult = new Doctrine_Query_ParserResult(
'',
array( // queryComponent
'dctrn' => array(
'metadata' => null,
'parent' => null,
'relation' => null,
'map' => null,
'scalar' => null,
),
),
array( // tableAliasMap
'dctrn' => 'dctrn',
)
);
$this->free(true);
}
......
......@@ -43,6 +43,13 @@ class Doctrine_Query_ParserResult extends Doctrine_Query_AbstractResult
*/
protected $_tableAliasSeeds = array();
/**
* Simple array of keys representing the fields used in query.
*
* @var array $_queryFields
*/
protected $_queryFields = array();
/**
* @nodoc
......@@ -62,6 +69,68 @@ class Doctrine_Query_ParserResult extends Doctrine_Query_AbstractResult
}
/**
* Defines the mapping fields.
*
* @param array $queryFields Query fields.
*/
public function setQueryComponents(array $queryFields)
{
$this->_queryFields = $queryFields;
}
/**
* Sets the declaration for given field alias.
*
* @param string $fieldAlias The field alias to set the declaration to.
* @param string $queryField Alias declaration.
*/
public function setQueryField($fieldAlias, array $queryField)
{
$this->_queryFields[$fieldAlias] = $queryField;
}
/**
* Gets the mapping fields.
*
* @return array Query fields.
*/
public function getQueryFields()
{
return $this->_queryComponents;
}
/**
* Get the declaration for given field alias.
*
* @param string $fieldAlias The field alias the retrieve the declaration from.
* @return array Alias declaration.
*/
public function getQueryField($fieldAlias)
{
if ( ! isset($this->_queryFields[$fieldAlias])) {
throw new Doctrine_Query_Exception('Unknown query field ' . $fieldAlias);
}
return $this->_queryFields[$fieldAlias];
}
/**
* Whether or not this object has a declaration for given field alias.
*
* @param string $fieldAlias Field alias the retrieve the declaration from.
* @return boolean True if this object has given alias, otherwise false.
*/
public function hasQueryField($fieldAlias)
{
return isset($this->_queryFields[$fieldAlias]);
}
/**
* Generates a table alias from given table name and associates
* it with given component alias
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
/**
* FieldIdentificationVariable = identifier
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class Doctrine_Query_Production_FieldIdentificationVariable extends Doctrine_Query_Production
{
protected $_componentAlias;
public function syntax($paramHolder)
{
// FieldIdentificationVariable = identifier
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
$this->_fieldAlias = $this->_parser->token['value'];
}
public function semantical($paramHolder)
{
$parserResult = $this->_parser->getParserResult();
if ($parserResult->hasQueryField($this->_fieldAlias)) {
// We should throw semantical error if there's already a component for this alias
$queryComponent = $parserResult->getQueryField($this->_fieldAlias);
$fieldName = $queryComponent['fieldName'];
$message = "Cannot re-declare field alias '{$this->_fieldAlias}'"
. "for '".$paramHolder->get('fieldName')."'. It was already declared for "
. "field '{$fieldName}'.";
$this->_parser->semanticalError($message);
}
}
}
......@@ -24,6 +24,7 @@
*
* @package Doctrine
* @subpackage Query
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.phpdoctrine.org
......
......@@ -37,7 +37,7 @@ class Doctrine_Query_Production_PathExpression extends Doctrine_Query_Production
protected $_fieldName;
private $_queryComponent;
protected $_queryComponent;
public function syntax($paramHolder)
......@@ -67,13 +67,13 @@ class Doctrine_Query_Production_PathExpression extends Doctrine_Query_Production
$queryComponents = $parserResult->getQueryComponents();
// Check if we have more than one queryComponent defined
if (count($queryComponents) != 1) {
if (count($queryComponents) != 2) {
$this->_parser->semanticalError("Undefined component alias for field '{$this->_fieldName}'", $this->_parser->token);
}
// Retrieve ClassMetadata
$k = array_keys($queryComponents);
$componentAlias = $k[0];
$componentAlias = $k[1];
$this->_queryComponent = $queryComponents[$componentAlias];
$classMetadata = $this->_queryComponent['metadata'];
......@@ -141,7 +141,7 @@ class Doctrine_Query_Production_PathExpression extends Doctrine_Query_Production
// Retrieve ClassMetadata
$k = array_keys($queryComponents);
$componentAlias = $k[0];
$componentAlias = $k[1];
}
// Generating the SQL piece
......
......@@ -34,6 +34,8 @@ class Doctrine_Query_Production_PathExpressionEndingWithAsterisk extends Doctrin
{
protected $_identifiers = array();
protected $_queryComponent;
public function syntax($paramHolder)
{
......@@ -55,37 +57,47 @@ class Doctrine_Query_Production_PathExpressionEndingWithAsterisk extends Doctrin
if (($l = count($this->_identifiers)) > 0) {
// We are dealing with component{.component}.*
$classMetadata = null;
$path = $this->_identifiers[0];
$this->_queryComponent = $parserResult->getQueryComponent($path);
// We should have a semantical error if the queryComponent does not exists yet
if ($this->_queryComponent === null) {
$this->_parser->semanticalError("Undefined component alias for '{$path}'", $this->_parser->token);
}
for ($i = 0; $i < $l; $i++) {
// Initializing ClassMetadata
$classMetadata = $this->_queryComponent['metadata'];
// Looping through relations
for ($i = 1; $i < $l; $i++) {
$relationName = $this->_identifiers[$i];
$path .= '.' . $relationName;
// We are still checking for relations
if ( $classMetadata !== null && ! $classMetadata->hasRelation($relationName)) {
if ( ! $classMetadata->hasRelation($relationName)) {
$className = $classMetadata->getClassName();
$this->_parser->semanticalError("Relation '{$relationName}' does not exist in component '{$className}'");
$this->_parser->semanticalError(
"Relation '{$relationName}' does not exist in component '{$className}' when trying to get the path '{$path}'",
$this->_parser->token
);
}
// Assigning new ClassMetadata
$classMetadata = $classMetadata->getRelation($relationName)->getClassMetadata();
} elseif ( $classMetadata === null ) {
$queryComponent = $parserResult->getQueryComponent($relationName);
// We inspect for queryComponent of relations, since we are using them
if ( ! $parserResult->hasQueryComponent($path)) {
$this->_parser->semanticalError("Cannot use the path '{$path}' without defining it in FROM.", $this->_parser->token);
}
// We should have a semantical error if the queryComponent does not exists yet
if ($queryComponent === null) {
$this->_parser->semanticalError("Undefined component alias for relation '{$relationName}'");
}
// Assigning new queryComponent and classMetadata
$this->_queryComponent = $parserResult->getQueryComponent($path);
// Initializing ClassMetadata
$classMetadata = $queryComponent['metadata'];
}
$classMetadata = $this->_queryComponent['metadata'];
}
} else {
// We are dealing with a simple * as our PathExpression.
// We need to check if there's only one query component.
$queryComponents = $parserResult->getQueryComponents();
if (count($queryComponents) != 1) {
if (count($queryComponents) != 2) {
$this->_parser->semanticalError(
"Cannot use * as selector expression for multiple components."
);
......@@ -94,13 +106,52 @@ class Doctrine_Query_Production_PathExpressionEndingWithAsterisk extends Doctrin
// We simplify our life adding the component alias to our AST,
// since we have it on hands now.
$k = array_keys($queryComponents);
$this->_identifiers[] = $k[0];
$componentAlias = $k[1];
$this->_queryComponent = $queryComponents[$componentAlias];
}
}
public function buildSql()
{
return '';
// Basic handy variables
$parserResult = $this->_parser->getParserResult();
// Retrieving connection
$manager = Doctrine_EntityManager::getManager();
$conn = $manager->getConnection();
// Looking for componentAlias to fetch
$componentAlias = implode('.', $this->_identifiers);
if (count($this->_identifiers) == 0) {
$queryComponents = $parserResult->getQueryComponents();
// Retrieve ClassMetadata
$k = array_keys($queryComponents);
$componentAlias = $k[1];
}
// Generating the SQL piece
$fields = $this->_queryComponent['metadata']->getMappedColumns();
$tableAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias);
$str = '';
foreach ($fields as $fieldName => $fieldMap) {
$str .= ($str != '') ? ', ' : '';
// DB Field name
$column = $tableAlias . '.' . $this->_queryComponent['metadata']->getColumnName($fieldName);
$column = $conn->quoteIdentifier($column);
// DB Field alias
$columnAlias = $tableAlias . '__' . $this->_queryComponent['metadata']->getColumnName($fieldName);
$columnAlias = $conn->quoteIdentifier($columnAlias);
$str .= $column . ' AS ' . $columnAlias;
}
return $str;
}
}
......@@ -129,7 +129,7 @@ class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_
'parent' => null,
'relation' => null,
'map' => null,
'agg' => null,
'scalar' => null,
);
} catch (Doctrine_Exception $e) {
//echo "Tried to load class metadata from '".$componentName."': " . $e->getMessage() . "\n";
......@@ -173,7 +173,7 @@ class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_
// We loop into others identifier to build query components
for ($i = 1, $l = count($this->_identifiers); $i < $l; $i++) {
$relationName = $this->_identifiers[$i];
$path = '.' . $relationName;
$path .= '.' . $relationName;
if ($parserResult->hasQueryComponent($path)) {
// We already have the query component on hands, get it
......@@ -211,7 +211,7 @@ class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_
'parent' => $parent,
'relation' => $relation,
'map' => null,
'agg' => null,
'scalar' => null,
);
$parent = $path;
......
......@@ -38,7 +38,7 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
protected $_isSubselect;
protected $_identificationVariable;
protected $_fieldIdentificationVariable;
private $__columnAliasInSql;
......@@ -51,12 +51,20 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
if ($this->_isPathExpressionEndingWithAsterisk()) {
$this->_leftExpression = $this->AST('PathExpressionEndingWithAsterisk', $paramHolder);
$fieldName = implode('.', $this->_leftExpression->getIdentifiers()) . '.*';
} elseif(($this->_isSubselect = $this->_isSubselect()) === true) {
$this->_parser->match('(');
$this->_leftExpression = $this->AST('Subselect', $paramHolder);
$this->_parser->match(')');
// [TODO] Any way to make it more fancy for user error?
$fieldName = '<Subselect>';
} else {
$this->_leftExpression = $this->AST('Expression', $paramHolder);
// [TODO] Any way to make it more fancy for user error?
$fieldName = '<Expression>';
}
if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) {
......@@ -64,17 +72,24 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
}
if ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) {
$this->_identificationVariable = $this->AST('IdentificationVariable', $paramHolder);
$paramHolder->set('fieldName', $fieldName);
// Will return an identifier, with the semantical check already applied
$this->_fieldIdentificationVariable = $this->AST('FieldIdentificationVariable', $paramHolder);
$paramHolder->remove('fieldName');
}
}
public function semantical($paramHolder)
{
$parserResult = $this->_parser->getParserResult();
// Here we inspect for duplicate IdentificationVariable, and if the
// left expression needs the identification variable. If yes, check
// its existance.
if ($this->_leftExpression instanceof Doctrine_Query_Production_PathExpressionEndingWithAsterisk && $this->_identificationVariable !== null) {
if ($this->_leftExpression instanceof Doctrine_Query_Production_PathExpressionEndingWithAsterisk && $this->_fieldIdentificationVariable !== null) {
$this->_parser->semanticalError(
"Cannot assign an identification variable to a path expression with asterisk (ie. foo.bar.* AS foobaz)."
);
......@@ -82,34 +97,15 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
$this->_leftExpression->semantical($paramHolder);
/*if ($this->_identificationVariable !== null) {
if ($this->_leftExpression instanceof Doctrine_Query_Production_PathExpression) {
// We bring the queryComponent from the class instance
// $queryComponent = $this->_leftExpression->getQueryComponent();
} else {
// We bring the default queryComponent
// $queryComponent = $parserResult->getQueryComponent(null);
}
$idx = count($queryComponent['scalar']);
$this->__columnAliasInSql .= '__' . $idx;
$queryComponent['scalar'][$idx] = $this->_identificationVariable;
//$parserResult->setQueryComponent($componentAlias, $queryComponent);
}*/
// We need to add scalar in queryComponent the item alias if identificationvariable is set.
//echo "SelectExpression:\n";
//echo get_class($this->_leftExpression) . "\n";
// The check for duplicate IdentificationVariable was already done
if($this->_fieldIdentificationVariable !== null) {
$this->_fieldIdentificationVariable->semantical($paramHolder);
}
}
public function buildSql()
{
return $this->_leftExpression->buildSql();// . ' AS ' . (($this->_identificationVariable !== null) ? $this->_identificationVariable : '');
return $this->_leftExpression->buildSql() . $this->_buildColumnAliasInSql();
}
......@@ -124,4 +120,54 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
return $token['value'] === '*';
}
protected function _buildColumnAliasInSql()
{
// Retrieving parser result
$parserResult = $this->_parser->getParserResult();
switch (get_class($this->_leftExpression)) {
case 'Doctrine_Query_Production_PathExpressionEndingWithAsterisk':
return '';
break;
case 'Doctrine_Query_Production_PathExpression':
// We bring the queryComponent from the class instance
$queryComponent = $this->_leftExpression->getQueryComponent();
break;
default:
// We bring the default queryComponent
$queryComponent = $parserResult->getQueryComponent('dctrn');
break;
}
// Retrieving connection
$manager = Doctrine_EntityManager::getManager();
$conn = $manager->getConnection();
$componentAlias = $parserResult->getComponentAlias($queryComponent);
if ($this->_fieldIdentificationVariable !== null) {
// We need to add scalar map in queryComponent if iidentificationvariable is set.
$idx = count($queryComponent['scalar']);
$queryComponent['scalar'][$idx] = $this->_fieldIdentificationVariable;
$parserResult->setQueryComponent($componentAlias, $queryComponent);
$columnAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias) . '__' . $idx;
} elseif ($this->_leftExpression instanceof Doctrine_Query_Production_PathExpression) {
// We need to build the column alias based on column name
$columnAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias)
. '__' . $queryComponent['metadata']->getColumnName($this->_leftExpression->getFieldName());
} else {
// The right thing should be return the index alone... but we need a better solution.
// Possible we can search the index for mapped values and add our item there.
// [TODO] Find another return value. We cant return 0 here.
$columnAlias = 'idx__' . 0;
}
return ' AS ' . $conn->quoteIdentifier($columnAlias);
}
}
......@@ -96,7 +96,7 @@ class Doctrine_Query_Production_VariableDeclaration extends Doctrine_Query_Produ
'parent' => null,
'relation' => null,
'map' => null,
'agg' => null,
'scalar' => null,
);
} catch (Doctrine_Exception $e) {
$this->_parser->semanticalError($e->getMessage());
......
......@@ -40,6 +40,7 @@ UpdateItem = PathExpression "=" (Expression | "NULL")
IdentificationVariableDeclaration = RangeVariableDeclaration [IndexBy] {Join [IndexBy]}
RangeVariableDeclaration = identifier {"." identifier} [["AS"] IdentificationVariable]
VariableDeclaration = identifier [["AS"] IdentificationVariable]
IdentificationVariable = identifier
Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression]
IndexBy = "INDEX" "BY" identifier
......@@ -59,9 +60,10 @@ Term = Factor {("*" | "/") Factor}
Factor = [("+" | "-")] Primary
Primary = PathExpression | Atom | "(" Expression ")" | Function | AggregateExpression
SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")" ) [["AS"] IdentificationVariable]
SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")" ) [["AS"] FieldIdentificationVariable]
PathExpression = identifier {"." identifier}
PathExpressionEndingWithAsterisk = {identifier "."} "*"
FieldIdentificationVariable = identifier
AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] Expression ")"
| "COUNT" "(" ["DISTINCT"] (Expression | "*") ")"
......
......@@ -48,7 +48,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertEquals(null, $decl['relation']);
$this->assertEquals(null, $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals(null, $decl['map']);
}
......@@ -63,7 +63,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertEquals(null, $decl['relation']);
$this->assertEquals(null, $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals('id', $decl['map']);
}
......@@ -78,7 +78,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertEquals(null, $decl['relation']);
$this->assertEquals(null, $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals('id', $decl['map']);
$decl = $parserResult->getQueryComponent('p');
......@@ -86,7 +86,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertTrue($decl['relation'] instanceof Doctrine_Relation);
$this->assertEquals('u', $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals(null, $decl['map']);
}
......@@ -102,7 +102,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertEquals(null, $decl['relation']);
$this->assertEquals(null, $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals('id', $decl['map']);
$decl = $parserResult->getQueryComponent('a');
......@@ -110,7 +110,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertTrue($decl['relation'] instanceof Doctrine_Relation);
$this->assertEquals('u', $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals(null, $decl['map']);
$decl = $parserResult->getQueryComponent('pn');
......@@ -118,7 +118,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase
$this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata);
$this->assertTrue($decl['relation'] instanceof Doctrine_Relation);
$this->assertEquals('u', $decl['parent']);
$this->assertEquals(null, $decl['agg']);
$this->assertEquals(null, $decl['scalar']);
$this->assertEquals('phonenumber', $decl['map']);
}
}
......@@ -336,7 +336,7 @@ class Orm_Query_LanguageRecognitionTest extends Doctrine_OrmTestCase
public function testIndexBySupportsJoins2()
{
$this->assertValidDql('SELECT u.*, u.phonenumbers.* FROM CmsUser u INDEX BY id LEFT JOIN u.phonenumbers p INDEX BY phonenumber');
$this->assertValidDql('SELECT u.*, p.* FROM CmsUser u INDEX BY id LEFT JOIN u.phonenumbers p INDEX BY phonenumber');
}
public function testBetweenExpressionSupported()
......
......@@ -46,9 +46,9 @@ class Orm_Query_SelectSqlGenerationTest extends Doctrine_OrmTestCase
$this->assertEquals('SELECT cu.id AS cu__id FROM cms_user cu WHERE 1 = 1', $q->getSql());
$q->free();
//$q->setDql('SELECT u.* FROM CmsUser u');
//$this->assertEquals('DELETE FROM cms_user cu WHERE 1 = 1', $q->getSql());
//$q->free();
$q->setDql('SELECT u.* FROM CmsUser u');
$this->assertEquals('SELECT cu.id AS cu__id, cu.status AS cu__status, cu.username AS cu__username, cu.name AS cu__name FROM cms_user cu WHERE 1 = 1', $q->getSql());
$q->free();
}
/*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment