Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctrine-dbal
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tomáš Trávníček
doctrine-dbal
Commits
394152e7
Commit
394152e7
authored
Jan 12, 2008
by
jepso
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updates to the new parser.
parent
71d1150e
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
284 additions
and
72 deletions
+284
-72
Parser.php
draft/Doctrine/Query/Parser.php
+61
-14
Production.php
draft/Doctrine/Query/Production.php
+30
-11
AbstractSchemaName.php
draft/Doctrine/Query/Production/AbstractSchemaName.php
+70
-0
IdentificationVariable.php
draft/Doctrine/Query/Production/IdentificationVariable.php
+19
-0
PathExpressionEndingWithAsterisk.php
...ine/Query/Production/PathExpressionEndingWithAsterisk.php
+16
-0
QueryLanguage.php
draft/Doctrine/Query/Production/QueryLanguage.php
+1
-1
RangeVariableDeclaration.php
draft/Doctrine/Query/Production/RangeVariableDeclaration.php
+4
-5
SelectExpression.php
draft/Doctrine/Query/Production/SelectExpression.php
+26
-3
Scanner.php
draft/Doctrine/Query/Scanner.php
+31
-0
Token.php
draft/Doctrine/Query/Token.php
+0
-8
query-language.txt
draft/query-language.txt
+26
-30
No files found.
draft/Doctrine/Query/Parser.php
View file @
394152e7
<?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>.
*/
/**
* An LL(k) parser for the context-free grammar of Doctrine Query Language.
* Parses a DQL query, reports any errors in it, and generates the corresponding
* SQL.
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class
Doctrine_Query_Parser
{
/**
...
...
@@ -46,21 +79,41 @@ class Doctrine_Query_Parser
protected
$_errorDistance
=
self
::
MIN_ERROR_DISTANCE
;
/**
* A query printer object used to print a parse tree from the input string.
* A query printer object used to print a parse tree from the input string
* for debugging purposes.
*
* @var Doctrine_Query_Printer
*/
protected
$_printer
;
/**
* The connection object used by this query.
*
* @var Doctrine_Connection
*/
protected
$_conn
;
/**
* Creates a new query parser object.
*
* @param string $input query string to be parsed
* @param Doctrine_Connection The connection object the query will use.
*/
public
function
__construct
(
$input
)
public
function
__construct
(
$input
,
Doctrine_Connection
$conn
=
null
)
{
$this
->
_scanner
=
new
Doctrine_Query_Scanner
(
$input
);
$this
->
_printer
=
new
Doctrine_Query_Printer
(
true
);
if
(
$conn
===
null
)
{
$conn
=
Doctrine_Manager
::
getInstance
()
->
getCurrentConnection
();
}
$this
->
_conn
=
$conn
;
}
public
function
getConnection
()
{
return
$this
->
_conn
;
}
public
function
getProduction
(
$name
)
...
...
@@ -90,26 +143,20 @@ class Doctrine_Query_Parser
}
if
(
$isMatch
)
{
//
$this->_printer->println($this->lookahead['value']);
$this
->
_printer
->
println
(
$this
->
lookahead
[
'value'
]);
$this
->
lookahead
=
$this
->
_scanner
->
next
();
$this
->
_errorDistance
++
;
}
else
{
$this
->
syntax
Error
();
$this
->
log
Error
();
}
}
public
function
syntaxError
()
{
$this
->
_error
(
'Unexpected "'
.
$this
->
lookahead
[
'value'
]
.
'"'
);
}
public
function
semanticalError
(
$message
)
public
function
logError
(
$message
=
''
)
{
$this
->
_error
(
$message
);
}
if
(
$message
===
''
)
{
$message
=
'Unexpected "'
.
$this
->
lookahead
[
'value'
]
.
'"'
;
}
protected
function
_error
(
$message
)
{
if
(
$this
->
_errorDistance
>=
self
::
MIN_ERROR_DISTANCE
)
{
$message
.=
'at line '
.
$this
->
lookahead
[
'line'
]
.
', column '
.
$this
->
lookahead
[
'column'
];
...
...
draft/Doctrine/Query/Production.php
View file @
394152e7
<?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>.
*/
/**
* An abstract base class that all query parser productions extend.
* An abstract base class for the productions of the Doctrine Query Language
* context-free grammar.
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
abstract
class
Doctrine_Query_Production
{
...
...
@@ -37,7 +66,6 @@ abstract class Doctrine_Query_Production
*/
public
function
__call
(
$method
,
$args
)
{
return
$this
->
_parser
->
getProduction
(
$method
)
->
execute
(
$args
);
$this
->
_parser
->
getPrinter
()
->
startProduction
(
$name
);
$retval
=
$this
->
_parser
->
getProduction
(
$method
)
->
execute
(
$args
);
$this
->
_parser
->
getPrinter
()
->
endProduction
();
...
...
@@ -53,13 +81,4 @@ abstract class Doctrine_Query_Production
* @return mixed
*/
abstract
public
function
execute
(
array
$params
=
array
());
protected
function
_isSubquery
()
{
$lookahead
=
$this
->
_parser
->
lookahead
;
$next
=
$this
->
_parser
->
getScanner
()
->
peek
();
return
$lookahead
[
'value'
]
===
'('
&&
$next
[
'type'
]
===
Doctrine_Query_Token
::
T_SELECT
;
}
}
draft/Doctrine/Query/Production/AbstractSchemaName.php
0 → 100644
View file @
394152e7
<?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>.
*/
/**
* This class represents the AbstractSchemaName production in DQL grammar.
*
* <code>
* AbstractSchemaName = identifier
* </code>
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class
Doctrine_Query_Production_AbstractSchemaName
extends
Doctrine_Query_Production
{
/**
* Executes the AbstractSchemaName production.
*
* <code>
* AbstractSchemaName = identifier
* </code>
*
* @param array $params This production does not take any parameters.
* @return Doctrine_Table|null the table object corresponding the identifier
* name
*/
public
function
execute
(
array
$params
=
array
())
{
$table
=
null
;
$token
=
$this
->
_parser
->
lookahead
;
if
(
$token
[
'type'
]
===
Doctrine_Query_Token
::
T_IDENTIFIER
)
{
$table
=
$this
->
_parser
->
getConnection
()
->
getTable
(
$token
[
'value'
]);
if
(
$table
===
null
)
{
$this
->
_parser
->
logError
(
'Table named "'
.
$name
.
'" does not exist.'
);
}
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
}
else
{
$this
->
_parser
->
logError
(
'Identifier expected'
);
}
return
$table
;
}
}
draft/Doctrine/Query/Production/IdentificationVariable.php
0 → 100644
View file @
394152e7
<?php
/**
* IdentificationVariable = identifier
*/
class
Doctrine_Query_Production_IdentificationVariable
extends
Doctrine_Query_Production
{
public
function
execute
(
array
$params
=
array
())
{
$token
=
$this
->
_parser
->
lookahead
;
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
/*
if ( ! isValidIdentificationVariable($token['value'])) {
$this->error('"' . $name . '" is not a identification variable.');
}
*/
}
}
draft/Doctrine/Query/Production/PathExpressionEndingWithAsterisk.php
0 → 100644
View file @
394152e7
<?php
/**
* PathExpressionEndingWithAsterisk = {identifier "."} "*"
*/
class
Doctrine_Query_Production_PathExpressionEndingWithAsterisk
extends
Doctrine_Query_Production
{
public
function
execute
(
array
$params
=
array
())
{
while
(
$this
->
_isNextToken
(
Doctrine_Query_Token
::
T_IDENTIFIER
))
{
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
$this
->
_parser
->
match
(
'.'
);
}
$this
->
_parser
->
match
(
'*'
);
}
}
draft/Doctrine/Query/Production/QueryLanguage.php
View file @
394152e7
...
...
@@ -18,7 +18,7 @@ class Doctrine_Query_Production_QueryLanguage extends Doctrine_Query_Production
$this
->
DeleteStatement
();
break
;
default
:
$this
->
_parser
->
syntax
Error
();
$this
->
_parser
->
log
Error
();
}
}
}
draft/Doctrine/Query/Production/RangeVariableDeclaration.php
View file @
394152e7
<?php
/**
* RangeVariableDeclaration =
PathExpression [["AS" ] identifier]
* RangeVariableDeclaration =
AbstractSchemaName ["AS"] IdentificationVariable
*/
class
Doctrine_Query_Production_RangeVariableDeclaration
extends
Doctrine_Query_Production
{
public
function
execute
(
array
$params
=
array
())
{
$this
->
PathExpression
();
$this
->
AbstractSchemaName
();
if
(
$this
->
_isNextToken
(
Doctrine_Query_Token
::
T_AS
))
{
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_AS
);
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
}
elseif
(
$this
->
_isNextToken
(
Doctrine_Query_Token
::
T_IDENTIFIER
))
{
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
}
$this
->
IdentificationVariable
();
}
}
draft/Doctrine/Query/Production/SelectExpression.php
View file @
394152e7
<?php
/**
* SelectExpression = (Expression | "(" Subselect ")" ) [["AS"] identifier]
* SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")")
* [["AS"] IdentificationVariable]
*/
class
Doctrine_Query_Production_SelectExpression
extends
Doctrine_Query_Production
{
private
function
_isPathExpressionEndingWithAsterisk
()
{
$token
=
$this
->
_parser
->
lookahead
;
$this
->
_parser
->
getScanner
()
->
resetPeek
();
while
((
$token
[
'type'
]
===
Doctrine_Query_Token
::
T_IDENTIFIER
)
||
(
$token
[
'value'
]
===
'.'
))
{
$token
=
$this
->
_parser
->
getScanner
()
->
peek
();
}
return
$token
[
'value'
]
===
'*'
;
}
private
function
_isSubquery
()
{
$lookahead
=
$this
->
_parser
->
lookahead
;
$next
=
$this
->
_parser
->
getScanner
()
->
peek
();
return
$lookahead
[
'value'
]
===
'('
&&
$next
[
'type'
]
===
Doctrine_Query_Token
::
T_SELECT
;
}
public
function
execute
(
array
$params
=
array
())
{
if
(
$this
->
_isSubquery
())
{
if
(
$this
->
_isPathExpressionEndingWithAsterisk
())
{
$this
->
PathExpressionEndingWithAsterisk
();
}
elseif
(
$this
->
_isSubquery
())
{
$this
->
_parser
->
match
(
'('
);
$this
->
Subselect
();
$this
->
_parser
->
match
(
')'
);
...
...
@@ -18,7 +41,7 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_AS
);
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
}
elseif
(
$this
->
_isNextToken
(
Doctrine_Query_Token
::
T_IDENTIFIER
))
{
$this
->
_parser
->
match
(
Doctrine_Query_Token
::
T_IDENTIFIER
);
$this
->
IdentificationVariable
(
);
}
}
}
draft/Doctrine/Query/Scanner.php
View file @
394152e7
<?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>.
*/
/**
* ...
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class
Doctrine_Query_Scanner
{
/**
...
...
draft/Doctrine/Query/Token.php
View file @
394152e7
...
...
@@ -50,14 +50,6 @@ final class Doctrine_Query_Token
const
T_SQRT
=
141
;
const
T_MOD
=
142
;
const
T_SIZE
=
143
;
const
T_CURRENT_DATE
=
144
;
const
T_CURRENT_TIMESTAMP
=
145
;
const
T_CURRENT_TIME
=
146
;
const
T_SUBSTRING
=
147
;
const
T_CONCAT
=
148
;
const
T_TRIM
=
149
;
const
T_LOWER
=
150
;
const
T_UPPER
=
151
;
private
function
__construct
()
{}
}
draft/query-language.txt
View file @
394152e7
/* Context-free grammar for Doctrine Query Language
*
* Document syntax:
* - non-terminals begin with an upper case character
* - terminals begin with a lower case character
* - parentheses (...) are used for grouping
* - square brackets [...] are used for defining an optional part, eg. zero or
* one time time
* - curly brackets {...} are used for repetion, eg. zero or more times
* - double quotation marks "..." define a terminal string
* - a vertical bar represents an alternative
*/
QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement
SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause]
...
...
@@ -20,11 +33,11 @@ OrderByItem = PathExpression ["ASC" | "DESC"]
GroupByItem = PathExpression
UpdateItem = PathExpression "=" (Expression | "NULL")
IdentificationVariableDeclaration =
RangeVariableDeclaration {Join}
RangeVariableDeclaration =
PathExpression [["AS" ] identifier<identification-variable>]
IdentificationVariableDeclaration = RangeVariableDeclaration {Join}
RangeVariableDeclaration =
AbstractSchemaName ["AS"] IdentificationVariable
Join = ["LEFT" | "INNER"] "JOIN" PathExpression
"AS" identifier [["ON" | "WITH"] ConditionalExpression] IndexBy
IndexBy = "INDEXBY"
identifier
Join = ["LEFT" | "INNER"] "JOIN" PathExpression
["AS"] IdentificationVariable [("ON" | "WITH") ConditionalExpression] [IndexBy]
IndexBy = "INDEXBY"
PathExpression
ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm}
ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor}
...
...
@@ -34,15 +47,16 @@ SimpleConditionalExpression
= Expression (ComparisonExpression | BetweenExpression | LikeExpression
| InExpression | NullComparisonExpression) | ExistsExpression
Atom = string
-literal | numeric-constant | input-
parameter
Atom = string
_literal | numeric_constant | input_
parameter
Expression
= Expression {("+" | "-" | "*" | "/") Expression
}
Expression = ("+" | "-") Expression
Expression = "(" Expression ")"
Expression = PathExpression | Atom |
| Function | AggregateExpression
Expression
= Term {("+" | "-") Term
}
Term = Factor {("*" | "/") Factor}
Factor = [("+" | "-")] Primary
Primary = PathExpression | Atom | "(" Expression ")"
| Function | AggregateExpression
SelectExpression = (
Expression | "(" Subselect ")" ) [["AS"] identifier
]
SelectExpression = (
PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")" ) [["AS"] IdentificationVariable
]
PathExpression = identifier {"." identifier}
PathExpressionEndingWithAsterisk = {identifier "."} "*"
AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] Expression ")"
| "COUNT" "(" ["DISTINCT"] (Expression | "*") ")"
...
...
@@ -51,26 +65,8 @@ QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")"
BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression
ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" )
InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")"
LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE"
escape_character
]
LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE"
string_literal
]
NullComparisonExpression = "IS" ["NOT"] "NULL"
ExistsExpression = ["NOT"] "EXISTS" "(" Subselect ")"
Function =
"CURRENT_DATE" |
"CURRENT_TIME" |
"CURRENT_TIMESTAMP" |
"LENGTH" "(" Expression ")" |
"LOCATE" "(" Expression "," Expression ["," Expression] ")" |
"ABS" "(" Expression ")" |
"SQRT" "(" Expression ")" |
"MOD" "(" Expression "," Expression ")" |
"SIZE" "(" Expression ")" |
"CONCAT" "(" Expression "," Expression ")" |
"SUBSTRING" "(" Expression "," Expression "," "Expression" ")" |
"TRIM" "(" [[TrimSpecification] [trim_character] "FROM"] string_primary ")" |
"LOWER" "(" string_primary ")" |
"UPPER" "(" string_primary ")" |
identifier "(" [Expression {"," Expression}]")" // Custom function
TrimSpecification = "LEADING" | "TRAILING" | "BOTH"
Function = identifier "(" [Expression {"," Expression}] ")"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment